Initial release v3.3.8
- 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
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,286 @@
|
||||
# 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:
|
||||
|
||||
1. **Low-Level Server Class** (Used in claude-mem)
|
||||
- Direct control over request handling
|
||||
- Manual registration with `setRequestHandler`
|
||||
- More flexibility for custom routing logic
|
||||
|
||||
```typescript
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: 'server-name',
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {} // Declare tool capability
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
2. **High-Level McpServer Class** (Alternative approach)
|
||||
- Simplified API with `registerTool`, `registerResource`, `registerPrompt`
|
||||
- Automatic routing and validation
|
||||
- Less boilerplate code
|
||||
|
||||
```typescript
|
||||
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)
|
||||
|
||||
```typescript
|
||||
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)
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
main().catch((error) => {
|
||||
console.error('Startup error:', error);
|
||||
process.exit(1); // Exit with error code
|
||||
});
|
||||
```
|
||||
|
||||
## Response Formatting
|
||||
|
||||
### Success Response Structure
|
||||
|
||||
```typescript
|
||||
{
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Response text'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response Structure
|
||||
|
||||
```typescript
|
||||
{
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Error: Description'
|
||||
}
|
||||
],
|
||||
isError: true
|
||||
}
|
||||
```
|
||||
|
||||
### Resource Link Response
|
||||
|
||||
```typescript
|
||||
{
|
||||
content: [
|
||||
{
|
||||
type: 'resource_link',
|
||||
uri: 'file:///path/to/resource',
|
||||
name: 'Resource Name',
|
||||
mimeType: 'text/plain',
|
||||
description: 'Resource description'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle Management
|
||||
|
||||
### Initialization Pattern
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
1. **Server Creation**
|
||||
- Use low-level Server for custom routing
|
||||
- Use McpServer for standard implementations
|
||||
|
||||
2. **Transport Usage**
|
||||
- Initialize backends before connecting transport
|
||||
- Use StdioServerTransport for CLI tools
|
||||
- Log to stderr, not stdout
|
||||
|
||||
3. **Error Handling**
|
||||
- Always validate tool arguments
|
||||
- Include isError flag in error responses
|
||||
- Log errors to stderr with context
|
||||
|
||||
4. **Response Format**
|
||||
- Always return content array
|
||||
- Use consistent type/text structure
|
||||
- Include isError for failures
|
||||
|
||||
5. **Lifecycle**
|
||||
- Clean initialization sequence
|
||||
- Graceful shutdown handlers
|
||||
- Proper exit codes (0 for success, 1 for error)
|
||||
|
||||
## References
|
||||
|
||||
- [MCP TypeScript SDK README](https://github.com/modelcontextprotocol/typescript-sdk)
|
||||
- [Low-Level Server Pattern](https://github.com/modelcontextprotocol/typescript-sdk#low-level-server-implementation)
|
||||
- [Stdio Transport Example](https://github.com/modelcontextprotocol/typescript-sdk#stdio-transport)
|
||||
- [Error Handling Examples](https://github.com/modelcontextprotocol/typescript-sdk#sqlite-explorer)
|
||||
@@ -0,0 +1,345 @@
|
||||
# MCP TypeScript SDK Stdio Transport Guide
|
||||
|
||||
## Documentation Source
|
||||
- **SDK Version**: @modelcontextprotocol/sdk v1.0.0
|
||||
- **Last Verified**: 2025-09-01
|
||||
- **Official Docs**: https://github.com/modelcontextprotocol/typescript-sdk
|
||||
|
||||
## Stdio Transport Overview
|
||||
|
||||
The StdioServerTransport enables MCP servers to communicate via standard input/output streams, making them ideal for CLI tools and direct integrations with Claude Code.
|
||||
|
||||
## Communication Channels
|
||||
|
||||
### Stream Usage
|
||||
- **stdin**: Receives MCP protocol messages (JSON-RPC)
|
||||
- **stdout**: Sends MCP protocol responses (JSON-RPC)
|
||||
- **stderr**: Logging and diagnostic output
|
||||
|
||||
### Important Rules
|
||||
1. **Never write non-protocol data to stdout** - This will break the protocol
|
||||
2. **Always use console.error() for logging** - Goes to stderr
|
||||
3. **Handle binary data carefully** - Protocol is text-based JSON
|
||||
|
||||
## Implementation Patterns
|
||||
|
||||
### Basic Stdio Server Setup
|
||||
|
||||
```typescript
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
|
||||
// Create server
|
||||
const server = new Server(
|
||||
{ name: 'my-server', version: '1.0.0' },
|
||||
{ capabilities: { tools: {} } }
|
||||
);
|
||||
|
||||
// Create and connect transport
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
// Server is now listening on stdin/stdout
|
||||
console.error('Server started'); // Note: console.error for logging
|
||||
```
|
||||
|
||||
### With McpServer (High-Level API)
|
||||
|
||||
```typescript
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'my-server',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
// Register tools, resources, prompts...
|
||||
server.registerTool(...);
|
||||
|
||||
// Connect to stdio
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
```
|
||||
|
||||
## CLI Entry Point Pattern
|
||||
|
||||
### Proper Module Detection (ES Modules)
|
||||
|
||||
```typescript
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Only run if executed directly
|
||||
if (process.argv[1] === __filename ||
|
||||
process.argv[1].endsWith('server.js')) {
|
||||
main().catch((error) => {
|
||||
console.error('Startup error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Main Function Pattern
|
||||
|
||||
```typescript
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
// 1. Initialize dependencies
|
||||
await initializeDatabase();
|
||||
|
||||
// 2. Create server
|
||||
const server = createServer();
|
||||
|
||||
// 3. Create transport
|
||||
const transport = new StdioServerTransport();
|
||||
|
||||
// 4. Connect
|
||||
await server.connect(transport);
|
||||
|
||||
// 5. Setup shutdown handlers
|
||||
setupShutdownHandlers();
|
||||
|
||||
// 6. Log readiness to stderr
|
||||
console.error('MCP server ready on stdio');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Shutdown Handling
|
||||
|
||||
### Graceful Shutdown Pattern
|
||||
|
||||
```typescript
|
||||
function setupShutdownHandlers(): void {
|
||||
const shutdown = async (signal: string) => {
|
||||
console.error(`\nReceived ${signal}, shutting down...`);
|
||||
|
||||
try {
|
||||
// Clean up resources
|
||||
await cleanupResources();
|
||||
|
||||
// Note: Transport cleanup is handled automatically
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Shutdown error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle termination signals
|
||||
process.on('SIGINT', () => shutdown('SIGINT')); // Ctrl+C
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM')); // Kill
|
||||
process.on('SIGHUP', () => shutdown('SIGHUP')); // Terminal closed
|
||||
|
||||
// Handle errors
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Uncaught exception:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error('Unhandled rejection:', reason);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Logging Best Practices
|
||||
|
||||
### Do's and Don'ts
|
||||
|
||||
```typescript
|
||||
// ✅ DO: Log to stderr
|
||||
console.error('Server initialized');
|
||||
console.error('Processing request:', requestId);
|
||||
console.error('Debug info:', { data });
|
||||
|
||||
// ❌ DON'T: Log to stdout
|
||||
console.log('This breaks the protocol!'); // NEVER DO THIS
|
||||
|
||||
// ✅ DO: Use structured logging to stderr
|
||||
const log = (level: string, message: string, data?: any) => {
|
||||
console.error(JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
level,
|
||||
message,
|
||||
...data
|
||||
}));
|
||||
};
|
||||
|
||||
log('info', 'Server started', { port: 'stdio' });
|
||||
```
|
||||
|
||||
### Debug Mode Pattern
|
||||
|
||||
```typescript
|
||||
const DEBUG = process.env.DEBUG === 'true';
|
||||
|
||||
const debug = (message: string, ...args: any[]) => {
|
||||
if (DEBUG) {
|
||||
console.error(`[DEBUG] ${message}`, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
// Usage
|
||||
debug('Request received:', request);
|
||||
```
|
||||
|
||||
## Testing Stdio Servers
|
||||
|
||||
### Manual Testing
|
||||
|
||||
```bash
|
||||
# Start server and interact manually
|
||||
node dist/server.js
|
||||
|
||||
# With debug logging
|
||||
DEBUG=true node dist/server.js
|
||||
|
||||
# Pipe test input
|
||||
echo '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' | node dist/server.js
|
||||
```
|
||||
|
||||
### Automated Testing Pattern
|
||||
|
||||
```typescript
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
function testServer() {
|
||||
const server = spawn('node', ['dist/server.js']);
|
||||
|
||||
// Capture stderr for logs
|
||||
server.stderr.on('data', (data) => {
|
||||
console.log('Server log:', data.toString());
|
||||
});
|
||||
|
||||
// Capture stdout for protocol
|
||||
let response = '';
|
||||
server.stdout.on('data', (data) => {
|
||||
response += data.toString();
|
||||
// Parse and validate response
|
||||
});
|
||||
|
||||
// Send test request
|
||||
server.stdin.write(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'initialize',
|
||||
params: {},
|
||||
id: 1
|
||||
}));
|
||||
server.stdin.end();
|
||||
}
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue 1: Protocol Corruption
|
||||
|
||||
**Problem**: Random text in stdout breaks communication
|
||||
**Solution**: Always use console.error() for logging
|
||||
|
||||
```typescript
|
||||
// Wrong
|
||||
console.log('Debug:', data); // Breaks protocol
|
||||
|
||||
// Right
|
||||
console.error('Debug:', data); // Safe for debugging
|
||||
```
|
||||
|
||||
### Issue 2: Server Not Responding
|
||||
|
||||
**Problem**: Server starts but doesn't respond to requests
|
||||
**Solution**: Ensure transport is connected
|
||||
|
||||
```typescript
|
||||
// Check connection is awaited
|
||||
await server.connect(transport); // Must await!
|
||||
console.error('Transport connected');
|
||||
```
|
||||
|
||||
### Issue 3: Premature Exit
|
||||
|
||||
**Problem**: Server exits immediately
|
||||
**Solution**: Don't close stdin/stdout
|
||||
|
||||
```typescript
|
||||
// Wrong
|
||||
process.stdin.end(); // Don't do this
|
||||
|
||||
// Right
|
||||
// Let the transport manage streams
|
||||
```
|
||||
|
||||
## Integration with Claude Code
|
||||
|
||||
### Configuration in .claude.json
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"my-server": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/dist/server.js"],
|
||||
"env": {
|
||||
"DEBUG": "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices for Claude Code Integration
|
||||
|
||||
1. **Startup Messages**: Log clear startup messages to stderr
|
||||
2. **Error Messages**: Provide actionable error messages
|
||||
3. **Ready Signal**: Log when server is ready to accept requests
|
||||
4. **Version Info**: Include version in startup logs
|
||||
|
||||
```typescript
|
||||
console.error(`Starting ${serverName} v${version}`);
|
||||
console.error('Initializing...');
|
||||
// ... initialization ...
|
||||
console.error(`${serverName} ready on stdio`);
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Buffering and Streaming
|
||||
|
||||
```typescript
|
||||
// For large responses, consider streaming
|
||||
import { Transform } from 'stream';
|
||||
|
||||
class ResponseStream extends Transform {
|
||||
_transform(chunk: any, encoding: string, callback: Function) {
|
||||
// Process chunk
|
||||
this.push(JSON.stringify(chunk));
|
||||
callback();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Management
|
||||
|
||||
```typescript
|
||||
// Clear large objects after use
|
||||
let largeData = await processData();
|
||||
// Use data...
|
||||
largeData = null; // Allow GC
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [StdioServerTransport Docs](https://github.com/modelcontextprotocol/typescript-sdk#stdio-transport)
|
||||
- [Server Examples](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/src/examples/server)
|
||||
- [MCP Protocol Specification](https://modelcontextprotocol.io/docs)
|
||||
Reference in New Issue
Block a user