/** * TouchBistro MCP Server - Server Class with Lazy Tool Loading */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; import type { TouchBistroApiClient } from './lib/api-client.js'; // Type definition for tool modules interface ToolDefinition { description: string; parameters: z.ZodTypeAny; handler: (args: any, client: TouchBistroApiClient) => Promise; } type ToolModule = Record; export class TouchBistroMCPServer { private server: Server; private client: TouchBistroApiClient; private toolModules: Map Promise>; private allToolDefinitions: Record = {}; constructor(client: TouchBistroApiClient) { this.client = client; this.toolModules = new Map(); this.server = new Server( { name: 'touchbistro-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupToolModules(); this.setupHandlers(); } /** * Register tool modules for lazy loading */ private setupToolModules(): void { this.toolModules.set('orders', async () => { const module = await import('./tools/orders.js'); return module.orderTools; }); this.toolModules.set('menu', async () => { const module = await import('./tools/menu.js'); return module.menuTools; }); this.toolModules.set('tables', async () => { const module = await import('./tools/tables.js'); return module.tableTools; }); this.toolModules.set('staff', async () => { const module = await import('./tools/staff.js'); return module.staffTools; }); this.toolModules.set('customers', async () => { const module = await import('./tools/customers.js'); return module.customerTools; }); this.toolModules.set('reservations', async () => { const module = await import('./tools/reservations.js'); return module.reservationTools; }); this.toolModules.set('inventory', async () => { const module = await import('./tools/inventory.js'); return module.inventoryTools; }); this.toolModules.set('payments', async () => { const module = await import('./tools/payments.js'); return module.paymentTools; }); } /** * Load all tools from registered modules */ private async loadAllTools(): Promise { const toolModules = await Promise.all( Array.from(this.toolModules.values()).map(loader => loader()) ); for (const toolModule of toolModules) { Object.assign(this.allToolDefinitions, toolModule); } console.error(`Loaded ${Object.keys(this.allToolDefinitions).length} TouchBistro tools`); } /** * Convert Zod schema to JSON Schema for MCP */ private zodToJsonSchema(schema: z.ZodTypeAny): any { if (schema instanceof z.ZodObject) { const shape = schema.shape; const properties: any = {}; const required: string[] = []; for (const [key, value] of Object.entries(shape)) { properties[key] = this.zodToJsonSchema(value as z.ZodTypeAny); if (!(value instanceof z.ZodOptional)) { required.push(key); } } return { type: 'object', properties, ...(required.length > 0 ? { required } : {}), }; } if (schema instanceof z.ZodString) { const base: any = { type: 'string' }; if (schema.description) base.description = schema.description; return base; } if (schema instanceof z.ZodNumber) { const base: any = { type: 'number' }; if (schema.description) base.description = schema.description; return base; } if (schema instanceof z.ZodBoolean) { const base: any = { type: 'boolean' }; if (schema.description) base.description = schema.description; return base; } if (schema instanceof z.ZodArray) { return { type: 'array', items: this.zodToJsonSchema(schema.element), ...(schema.description ? { description: schema.description } : {}), }; } if (schema instanceof z.ZodEnum) { return { type: 'string', enum: schema.options, ...(schema.description ? { description: schema.description } : {}), }; } if (schema instanceof z.ZodOptional) { return this.zodToJsonSchema(schema.unwrap()); } return { type: 'string' }; } /** * Setup MCP protocol handlers */ private setupHandlers(): void { // List tools handler this.server.setRequestHandler(ListToolsRequestSchema, async () => { if (Object.keys(this.allToolDefinitions).length === 0) { await this.loadAllTools(); } const mcpTools = Object.entries(this.allToolDefinitions).map(([name, tool]) => ({ name, description: tool.description, inputSchema: this.zodToJsonSchema(tool.parameters), })); return { tools: mcpTools, }; }); // Call tool handler this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (Object.keys(this.allToolDefinitions).length === 0) { await this.loadAllTools(); } const { name, arguments: args } = request.params; const tool = this.allToolDefinitions[name as keyof typeof this.allToolDefinitions]; if (!tool) { throw new Error(`Unknown tool: ${name}`); } try { const validatedArgs = tool.parameters.parse(args); const result = await tool.handler(validatedArgs, this.client); return result; } catch (error: any) { if (error instanceof z.ZodError) { throw new Error(`Invalid parameters: ${error.message}`); } throw new Error(`Tool execution failed: ${error.message}`); } }); } /** * Start the server with the given transport */ async start(transport: any): Promise { await this.server.connect(transport); } }