Skip to content

Instantly share code, notes, and snippets.

@Sefy-Tofan
Last active March 29, 2025 18:25
Show Gist options
  • Save Sefy-Tofan/63cf27b855d8a2a5553684a7f0b4333f to your computer and use it in GitHub Desktop.
Save Sefy-Tofan/63cf27b855d8a2a5553684a7f0b4333f to your computer and use it in GitHub Desktop.
# 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