import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import type { MCPToolDefinition } from '../types/mcp-tool.js';
import type { Config } from '../config/schema.js';
import { executeTool, type ToolArgs } from '../proxy/executor.js';
import { log } from '../utils/logger.js';
/**
* Convert our JSONSchema to Zod schema for MCP SDK registration
* Note: This is a simplified conversion for common types
*/
function jsonSchemaToZodShape(schema: MCPToolDefinition['inputSchema']): Record<string, z.ZodTypeAny> {
const shape: Record<string, z.ZodTypeAny> = {};
if (!schema.properties) {
return shape;
}
const requiredSet = new Set(schema.required ?? []);
for (const [name, prop] of Object.entries(schema.properties)) {
let zodType: z.ZodTypeAny;
switch (prop.type) {
case 'string':
zodType = z.string();
if (prop.enum) {
zodType = z.enum(prop.enum as [string, ...string[]]);
}
break;
case 'number':
zodType = z.number();
if (prop.minimum !== undefined) {
zodType = (zodType as z.ZodNumber).min(prop.minimum);
}
if (prop.maximum !== undefined) {
zodType = (zodType as z.ZodNumber).max(prop.maximum);
}
break;
case 'integer':
zodType = z.number().int();
break;
case 'boolean':
zodType = z.boolean();
break;
case 'array':
zodType = z.array(z.unknown());
break;
case 'object':
zodType = z.record(z.unknown());
break;
default:
zodType = z.unknown();
}
// Add description
if (prop.description) {
zodType = zodType.describe(prop.description);
}
// Make optional if not required
if (!requiredSet.has(name)) {
zodType = zodType.optional();
}
shape[name] = zodType;
}
return shape;
}
/**
* Create and configure an MCP server with tools
*/
export function createMcpServer(
tools: MCPToolDefinition[],
config: Config
): McpServer {
const server = new McpServer({
name: 'openapi-mcp',
version: '1.0.0',
});
// Register enabled tools
const enabledTools = tools.filter((t) => t._ui.enabled);
log.info('Registering MCP tools', { count: enabledTools.length });
for (const tool of enabledTools) {
const inputShape = jsonSchemaToZodShape(tool.inputSchema);
server.registerTool(
tool.name,
{
title: tool.title,
description: tool.description,
inputSchema: inputShape,
},
async (args: ToolArgs) => {
log.debug('Tool called', { tool: tool.name });
const result = await executeTool(tool, args, config);
// Return in the format expected by MCP SDK
return {
content: result.content.map((c) => ({
type: 'text' as const,
text: c.text,
})),
isError: result.isError,
};
}
);
log.debug('Registered tool', { name: tool.name, title: tool.title });
}
return server;
}
/**
* Get list of registered tools for diagnostics
*/
export function getToolList(tools: MCPToolDefinition[]): Array<{
name: string;
title: string;
enabled: boolean;
method: string;
path: string;
}> {
return tools.map((t) => ({
name: t.name,
title: t.title,
enabled: t._ui.enabled,
method: t._proxy.method,
path: t._proxy.path,
}));
}