# MCP Server API Reference ## Tool Handler Base Class All tools extend the `ToolHandler` abstract class which provides lifecycle management, validation, and error handling. ### Class Definition ```typescript abstract class ToolHandler { constructor( config: ToolConfig, inputSchema: z.ZodSchema, outputSchema: z.ZodSchema ); // Main execution method async execute(input: unknown, context: ToolContext): Promise; // Lifecycle hooks (override in subclass) protected async initialize(): Promise; protected async validate(input: TInput): Promise; protected abstract handle(input: TInput, context: ToolContext): Promise; protected async cleanup(): Promise; protected async checkPermissions(context: ToolContext): Promise; } ``` ### Tool Configuration ```typescript interface ToolConfig { name: string; // Unique tool identifier description: string; // Human-readable description timeout?: number; // Execution timeout in ms (default: 30000) permissions?: string[]; // Required permissions } ``` ### Tool Context ```typescript interface ToolContext { requestId: string; // Unique request identifier permissions: string[]; // Granted permissions userId?: string; // Optional user identifier metadata?: Record; // Additional context } ``` ## Creating a Custom Tool ### Basic Example ```typescript import { z } from 'zod'; import { ToolHandler, ToolContext } from '@mcp/tools'; // Define schemas const InputSchema = z.object({ message: z.string(), uppercase: z.boolean().optional(), }); const OutputSchema = z.object({ result: z.string(), length: z.number(), }); type Input = z.infer; type Output = z.infer; // Implement tool export class EchoTool extends ToolHandler { constructor() { super( { name: 'echo', description: 'Echo a message with transformations', timeout: 5000, }, InputSchema, OutputSchema ); } protected async handle(input: Input, context: ToolContext): Promise { let result = input.message; if (input.uppercase) { result = result.toUpperCase(); } this.logger.info({ message: result }, 'Echoing message'); return { result, length: result.length, }; } } ``` ### Advanced Example with Permissions ```typescript export class DatabaseQueryTool extends ToolHandler { private db: Database; constructor() { super( { name: 'db_query', description: 'Execute database queries', timeout: 60000, permissions: ['database:read', 'database:write'], }, QueryInputSchema, QueryOutputSchema ); } protected async initialize(): Promise { // Connect to database this.db = await Database.connect(process.env.DATABASE_URL); } protected async checkPermissions(context: ToolContext): Promise { const isWrite = this.isWriteQuery(this.currentInput.query); if (isWrite && !context.permissions.includes('database:write')) { throw new Error('Permission denied: database:write required'); } if (!context.permissions.includes('database:read')) { throw new Error('Permission denied: database:read required'); } } protected async validate(input: QueryInput): Promise { // Additional validation beyond schema if (this.containsSqlInjection(input.query)) { throw new Error('Invalid query: potential SQL injection'); } return input; } protected async handle(input: QueryInput, context: ToolContext): Promise { const startTime = Date.now(); try { const results = await this.db.query(input.query, input.params); return { rows: results.rows, rowCount: results.rowCount, duration: Date.now() - startTime, }; } catch (error) { this.logger.error({ error, query: input.query }, 'Query failed'); throw new Error(`Database query failed: ${error.message}`); } } protected async cleanup(): Promise { // Close database connection if (this.db) { await this.db.close(); } } } ``` ## Tool Registration ### Registering Built-in Tools ```typescript import { ToolRegistry } from '@mcp/registry'; import { FileReadTool, FileWriteTool } from './tools'; const registry = new ToolRegistry(natsClient); // Built-in tools are registered automatically // But you can also register manually registry.registerBuiltinTool('file_read', new FileReadTool()); registry.registerBuiltinTool('file_write', new FileWriteTool()); ``` ### Registering External Module Tools ```typescript // External tools are discovered via NATS // Modules announce their tools on startup natsClient.publish('tools.discovery', { module: 'my-module', tools: [ { name: 'my_tool', description: 'Custom tool from module', inputSchema: { /* ... */ }, permissions: ['custom:permission'], } ] }); ``` ## Error Handling ### Error Types ```typescript // Base error class export class ToolError extends Error { constructor( message: string, public code: string, public details?: any ) { super(message); this.name = 'ToolError'; } } // Specific error types export class ValidationError extends ToolError { constructor(message: string, details?: any) { super(message, 'VALIDATION_ERROR', details); } } export class PermissionError extends ToolError { constructor(message: string, required: string[]) { super(message, 'PERMISSION_ERROR', { required }); } } export class TimeoutError extends ToolError { constructor(timeout: number) { super(`Operation timed out after ${timeout}ms`, 'TIMEOUT_ERROR'); } } ``` ### Error Handling in Tools ```typescript protected async handle(input: Input, context: ToolContext): Promise { try { // Tool logic } catch (error) { if (error.code === 'ENOENT') { throw new ToolError('File not found', 'FILE_NOT_FOUND', { path: input.path }); } // Re-throw unknown errors throw error; } } ``` ## Testing Tools ### Unit Testing ```typescript import { MyTool } from './MyTool'; import { ToolContext } from '@mcp/tools'; describe('MyTool', () => { let tool: MyTool; beforeEach(() => { tool = new MyTool(); }); const createContext = (permissions: string[] = []): ToolContext => ({ requestId: 'test-request', permissions, }); it('should execute successfully', async () => { const result = await tool.execute( { input: 'test' }, createContext(['required:permission']) ); expect(result).toEqual({ output: 'expected' }); }); it('should require permissions', async () => { await expect( tool.execute({ input: 'test' }, createContext()) ).rejects.toThrow('Permission denied'); }); }); ``` ### Integration Testing ```typescript import { ToolRegistry } from '@mcp/registry'; import { NatsClient } from '@mcp/nats'; describe('Tool Integration', () => { let registry: ToolRegistry; let nats: NatsClient; beforeAll(async () => { nats = new NatsClient(); await nats.connect(); registry = new ToolRegistry(nats); }); afterAll(async () => { await nats.close(); }); it('should execute tool via registry', async () => { const result = await registry.executeTool( 'my_tool', { input: 'test' } ); expect(result).toBeDefined(); }); }); ``` ## Performance Considerations ### Timeout Management ```typescript protected async handle(input: Input, context: ToolContext): Promise { // Use AbortController for cancellable operations const controller = new AbortController(); const timeoutId = setTimeout( () => controller.abort(), this.config.timeout || 30000 ); try { const result = await fetch(input.url, { signal: controller.signal, }); return processResult(result); } finally { clearTimeout(timeoutId); } } ``` ### Resource Management ```typescript export class ResourceIntensiveTool extends ToolHandler { private pool: ResourcePool; protected async initialize(): Promise { // Initialize resource pool this.pool = new ResourcePool({ max: 10 }); } protected async handle(input: Input, context: ToolContext): Promise { // Acquire resource from pool const resource = await this.pool.acquire(); try { return await this.processWithResource(resource, input); } finally { // Always release resource this.pool.release(resource); } } protected async cleanup(): Promise { // Drain pool on cleanup await this.pool.drain(); } } ``` ## Security Best Practices 1. **Always validate input** - Use Zod schemas and additional validation 2. **Check permissions** - Implement checkPermissions for sensitive operations 3. **Sanitize paths** - Prevent directory traversal attacks 4. **Limit resource usage** - Implement timeouts and size limits 5. **Log security events** - Track permission denials and suspicious activity 6. **Use prepared statements** - Prevent SQL injection in database tools 7. **Validate URLs** - Block internal/private IP ranges in HTTP tools ## Debugging Tools ### Logging ```typescript protected async handle(input: Input, context: ToolContext): Promise { this.logger.debug({ input }, 'Processing request'); try { const result = await this.process(input); this.logger.info({ result }, 'Request successful'); return result; } catch (error) { this.logger.error({ error, input }, 'Request failed'); throw error; } } ``` ### Metrics ```typescript protected async handle(input: Input, context: ToolContext): Promise { const timer = this.metrics.startTimer('tool_execution_duration', { tool: this.config.name, }); try { const result = await this.process(input); this.metrics.increment('tool_execution_success', { tool: this.config.name, }); return result; } catch (error) { this.metrics.increment('tool_execution_error', { tool: this.config.name, error: error.code, }); throw error; } finally { timer.end(); } } ```