Files
apifox-mcp-server/index.js
T

234 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 { ApifoxService } from './apifox.js'
// 创建 MCP 服务器实例
// 为其设置一个名字和版本
const server = new McpServer(
{
name: "apifox-mcp-server",
version: "1.0.0",
},
{
capabilities: {
prompts: {},
tools: {},
},
}
);
const apifoxService = new ApifoxService({
projectId: Number(process.env.APIFOX_PROJECT_ID),
token: process.env.APIFOX_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 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 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,
};
}
}
)
// 定义 prompts
server.registerPrompt(
'gen-api-by-path',
{
title: 'Generate API by Path',
description: '根据输入的接口路径获取Apifox接口文档定义,然后根据定义生成 api 接口代码定义',
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `请根据以下步骤获取Apifox的接口定义并生成 api 定义代码。
步骤:
1. 使用 get_api_by_path 工具获取对应的接口定义schema
2. 根据返回的 path,method, requestSchema, responseSchema 和其他必要字段,生成 api 定义代码
3. 接口的参数和返回值的类型定义,可以分别参考 requestSchema 和 responseSchema 里面的jsonSchema定义,可以根据description要补充相关的注释
4. 所有字段先定义为可空类型
5. 请根据当前项目的项目接口和代码风格生成 api 接口代码
请开始执行这些步骤。`,
},
},
],
})
);
server.registerPrompt(
'gen-api-by-folder',
{
title: 'Generate API by Folder',
description: '根据输入的接口文件夹获取Apifox相关文件夹下所有的接口文档定义,然后根据定义生成 api 接口代码定义',
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `请根据以下步骤获取Apifox的接口定义并生成 api 定义代码。
步骤:
1. 使用 get_api_by_folder 工具获取对应的接口定义schema,可能会返回多条接口的数据
2. 根据返回的 path,method, requestSchema, responseSchema 和其他必要字段,生成 api 定义代码
3. 接口的参数和返回值的类型定义,可以分别参考 requestSchema 和 responseSchema 里面的jsonSchema定义,可以根据description要补充相关的注释
4. 所有字段先定义为可空类型
5. 请根据当前项目的项目接口和代码风格生成 api 接口代码
请开始执行这些步骤。`,
},
},
],
})
);
// 启动服务器,使用标准输入/输出(stdio)与客户端通信
// 启动服务器,使用标准输入/输出(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;
}
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`);
});
} 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 };