Files
apifox-mcp-server/index.js
T

196 lines
5.7 KiB
JavaScript

#!/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 };