UNPKG

@goparrot/franchise-mcp-server

Version:

MCP Server for Franchise API

163 lines (162 loc) 6.41 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { decodeAndValidateToken, handleTool } from './common/index.js'; import { dashboardMethodsMap, dashboardHandlersMap } from './dashboard/index.js'; import { webstoreServiceHandlersMap, webstoreServiceMethodsMap } from './webstore/index.js'; // Create a new MCP server instance const server = new McpServer({ name: 'franchise-mcp-server', version: '0.0.1', }); server.tool('list_webstore_available_methods', 'Fetch the list of all available API methods for the online ordering system (webstore and mobile app).', {}, async () => { return { content: [ { type: 'text', text: JSON.stringify(webstoreServiceMethodsMap, null, 2), }, ], }; }); server.tool('list_dashboard_available_methods', `Fetch all available dashboard API methods, enabling franchise owners, regular merchants, and membership users to manage stores, view analytics, and configure both franchise-wide and individual settings`, {}, async () => { return { content: [ { type: 'text', text: JSON.stringify(dashboardMethodsMap, null, 2), }, ], }; }); server.tool('make_web_api_request', `Invoke a unified online-ordering API call by specifying the service. Be sure to get types before calling. Available services: ${Object.keys(webstoreServiceMethodsMap) .map((name) => name.toLowerCase()) .join(', ')}.`, { service: z.string().describe("Service category (e.g., 'store', 'menu', 'order')"), method: z.string().describe("The API method to call (e.g., 'retrieve', 'list', 'create')"), request: z.object({}).passthrough().optional().describe('The request object for the API call.'), }, async (params) => { const disallowWrites = !!(process.env.DISALLOW_WRITES ?? true); const { request } = params; try { const { handler } = handleTool(params, webstoreServiceMethodsMap, webstoreServiceHandlersMap, disallowWrites); const result = await handler(request || {}); return { content: [ { type: 'text', text: result, }, ], }; } catch (err) { return { content: [ { type: 'text', text: JSON.stringify({ error: err.message, details: err.errors || err.stack, }, null, 2), }, ], isError: true, }; } }); server.tool('decode_current_token', 'Decodes and validates the current access token, showing expiration and other details', {}, async () => { try { const accessToken = process.env.DASHBOARD_ACCESS_TOKEN; if (!accessToken) { throw new Error('No access token found. Please run setup first.'); } const decoded = decodeAndValidateToken(accessToken); const now = Math.floor(Date.now() / 1000); const timeRemainingSeconds = decoded.exp - now; return { content: [ { type: 'text', text: JSON.stringify({ decoded, formatted: { issuedAt: new Date(decoded.iat * 1000).toLocaleString(), expiresAt: new Date(decoded.exp * 1000).toLocaleString(), timeRemaining: timeRemainingSeconds > 0 ? `${Math.floor(timeRemainingSeconds / 3600)} hours ${Math.floor((timeRemainingSeconds % 3600) / 60)} minutes` : 'EXPIRED', isValid: timeRemainingSeconds > 0, }, }, null, 2), }, ], }; } catch (err) { return { content: [ { type: 'text', text: JSON.stringify({ error: err.message, details: err.stack, }, null, 2), }, ], isError: true, }; } }); server.tool('make_dashboard_api_request', `Unified tool for all dashboard operations (franchise merchants and regular merchants). Be sure to get types before calling. Available services: ${Object.keys(dashboardMethodsMap) .map((name) => name.toLowerCase()) .join(', ')}.`, { service: z .string() .describe("The dashboard service category (e.g., 'store', 'menu', 'order', 'orchestration')"), method: z.string().describe("The dashboard method to call (e.g., 'list', 'create')"), request: z.object({}).passthrough().optional().describe('The request object for the API call.'), }, async (params) => { try { let { request } = params; const { handler } = handleTool(params, dashboardMethodsMap, dashboardHandlersMap, !!process.env.DISALLOW_WRITES); // Use token from environment variables const accessToken = process.env.DASHBOARD_ACCESS_TOKEN; if (!accessToken) { throw new Error('No access token found. Please run setup first.'); } const { merchantId } = decodeAndValidateToken(accessToken); request = { ...request, merchantId }; const result = await handler(accessToken, request); return { content: [ { type: 'text', text: result, }, ], }; } catch (err) { return { content: [ { type: 'text', text: JSON.stringify({ error: err.message, details: err.errors || err.stack, }, null, 2), }, ], isError: true, }; } }); export async function startServer() { // Connect to the transport and start listening await server.connect(new StdioServerTransport()); console.error('🟢 MCP server running via stdio transport'); }