@goparrot/franchise-mcp-server
Version:
MCP Server for Franchise API
168 lines (167 loc) • 6.71 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 all available API methods for the online ordering system, including webstore and mobile app. Supports both Franchise and Regular merchants.
Use this to discover supported operations across ordering, menus, and user interactions.`, {}, async () => {
return {
content: [
{
type: 'text',
text: JSON.stringify(webstoreServiceMethodsMap, null, 2),
},
],
};
});
server.tool('list_dashboard_available_methods', `Fetch all available dashboard API methods. Supports Franchise owners, Regular merchants, and Membership users.
Use this to discover capabilities related to store management, analytics, and both franchise-wide and individual settings.`, {}, async () => {
return {
content: [
{
type: 'text',
text: JSON.stringify(dashboardMethodsMap, null, 2),
},
],
};
});
server.tool('make_web_api_request', `Unified tool to perform online-ordering API calls by specifying the target service. Supports both Franchise and Regular merchants.
Be sure to retrieve the correct types before invoking. 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. Supports both Franchise 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, if applicable.'),
}, 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');
}