Developing with Anthropic MCP (Part 1)

Developing with Anthropic MCP (Part 1)

Anthropic has just released the Model Context Protocol and a new version of Claude Desktop as a new way of integrating its LLM with external code.

In this first part of 3, I will present Anthropic's MCP protocol, using a component I generated with Claude to access the Perplexity API and provide Perplexity search directly from within Claude.

This is also a good demonstration of a new development workflow enabled by the filesystem access provided by MCP. This is a free extension you can use, and which incidentally provides a powerful and efficient way of developing with Claude.

MCP is essentially beta code, but it's a good beta. Like any beta, hours can sometimes be wasted with strange errors and incomprehensible documentation. However, this is your lucky day because I took the beta lumps so you don't have to! ;)

MCP: Qu'est-ce que c'est?

No, MCP does not stand for the Master Control Program from Tron, although one might be forgiven to think so.

In fact, Anthropic has created a tentacular protocol that gives Claude full control of your machine and other machines, if you let it. It does this by talking to other programs that have been build to understand the MCP protocol.

These programs can be running on your local computer and exchange with Claude using "standard I/O" i.e. the same approach as a command-line tool. The programs can also expose an HTTP endpoint in order to support the JSON RPC transport, although this is more useful for remote access of the program. However, even implementing the "standard I/O" approach, an MCP "server" can use remote resources and in this article I will explain how I built one capable of calling the Perplexity APIs and bringing search results directly into Claude.

Anthropic MCP architecture

You can read more about the details in the (fledgling and sometimes wrong) Anthropic docs, but for the purposes of this article you can skip it.


Perplexity MCP Server

To learn about MCP, I've created a server that allows Claude to call on Perplexity's news and internet search from within the Claude Desktop application. (You cannot use MCPs for the moment in the web application.)

The full code is available here as an open source project. Following the spirit of the newsletter, this code is entirely generated by my AI coding assistant and I will be describing it below

Filesystem MCP Sample Server

Anthropic has provided a Filesystem MCP server which is an invaluable addition to your AI coding toolkit. It is useful in generating files in Claude while viewing them at the same time directly in your project using your IDE of choice. This is like a reverse workflow from using tools like Github copilot, Cursor or Cline.

The great advantage of this is that it provides a much more efficient use of tokens by leveraging the chat memory and letting the LLM drive the file access. It's also leveraging the Project Knowledge files.

When using the IDE as front end calling the APIs, you need to pay per token, and a lot of calls are spent asking the LLM what files it wants to see for the next task. Working with Cline, I would regularly exceed 80,000 tokens per minute (and get an error!).

By using the Filesystem MCP server, I can have a chat with Claude about developing multiple designs and files, and it will remember them from prompt to prompt and know which ones to access without having to waste time and. tokens probing around for the right file, as long as it was previously read or generated in the current chat. When starting a new chat, it maybe necessary for it to do a file search to load the right file, but cunningly it uses a filesystem command to look for keywords! Or it can be told to read a document or just follow a direction. Sometimes it will guess the fiile name. In general, the access is very efficient.

Another interesting thing is that it leverages the subscription pricing where you get a certain number of messages per hour, as opposed to paying for each token. This can be significant depending on your budget.

For more on setting up the filesystem server, see later in this article, or check out the Github repo:

As a predecessor to developing the Perplexity MCP server, I installed the fillesystem MCP server and gave it access to my dev folder.

Designing the Perplexity MCP Project

Getting Started

Because LLMs are transformative in nature, I find I'm most successful when starting with an example to emulate, so I packaged up source code and documentation from the MCP SDK and samples servers, using my open source projectPackager utility that was one of the first useful programs I created with AI Coding. Although I could have Claude access each individual file of the SDK and docs, that would be pretty costly so I prefer to bundle up what I believe is useful and bring them into my project.

For information that I'm going to need in several project Chats, I put the files in the Project Knowledge. Otherwise just attaching the file is fine, this way if I decide to start a new chat I won't be burdened with unnecessary context that uses up tokens for nothing.

Most of this prompt is boilerplate text that I use to start designs and have augmented over several projects, in some cases with the help of Claude for effectiveness. It may seem surprising that so many directives are needed, or even that Claude heeds them as with ChatGPT very long prompts tend to cause confusion. However one strength of Claude Sonnet 3.5 is its ability to "find a needle in a haystack" i.e. pay attention to details even if among a lot of other text, behaving like a kind of coding savant (or maybe just the typical OCD "die hard" developer!) This is actually one of the reasons I started seriously experimenting with Claude Sonnet 3.5 because I had previously been disappointed that my directives were getting ignored by the LLMs and causing the end result to be incomplete and requiring a lot of work to make something of it.

Diagrams FTW

I find that having Claude generate mermaid diagrams is a great way to see at a glance if the task was properly defined. I also like the format of Mermaid Script - it looks like succinct notes so I have come to believe it helps keep the project on track over time:



MCP Perplexity Server Startup Sequence


Part of the startup sequence - initializing handlers



Handling a request to call Perplexity



Class design

At this stage, I can have Claude modify individual module designs, adding or removing details that I think will be important for code generation. I can rename classes, split out functionality, move responsibilities around etc. This is still high level and costs less in terms of tokens than doing it directly on code, especially when there are test cases involved that would also need updating. (I can add testing notes too or request complete test designs when pertinent.)

Module Designs

Based on the identified classes and flows, Claude proceeds to generate minimal class designs which I can review and amend. Here are a few abridged samples to illustrate, you can find the full set of design docs and diagrams in my github repo.


Class: PerplexityService

Overview

Singleton service that handles all direct communication with the Perplexity API

Design Requirements

1. MUST be implemented as a singleton service pattern

Source: High-level design document

2. MUST use axios for API calls

Source: Custom instructions and high-level design

3. MUST implement retry logic and error handling

Source: High-level design document

4. MUST validate all inputs before sending to API

Source: API safety best practices

Design Considerations

1. Request Configuration

- WHY: Ensure consistent API call setup

- HOW: Create base axios instance with common config

- EXAMPLE:

   private readonly client = axios.create({
       baseURL: API_CONSTANTS.BASE_URL,
       timeout: API_CONSTANTS.TIMEOUT_MS,
       headers: {
         'Content-Type': 'application/json'
       }
     });             

2. Error Handling

- WHY: Provide meaningful error information

- HOW: Wrap API errors with additional context

- EXAMPLE:

    try {
       await this.client.post('/chat/completions', data);
     } catch (error) {
       if (axios.isAxiosError(error)) {
         throw new PerplexityApiError(error);
       }
       throw error;
     }             

3. Retry Strategy

- WHY: Handle transient failures gracefully

- HOW: Implement exponential backoff

- EXAMPLE:

for (let attempt = 1; attempt <= API_CONSTANTS.MAX_RETRIES; attempt++) {
       try {
         return await this.makeRequest();
       } catch (error) {
         if (!this.shouldRetry(error)) throw error;
         await this.delay(attempt);
       }
     }        

Main Functions

1. async createChatCompletion(config: PerplexityConfig): Promise<ChatCompletionResponse>

- Primary method for making chat completion requests

- Pseudocode:

 - Validate config using PerplexityValidator
     - Prepare request data
     - If retry needed
       - Execute with retry logic
     - Else
       - Make single request
     - Validate response
     - Return typed response        

2. private async makeRequest(endpoint: string, data: unknown): Promise<unknown>

- Internal method for making HTTP requests with retry logic

- Pseudocode:

   - For each retry attempt:
       - Try to make request
       - If success, return response
       - If error and should retry:
         - Wait with exponential backoff
         - Continue to next attempt
       - If error and shouldn't retry:
         - Throw error
     - If max retries exceeded:
       - Throw error        

PerplexityServer Design Context and Background

Purpose

This server provides integration between the Model Context Protocol (MCP) and Perplexity's AI-powered internet search API. Unlike a simple chat completion service, this implementation focuses specifically on enabling internet search capabilities with AI-enhanced responses.

Key Design Decisions

  1. Internet Search Focus
  2. Logging Architecture
  3. MCP Integration Points

Maintenance Notes

  • Tool Configuration: When updating tool parameters, ensure corresponding updates in PerplexityConfig
  • Error Handling: All errors must be formatted as MCP responses
  • Logging: Add logs for significant operations to help with debugging
  • Citations: Always include source citations in responses for transparency


Class: PerplexityServer

Overview

Main server class that implements the Model Context Protocol and integrates with the Perplexity API for internet search capabilities

Design Requirements

  1. MUST implement MCP protocol using SDK Source: High-level design document
  2. MUST use PerplexityService for API calls Source: High-level design document
  3. MUST handle tool registration and execution Source: High-level design document
  4. MUST follow example filesystem server pattern Source: Project knowledge base
  5. MUST expose server logs through MCP resources Source: Example filesystem server implementation

Design Considerations

  1. Tool Registration Strategy

- WHY: Provide clear interface for MCP tools

- HOW: Register tools with detailed schemas and handlers

- EXAMPLE:

this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
       tools: [
         {
           name: "perplexity_search",
           description:
             "Search the internet and get AI-powered answers using Perplexity API",
           inputSchema: {
             type: "object",
             properties: {
               messages: {
                 type: "array",
                 items: messageSchema,
               },
               searchDomainFilter: {
                 type: "array",
                 items: { type: "string" },
                 description:
                   "Optional list of domains to restrict search results",
               },
             },
           },
         },
       ],
     }));        

2. Resource Handlers

- WHY: Expose server logs through MCP protocol

- HOW: Implement ListResourcesRequestSchema and ReadResourceRequestSchema handlers

- EXAMPLE:

   this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
       resources: [
         {
           uri: "logs://current",
           name: "Server Logs",
           description: "View the current server logs without clearing them",
           mimeType: "text/plain",
         },
       ],
     }));

     this.server.setRequestHandler(
       ReadResourceRequestSchema,
       async (request) => {
         if (request.params.uri === "logs://current") {
           return {
             contents: [
               {
                 uri: request.params.uri,
                 mimeType: "text/markdown",
                 text:
                   Logger.getLogContent().join("<BR>\n") || "No logs available",
               },
             ],
           };
         }
         throw new Error(`Resource not found: ${request.params.uri}`);
       }
     );        

3. Tool Request Handling

- WHY: Process tool calls to perplexity_search

- HOW: Implement CallToolRequestSchema handler

- EXAMPLE:

his.server.setRequestHandler(CallToolRequestSchema, async (request) => {
       const { name, arguments: args } = request.params;

       switch (name) {
         case "perplexity_search": {
           await Logger.trace(`Tool call received: ${name}`);

           try {
             const config: PerplexityConfig = {
               model: API_CONSTANTS.DEFAULT_MODEL,
               messages: args.messages,
               searchDomainFilter: args.searchDomainFilter,
               temperature: API_CONSTANTS.DEFAULT_TEMPERATURE,
               topP: API_CONSTANTS.DEFAULT_TOP_P,
               returnRelatedQuestions: true,
             };

             const response = await this.perplexityService.createChatCompletion(
               config
             );
             await Logger.trace("API response received");

             return {
               content: [
                 {
                   type: "text",
                   text: response.choices[0].message.content,
                 },
                 {
                   type: "text",
                   text: "\n\nSources:\n" + response.citations.join("\n"),
                 },
               ],
             };
           } catch (error) {
             await Logger.trace(`Error in tool call: ${error}`);
             return {
               content: [
                 {
                   type: "text",
                   text: `Error: ${
                     error instanceof Error ? error.message : String(error)
                   }`,
                 },
               ],
               isError: true,
             };
           }
         }

         default: {
           throw new Error(`Unknown tool: ${name}`);
         }
       }
     });        

Main Functions

  1. constructor(apiKey: string)
  2. private setupHandlers()
  3. private async handlePerplexitySearch(request: CallToolRequest): Promise
  4. async initialize(): Promise
  5. getServer(): Server

Attributes

The Perplexity API requires a conversation, however, for now we will just accept a single query and create a user message with it.

class PerplexityServer {
    private server: Server;
    private perplexityService: PerplexityService;

    constructor(apiKey: string) {
        this.server = new Server({
            name: 'perplexity-mcp-server',
            version: '1.0.0'
        }, {
            capabilities: {
                tools: {
                    perplexity_search: {
                        description: 'Search the internet and get AI-powered answers using Perplexity API',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                query: {
                                  type: 'string'
                                }
                                searchDomainFilter: {
                                    type: 'array',
                                    items: { type: 'string' },
                                    description: 'Optional list of domains to restrict search results'
                                }
                            },
                            required: ['messages']
                        }
                    }
                },
                prompts: {},
                sampling: {},
                resources: {}
            }
        });

        this.perplexityService = PerplexityService.getInstance(apiKey);
    }
}        

StdioServer Design

Overview

The StdioServer implements the MCP protocol over standard input/output. Due to protocol requirements, stdout is reserved for protocol responses, requiring careful handling of logging and debug output.

Design Requirements

  1. Debug Tracing
  2. Protocol Message Handling
  3. Error Handling
  4. Server Lifecycle

Message Flow

  1. Normal Mode (--debug not set)
  2. Debug Mode (--debug set)

Event Categories

  • Protocol commands and responses
  • API requests and responses
  • Error conditions (logged in all modes)
  • Server lifecycle events



There are other files - I try to have one per module. The exact format can vary - the point is to be able to think about the overall project organization in a way that I won't be doing a lot of backtracking when it comes time to generate code, because that's one way to seriously confuse the LLM and have it start generating strange and horrific mixtures of code, all the while burning up your credits.

At the same time, I keep in mind that there's a law of diminishing returns at play, and if the "design" information becomes too complex, the LLM can also start hallucinating or ignoring important parts. (I found this true with other models such as o1 as well).

Claude in the driver's seat

In the design prompt (shown earlier in the article), I told Claude to save the design docs and code in my dev folder, in a project location called mcp-perplexity.

In order to access, it Claude needs to use the filesystem MCP. To ensure it's working properly and not just hallucinating the file system, I ask it to list the content of the folder:

Whenever a tool exposed by an MCP server is called, permissions need to be granted by the user. I choose to allow the tool to be called for the rest of the Chat otherwise I would spent a lot of time clicking on permission dialogs. Unfortunately you can't give permissions for the duration of the project, only one chat within the project.

You need to give permission separately for each tool, i.e. once for listing files, once for reading, once for writing etc.

Claude then goes ahead with listing the folder - which is currently empty.

It says the folder is empty because at this point I hadn't create any of the design artifacts yet, so I spoiled away the final result in order to make it clearer what we hope to get out of the design phase.

At this point I provide it an additional prompt to generate the design documents:


The prompt to generate design documents

Again this is a pretty big prompt with a lot of details emerging from a lot of blood, sweat and tears trying to get AI Code generation to work in a useful manner!

There were already a few artifacts - high level design plus diagrams. They only existed in the chat so far, but following my directives Claude puts them into the design folder:

The instructions tell it to generate missing files, because at some point the Chat becomes too long and it's necessary to create a new one, so the prompt includes this detail allowing Claude to land on its feet and pick up where it left off in the previous chat. The "high level design" allows it to have general instructions about the project content and avoid coming up with unexpected and undesirable additional modules.

Creating a module design

Let's take a look at how Claude creates the design for the "Constants" module, which centralizes certain values to be used in the project, applying the DRY ("Don't Repeat Yourself") principle:

It starts by ensuring the appropriate folders exist, then, based on its inference from the diargams and high-level design and requirements, it creates the Constants design doc directly in the design folder.


Checking the output and iterating

I go to the dev/mcp-perplexity folder in VSCode so I can look at the files directly, as Claude puts them there.

I noticed too late that a structure for Logging was put in this module, so I tell it to save it for a separate module focused on Logging.

I also notice that it created a bunch of subfolders in the design subfolder, which isn't really that useful and will cause complications in future processing, so I tell it to keep all the designs in that folder. Because I saw this already in the IDE, I just deleted the unwanted subfolder myself to save on tokens.

The next design is the Logger. However, it's overly complicated with a template substitution mechanism I didn't ask for. I tell it to simplify and the design file gets updated with the leaner approach.

Part of the prompt tells Claude to tackle each file one at a time, and wait for my approval. This is because it's very necessary to review everything the LLM generates! There can be unpleasant surprises.

The other reason is that there's a limit of 8K tokens on the maximum output for one prompt. By having it stop, it resets the token couter and we can usually avoid running out of tokens mid-response and have to restart it.

Testing the context

Before using the Filesystem MCP server, I used Claude Artifacts, one per document/module. Claude would automatically bear in mind what it put in these artifacts, or so it seemed. I wasn't sure this was the same if Claude wrote out the files directly using the MCP server, so I asked it about a specific point to see how it would handle it with this new approach:

So it was able to answer it based on the chat knowledge, and without having to read back what it had written. This is reassuring otherwise if it was forgetting everything as it went along, design and code generation would suffer.

Refactoring

The Perplexity API was modeled on the OpenAI Chat completion API, and Perplexity recommends using the OpenAI SDK to call it. This is because Perplexity has its own LLM model and even though the Perplexity application focuses on searching, the heart of it is AI chatting.

In creating this design, Claude focused on that aspect and called the "chat_completion", essentially exposing the Perplexity Chat through to Claude. Essentially this is one LLM (Claude) calling another LLM (Perplexity) based on an API for yet another LLM (OpenAI) . (Go ahead - call me crazy!)

I decide to rename the tool perplexity_search. This is important not just so users understand what it does, but it's also important so Claude understands what it does. The tools get involved when Claude decides it needs the specific functionality exposed by the tool. Users make requests in free-form text, and from those requests Claude will decide or not to use tools. So unless the end-user tells Claude it wants to chat with Perplexity, the tool would not be used. On the other hand, if the user says they want a search done, then Claude is more likely to recognize that this is the right one to try.

(I say more likely because there's not going to be a "Seach Perplexity" button appearing in Claude - at least for now. It's all based on the natural language processing and having Claude associate the tool with the processing)

One-line refactoring of multiple parts is one of the ways AI coding is a productivity enhancer. You still need to check what was done but there's a lot of time saved nevertheless if you were able to keep the LLM mainly on the intended path.

I notice something vague and concerned about feature creep, I call it out:

I get an explanation that satisfies me. I need to tell it not to remove this functionality. Claude tends to react to questions about what it generated as a sign that there's something wrong, and I have to tell it from time to time to restore things it deleted just because I asked about them! Ideally I need to specify "do not change the file" when doing my query but - it slipped my mind! No harm done.

Expanding on the design details

This program is a gateway to calling the Perplexity API, however there are not many details on just that in fact. I ask it to expand on it.

It goes ahead and adds explanations into the design document, which I review in VSCode.At this point I feel ok with the various design notes so I tell it to wrap it up. I writes some updates and throws in a free introduction I didn't ask for - but I'll "take the win"!



PRO TIP: By writing the files into the filesystem, I lose the ability to preview the files using the Artifact window in Claude. However, it's not a bit loss for this.

VSCode has the ability to format markdown, and I use an extension from Microsoft called Live Preview that lets me see the markdown in a formatted window, side-by-side. If anything, it gives me more control over the window size than the Claude artifact window.

I can also use Obsidian for viewing the markdowns, this is useful to create more structured documentation around it, navigating them with graphs etc.

A markdown document previewed in VSCode, with the full project on the left


A generated code file, with the full set of modules visible on the left

When LLMs get lazy

Once in a while, LLMs leave out stuff as an "exercise for the reader", which is pretty annoying, to put it mildly. Cline has even incorporated a detection algorithm to tell you when it thinks the LLM has truncated your file, which could be disastrous as half your code suddenly goes missing.

I notice that Claude got into one of its lazy moods and try to get it to recover:

Now I probably could have gone back and cobbled together the part of the document that was lost, but since it was generated in this chat, Claude thankfully remembers it so it goes ahead and restores it properly.


Putting it all together

Once I have generated all the code and related config files, build it and fix all the typescript errors (not too many! and I let Claude fix most or all of them these days, it's gotten a lot better), it's type to put it to the test.

I want to use this Perplexity search from within Claude. Unfortunately, since this is a "bleeding edge" entry in the so-called Agentic Wars, Anthropic leave it as an exercise for the early adopters to use a text editor and create / modify the Claude Desktop configuration file. You need to edit the JSON structure to define all of the servers you intend on using, as well as indicating how Claude can access it. If it runs locally, you would typically configure it to be spawned as a child process using the "stdio" protocol, otherwise you would host it in web server and expose a JSON RPC endpoint.

For servers like the filesystem, the code has been published so that you can use npx (for node-based servers) or uvx (for python servers) so you don't need to actually install it somewhere on your disk, and that's what I did for the the filesystem server.

For my Perplexity server, I configure it with the path to where the generated code is and run it with node as the code to run is a node script called stdio-server.js (once transpiled from typescript). Node runs it as an executable, not as a web app. I provided additional parameters - an api key (faked in the image below - sorry hackers!) and an optional --debug flag I created to activate the logging and make it easier to see what's going on.

(For non-developers who want to try this: you need to use notepad, not word, because it's a plain text file and Claude would not be able to read a formatted document.)

If successful, you'll now see a little hammer icon in the lower righthand corner of your Claude prompt box with the number of MCP tools now available:

Clicking on it shows you the tool names and descriptions provided by the servers:

We can see, in addition to all the filesystem stuff, the new "perplexity_search" tool.

This hammer icon is just informational - you don't actually do anything with it except to see what are the available tools.

To activate a tool, you need to give a prompt that Claude will figure requires the tool. I ask it to do a search on tech stocks. I added the word "perplexity search" to make it quite clear what I wanted, but in other cases that might always not be necessary if Claude can figure it out.

I first get the usual permissions request:

And then - Claude crashes!

This message is a lie - if you click on working on it, you go to a web page showing the Claude service is green. This is just a crash of the MCP running locally on your machine, and it happens from time to time. You can just exit Claude Desktop and restart it to keep going.

After doing so and trying again, this time it doesn't crash, however, there is an error returned by the server, which I can see by logs but also by expanding the button "View Result from perplexity_search"

No joy
Error information returned by the Perplexity search server


As part of the server, I implemented something called a "Resource" in the MCP architecture. This represents a dataset that you might want to incorporate in your work with Claude. Unfortunately the documentation is VERY thin on how to use it within Claude, or so I have expressed to the Claude people.

Basically when Resources are available, a little plug-and-socket "Attach from MCP" button appears when you click on the paperclip for attachments. It is hidden the rest of the time, much to my chagrin and additional gray hairs!

Clicking on the attach icon shows you a special dialog from which you can select an "Integration"

MCP resources

This represents resources exposed by the server. I added a "server logs" resource in my code. Clicking on it retrieves the latest logs with the error details and puts them as an attachment - as if I had uploaded a file from my local drive.

MCP resource retrieved and attached


I can click on it and see what it contains:

And sure enough I see the messages that were already visible to me in the log file. To be honest this is not actually very useful but is a POC to demonstrate the difference between Tools and Resources: Tools are used by the LLM when it feels like it, Resources (for now at least) are available for the user to include as part of a prompt.

Enter - The Inspector

Anthropic provide a tool called the "Inspector" which is a test harness for experimenting with an MCP server's different endpoints.

You start it up with npx, it downloads the latest release and starts up your server. You can then connect/disconnect, query available tools, resources, etc and interact. This is perfect to complete development and not waste messages/tokens using Claude Desktop.


It's important to use at least version 0.2.7 according to the Anthropic support people I wrote as version 0.2.6 DOESN'T WORK! I found myself inspecting the inspector for a little while because asking for help. I can now vouch for 0.2.7, it fully works.

Anatomy of an MCP server

The MCP SDK provides a Server class that servers as interface between the server and the MCP client i.e. Claude Desktop in our example. (Other MCP clients exist including Cody and the Zed editor.)

Through the initialization of the Server object, the program provides descriptive metadata such as the server name, version, as well as definitions of capabilities such as tools, resources, and other things. However, it's also possible to add these after the Server object is created, which is how some of the examples do it, so for simplicity all this has been moved to a function called "setupHandlers". The constructor also initializes a client instance with which it will be making calls to Perplexity.

Creating a Server object

Using the MCP protocol, the Claude Desktop (or other MCP client) sends standard events to the server, defined in the SDK. This setupHandlers registers the functions of the server that will receive and respond to those events with the Server instance's setRequestHandler general-purpose registration function.

The Server instance is not returned - in effect, it will be establishing the communication channel that will be used by Claude Desktop (or other client) to send requests. It can also initiate certain special requests called "Sampling" as the communication is bi-directional.

If we are starting a "stdio" server, then this class will start reading from the stdin handle in order to receive commands from Claude. The Perplexity server does this.

If we are starting a JSON RPC server, then it will listen at an HTTP endpoint for commands.

ListToolsRequestSchema Event

ListToolsRequestSchema is a request from Claude Desktop to describe the tools supported by the server. This includes a unique tool identifier, a description, a schema of the parameters to be sent to the tool by Claude. The response is always a string.

Describing the search tool

CallToolRequestSchema Event

This event is used to actually invoke a tool, which makes the "Schema" in the name a bit misleading.

The request contains the name (previously returned in the ListTools event) which is used to execute the right tool. In this example server, we only expose one tool, but the filesystem exposes 9.

The parameters for the action are prepared by Claude through its ability to invoke functions and synthesize appropriate arguments from natural language, user-provided or as part of its processing.

This is where we do a call to the Perplexity API to perform the search. We then return the answer as a series of text objects. Here this means returning the search result, plus a second object with the references that Perplexity uses as a second object. However there is no rule as to how to organize the response.

Calling the Perplexity search API

ListResourcesRequestSchema Event

Resources are meant to be datasets that can support the prompting of an LLM. The resource to return is identified using a URI, with a made-up protocol name and arbitrary route segments that should mean something to our server when it is called later.

In the case of this server, I declare a first resource with the URI logs://current, to mean returning all of the logs currently available. These logs are messages created during the execution of the Perplexity server for purposes of troubleshooting.

Returning the resource endpoints to Claude

ReadResourceRequestSchema Event

This is the event to actually read the resource value. As with tools, the word Schema is out of place here, because it's actually data not its schema that is returned.

We need to demultiplex the uri parameter to know what values to return. In the case of our logs://current endpoint, we just convert the current logs into a large text document.

Return the current logs to Claude


Testing the Perplexity Search using the Inspector

Back in the Inspector, we can select "List Tools" to trigger the "ListTools" event and get back what the server has to offer:


We click on a particular tool to bring up an invocation UI that lets us provide parameters. In this example, we have modified the code from the original which was expecting messages to send the Perplexity Chat. Instead, we will ask for a query string and build the Chat messages ourselves.

Looks like a good week coming up for the NASDAQ, according to analysts. Better sell!


And with the correct parameters, the call to Perplexity goes through successfully and we get back a full Perplexity response. We can look at the debug log to get a better idea of how this happened:

2024-11-30T20:21:59.845Z - SERVER EVENT: Starting server
{
  "apiKeyPresent": true
}
2024-11-30T20:21:59.845Z - Setting up request handlers
2024-11-30T20:21:59.845Z - Debug mode enabled
2024-11-30T20:21:59.845Z - Server initializing
2024-11-30T20:21:59.846Z - Perplexity MCP Server starting
2024-11-30T20:21:59.846Z - SERVER EVENT: Server ready
2024-11-30T20:22:04.916Z - Protocol: ListTools request received
2024-11-30T20:22:04.916Z - Protocol: ListTools response ready
2024-11-30T20:22:28.850Z - Protocol: CallTool request for perplexity_search
2024-11-30T20:22:28.852Z - Request params:
{
  "_meta": {
    "progressToken": 0
  },
  "name": "perplexity_search",
  "arguments": {
    "query": "What are analysts saying about NASDAQ and the coming week"
  }
}
2024-11-30T20:22:28.852Z - Tool call received
{
  "tool": "perplexity_search",
  "query": "What are analysts saying about NASDAQ and the coming week"
}
2024-11-30T20:22:28.852Z - Validating messages:
2024-11-30T20:22:28.853Z - Validation complete
{
  "type": "query"
}
2024-11-30T20:22:28.853Z - Validating config:
{
  "model": "llama-3.1-sonar-small-128k-online",
  "messages": [
    {
      "role": "user",
      "content": "What are analysts saying about NASDAQ and the coming week"
    }
  ],
  "temperature": 0.2,
  "topP": 0.9,
  "returnRelatedQuestions": true
}
2024-11-30T20:22:28.855Z - Validation complete
{
  "type": "config",
  "config": {
    "model": "llama-3.1-sonar-small-128k-online",
    "messages": [
      {
        "role": "user",
        "content": "What are analysts saying about NASDAQ and the coming week"
      }
    ],
    "temperature": 0.2,
    "topP": 0.9,
    "returnRelatedQuestions": true
  }
}
2024-11-30T20:22:28.856Z - Calling Perplexity API
{
  "config": {
    "model": "llama-3.1-sonar-small-128k-online",
    "messages": [
      {
        "role": "user",
        "content": "What are analysts saying about NASDAQ and the coming week"
      }
    ],
    "temperature": 0.2,
    "topP": 0.9,
    "returnRelatedQuestions": true
  }
}
2024-11-30T20:22:28.856Z - Making API request with config:
{
  "model": "llama-3.1-sonar-small-128k-online",
  "messages": [
    {
      "role": "user",
      "content": "What are analysts saying about NASDAQ and the coming week"
    }
  ],
  "temperature": 0.2,
  "topP": 0.9,
  "returnRelatedQuestions": true
}
2024-11-30T20:22:28.857Z - Calling Perplexity API
2024-11-30T20:22:31.532Z - Received API response
2024-11-30T20:22:31.533Z - Raw API response:
{
  "id": "4c325bba-f2fc-4ddf-940c-212589265d16",
  "model": "llama-3.1-sonar-small-128k-online",
  "object": "chat.completion",
  "created": 1732998151,
  "citations": [
    "https://www.schwab.com/learn/story/weekly-traders-outlook",
    "https://www.edwardjones.com/us-en/market-news-insights/stock-market-news/stock-market-weekly-update",
    "https://www.nasdaq.com/news-and-insights",
    "https://www.nasdaq.com/market-activity/quotes/analyst-research",
    "https://www.nasdaq.com/market-activity/stocks/ndaq/analyst-research"
  ],
  "choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "Analysts are generally bullish about the NASDAQ and the coming week, despite some potential challenges. Here are the key points:\n\n1. **Bullish Outlook**:\n   - Analysts at Charles Schwab are optimistic about the market, citing bullish seasonality, technicals, and a strong fundamental backdrop. They expect a bullish week ahead, although they note that higher bond yields and a hotter-than-expected PCE report could challenge this outlook[1].\n\n2. **Market Momentum**:\n   - Edward Jones anticipates strong momentum into 2025 but cautions against potential curveballs. They highlight that valuations are elevated, particularly in mega-cap technology stocks, but other sectors like financials and industrials are stepping up to lead the market[2].\n\n3. **Economic Data**:\n   - Nasdaq.com emphasizes that economic data remains solid, with consumers remaining resilient and broad-based demand globally. This supports a bullish outlook for 2025[3].\n\n4. **Technical Analysis**:\n   - The performance of the Nasdaq 100 index relative to the S&P 500 shows that the tech sector is experiencing a period of consolidation, but other sectors are filling the gap. This broadening of market leadership is seen as a healthy development for the sustainability of the market rally[2].\n\nOverall, while there are potential risks like higher bond yields and inflation data, the consensus leans towards a bullish outlook for the NASDAQ and the coming week."
      },
      "delta": {
        "role": "assistant",
        "content": ""
      }
    }
  ],
  "usage": {
    "prompt_tokens": 11,
    "completion_tokens": 293,
    "total_tokens": 304
  }
}        
2024-11-30T20:22:31.534Z - Response formatting complete
{
  "response": {
    "content": [
      {
        "type": "text",
        "text": "Analysts are generally bullish about the NASDAQ and the coming week, despite some potential challenges. Here are the key points:\n\n1. **Bullish Outlook**:\n   - Analysts at Charles Schwab are optimistic about the market, citing bullish seasonality, technicals, and a strong fundamental backdrop. They expect a bullish week ahead, although they note that higher bond yields and a hotter-than-expected PCE report could challenge this outlook[1].\n\n2. **Market Momentum**:\n   - Edward Jones anticipates strong momentum into 2025 but cautions against potential curveballs. They highlight that valuations are elevated, particularly in mega-cap technology stocks, but other sectors like financials and industrials are stepping up to lead the market[2].\n\n3. **Economic Data**:\n   - Nasdaq.com emphasizes that economic data remains solid, with consumers remaining resilient and broad-based demand globally. This supports a bullish outlook for 2025[3].\n\n4. **Technical Analysis**:\n   - The performance of the Nasdaq 100 index relative to the S&P 500 shows that the tech sector is experiencing a period of consolidation, but other sectors are filling the gap. This broadening of market leadership is seen as a healthy development for the sustainability of the market rally[2].\n\nOverall, while there are potential risks like higher bond yields and inflation data, the consensus leans towards a bullish outlook for the NASDAQ and the coming week."
      },
      {
        "type": "text",
        "text": "\n\nSources:\nhttps://www.schwab.com/learn/story/weekly-traders-outlook\nhttps://www.edwardjones.com/us-en/market-news-insights/stock-market-news/stock-market-weekly-update\nhttps://www.nasdaq.com/news-and-insights\nhttps://www.nasdaq.com/market-activity/quotes/analyst-research\nhttps://www.nasdaq.com/market-activity/stocks/ndaq/analyst-research"
      }
    ]
  }
}
        

Testing the Resources endpoint

By clicking on the "Resources tab, we can then retrieve the available resources by clicking "List Resources" which will send a ListResource event to our handler. The "Server Logs" endpoint is displayed, and when we click on it we see the full set of logs appear to the right of the ool.


Trying it out in Claude

Now that it's working in the Inspector, I try doing a search in Claude Desktop:

Interestingly, Claude rephrases my query, leaving out the "Do a perplexity search" part, presumably as it's unnecessary for the server.

After the call I click on "View Result from perplexity_search" and see the following:

{
  `query`: `latest analysis tech stocks NASDAQ market outlook next week`
}
The latest analysis on tech stocks and the NASDAQ market outlook for the next week is as follows:

1. **NVIDIA's Earnings Impact**: NVIDIA, a leading tech company, reported quarterly earnings that beat expectations but provided modest revenue guidance for the next quarter. Despite this, the stock did not see significant gains post-earnings, indicating a period of consolidation in the tech sector. The semiconductor sector is currently underperforming, partly due to concerns about tighter export restrictions to China by the incoming administration[1].

2. **Tech Sector Consolidation**: The tech sector is experiencing a period of consolidation, digesting its outsized gains from the last two years. This consolidation is not unique to NVIDIA; the Nasdaq 100 index has been moving sideways since its peak in July. Other sectors, such as financials and industrials, have stepped up to lead the market, broadening the leadership and creating a healthier backdrop for the bull market[2][4].

3. **Market Psychology**: Despite the consolidation in tech, optimism around potential positive impacts from policy changes is dominating market psychology. This optimism, combined with the broader economic resiliency and growth prospects from tech innovations, has helped U.S. equity markets hold the lead[2][4].

4. **Economic Releases Next Week**: Next week's economic releases include domestic third-quarter GDP and U.S. PCE inflation data. These releases could provide insights into the broader economic health and influence market sentiment[2][4].

5. **Market Volatility**: With Thanksgiving week approaching, stocks will only have three-and-a-half days of trading, which typically results in lighter volume and higher volatility. Historically, November is a bullish month for stocks, and Thanksgiving week tends to be an up week more often than not[1].

In summary, while the tech sector is consolidating, the broader market remains optimistic, and other sectors are stepping up to lead the market. Key economic releases next week could further shape market sentiment.


Sources:
https://www.schwab.com/learn/story/weekly-traders-outlook
https://www.edwardjones.ca/ca-en/market-news-insights/stock-market-news/stock-market-weekly-update
https://www.fxempire.com/forecasts/tech100-usd
https://www.edwardjones.com/us-en/market-news-insights/stock-market-news/stock-market-weekly-update
https://www.nasdaq.com
        


However, this gets massaged by Claude and the end user sees a final result that is a bit different, according to the prompt and the style chosen by the system prompt:

Claude does not approve of day trading!

Dynamic Resources

Resources are meant to be retrievable using an id or other dynamic information. I decide to implement a new resource called logs://tail to retrieve the last N logs from the end of the file. The number N will be a dynamic parameter that will be passed in the URI, kind of like how web apps often dedicate part of the URL to identifying what item needs to be displayed on the page.

The URI for dynamic Resources is therefore a template, and Anthropic has chosen to follow the standard for URI Templates proposed by the IETF. For most purposes, it really just means putting placeholder names in squiggly brackets to indicate we expect a value to be provided.

So for example, in order to retrieve the last N logs, we need the caller to provide a value for N e.g. logs://tail/{length} with length indicating how many logs to retrieve from the end.

I go back to my coding assistant to implement this new feature. I start with updating the design so that I can better control the code generation.

This sounds plausible so I tell it to proceed and update the document.


The initial implementation generated by Claude incorrectly defined the new Resources like the first one, i.e. as if it were a single fixed endpoint for a regular Resource. This was apparent when I went into the Inspector and saw two Regular resources and no Resource Template

However because we have the parameter determining how many logs, this is actually a dynamic endpoint that can match multiple URIs, so it needs to be treated like a Resource Template, otherwise we get an error trying to access it:

I attach the documentation about Resources from the MCP SDK and ask it to try again.

I want a default to be used if no numeric parameter is passed i.e. if calling just "logs://tail"


Unfortunately, the code still doesn't work.

More guesswork by Claude however this still didn't work. I looked at the SDK doc myself and found that it was very sparse on details for Resource Templates, hence Claude's inability to generate something that worked.

I did a search in the cloned codebase and found a Brave Search sample server that implemented the resource template, so I decided to use that as example. (I had seen some bug reports where this server was mentioned so not quite sure it was fully functional but I was starting to get desperate!) Basically it seemed to be using a different Event, ListResourceTemplatesRequestSchema.

Ah, I see! This time for sure!

And sure enough - this time the Inspector was able to retrieve the "Last N Server Logs" Resource Template. Clicking on it brings up a UI to provide the parameters it needs, just like it does for tools.




I put in 5, click on "Read Resource" - and success!

I go back into Claude - but unfortunately, I don't see the new dynamic template! I think this is a case of the SDK team getting ahead of the product team, and I submitted a ticket to find out what exactly is implemented in Claude. No answer yet!


End of Part 1

In this article, we explored MCP Tools and Resources, as well as the Inspector. We used Claude to invoke functionality we built. One can argue that being able to use the Perplexity APIs within Claude is a potentially useful tool, illustrating some of the possibilities for bringing information from third parties.

Very powerful as well is the use of the filesystem MCP server which allows Claude to generate design artifacts and code directly into our project folders. This is a game-changer for using Claude projects as an AI Coding front end, which is the best way to get accurate and useful designs and code from Claude.

There is still a place for tools like Cline, Cursor and github Copilot, but compared to driving the process from within Claude's full project context, they can seem wastefully iterating to find the right files to work on and do more complex refactorings efficiently. It's also faster and with a capped price, rather than paying by the token.

In part 2, I will talk about another MCP server I call Dev Prompter, that focuses on the prompts functionality.

Finally, in part 3 I will discuss the samplings functionality. Unfortunately, based on my experimenting, it seems that while Inspector implements the full Sampling workflow, Claude does not for not. So part 3 may have to way a few weeks until Claude Desktop catches up.

Stay tuned!


As a software pro in 2024, Martin Béchard believes it would be irresponsible to not see what exactly an AI Coding assistant can bring to real development, especially now that they are starting to integrate better with other systems




要查看或添加评论,请登录

Martin Bechard的更多文章

  • Reasonings found in a bathtub

    Reasonings found in a bathtub

    Since the end of 2024, the latest evolution of Large Language Models is dominated by so-called Reasoning models, with…

  • ClaudePS: A Prompting Tool for Claude Sonnet

    ClaudePS: A Prompting Tool for Claude Sonnet

    If you are, like me, an extensive user of Claude Sonnet 3.5, you create multiple projects, each having dozens of…

  • Architecting a Queuing Solution With Claude Sonnet 3.5

    Architecting a Queuing Solution With Claude Sonnet 3.5

    The other day, I did some Yak shaving. I had a little problem which, upon reflection, turned into a big problem with…

    2 条评论
  • Cline - New (Old) Kid in Town

    Cline - New (Old) Kid in Town

    There's a new AI Codeslinger in town called Cline. Born ClaudeDev, Cline got a name change for marketing reasons.

  • Perplexity vs. OpenAI: Battle of the AI Search Titans

    Perplexity vs. OpenAI: Battle of the AI Search Titans

    Earlier today I saw that OpenAI posted on LinkedIn that it had released its much-vaunted "AI Search" which had been in…

  • Building Swarm-JS (Part 1)

    Building Swarm-JS (Part 1)

    Recently Anthropic released Swarm, an "Agentic" open-source framework in python. As the README says: An educational…

  • Putting the "New" Claude Sonnet 3.5 through its paces

    Putting the "New" Claude Sonnet 3.5 through its paces

    I was recently hitting the limitations on Claude Sonnet's output on a regular basis, as part of getting Claude to…

    1 条评论
  • Perplexity: Secret Agent Man

    Perplexity: Secret Agent Man

    Perplexity, the leading AI search engine that is becoming the new Google for AI-savvy searchers, is getting on the…

  • Anthropic Claude's Computer Use Demo is Worth Seeing

    Anthropic Claude's Computer Use Demo is Worth Seeing

    Anthropic just released a new version of Claude Sonnet 3.5 with "Computer Use", intended to allow Claude to take…

  • An (AI) Diagram is Worth a Thousand Words

    An (AI) Diagram is Worth a Thousand Words

    If like me you've been using Claude Projects for text analysis and generation, you will rapidly create multiple related…

    4 条评论

社区洞察

其他会员也浏览了