> ## Documentation Index
> Fetch the complete documentation index at: https://mcp.vyagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

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