import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { CloverClient } from './clients/clover.js'; import { CloverConfig } from './types/index.js'; // Tool imports import { createOrdersTools } from './tools/orders-tools.js'; import { createInventoryTools } from './tools/inventory-tools.js'; import { createCustomersTools } from './tools/customers-tools.js'; import { createEmployeesTools } from './tools/employees-tools.js'; import { createPaymentsTools } from './tools/payments-tools.js'; import { createMerchantsTools } from './tools/merchants-tools.js'; import { createDiscountsTools } from './tools/discounts-tools.js'; import { createTaxesTools } from './tools/taxes-tools.js'; import { createReportsTools } from './tools/reports-tools.js'; import { createCashTools } from './tools/cash-tools.js'; import { createLineItemsTools } from './tools/line-items-tools.js'; import { createRefundsTools } from './tools/refunds-tools.js'; import { createCategoriesTools } from './tools/categories-tools.js'; import { createModifiersTools } from './tools/modifiers-tools.js'; import { createTipsTools } from './tools/tips-tools.js'; import { createShiftsTools } from './tools/shifts-tools.js'; import { createDevicesTools } from './tools/devices-tools.js'; import { createAppsTools } from './tools/apps-tools.js'; // React app imports import { readFileSync, readdirSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export class CloverServer { private server: Server; private client: CloverClient; private tools: Map; private apps: Map; constructor(config: CloverConfig) { this.server = new Server( { name: 'clover-server', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, }, } ); this.client = new CloverClient(config); this.tools = new Map(); this.apps = new Map(); this.initializeTools(); this.loadApps(); this.setupHandlers(); } private initializeTools() { const toolGroups = [ createOrdersTools(this.client), createInventoryTools(this.client), createCustomersTools(this.client), createEmployeesTools(this.client), createPaymentsTools(this.client), createMerchantsTools(this.client), createDiscountsTools(this.client), createTaxesTools(this.client), createReportsTools(this.client), createCashTools(this.client), createLineItemsTools(this.client), createRefundsTools(this.client), createCategoriesTools(this.client), createModifiersTools(this.client), createTipsTools(this.client), createShiftsTools(this.client), createDevicesTools(this.client), createAppsTools(this.client), ]; toolGroups.forEach((group) => { Object.entries(group).forEach(([name, tool]) => { this.tools.set(name, tool); }); }); } private loadApps() { const appsDir = join(__dirname, 'ui', 'react-app'); try { const files = readdirSync(appsDir).filter((f) => f.endsWith('.tsx')); files.forEach((file) => { const appName = file.replace('.tsx', ''); const content = readFileSync(join(appsDir, file), 'utf-8'); this.apps.set(appName, content); }); } catch (error) { console.warn('Could not load React apps:', error); } } private setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { const tools = Array.from(this.tools.entries()).map(([name, tool]) => ({ name, description: tool.description, inputSchema: tool.inputSchema, })); return { tools }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const tool = this.tools.get(name); if (!tool) { throw new Error(`Unknown tool: ${name}`); } try { const result = await tool.handler(args || {}); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error: ${error.message}`, }, ], isError: true, }; } }); this.server.setRequestHandler(ListResourcesRequestSchema, async () => { const resources = Array.from(this.apps.keys()).map((appName) => ({ uri: `clover://app/${appName}`, name: appName.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()), mimeType: 'text/tsx', description: `Clover ${appName.replace(/-/g, ' ')} React app`, })); return { resources }; }); this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; const match = uri.match(/^clover:\/\/app\/(.+)$/); if (!match) { throw new Error(`Invalid resource URI: ${uri}`); } const appName = match[1]; const appCode = this.apps.get(appName); if (!appCode) { throw new Error(`App not found: ${appName}`); } return { contents: [ { uri, mimeType: 'text/tsx', text: appCode, }, ], }; }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); } }