# Tutorials

# building-a-client-node

<Tab title="Node"> \## System Requirements

Before starting, ensure your system meets these requirements: - Mac or Windows computer - Node.js version 16 or higher installed - npm (comes with Node.js)

## Setting Up Your Environment

First, create a new Node.js project:

``` bash
# Create project directory
mkdir mcp-client
cd mcp-client

# Initialize npm project
npm init -y

# Install dependencies
npm install @modelcontextprotocol/sdk @anthropic-ai/sdk dotenv
npm install -D typescript @types/node

# Create TypeScript config
npx tsc --init

# Create necessary files
mkdir src
touch src/client.ts
touch .env
```

Update your `package.json` to add necessary configuration:

``` json
{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node build/client.js"
  }
}
```

Update your `tsconfig.json` with appropriate settings:

``` json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}
```

## Setting Up Your API Key

You’ll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).

Create a `.env` file:

``` bash
ANTHROPIC_API_KEY=your_key_here
```

Add `.env` to your `.gitignore`:

``` bash
echo ".env" >> .gitignore
```

## Creating the Client

First, let’s set up our imports and create the basic client class in `src/client.ts`:

``` typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import Anthropic from "@anthropic-ai/sdk";
import dotenv from "dotenv";
import {
  CallToolResultSchema,
  ListToolsResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
import * as readline from "node:readline";

dotenv.config();

interface MCPClientConfig {
  name?: string;
  version?: string;
}

class MCPClient {
  private client: Client | null = null;
  private anthropic: Anthropic;
  private transport: StdioClientTransport | null = null;

  constructor(config: MCPClientConfig = {}) {
    this.anthropic = new Anthropic();
  }

  // Methods will go here
}
```

## Server Connection Management

Next, implement the method to connect to an MCP server:

``` typescript
  async connectToServer(serverScriptPath: string): Promise<void> {
    const isPython = serverScriptPath.endsWith(".py");
    const isJs = serverScriptPath.endsWith(".js");

    if (!isPython && !isJs) {
      throw new Error("Server script must be a .py or .js file");
    }

    const command = isPython ? "python" : "node";

    this.transport = new StdioClientTransport({
      command,
      args: [serverScriptPath],
    });

    this.client = new Client(
      {
        name: "mcp-client",
        version: "1.0.0",
      },
      {
        capabilities: {},
      }
    );

    await this.client.connect(this.transport);

    // List available tools
    const response = await this.client.request(
      { method: "tools/list" },
      ListToolsResultSchema
    );

    console.log(
      "\nConnected to server with tools:",
      response.tools.map((tool: any) => tool.name)
    );
  }
```

## Query Processing Logic

Now add the core functionality for processing queries and handling tool calls:

``` typescript
  async processQuery(query: string): Promise<string> {
    if (!this.client) {
      throw new Error("Client not connected");
    }

    // Initialize messages array with user query
    let messages: Anthropic.MessageParam[] = [
      {
        role: "user",
        content: query,
      },
    ];

    // Get available tools
    const toolsResponse = await this.client.request(
      { method: "tools/list" },
      ListToolsResultSchema
    );

    const availableTools = toolsResponse.tools.map((tool: any) => ({
      name: tool.name,
      description: tool.description,
      input_schema: tool.inputSchema,
    }));

    const finalText: string[] = [];
    let currentResponse = await this.anthropic.messages.create({
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1000,
      messages,
      tools: availableTools,
    });

    // Process the response and any tool calls
    while (true) {
      // Add Claude's response to final text and messages
      for (const content of currentResponse.content) {
        if (content.type === "text") {
          finalText.push(content.text);
        } else if (content.type === "tool_use") {
          const toolName = content.name;
          const toolArgs = content.input;

          // Execute tool call
          const result = await this.client.request(
            {
              method: "tools/call",
              params: {
                name: toolName,
                arguments: toolArgs,
              },
            },
            CallToolResultSchema
          );

          finalText.push(
            `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
          );

          // Add Claude's response (including tool use) to messages
          messages.push({
            role: "assistant",
            content: currentResponse.content,
          });

          // Add tool result to messages
          messages.push({
            role: "user",
            content: [
              {
                type: "tool_result",
                tool_use_id: content.id,
                content: [
                  { type: "text", text: JSON.stringify(result.content) },
                ],
              },
            ],
          });

          // Get next response from Claude with tool results
          currentResponse = await this.anthropic.messages.create({
            model: "claude-3-5-sonnet-20241022",
            max_tokens: 1000,
            messages,
            tools: availableTools,
          });

          // Add Claude's interpretation of the tool results to final text
          if (currentResponse.content[0]?.type === "text") {
            finalText.push(currentResponse.content[0].text);
          }

          // Continue the loop to process any additional tool calls
          continue;
        }
      }

      // If we reach here, there were no tool calls in the response
      break;
    }

    return finalText.join("\n");
  }
```

## Interactive Chat Interface

Add the chat loop and cleanup functionality:

``` typescript
  async chatLoop(): Promise<void> {
    console.log("\nMCP Client Started!");
    console.log("Type your queries or 'quit' to exit.");

    // Using Node's readline for console input
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    const askQuestion = () => {
      rl.question("\nQuery: ", async (query: string) => {
        try {
          if (query.toLowerCase() === "quit") {
            await this.cleanup();
            rl.close();
            return;
          }

          const response = await this.processQuery(query);
          console.log("\n" + response);
          askQuestion();
        } catch (error) {
          console.error("\nError:", error);
          askQuestion();
        }
      });
    };

    askQuestion();
  }

  async cleanup(): Promise<void> {
    if (this.transport) {
      await this.transport.close();
    }
  }
```

## Main Entry Point

Finally, add the main execution logic outside the class:

``` typescript
// Main execution
async function main() {
  if (process.argv.length < 3) {
    console.log("Usage: ts-node client.ts <path_to_server_script>");
    process.exit(1);
  }

  const client = new MCPClient();
  try {
    await client.connectToServer(process.argv[2]);
    await client.chatLoop();
  } catch (error) {
    console.error("Error:", error);
    await client.cleanup();
    process.exit(1);
  }
}

// Run main if this is the main module
if (import.meta.url === new URL(process.argv[1], "file:").href) {
  main();
}

export default MCPClient;
```

## Running the Client

To run your client with any MCP server:

``` bash
# Build the TypeScript code. Make sure to rerun this every time you update `client.ts`!
npm run build

# Run the client
node build/client.js path/to/server.py  # for Python servers
node build/client.js path/to/server.js  # for Node.js servers
```

The client will: 1. Connect to the specified server 2. List available tools 3. Start an interactive chat session where you can: - Enter queries - See tool executions - Get responses from Claude

## Key Components Explained

#### 1. Client Initialization

- The `MCPClient` class initializes with session management and API clients
- Sets up the MCP client with basic capabilities
- Configures the Anthropic client for Claude interactions

#### 2. Server Connection

- Supports both Python and Node.js servers
- Validates server script type
- Sets up proper communication channels
- Lists available tools on connection

#### 3. Query Processing

- Maintains conversation context
- Handles Claude’s responses and tool calls
- Manages the message flow between Claude and tools
- Combines results into a coherent response

#### 4. Interactive Interface

- Provides a simple command-line interface
- Handles user input and displays responses
- Includes basic error handling
- Allows graceful exit

#### 5. Resource Management

- Proper cleanup of resources
- Error handling for connection issues
- Graceful shutdown procedures

### Common Customization Points

1.  **Tool Handling**
    - Modify `processQuery()` to handle specific tool types
    - Add custom error handling for tool calls
    - Implement tool-specific response formatting
2.  **Response Processing**
    - Customize how tool results are formatted
    - Add response filtering or transformation
    - Implement custom logging
3.  **User Interface**
    - Add a GUI or web interface
    - Implement rich console output
    - Add command history or auto-completion

### Best Practices

1.  **Error Handling**
    - Always wrap tool calls in try-catch blocks
    - Provide meaningful error messages
    - Gracefully handle connection issues
2.  **Resource Management**
    - Use proper cleanup methods
    - Close connections when done
    - Handle server disconnections
3.  **Security**
    - Store API keys securely in `.env`
    - Validate server responses
    - Be cautious with tool permissions

### Troubleshooting

#### Server Path Issues

- Double-check the path to your server script
- Use absolute paths if relative paths aren’t working
- For Windows users, use forward slashes (/) or escaped backslashes (\\)
- Verify the server file has the correct extension (.py or .js)

Example of correct path usage:

``` bash
# Relative path
node build/client.js ./server/weather.js

# Absolute path
node build/client.js /Users/username/projects/mcp-server/weather.js

# Windows path (either format works)
node build/client.js C:/projects/mcp-server/weather.js
node build/client.js C:\\projects\\mcp-server\\weather.js
```

#### Connection Issues

- Verify the server script exists and has correct permissions
- Check that the server script is executable
- Ensure the server script’s dependencies are installed
- Try running the server script directly to check for errors

#### Tool Execution Issues

- Check server logs for error messages
- Verify tool input arguments match the schema
- Ensure tool dependencies are available
- Add debug logging to track execution flow </Tab>

# <!-- page slug: mcp-docs-tutorials-building-mcp-with-llms | order: 12 -->

title: “Building MCP with LLMs” description: “Speed up your MCP development using LLMs such as Claude!” —

This guide will help you use LLMs to help you build custom Model Context Protocol (MCP) servers and clients. We’ll be focusing on Claude for this tutorial, but you can do this with any frontier LLM.

## Preparing the documentation

Before starting, gather the necessary documentation to help Claude understand MCP:

1.  Visit https://modelcontextprotocol.io/llms-full.txt and copy the full documentation text
2.  Navigate to either the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) or [Python SDK repository](https://github.com/modelcontextprotocol/python-sdk)
3.  Copy the README files and other relevant documentation
4.  Paste these documents into your conversation with Claude

## Describing your server

Once you’ve provided the documentation, clearly describe to Claude what kind of server you want to build. Be specific about:

- What resources your server will expose
- What tools it will provide
- Any prompts it should offer
- What external systems it needs to interact with

For example:

    Build an MCP server that:
    - Connects to my company's PostgreSQL database
    - Exposes table schemas as resources
    - Provides tools for running read-only SQL queries
    - Includes prompts for common data analysis tasks

## Working with Claude

When working with Claude on MCP servers:

1.  Start with the core functionality first, then iterate to add more features
2.  Ask Claude to explain any parts of the code you don’t understand
3.  Request modifications or improvements as needed
4.  Have Claude help you test the server and handle edge cases

Claude can help implement all the key MCP features:

- Resource management and exposure
- Tool definitions and implementations
- Prompt templates and handlers
- Error handling and logging
- Connection and transport setup

## Best practices

When building MCP servers with Claude:

- Break down complex servers into smaller pieces
- Test each component thoroughly before moving on
- Keep security in mind - validate inputs and limit access appropriately
- Document your code well for future maintenance
- Follow MCP protocol specifications carefully

## Next steps

After Claude helps you build your server:

1.  Review the generated code carefully
2.  Test the server with the MCP Inspector tool
3.  Connect it to Claude.app or other MCP clients
4.  Iterate based on real usage and feedback

Remember that Claude can help you modify and improve your server as requirements change over time.

Need more guidance? Just ask Claude specific questions about implementing MCP features or troubleshooting issues that arise.