What can we help you with?
MCP Server Development
MCP Server Development
Instructions
MCP Protocol Fundamentals
- Core concepts:
- Server: exposes tools, resources, and prompts to AI clients
- Client: an AI application (Claude, Cursor, etc.) that connects to servers
- Tools: functions the AI can invoke with parameters and receive results
- Resources: read-only data the AI can access (files, API responses, database records)
- Prompts: reusable prompt templates the server provides to clients
- Transport: communication layer (stdio for local, HTTP/SSE for remote)
- Protocol flow:
Client → initialize → Server (capabilities exchange)
Client → tools/list → Server (discover available tools)
Client → tools/call → Server (invoke a tool)
Client → resources/list → Server (discover resources)
Client → resources/read → Server (fetch a resource)
Server Implementation (TypeScript)
- Project setup:
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
- Basic server structure:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
server.tool(
"get_weather",
"Get current weather for a location",
{
location: z.string().describe("City name or zip code"),
units: z.enum(["fahrenheit", "celsius"]).default("fahrenheit"),
},
async ({ location, units }) => {
const weather = await fetchWeather(location, units);
return {
content: [{ type: "text", text: JSON.stringify(weather) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
- Resource provider:
server.resource(
"config://app/settings",
"Application settings",
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(await loadSettings()),
}],
})
);
Server Implementation (Python)
- Project setup:
pip install mcp
- Basic server structure:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def get_weather(location: str, units: str = "fahrenheit") -> str:
"""Get current weather for a location."""
weather = fetch_weather(location, units)
return json.dumps(weather)
@mcp.resource("config://app/settings")
def get_settings() -> str:
"""Application settings."""
return json.dumps(load_settings())
- Run with stdio:
python -m mcp.server.stdio my_server:mcp
Tool Schema Design
- Naming conventions:
- Use
snake_casefor tool names:get_weather,create_event,search_documents - Use verb-noun pattern:
get_,create_,update_,delete_,search_,list_ - Be specific:
search_recipesnotsearch
- Description quality:
- First sentence: what the tool does in one line
- Include: when to use this tool, what it returns
- Mention limitations or requirements
- Example: “Search for recipes by ingredient, cuisine, or dietary restriction. Returns up to 10 matching recipes with titles, ingredients, and cook times. Requires at least one search parameter.”
- Parameter design:
- Use descriptive parameter names with
.describe()annotations - Set sensible defaults where possible
- Use enums for constrained values
- Mark truly required parameters as required; make everything else optional
- Keep parameter count under 8; group related params into objects if more
- Return format:
- Return structured data as JSON strings in text content
- For errors, use
isError: truein the response - Include metadata (result count, pagination info) alongside data
- Keep responses under 10KB for optimal client handling
Transport Configuration
Stdio (Local)
- Default for local development and desktop clients
- Server runs as a child process of the client
- Communication via stdin/stdout; stderr for logging
- Client configuration (Cursor/Claude Desktop):
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["path/to/server.js"],
"env": { "API_KEY": "value" }
}
}
}
HTTP + SSE (Remote)
- For remote/shared servers
- HTTP POST for client-to-server messages
- Server-Sent Events for server-to-client messages
- Requires authentication for production use
Authentication
- Local servers (stdio): inherit the user’s environment; no additional auth needed
- Remote servers (HTTP): implement OAuth 2.0 or API key authentication
- API key pattern:
- Accept via environment variable (stdio) or Authorization header (HTTP)
- Never include API keys in tool responses
- OAuth pattern:
- Implement the MCP auth flow for OAuth-capable clients
- Support token refresh for long-running sessions
Testing Strategy
- Unit tests: test each tool function in isolation with mocked dependencies
- Integration tests: use the MCP Inspector tool:
npx @modelcontextprotocol/inspector node path/to/server.js
- Verify tool listing returns correct schemas
- Test each tool with valid and invalid inputs
- Verify error handling for edge cases
- Client compatibility testing:
- Test with Claude Desktop (stdio transport)
- Test with Cursor (stdio transport)
- Test with the MCP Inspector (interactive testing)
- Load testing (for remote servers):
- Concurrent tool calls
- Large response handling
- Connection timeout behavior
Deployment
- Local (stdio):
- Distribute as an npm package or Python package
- Users configure in their client’s MCP settings
- Include clear setup instructions in README
- Remote (HTTP):
- Deploy behind a reverse proxy with TLS
- Implement rate limiting per client
- Monitor server health and tool latency
- Log tool invocations for debugging (exclude sensitive parameters)
- Versioning:
- Semantic versioning for the server package
- Backward-compatible tool changes (new optional params) in minor versions
- Breaking changes (renamed tools, removed params) in major versions
- Document breaking changes in CHANGELOG
Inputs Required
- Tools to expose (name, parameters, return types, data sources)
- Resources to provide (URIs, data sources)
- Transport type (stdio for local, HTTP for remote)
- Authentication requirements
- Target clients (Claude Desktop, Cursor, custom)
- Deployment target (npm package, Docker container, hosted service)
Output Format
MCP Server Project Structure
my-mcp-server/
src/
index.ts — server entry point and transport setup
tools/
weather.ts — weather tool implementation
calendar.ts — calendar tool implementation
resources/
settings.ts — settings resource provider
auth/
oauth.ts — OAuth handler (if remote)
utils/
validation.ts — parameter validation helpers
tests/
tools.test.ts — tool unit tests
integration.test.ts — MCP Inspector-based tests
package.json
tsconfig.json
README.md — setup instructions and tool documentation
Tool Documentation Template
## Tool: get_weather
**Description**: Get current weather for a location.
**Parameters**:
| Name | Type | Required | Description |
|------|------|----------|-------------|
| location | string | yes | City name or zip code |
| units | enum | no | "fahrenheit" (default) or "celsius" |
**Returns**: JSON with temperature, conditions, humidity, wind speed.
**Example**:
Input: { "location": "Atlanta, GA", "units": "fahrenheit" }
Output: { "temperature": 78, "conditions": "Partly cloudy", ... }
Anti-Patterns
- Exposing too many tools — clients have context limits; expose 5–15 focused tools rather than 50 generic ones
- Vague tool descriptions — the AI uses descriptions to decide when to call a tool; vague descriptions cause misuse
- Returning raw API responses — transform data into clean, relevant structures; don’t dump raw third-party API JSON
- Missing error handling — tools that throw unhandled exceptions crash the server; always return structured errors
- Hardcoding credentials in server code — use environment variables; document required env vars in README
- Skipping the MCP Inspector — interactive testing catches issues that unit tests miss; always test with the Inspector
- Mixing stdio logging with protocol output — log to stderr only; stdout is reserved for MCP protocol messages
- Not documenting tool behavior — every tool needs a description, parameter docs, and example in the README
