#!/usr/bin/env node // 引入必要的模块 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import http from "http"; import { z } from "zod"; import { AsyncLocalStorage } from 'async_hooks'; import { ApifoxService } from './apifox.js' const requestContext = new AsyncLocalStorage(); // 创建 MCP 服务器实例 // 为其设置一个名字和版本 const server = new McpServer( { name: "apifox-mcp-server", version: "1.0.0", }, { capabilities: { prompts: {}, tools: {}, }, } ); function getApifoxService() { const store = requestContext.getStore(); const token = store?.token || process.env.APIFOX_TOKEN; const projectId = store?.projectId ? Number(store.projectId) : Number(process.env.APIFOX_PROJECT_ID); if (!token) { throw new Error("Apifox Token not configured (env: APIFOX_TOKEN or header: Authorization)"); } return new ApifoxService({ projectId, token }); } server.registerTool( 'get_api_by_path', { description: "根据接口path获取Apifox接口定义数据", inputSchema: z.object({ path: z.string().describe("接口路径"), projectId: z.number().optional().describe("Apifox项目ID"), }), }, async ({ path, projectId }) => { try { const apifoxService = getApifoxService(); const apiData = await apifoxService.getApiDetailByPath(path, projectId); return { content: [ { type: "text", text: JSON.stringify(apiData, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `获取接口详情失败: ${error.message}`, }, ], isError: true, }; } } ) server.registerTool( 'get_apis_by_folder', { description: "根据接口目录获取Apifox目录下所有接口定义数据", inputSchema: z.object({ folderName: z.string().describe("接口路径"), projectId: z.number().optional().describe("Apifox项目ID"), }), }, async ({ folderName, projectId }) => { try { const apifoxService = getApifoxService(); const apiData = await apifoxService.getApiListByFolderName(folderName, projectId); return { content: [ { type: "text", text: JSON.stringify(apiData, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `获取接口数据失败: ${error.message}`, }, ], isError: true, }; } } ) // 启动服务器,使用标准输入/输出(stdio)与客户端通信 async function main() { const args = process.argv.slice(2); const transportArgIndex = args.indexOf('--transport') !== -1 ? args.indexOf('--transport') : args.indexOf('-t'); const transportType = transportArgIndex !== -1 ? args[transportArgIndex + 1] : 'stdio'; const portArgIndex = args.indexOf('--port') !== -1 ? args.indexOf('--port') : args.indexOf('-p'); const port = portArgIndex !== -1 ? parseInt(args[portArgIndex + 1]) : 8889; if (transportType === 'http') { const transport = new StreamableHTTPServerTransport({}); await server.connect(transport); const httpServer = http.createServer(async (req, res) => { // 设置 CORS 头 res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, mcp-session-id'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } if (req.method === 'GET' && req.url === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok' })); return; } const authHeader = req.headers['authorization']; const token = authHeader && authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null; const projectId = req.headers['x-apifox-project-id']; const context = {}; if (token) context.token = token; if (projectId) context.projectId = projectId; requestContext.run(context, async () => { try { await transport.handleRequest(req, res); } catch (error) { console.error("Error handling request:", error); if (!res.headersSent) { res.writeHead(500); res.end("Internal Server Error"); } } }); }); httpServer.listen(port, () => { console.error(`Apifox MCP 服务器已通过 http 启动,监听端口 ${port}`); console.error(`MCP 服务 endpoint: http://localhost:${port}/sse`); console.error(`MCP 消息 endpoint: http://localhost:${port}/messages`); }); } else { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Apifox MCP 服务器已通过 stdio 启动"); // 日志输出到 stderr } } // 运行主函数,并捕获可能的错误 main().catch((error) => { console.error("服务器运行出现致命错误:", error); process.exit(1); }); export { server };