feat: comprehensive refactor of index.js and documentation update
This commit is contained in:
1 parent
1ea6e0e9f0
commit
dd1d86706c
12 files changed
+4520
-68
No files matched your search
@@ -118,78 +118,221 @@ server.registerTool(
|
||||
)
|
||||
|
||||
|
||||
// 启动服务器,使用标准输入/输出(stdio)与客户端通信
|
||||
async function main() {
|
||||
/**
|
||||
* 解析命令行参数
|
||||
*/
|
||||
function parseArguments() {
|
||||
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
|
||||
// 检查是否需要帮助信息
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 解析传输方式
|
||||
const transportArgIndex = args.indexOf('--transport') !== -1
|
||||
? args.indexOf('--transport')
|
||||
: args.indexOf('-t');
|
||||
const transportType = transportArgIndex !== -1
|
||||
? args[transportArgIndex + 1]
|
||||
: 'stdio';
|
||||
|
||||
// 验证传输方式
|
||||
if (!['stdio', 'http'].includes(transportType)) {
|
||||
console.error(`错误: 不支持的传输方式 "${transportType}". 支持的方式: stdio, http`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 解析端口号
|
||||
const portArgIndex = args.indexOf('--port') !== -1
|
||||
? args.indexOf('--port')
|
||||
: args.indexOf('-p');
|
||||
const port = portArgIndex !== -1
|
||||
? parseInt(args[portArgIndex + 1])
|
||||
: 8889;
|
||||
|
||||
// 验证端口号
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
console.error(`错误: 无效的端口号 "${args[portArgIndex + 1]}". 端口号必须在 1-65535 之间`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return { transportType, port };
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印帮助信息
|
||||
*/
|
||||
function printHelp() {
|
||||
console.log(`
|
||||
Apifox MCP 服务器 - Model Context Protocol 服务器实现
|
||||
|
||||
用法:
|
||||
node index.js [选项]
|
||||
|
||||
选项:
|
||||
-t, --transport <type> 传输方式 (stdio|http) [默认: stdio]
|
||||
-p, --port <number> HTTP 服务器端口号 [默认: 8889]
|
||||
-h, --help 显示帮助信息
|
||||
|
||||
示例:
|
||||
# 使用 stdio 传输方式启动 (用于 Claude Desktop 等客户端)
|
||||
node index.js
|
||||
node index.js --transport stdio
|
||||
|
||||
# 使用 HTTP 传输方式启动 (用于远程访问或调试)
|
||||
node index.js --transport http
|
||||
node index.js --transport http --port 3000
|
||||
|
||||
环境变量:
|
||||
APIFOX_TOKEN Apifox 个人访问令牌 (必需)
|
||||
APIFOX_PROJECT_ID 默认的 Apifox 项目 ID (可选)
|
||||
|
||||
HTTP 模式端点:
|
||||
GET /health 健康检查
|
||||
POST /sse MCP 服务端点 (Server-Sent Events)
|
||||
POST /messages MCP 消息端点
|
||||
|
||||
HTTP 模式请求头:
|
||||
APIFOX_TOKEN token (可选,覆盖 APIFOX_TOKEN)
|
||||
APIFOX_PROJECT_ID 项目 ID (可选,覆盖 APIFOX_PROJECT_ID)
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 stdio 传输方式
|
||||
*/
|
||||
async function startStdioTransport() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
// stdio 模式下,日志必须输出到 stderr,避免干扰 stdout 上的 JSON-RPC 消息
|
||||
console.error("========================================");
|
||||
console.error("Apifox MCP 服务器已启动");
|
||||
console.error("传输方式: stdio");
|
||||
console.error("========================================");
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 HTTP 传输方式
|
||||
*/
|
||||
async function startHttpTransport(port) {
|
||||
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, APIFOX_TOKEN, APIFOX_PROJECT_ID, mcp-session-id');
|
||||
|
||||
// 提取认证信息
|
||||
const token = req.headers['APIFOX_TOKEN'];
|
||||
const projectId = req.headers['APIFOX_PROJECT_ID'];
|
||||
|
||||
// 记录请求日志
|
||||
const tokenStatus = token ? (token.substring(0, 4) + '***') : 'Missing';
|
||||
console.log(`[HTTP] ${new Date().toISOString()} | ${req.method} ${req.url} | ProjectId: ${projectId || 'N/A'} | Token: ${tokenStatus}`);
|
||||
|
||||
// 处理 CORS 预检请求
|
||||
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',
|
||||
server: 'apifox-mcp-server',
|
||||
version: '1.0.0',
|
||||
transport: 'http'
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置请求上下文
|
||||
const context = {};
|
||||
if (token) context.token = token;
|
||||
if (projectId) context.projectId = projectId;
|
||||
|
||||
// 在上下文中处理 MCP 请求
|
||||
requestContext.run(context, async () => {
|
||||
try {
|
||||
await transport.handleRequest(req, res);
|
||||
console.log(`[HTTP] ${new Date().toISOString()} | Success: ${req.method} ${req.url}`);
|
||||
} catch (error) {
|
||||
console.error(`[HTTP] ${new Date().toISOString()} | Error: ${req.method} ${req.url}`, error);
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
error: 'Internal Server Error',
|
||||
message: error.message
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 启动 HTTP 服务器
|
||||
return new Promise((resolve, reject) => {
|
||||
httpServer.listen(port, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
console.log("========================================");
|
||||
console.log("Apifox MCP 服务器已启动");
|
||||
console.log(`传输方式: HTTP`);
|
||||
console.log(`监听端口: ${port}`);
|
||||
console.log(`MCP 服务端点: http://localhost:${port}/sse`);
|
||||
console.log(`MCP 消息端点: http://localhost:${port}/messages`);
|
||||
console.log(`健康检查: http://localhost:${port}/health`);
|
||||
console.log("========================================");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// 处理服务器错误
|
||||
httpServer.on('error', (error) => {
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
console.error(`错误: 端口 ${port} 已被占用`);
|
||||
} else {
|
||||
console.error('HTTP 服务器错误:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数 - 启动 MCP 服务器
|
||||
*/
|
||||
async function main() {
|
||||
// 解析命令行参数
|
||||
const { transportType, port } = parseArguments();
|
||||
|
||||
// 验证必需的环境变量
|
||||
if (!process.env.APIFOX_TOKEN) {
|
||||
console.error("错误: 未设置环境变量 APIFOX_TOKEN");
|
||||
console.error("请设置 Apifox 个人访问令牌:");
|
||||
console.error(" export APIFOX_TOKEN=your_token_here");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 根据传输方式启动服务器
|
||||
if (transportType === 'http') {
|
||||
await startHttpTransport(port);
|
||||
} else {
|
||||
await startStdioTransport();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行主函数,并捕获可能的错误
|
||||
main().catch((error) => {
|
||||
console.error("服务器运行出现致命错误:", error);
|
||||
console.error("服务器启动失败:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user