UNPKG

agent-rules-kit

Version:

CLI tool to bootstrap AI agent rules for multiple IDEs and frameworks. Generates optimized rules for Cursor, VS Code, Claude, and 6+ other AI coding assistants.

722 lines (608 loc) 15.4 kB
--- globs: <root>/**/*.ts,<root>/**/*.js,<root>/package.json,<root>/tsconfig.json alwaysApply: false --- # MCP TypeScript SDK Implementation Guide ## TypeScript SDK Features The TypeScript SDK provides full type safety and modern JavaScript patterns for building MCP servers and clients with excellent developer experience. ### Installation and Setup ```json // package.json { "name": "my-mcp-server", "version": "1.0.0", "type": "module", "main": "dist/index.js", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsx watch src/index.ts", "test": "vitest" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.0.0", "tsx": "^4.0.0", "vitest": "^1.0.0" } } ``` ```json // tsconfig.json { "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist", "rootDir": "./src", "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` ### Basic Server Implementation ```typescript import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; // Define types for better type safety interface EchoToolArgs { message: string; } interface QueryToolArgs { query: string; limit?: number; } // Create server instance const server = new Server( { name: 'my-mcp-server', version: '1.0.0', }, { capabilities: { resources: { subscribe: true, listChanged: true }, tools: { listChanged: true }, }, } ); // Resource handlers server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'file://example.txt', name: 'Example File', description: 'An example text file', mimeType: 'text/plain', }, ], }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; if (uri === 'file://example.txt') { return { contents: [ { uri, mimeType: 'text/plain', text: 'This is example content from TypeScript MCP server', }, ], }; } throw new Error(`Resource not found: ${uri}`); }); // Tool handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'echo', description: 'Echo back the input message', inputSchema: { type: 'object', properties: { message: { type: 'string', description: 'Message to echo back', }, }, required: ['message'], }, }, { name: 'query', description: 'Execute a database query', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'SQL query to execute', }, limit: { type: 'number', description: 'Maximum number of results', default: 100, }, }, required: ['query'], }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case 'echo': { const { message } = args as EchoToolArgs; return { content: [ { type: 'text', text: `Echo: ${message}`, }, ], }; } case 'query': { const { query, limit = 100 } = args as QueryToolArgs; // Simulate database query const results = await simulateDbQuery(query, limit); return { content: [ { type: 'text', text: `Query executed: ${query}\nResults: ${JSON.stringify( results, null, 2 )}`, }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } }); // Utility functions async function simulateDbQuery(query: string, limit: number) { // Simulate async database operation await new Promise((resolve) => setTimeout(resolve, 100)); return { query, rowCount: Math.floor(Math.random() * limit), executionTime: Math.random() * 100, }; } // Main function async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP server running on stdio'); } if (import.meta.url === `file://${process.argv[1]}`) { main().catch((error) => { console.error('Server error:', error); process.exit(1); }); } ``` ### Advanced Server Features #### Type-Safe Resource Management ```typescript // Define resource types interface FileResource { type: 'file'; path: string; content?: string; } interface DatabaseResource { type: 'database'; table: string; schema?: object; } type ResourceData = FileResource | DatabaseResource; class TypedResourceManager { private resources = new Map<string, ResourceData>(); registerResource(uri: string, data: ResourceData): void { this.resources.set(uri, data); } async getResource(uri: string): Promise<ResourceData | undefined> { return this.resources.get(uri); } async readFileResource(resource: FileResource): Promise<string> { if (resource.content) { return resource.content; } // Read from filesystem const fs = await import('fs/promises'); return await fs.readFile(resource.path, 'utf-8'); } async queryDatabaseResource( resource: DatabaseResource, query: string ): Promise<any[]> { // Execute database query return []; } } const resourceManager = new TypedResourceManager(); // Register resources resourceManager.registerResource('file://config.json', { type: 'file', path: './config.json', }); resourceManager.registerResource('db://users', { type: 'database', table: 'users', }); ``` #### Middleware Pattern ```typescript type MiddlewareFunction = ( request: any, next: () => Promise<any> ) => Promise<any>; class MiddlewareManager { private middlewares: MiddlewareFunction[] = []; use(middleware: MiddlewareFunction): void { this.middlewares.push(middleware); } async execute(request: any, handler: () => Promise<any>): Promise<any> { let index = 0; const next = async (): Promise<any> => { if (index >= this.middlewares.length) { return await handler(); } const middleware = this.middlewares[index++]; return await middleware(request, next); }; return await next(); } } // Create middleware const authMiddleware: MiddlewareFunction = async (request, next) => { // Check authentication const authHeader = request.headers?.authorization; if (!authHeader) { throw new Error('Authentication required'); } return await next(); }; const loggingMiddleware: MiddlewareFunction = async (request, next) => { const start = Date.now(); console.log( `[${new Date().toISOString()}] ${request.method} request started` ); try { const result = await next(); const duration = Date.now() - start; console.log( `[${new Date().toISOString()}] ${ request.method } completed in ${duration}ms` ); return result; } catch (error) { const duration = Date.now() - start; console.error( `[${new Date().toISOString()}] ${ request.method } failed in ${duration}ms:`, error ); throw error; } }; // Use middleware const middlewareManager = new MiddlewareManager(); middlewareManager.use(loggingMiddleware); middlewareManager.use(authMiddleware); ``` ### Client Implementation ```typescript import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; class MCPClient { private client: Client; private transport: StdioClientTransport; constructor() { this.transport = new StdioClientTransport({ command: 'node', args: ['dist/server.js'], }); this.client = new Client( { name: 'mcp-client', version: '1.0.0', }, { capabilities: {}, } ); } async connect(): Promise<void> { await this.client.connect(this.transport); } async disconnect(): Promise<void> { await this.client.close(); } async listResources() { const response = await this.client.request( { method: 'resources/list' }, ListResourcesRequestSchema ); return response.resources; } async readResource(uri: string) { const response = await this.client.request( { method: 'resources/read', params: { uri } }, ReadResourceRequestSchema ); return response.contents; } async callTool<T = any>(name: string, arguments_: object): Promise<T> { const response = await this.client.request( { method: 'tools/call', params: { name, arguments: arguments_ }, }, CallToolRequestSchema ); return response.content; } } // Usage example async function clientExample() { const client = new MCPClient(); try { await client.connect(); // List and read resources const resources = await client.listResources(); console.log('Available resources:', resources); for (const resource of resources) { const content = await client.readResource(resource.uri); console.log(`Resource ${resource.name}:`, content); } // Call tools const echoResult = await client.callTool('echo', { message: 'Hello from TypeScript!', }); console.log('Echo result:', echoResult); const queryResult = await client.callTool('query', { query: 'SELECT * FROM users', limit: 10, }); console.log('Query result:', queryResult); } finally { await client.disconnect(); } } ``` ### Error Handling ```typescript class MCPError extends Error { constructor(public code: string, message: string, public data?: any) { super(message); this.name = 'MCPError'; } } class ResourceNotFoundError extends MCPError { constructor(uri: string) { super('RESOURCE_NOT_FOUND', `Resource not found: ${uri}`); } } class ToolNotFoundError extends MCPError { constructor(name: string) { super('TOOL_NOT_FOUND', `Tool not found: ${name}`); } } // Error handling wrapper function withErrorHandling<T extends (...args: any[]) => any>(handler: T): T { return (async (...args: Parameters<T>) => { try { return await handler(...args); } catch (error) { if (error instanceof MCPError) { throw error; } console.error('Unexpected error:', error); throw new MCPError('INTERNAL_ERROR', 'An internal error occurred', { originalError: error.message, }); } }) as T; } // Usage const safeReadResource = withErrorHandling(async (uri: string) => { if (!uri.startsWith('file://')) { throw new ResourceNotFoundError(uri); } // Read resource implementation return 'resource content'; }); ``` ### Testing with Vitest ```typescript import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; describe('MCP Server', () => { let server: Server; beforeEach(() => { server = new Server( { name: 'test-server', version: '1.0.0' }, { capabilities: { tools: {} } } ); }); afterEach(async () => { if (server) { await server.close(); } }); it('should list resources', async () => { server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: 'test://resource', name: 'Test Resource', mimeType: 'text/plain', }, ], })); // Test implementation would depend on testing framework // This is a conceptual example }); it('should handle tool calls', async () => { server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === 'test_tool') { return { content: [{ type: 'text', text: 'success' }], }; } throw new Error(`Unknown tool: ${name}`); }); // Test tool execution // Implementation would depend on testing setup }); }); ``` ### Performance Optimization #### Connection Pooling ```typescript class ConnectionPool<T> { private connections: T[] = []; private activeConnections = new Set<T>(); private readonly maxSize: number; private readonly createConnection: () => Promise<T>; private readonly destroyConnection: (conn: T) => Promise<void>; constructor( maxSize: number, createConnection: () => Promise<T>, destroyConnection: (conn: T) => Promise<void> ) { this.maxSize = maxSize; this.createConnection = createConnection; this.destroyConnection = destroyConnection; } async acquire(): Promise<T> { // Return existing connection if available if (this.connections.length > 0) { const connection = this.connections.pop()!; this.activeConnections.add(connection); return connection; } // Create new connection if under limit if (this.activeConnections.size < this.maxSize) { const connection = await this.createConnection(); this.activeConnections.add(connection); return connection; } // Wait for connection to become available return new Promise((resolve) => { const checkForConnection = () => { if (this.connections.length > 0) { const connection = this.connections.pop()!; this.activeConnections.add(connection); resolve(connection); } else { setTimeout(checkForConnection, 10); } }; checkForConnection(); }); } async release(connection: T): Promise<void> { this.activeConnections.delete(connection); this.connections.push(connection); } async destroy(): Promise<void> { await Promise.all([ ...this.connections.map((conn) => this.destroyConnection(conn)), ...Array.from(this.activeConnections).map((conn) => this.destroyConnection(conn) ), ]); this.connections.length = 0; this.activeConnections.clear(); } } ``` #### Caching Layer ```typescript interface CacheEntry<T> { value: T; timestamp: number; ttl: number; } class LRUCache<K, V> { private cache = new Map<K, CacheEntry<V>>(); private accessOrder = new Map<K, number>(); private accessCounter = 0; constructor( private maxSize: number, private defaultTtl: number = 300000 // 5 minutes ) {} get(key: K): V | undefined { const entry = this.cache.get(key); if (!entry) return undefined; // Check TTL if (Date.now() - entry.timestamp > entry.ttl) { this.cache.delete(key); this.accessOrder.delete(key); return undefined; } // Update access order this.accessOrder.set(key, ++this.accessCounter); return entry.value; } set(key: K, value: V, ttl: number = this.defaultTtl): void { // Evict if at capacity if (this.cache.size >= this.maxSize && !this.cache.has(key)) { this.evictLRU(); } this.cache.set(key, { value, timestamp: Date.now(), ttl, }); this.accessOrder.set(key, ++this.accessCounter); } private evictLRU(): void { let oldestKey: K | undefined; let oldestAccess = Infinity; for (const [key, access] of this.accessOrder) { if (access < oldestAccess) { oldestAccess = access; oldestKey = key; } } if (oldestKey !== undefined) { this.cache.delete(oldestKey); this.accessOrder.delete(oldestKey); } } } ``` This TypeScript SDK implementation guide provides comprehensive patterns for building type-safe, performant MCP servers and clients with modern JavaScript/TypeScript development practices.