598369e894
- Hook system for customization - Documentation and installation scripts - Multi-platform support via GitHub releases - Binaries available for Windows, Linux (x64/ARM64), macOS (Intel/Apple Silicon) Generated with Claude Code via Happy
6.3 KiB
6.3 KiB
MCP TypeScript SDK Server Implementation Guide
Documentation Source
- SDK Version: @modelcontextprotocol/sdk v1.0.0
- Last Verified: 2025-09-01
- Official Docs: https://github.com/modelcontextprotocol/typescript-sdk
Server Creation Patterns
Low-Level Server vs McpServer
The SDK provides two approaches for creating MCP servers:
- Low-Level Server Class (Used in claude-mem)
- Direct control over request handling
- Manual registration with
setRequestHandler - More flexibility for custom routing logic
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
const server = new Server(
{
name: 'server-name',
version: '1.0.0'
},
{
capabilities: {
tools: {} // Declare tool capability
}
}
);
- High-Level McpServer Class (Alternative approach)
- Simplified API with
registerTool,registerResource,registerPrompt - Automatic routing and validation
- Less boilerplate code
- Simplified API with
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const server = new McpServer({
name: 'server-name',
version: '1.0.0'
});
Tool Handler Registration
Pattern 1: Single Handler with CallToolRequestSchema (claude-mem approach)
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Validate arguments exist
if (!args) {
throw new Error(`No arguments provided for tool: ${name}`);
}
// Route to specific tool implementation
switch (name) {
case 'tool-name':
// Tool implementation
return {
content: [{
type: 'text',
text: 'Result'
}]
};
default:
throw new Error(`Unknown tool: ${name}`);
}
});
Pattern 2: Individual Tool Registration (McpServer approach)
server.registerTool(
'tool-name',
{
title: 'Tool Title',
description: 'Tool description',
inputSchema: { param: z.string() }
},
async ({ param }) => ({
content: [{
type: 'text',
text: `Result for ${param}`
}]
})
);
Stdio Transport Usage
Standard Pattern for CLI-based Servers
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
async function main() {
// 1. Initialize backend services first
await initializeBackend();
// 2. Create transport
const transport = new StdioServerTransport();
// 3. Connect server to transport
await server.connect(transport);
// 4. Log to stderr (stdout is for protocol)
console.error('Server started on stdio');
}
Key Points:
- Stdin: Receives MCP protocol messages
- Stdout: Sends MCP protocol responses
- Stderr: Used for logging and diagnostics
Error Handling Patterns
Tool Error Response
try {
// Tool implementation
return {
content: [{
type: 'text',
text: 'Success result'
}]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Log to stderr for debugging
console.error(`[Error] Tool: ${name}, Error: ${errorMessage}`);
// Return error response with isError flag
return {
content: [{
type: 'text',
text: `Error: ${errorMessage}`
}],
isError: true // Important: Indicates tool failure
};
}
Startup Error Handling
main().catch((error) => {
console.error('Startup error:', error);
process.exit(1); // Exit with error code
});
Response Formatting
Success Response Structure
{
content: [
{
type: 'text',
text: 'Response text'
}
]
}
Error Response Structure
{
content: [
{
type: 'text',
text: 'Error: Description'
}
],
isError: true
}
Resource Link Response
{
content: [
{
type: 'resource_link',
uri: 'file:///path/to/resource',
name: 'Resource Name',
mimeType: 'text/plain',
description: 'Resource description'
}
]
}
Lifecycle Management
Initialization Pattern
async function initializeServer(): Promise<void> {
try {
// Initialize backend connections
await backend.connect();
console.error('Backend initialized');
} catch (error) {
console.error('Initialization failed:', error);
throw error; // Prevent server startup
}
}
Shutdown Pattern
function setupShutdownHandlers(): void {
const handleShutdown = async (signal: string) => {
console.error(`Received ${signal}, shutting down...`);
try {
await backend.disconnect();
process.exit(0); // Clean exit
} catch (error) {
console.error('Shutdown error:', error);
process.exit(1); // Error exit
}
};
process.on('SIGINT', () => handleShutdown('SIGINT'));
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
// Handle unexpected errors
process.on('uncaughtException', async (error) => {
console.error('Uncaught exception:', error);
await backend.disconnect();
process.exit(1);
});
}
Best Practices Summary
-
Server Creation
- Use low-level Server for custom routing
- Use McpServer for standard implementations
-
Transport Usage
- Initialize backends before connecting transport
- Use StdioServerTransport for CLI tools
- Log to stderr, not stdout
-
Error Handling
- Always validate tool arguments
- Include isError flag in error responses
- Log errors to stderr with context
-
Response Format
- Always return content array
- Use consistent type/text structure
- Include isError for failures
-
Lifecycle
- Clean initialization sequence
- Graceful shutdown handlers
- Proper exit codes (0 for success, 1 for error)