From fe2ab948eb8d8940abdb58bae66909713da0ee16 Mon Sep 17 00:00:00 2001 From: Zachary Lee Date: Tue, 10 Dec 2024 18:32:39 -0500 Subject: [PATCH] moved out node for now --- tutorials/building-a-client-node.mdx | 463 +++++++++++++++++++++++++++ tutorials/building-a-client.mdx | 462 -------------------------- 2 files changed, 463 insertions(+), 462 deletions(-) create mode 100644 tutorials/building-a-client-node.mdx diff --git a/tutorials/building-a-client-node.mdx b/tutorials/building-a-client-node.mdx new file mode 100644 index 0000000..9cc5ccc --- /dev/null +++ b/tutorials/building-a-client-node.mdx @@ -0,0 +1,463 @@ + +## 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 { Tool } from "@anthropic-ai/sdk/resources/messages.js"; +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 { + 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 { + 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: Tool[] = 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, + args: 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 { + 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 { + 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 "); + 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 + \ No newline at end of file diff --git a/tutorials/building-a-client.mdx b/tutorials/building-a-client.mdx index ac0f642..7e1b5dc 100644 --- a/tutorials/building-a-client.mdx +++ b/tutorials/building-a-client.mdx @@ -387,469 +387,7 @@ If you see: - `Timeout error`: Consider increasing the timeout in your client configuration - -## 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 { Tool } from "@anthropic-ai/sdk/resources/messages.js"; -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 { - 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 { - 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: Tool[] = 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, - args: 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 { - 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 { - 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 "); - 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 - ## Next steps