Last active
March 29, 2025 18:25
-
-
Save Sefy-Tofan/63cf27b855d8a2a5553684a7f0b4333f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Universal Guide to Building Model Context Protocol (MCP) Servers | |
| This guide provides comprehensive instructions for building and connecting Model Context Protocol (MCP) servers to integrate any external API or service with large language models like Claude. | |
| ## What is an MCP? | |
| The Model Context Protocol (MCP) is a standardized way for large language models (LLMs) like Claude to communicate with external tools and services. MCPs enable LLMs to: | |
| - Access real-time data from APIs | |
| - Control external services and systems | |
| - Retrieve specialized information | |
| - Perform actions the LLM cannot do directly | |
| By building an MCP server, you create a bridge between the LLM and any external service you want to make available to it. | |
| ## MCP Architecture Overview | |
| ``` | |
| ┌────────────┐ ┌────────────┐ ┌────────────┐ | |
| │ │ HTTP/ │ │ Your │ │ | |
| │ LLM │◄────────►│ MCP Server │◄────────►│ External │ | |
| │ (Claude) │ stdio │ (Node.js) │ API │ Service │ | |
| │ │ │ │ Calls │ │ | |
| └────────────┘ └────────────┘ └────────────┘ | |
| ``` | |
| ## Project Structure Template | |
| ``` | |
| your-mcp-server/ | |
| ├── src/ | |
| │ ├── main.ts # MCP server setup and tool definitions | |
| │ └── service-api.ts # API client for your service | |
| ├── .env # Environment variables (API keys, etc.) | |
| ├── package.json # Project dependencies | |
| ├── tsconfig.json # TypeScript configuration | |
| └── README.md # Project documentation | |
| ``` | |
| ## Step 1: Set Up the Project | |
| ### Initialize the Project | |
| ```bash | |
| # Create project directory | |
| mkdir your-mcp-server | |
| cd your-mcp-server | |
| # Initialize npm project | |
| npm init -y | |
| ``` | |
| Edit the `package.json` to include: | |
| ```json | |
| { | |
| "name": "your-mcp-server", | |
| "version": "1.0.0", | |
| "main": "index.js", | |
| "type": "module", | |
| "scripts": { | |
| "start": "tsx src/main.ts", | |
| "dev": "tsx watch src/main.ts", | |
| "build": "tsc" | |
| }, | |
| "keywords": [ | |
| "mcp", | |
| "claude", | |
| "api-integration" | |
| ], | |
| "license": "MIT" | |
| } | |
| ``` | |
| ### Install Dependencies | |
| ```bash | |
| # Core dependencies | |
| npm install @modelcontextprotocol/sdk dotenv tsx typescript zod | |
| # Development dependencies | |
| npm install --save-dev @types/node | |
| ``` | |
| ### Create TypeScript Configuration | |
| Create a `tsconfig.json` file: | |
| ```json | |
| { | |
| "compilerOptions": { | |
| "target": "ESNext", | |
| "module": "NodeNext", | |
| "moduleResolution": "NodeNext", | |
| "esModuleInterop": true, | |
| "forceConsistentCasingInFileNames": true, | |
| "strict": true, | |
| "skipLibCheck": true, | |
| "outDir": "dist" | |
| }, | |
| "include": ["src/**/*"] | |
| } | |
| ``` | |
| ## Step 2: Implement the API Client | |
| Create `src/service-api.ts` with methods to interact with your external API. Here's a template: | |
| ```typescript | |
| /** | |
| * Service API Client | |
| * | |
| * This module provides methods to interact with your external API service. | |
| */ | |
| // Define interfaces for your API responses | |
| export interface ApiResponse { | |
| // Add properties based on your API response | |
| } | |
| export class ServiceApiClient { | |
| private apiKey: string; | |
| private baseUrl: string; | |
| constructor(apiKey: string, baseUrl: string) { | |
| this.apiKey = apiKey; | |
| this.baseUrl = baseUrl; | |
| // Validate API key if needed | |
| if (!apiKey) { | |
| console.error("API Key is empty or not provided!"); | |
| } | |
| } | |
| /** | |
| * Example method - replace with methods specific to your API | |
| */ | |
| async exampleMethod(param1: string, param2: number): Promise<ApiResponse> { | |
| try { | |
| const url = `${this.baseUrl}/endpoint`; | |
| const response = await fetch(url, { | |
| method: "POST", // or GET, PUT, DELETE as needed | |
| headers: { | |
| "Authorization": `Bearer ${this.apiKey}`, | |
| "Content-Type": "application/json", | |
| "Accept": "application/json" | |
| }, | |
| body: JSON.stringify({ | |
| param1, | |
| param2 | |
| }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`API request failed: ${response.status} ${response.statusText}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error("API request error:", error); | |
| throw error; | |
| } | |
| } | |
| // Add more methods as needed for your API | |
| } | |
| ``` | |
| ## Step 3: Create the MCP Server | |
| Create `src/main.ts` to set up the MCP server and define tools: | |
| ```typescript | |
| // Load environment variables from .env file | |
| import * as dotenv from 'dotenv'; | |
| dotenv.config(); | |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | |
| import { z } from "zod"; | |
| import { ServiceApiClient } from "./service-api.js"; | |
| // Create a new MCP server | |
| const server = new McpServer({ | |
| name: "Your Service Name", | |
| version: "1.0.0", | |
| }); | |
| // Get API key from environment variables | |
| const API_KEY = process.env.YOUR_API_KEY || ""; | |
| const BASE_URL = process.env.YOUR_API_BASE_URL || "https://api.example.com"; | |
| // Initialize the API client | |
| const apiClient = new ServiceApiClient(API_KEY, BASE_URL); | |
| // Example Tool - replace with your own tools | |
| server.tool( | |
| "exampleTool", | |
| { | |
| // Define parameters with Zod schema | |
| param1: z.string().describe("Description of parameter 1"), | |
| param2: z.number().optional().describe("Description of optional parameter 2"), | |
| }, | |
| async ({ param1, param2 = 0 }) => { | |
| if (!API_KEY) { | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: "Error: API key not set. Please set the YOUR_API_KEY environment variable.", | |
| }, | |
| ], | |
| }; | |
| } | |
| try { | |
| const result = await apiClient.exampleMethod(param1, param2); | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: JSON.stringify(result, null, 2), | |
| }, | |
| ], | |
| }; | |
| } catch (error) { | |
| console.error("Error in exampleTool:", error); | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Error: ${error instanceof Error ? error.message : String(error)}`, | |
| }, | |
| ], | |
| }; | |
| } | |
| } | |
| ); | |
| // Define more tools as needed | |
| // Optional: Add a test function for local testing | |
| async function testFunction() { | |
| if (!API_KEY) { | |
| console.error("Error: API key not set. Please set the YOUR_API_KEY environment variable."); | |
| process.exit(1); | |
| } | |
| try { | |
| // Add test logic here | |
| const result = await apiClient.exampleMethod("test", 123); | |
| console.log("Test result:", JSON.stringify(result, null, 2)); | |
| } catch (error) { | |
| console.error("Test error:", error); | |
| } | |
| process.exit(0); | |
| } | |
| // Check if the script was run with the --test flag | |
| if (process.argv.includes('--test')) { | |
| testFunction(); | |
| } else { | |
| // Connect the server to standard I/O for communication with Claude | |
| const transport = new StdioServerTransport(); | |
| server.connect(transport).then(() => { | |
| console.log("MCP server started and connected to transport"); | |
| }).catch((error) => { | |
| console.error("Error connecting to transport:", error); | |
| }); | |
| } | |
| ``` | |
| ## Step 4: Set Up Environment Variables | |
| Create a `.env` file in the root directory: | |
| ``` | |
| YOUR_API_KEY=your-api-key-here | |
| YOUR_API_BASE_URL=https://api.example.com | |
| ``` | |
| ## Step 5: Common Response Formats for MCP Tools | |
| MCP tools must return responses in specific formats. Here are common patterns: | |
| ### Text Response | |
| ```typescript | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: "Your text response here", | |
| }, | |
| ], | |
| }; | |
| ``` | |
| ### JSON Response (as text) | |
| ```typescript | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: JSON.stringify(dataObject, null, 2), | |
| }, | |
| ], | |
| }; | |
| ``` | |
| ### Image Response | |
| ```typescript | |
| return { | |
| content: [ | |
| { | |
| type: "image", | |
| data: base64EncodedImageData, | |
| mimeType: "image/jpeg", // or png, gif, etc. | |
| }, | |
| ], | |
| }; | |
| ``` | |
| ### Mixed Content Response | |
| ```typescript | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: "Here's an explanation", | |
| }, | |
| { | |
| type: "image", | |
| data: base64EncodedImageData, | |
| mimeType: "image/jpeg", | |
| }, | |
| ], | |
| }; | |
| ``` | |
| ## Step 6: Running the Server | |
| To start the server: | |
| ```bash | |
| # Install dependencies | |
| npm install | |
| # Run the server | |
| npm start | |
| ``` | |
| Testing locally: | |
| ```bash | |
| # Test with the --test flag | |
| npx tsx src/main.ts --test | |
| ``` | |
| ## Step 7: Connecting to Claude | |
| ### Setting Up the MCP Configuration | |
| To connect your MCP server to Claude, you need to create an `mcp.json` file. | |
| #### Windows Configuration | |
| Create a file at `%LOCALAPPDATA%\Claude\MCPs\mcp.json`: | |
| ```json | |
| { | |
| "mcps": [ | |
| { | |
| "name": "your-mcp-name", | |
| "command": "npx tsx \"C:/path/to/your-mcp-server/src/main.ts\"", | |
| "toolNames": ["exampleTool", "otherTool"] | |
| } | |
| ] | |
| } | |
| ``` | |
| #### macOS Configuration | |
| Create a file at `~/Library/Application Support/Claude/MCPs/mcp.json`: | |
| ```json | |
| { | |
| "mcps": [ | |
| { | |
| "name": "your-mcp-name", | |
| "command": "npx tsx \"/path/to/your-mcp-server/src/main.ts\"", | |
| "toolNames": ["exampleTool", "otherTool"] | |
| } | |
| ] | |
| } | |
| ``` | |
| Replace `/path/to/your-mcp-server` with the absolute path to your project directory. | |
| ### Using the Claude MCP CLI (Alternative) | |
| If you have Claude's MCP CLI installed, you can add the MCP with: | |
| ```bash | |
| # Windows (PowerShell) | |
| claude mcp add "your-mcp-name" "npx tsx 'C:/path/to/your-mcp-server/src/main.ts'" | |
| # macOS/Linux | |
| claude mcp add "your-mcp-name" "npx tsx '/path/to/your-mcp-server/src/main.ts'" | |
| ``` | |
| ## Step 8: Best Practices for MCP Development | |
| ### 1. Error Handling | |
| Always include robust error handling in your MCP tools: | |
| ```typescript | |
| try { | |
| // Your code here | |
| } catch (error) { | |
| console.error("Error details:", error); | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: `An error occurred: ${error instanceof Error ? error.message : String(error)}`, | |
| }, | |
| ], | |
| }; | |
| } | |
| ``` | |
| ### 2. Input Validation | |
| Use Zod to validate inputs and provide clear error messages: | |
| ```typescript | |
| // In your tool definition | |
| { | |
| username: z.string().min(3).describe("Username (minimum 3 characters)"), | |
| age: z.number().min(0).max(120).optional().describe("Age (0-120, optional)"), | |
| } | |
| ``` | |
| ### 3. Documentation | |
| Document your tools clearly for Claude to understand how to use them: | |
| ```typescript | |
| server.tool( | |
| "searchData", | |
| { | |
| query: z.string().describe("Search query string"), | |
| limit: z.number().min(1).max(100).optional().describe("Maximum number of results (1-100)"), | |
| sortBy: z.enum(["relevance", "date", "popularity"]).optional().describe("Sort order for results"), | |
| }, | |
| async ({ query, limit = 10, sortBy = "relevance" }) => { | |
| // Implementation | |
| } | |
| ); | |
| ``` | |
| ### 4. Secure Authentication | |
| Never hardcode API keys. Use environment variables and handle authentication securely: | |
| ```typescript | |
| // In .env file | |
| API_KEY=your-secret-key | |
| // In your code | |
| const API_KEY = process.env.API_KEY; | |
| if (!API_KEY) { | |
| throw new Error("API key not found in environment variables"); | |
| } | |
| ``` | |
| ### 5. Logging | |
| Implement appropriate logging for debugging: | |
| ```typescript | |
| console.log("Info message"); // Basic info | |
| console.error("Error message"); // Errors | |
| console.debug("Debug message"); // Detailed debugging (use sparingly) | |
| ``` | |
| ## Step 9: Advanced MCP Features | |
| ### 1. Streaming Responses | |
| For long-running operations, you can implement a status check pattern: | |
| ```typescript | |
| server.tool( | |
| "startLongProcess", | |
| { /* parameters */ }, | |
| async (params) => { | |
| // Start the process and return an ID | |
| const processId = await startBackgroundProcess(params); | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Process started with ID: ${processId}. Check status with checkProcessStatus tool.`, | |
| }, | |
| ], | |
| }; | |
| } | |
| ); | |
| server.tool( | |
| "checkProcessStatus", | |
| { processId: z.string() }, | |
| async ({ processId }) => { | |
| const status = await getProcessStatus(processId); | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Status: ${status.state}. Progress: ${status.progress}%.`, | |
| }, | |
| ], | |
| }; | |
| } | |
| ); | |
| ``` | |
| ### 2. File Operations | |
| If your tool needs to work with files: | |
| ```typescript | |
| import * as fs from 'fs'; | |
| import * as path from 'path'; | |
| // In your tool implementation | |
| const outputPath = path.join(process.cwd(), "output.json"); | |
| fs.writeFileSync(outputPath, JSON.stringify(data, null, 2)); | |
| return { | |
| content: [ | |
| { | |
| type: "text", | |
| text: `Data saved to ${outputPath}`, | |
| }, | |
| ], | |
| }; | |
| ``` | |
| ## Troubleshooting | |
| ### Connection Issues | |
| - **MCP not found**: Verify the path in your mcp.json is correct and absolute | |
| - **Error starting MCP**: Check that Node.js is installed and your code has no syntax errors | |
| - **Tool not available**: Ensure the tool is properly registered and included in the toolNames list | |
| ### API Communication Problems | |
| - **401/403 errors**: Check your API key and permissions | |
| - **Network errors**: Verify internet connection and API endpoint URL | |
| - **Timeout errors**: Consider implementing retry logic for unreliable APIs | |
| ### Data Handling Issues | |
| - **Invalid data**: Implement proper validation and type checking | |
| - **Unexpected formats**: Add debug logging to inspect API responses | |
| - **Missing fields**: Use optional chaining (`?.`) and provide fallback values | |
| ## Resources | |
| - [Model Context Protocol Documentation](https://docs.anthropic.com/claude/docs/model-context-protocol) | |
| - [TypeScript Documentation](https://www.typescriptlang.org/docs/) | |
| - [Zod Documentation](https://zod.dev/) | |
| - [Node.js Documentation](https://nodejs.org/en/docs/) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment