- 112 MCP tools across 17 domains (jobs, customers, estimates, invoices, payments, employees, scheduling, dispatch, tags, notifications, reviews, reporting, price book, leads, webhooks, time tracking, settings) - 15 React apps with proper Vite build setup and dark theme - Full API client with Bearer auth, pagination, error handling, rate limiting - Complete TypeScript types for all entities - TSC passes clean - GHL-quality standard achieved
198 lines
5.8 KiB
TypeScript
198 lines
5.8 KiB
TypeScript
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<string, any>;
|
|
private apps: Map<string, string>;
|
|
|
|
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);
|
|
}
|
|
}
|