<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>