import type { Context } from 'hono';
// Define MCP tool schemas
const tools = [
{
name: 'list_products',
description: 'List all products with optional filtering by category or stock status',
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Filter by product category (e.g., Electronics, Home, Sports)',
},
inStock: {
type: 'boolean',
description: 'Filter by stock availability',
},
},
},
},
{
name: 'get_product',
description: 'Get details of a specific product by ID',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'The product ID',
},
},
required: ['id'],
},
},
{
name: 'create_product',
description: 'Create a new product',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Product name',
},
price: {
type: 'number',
description: 'Product price',
},
description: {
type: 'string',
description: 'Product description',
},
category: {
type: 'string',
description: 'Product category',
},
inStock: {
type: 'boolean',
description: 'Whether the product is in stock',
},
},
required: ['name', 'price', 'description', 'category'],
},
},
{
name: 'update_product',
description: 'Update an existing product',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'The product ID to update',
},
name: {
type: 'string',
description: 'Product name',
},
price: {
type: 'number',
description: 'Product price',
},
description: {
type: 'string',
description: 'Product description',
},
category: {
type: 'string',
description: 'Product category',
},
inStock: {
type: 'boolean',
description: 'Whether the product is in stock',
},
},
required: ['id'],
},
},
{
name: 'delete_product',
description: 'Delete a product by ID',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'The product ID to delete',
},
},
required: ['id'],
},
},
];
// Execute tool calls by making internal API requests
async function executeTool(name: string, args: any, baseUrl: string) {
let response: Response;
switch (name) {
case 'list_products': {
const params = new URLSearchParams();
if (args?.category) params.append('category', String(args.category));
if (args?.inStock !== undefined) params.append('inStock', String(args.inStock));
const url = `${baseUrl}/api/products${params.toString() ? '?' + params.toString() : ''}`;
response = await fetch(url);
break;
}
case 'get_product': {
response = await fetch(`${baseUrl}/api/products/${args?.id}`);
break;
}
case 'create_product': {
response = await fetch(`${baseUrl}/api/products`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args),
});
break;
}
case 'update_product': {
const { id, ...updateData } = args as any;
response = await fetch(`${baseUrl}/api/products/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updateData),
});
break;
}
case 'delete_product': {
response = await fetch(`${baseUrl}/api/products/${args?.id}`, {
method: 'DELETE',
});
break;
}
default:
throw new Error(`Unknown tool: ${name}`);
}
const data = await response.json();
return data;
}
// MCP HTTP handler for JSON-RPC requests
export async function mcpHandler(c: Context) {
try {
const body = await c.req.json();
const requestId = body.id !== undefined ? body.id : 1;
// Get base URL from request
const url = new URL(c.req.url);
const baseUrl = `${url.protocol}//${url.host}`;
// Build JSON-RPC response
let response;
// Handle tools/list request
if (body.method === 'tools/list') {
response = {
jsonrpc: '2.0',
id: requestId,
result: { tools },
};
}
// Handle tools/call request
else if (body.method === 'tools/call') {
const { name, arguments: args } = body.params;
try {
const result = await executeTool(name, args, baseUrl);
response = {
jsonrpc: '2.0',
id: requestId,
result: {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id: requestId,
error: {
code: -32603,
message: error instanceof Error ? error.message : 'Unknown error',
},
};
}
}
// Handle initialize request (MCP handshake)
else if (body.method === 'initialize') {
response = {
jsonrpc: '2.0',
id: requestId,
result: {
protocolVersion: '2024-11-05',
capabilities: {
tools: {},
},
serverInfo: {
name: 'cf-mcp-server',
version: '1.0.0',
},
},
};
// Return with optional Mcp-Session-Id header for future use
const sessionId = crypto.randomUUID();
return c.json(response, 200, {
'Mcp-Session-Id': sessionId,
});
}
// Method not found
else {
response = {
jsonrpc: '2.0',
id: requestId,
error: {
code: -32601,
message: 'Method not found',
},
};
}
// Return JSON-RPC response
return c.json(response);
} catch (error) {
const errorResponse = {
jsonrpc: '2.0',
id: 1,
error: {
code: -32700,
message: error instanceof Error ? error.message : 'Parse error',
},
};
return c.json(errorResponse, 500);
}
}