import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { N8NDocumentationMCPServer } from '../../../src/mcp/server'; export class TestableN8NMCPServer { private mcpServer: N8NDocumentationMCPServer; private server: Server; private transport?: Transport; constructor() { this.server = new Server({ name: 'n8n-documentation-mcp', version: '1.0.0' }, { capabilities: { tools: {} } }); this.mcpServer = new N8NDocumentationMCPServer(); this.setupHandlers(); } private setupHandlers() { // Initialize handler this.server.setRequestHandler(InitializeRequestSchema, async () => { return { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'n8n-documentation-mcp', version: '1.0.0' } }; }); // List tools handler this.server.setRequestHandler(ListToolsRequestSchema, async () => { const tools = await this.mcpServer.executeTool('tools/list', {}); return tools; }); // Call tool handler this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const result = await this.mcpServer.executeTool(request.params.name, request.params.arguments || {}); // Convert result to content array if needed if (Array.isArray(result) && result.length > 0 && result[0].content) { return { content: result[0].content }; } return { content: [ { type: 'text' as const, text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) } ] }; } catch (error: any) { // If it's already an MCP error, throw it as is if (error.code && error.message) { throw error; } // Otherwise, wrap it in an MCP error throw new McpError( ErrorCode.InternalError, error.message || 'Unknown error' ); } }); } async initialize(): Promise { // The MCP server initializes its database lazily // We can trigger initialization by calling executeTool try { await this.mcpServer.executeTool('get_database_statistics', {}); } catch (error) { // Ignore errors, we just want to trigger initialization } } async connectToTransport(transport: Transport): Promise { this.transport = transport; // Ensure transport has required properties before connecting if (!transport || typeof transport !== 'object') { throw new Error('Invalid transport provided'); } // Set up any missing transport handlers to prevent "Cannot set properties of undefined" errors if (transport && typeof transport === 'object') { const transportAny = transport as any; if (transportAny.serverTransport && !transportAny.serverTransport.onclose) { transportAny.serverTransport.onclose = () => {}; } } await this.server.connect(transport); } async close(): Promise { // The server handles closing the transport await this.mcpServer.shutdown(); } }