Jake Shore 6d342a1545 Phase 1: Tier 2 complete — 13 servers upgraded to gold standard architecture
- READMEs added: asana, close, freshdesk, google-console, gusto, square
- main.ts + server.ts (lazy loading): activecampaign, clickup, klaviyo, mailchimp, pipedrive, trello, touchbistro, closebot, close, google-console
- All 13 compile with 0 TSC errors
2026-02-14 05:47:14 -05:00

226 lines
6.0 KiB
TypeScript

/**
* 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<any>;
}
type ToolModule = Record<string, ToolDefinition>;
export class TouchBistroMCPServer {
private server: Server;
private client: TouchBistroApiClient;
private toolModules: Map<string, () => Promise<ToolModule>>;
private allToolDefinitions: Record<string, ToolDefinition> = {};
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<void> {
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<void> {
await this.server.connect(transport);
}
}