Jake Shore 7ee40342c8 Clover: Complete MCP server with 50+ tools and 18 React apps
- API client with Clover REST API v3 integration (OAuth2 + API key auth)
- 50+ comprehensive tools across 10 categories:
  * Orders: list, get, create, update, delete, add/remove line items, discounts, payments, fire order
  * Inventory: items, categories, modifiers, stock management
  * Customers: CRUD, search, addresses, payment cards
  * Employees: CRUD, roles, shifts, clock in/out
  * Payments: list, get, refunds
  * Merchants: settings, devices, tender types
  * Discounts: CRUD operations
  * Taxes: CRUD, tax rates
  * Reports: sales summary, revenue by item/category, employee performance
  * Cash: cash drawer tracking and events

- 18 React MCP apps with full UI:
  * Order management: dashboard, detail, grid
  * Inventory: dashboard, detail, category manager
  * Customer: detail, grid
  * Employee: dashboard, schedule
  * Payment history
  * Analytics: sales dashboard, revenue by item, revenue by category
  * Configuration: discount manager, tax manager, device manager
  * Cash drawer

- Complete TypeScript types for Clover API
- Pagination support with automatic result fetching
- Comprehensive error handling
- Full README with examples and setup guide
2026-02-12 17:42:59 -05:00

182 lines
5.0 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';
// 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),
];
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);
}
}