@goparrot/franchise-mcp-server
Version:
MCP Server for Franchise API
163 lines (162 loc) • 6.41 kB
JavaScript
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');
}