Skip to main content
Glama

Apifox MCP

by Warren-W
index.ts18 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js'; import { ApifoxClient } from './apifox-client.js'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; // 获取 package.json 路径 const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJsonPath = join(__dirname, '../package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); /** * 解析命令行参数 */ function parseArgs() { const args = process.argv.slice(2); const config: Record<string, string> = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--')) { const key = arg.slice(2); const value = args[i + 1]; if (value && !value.startsWith('--')) { config[key] = value; i++; } } } return config; } /** * 从命令行参数或环境变量获取配置 * 优先级:命令行参数 > 环境变量 */ function getConfig() { const cliArgs = parseArgs(); const token = cliArgs['token'] || process.env.APIFOX_TOKEN; const projectId = cliArgs['project-id'] || process.env.APIFOX_PROJECT_ID; const baseUrl = cliArgs['base-url'] || process.env.APIFOX_BASE_URL; if (!token || !projectId) { console.error('\n❌ 缺少必要的配置参数!\n'); console.error('请通过以下任一方式提供配置:\n'); console.error('方式 1: 命令行参数'); console.error(' node dist/index.js --token "your-token" --project-id "your-project-id"\n'); console.error('方式 2: 环境变量'); console.error(' export APIFOX_TOKEN="your-token"'); console.error(' export APIFOX_PROJECT_ID="your-project-id"'); console.error(' node dist/index.js\n'); console.error('可选参数:'); console.error(' --base-url "https://api.apifox.com" # Apifox API 基础 URL\n'); process.exit(1); } return { token, projectId, baseUrl }; } /** * 定义 MCP 工具 */ const tools: Tool[] = [ { name: 'import_openapi', description: '维护 Apifox 接口文档:将 OpenAPI/Swagger 规范导入到 Apifox 项目。支持 OpenAPI 3.0/3.1 和 Swagger 2.0 格式。\n\n⭐ 最佳实践(强烈推荐):\n1. 【分批导入】按模块分批导入(如先导入 /api/users,再导入 /api/products),而非一次性导入所有接口\n2. 【智能范围检测】每次只导入一个功能模块的完整规范,系统会自动识别范围\n3. 【启用废弃标记】设置 markDeprecatedEndpoints: true,自动保留并标记已删除的接口\n\n分批导入的好处:更安全、更可控、支持模块独立维护、避免大规模误操作。', inputSchema: { type: 'object', properties: { spec: { type: 'object', description: 'OpenAPI/Swagger 规范的 JSON 对象。必须包含 openapi 或 swagger 字段、info 字段、paths 字段' }, options: { type: 'object', description: '导入选项(可选)', properties: { endpointOverwriteBehavior: { type: 'string', enum: ['OVERWRITE_EXISTING', 'AUTO_MERGE', 'KEEP_EXISTING', 'CREATE_NEW'], description: '接口覆盖行为,默认为 OVERWRITE_EXISTING' }, schemaOverwriteBehavior: { type: 'string', enum: ['OVERWRITE_EXISTING', 'AUTO_MERGE', 'KEEP_EXISTING', 'CREATE_NEW'], description: '数据模型覆盖行为,默认为 OVERWRITE_EXISTING' }, markDeprecatedEndpoints: { type: 'boolean', description: '是否自动标记废弃的接口(推荐启用)。启用后会:1) 导出现有接口;2) 智能检测导入范围(如只导入 /api/marketing 模块);3) 只对比该范围内的接口;4) 标记已删除的接口为 deprecated。支持部分模块导入,不会误标记其他模块。默认为 false' }, confirmHighDeprecation: { type: 'boolean', description: '确认高比例废弃操作。当废弃比例超过 50% 时,必须设置为 true 才能继续。这是一个安全机制,防止误操作导致大量接口被标记为废弃。如果不设置此参数,操作会被阻止并返回错误信息。' } } } }, required: ['spec'] } }, { name: 'export_openapi', description: '查看 Apifox 接口文档:从 Apifox 项目导出接口信息。重要:在导入新接口前,务必先使用 summary 模式查看现有目录结构和接口列表,以便将新接口放入合适的目录中,保持项目结构一致性。', inputSchema: { type: 'object', properties: { mode: { type: 'string', enum: ['summary', 'full'], description: '导出模式:summary=仅导出目录结构和接口列表(推荐,节省上下文),full=导出完整的 OpenAPI 规范。默认为 summary' }, oasVersion: { type: 'string', enum: ['2.0', '3.0', '3.1'], description: 'OpenAPI 规范版本,默认为 3.0(仅 full 模式有效)' }, exportFormat: { type: 'string', enum: ['JSON', 'YAML'], description: '导出格式,默认为 JSON(仅 full 模式有效)' }, pathFilter: { type: 'string', description: '路径过滤器,只导出匹配的接口路径(支持前缀匹配),如 "/api/user" 只导出用户相关接口' } } } } ]; /** * 主函数 */ async function main() { // 检查特殊参数(在获取配置之前) const args = process.argv.slice(2); if (args.includes('--version') || args.includes('-v')) { console.log(`apifox-openapi-mcp v${packageJson.version}`); process.exit(0); } if (args.includes('--help') || args.includes('-h')) { console.log(` Apifox OpenAPI MCP Server v${packageJson.version} Model Context Protocol server for Apifox API documentation management. Usage: apifox-mcp --token <token> --project-id <project-id> [options] Required: --token <token> Apifox API token --project-id <project-id> Apifox project ID Options: --base-url <url> Apifox API base URL (default: https://api.apifox.com) --version, -v Show version number --help, -h Show this help message Environment Variables: APIFOX_TOKEN Apifox API token APIFOX_PROJECT_ID Apifox project ID APIFOX_BASE_URL Apifox API base URL Examples: # Using command line arguments apifox-mcp --token "your-token" --project-id "your-project-id" # Using environment variables export APIFOX_TOKEN="your-token" export APIFOX_PROJECT_ID="your-project-id" apifox-mcp Documentation: https://github.com/warren/apifox-mcp `); process.exit(0); } // 获取配置 const config = getConfig(); const apifoxClient = new ApifoxClient(config); // 创建 MCP 服务器 const server = new Server( { name: 'apifox-mcp-server', version: '1.0.0' }, { capabilities: { tools: {} } } ); // 处理工具列表请求 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'import_openapi': { const { spec, options } = args as { spec: any; options?: any }; // 验证 OpenAPI 规范的基本结构 if (!spec || typeof spec !== 'object') { throw new Error('spec 参数必须是一个对象'); } if (!spec.openapi && !spec.swagger) { throw new Error('spec 必须包含 openapi 或 swagger 字段'); } if (!spec.info) { throw new Error('spec 必须包含 info 字段'); } if (!spec.paths) { throw new Error('spec 必须包含 paths 字段'); } const result = await apifoxClient.importOpenApi(spec, options); // 格式化导入结果 let resultText = '✅ OpenAPI 规范导入完成!\n\n'; // 显示警告信息(如果有) if (result._warnings && result._warnings.length > 0) { resultText += '⚠️ 重要提示:\n'; result._warnings.forEach((warning: string) => { resultText += ` ${warning}\n`; }); resultText += '\n'; } // 显示废弃接口统计(如果有) if (result._deprecatedInfo) { const { count, ratio, scope } = result._deprecatedInfo; resultText += '🔖 废弃接口标记:\n'; resultText += ` - 标记数量: ${count} 个\n`; resultText += ` - 废弃比例: ${ratio.toFixed(1)}%\n`; if (scope.length > 0) { resultText += ` - 影响范围: ${scope.join(', ')}\n`; } resultText += '\n'; } if (result.data && result.data.counters) { const counters = result.data.counters; // 接口统计 resultText += '📋 接口导入统计:\n'; resultText += ` - 创建: ${counters.endpointCreated || 0}\n`; resultText += ` - 更新: ${counters.endpointUpdated || 0}\n`; resultText += ` - 失败: ${counters.endpointFailed || 0}\n`; resultText += ` - 忽略: ${counters.endpointIgnored || 0}\n`; // 数据模型统计 resultText += '\n📦 数据模型导入统计:\n'; resultText += ` - 创建: ${counters.schemaCreated || 0}\n`; resultText += ` - 更新: ${counters.schemaUpdated || 0}\n`; resultText += ` - 失败: ${counters.schemaFailed || 0}\n`; resultText += ` - 忽略: ${counters.schemaIgnored || 0}\n`; // 错误信息 if (result.data.errors && result.data.errors.length > 0) { resultText += '\n⚠️ 错误信息:\n'; result.data.errors.forEach((error: any) => { resultText += ` - [${error.code}] ${error.message}\n`; }); } // 总结 const totalCreated = counters.endpointCreated + counters.schemaCreated; const totalFailed = counters.endpointFailed + counters.schemaFailed; if (totalCreated === 0 && totalFailed === 0) { resultText += '\n💡 提示:没有新增或修改任何内容,可能是因为项目中已存在相同的 API。\n'; } else if (totalFailed > 0) { resultText += `\n⚠️ 警告:有 ${totalFailed} 个项导入失败,请检查错误信息。\n`; } else { resultText += `\n🎉 成功导入 ${totalCreated} 个项!\n`; } } return { content: [ { type: 'text', text: resultText } ] }; } case 'export_openapi': { const { mode, oasVersion, exportFormat, pathFilter } = args as { mode?: 'summary' | 'full'; oasVersion?: '2.0' | '3.0' | '3.1'; exportFormat?: 'JSON' | 'YAML'; pathFilter?: string; }; const exportMode = mode || 'summary'; const result = await apifoxClient.exportOpenApi({ oasVersion: oasVersion || '3.0', exportFormat: exportFormat || 'JSON' }); // 路径过滤 let filteredPaths = result.paths; if (pathFilter) { filteredPaths = {}; Object.keys(result.paths).forEach(path => { if (path.startsWith(pathFilter)) { filteredPaths[path] = result.paths[path]; } }); } // Summary 模式:只返回目录结构和接口列表 if (exportMode === 'summary') { let resultText = '✅ 接口文档概览(Summary 模式)\n\n'; // 统计信息 const pathsCount = Object.keys(filteredPaths).length; const totalCount = Object.keys(result.paths).length; resultText += `📊 统计信息:\n`; resultText += ` - 项目标题: ${result.info?.title || '未命名'}\n`; if (pathFilter) { resultText += ` - 过滤条件: ${pathFilter}\n`; resultText += ` - 匹配接口: ${pathsCount} / ${totalCount}\n`; } else { resultText += ` - 总接口数: ${pathsCount}\n`; } resultText += '\n'; // 按路径前缀分组(模拟目录结构) const groups: { [key: string]: any[] } = {}; Object.keys(filteredPaths).forEach(path => { const pathObj = filteredPaths[path]; const methods = Object.keys(pathObj).filter(m => m !== 'parameters'); methods.forEach(method => { const operation = pathObj[method]; const tags = operation.tags || ['未分类']; const tag = tags[0] || '未分类'; if (!groups[tag]) { groups[tag] = []; } groups[tag].push({ path, method: method.toUpperCase(), summary: operation.summary || '无描述' }); }); }); // 显示目录结构 resultText += '📁 目录结构和接口列表:\n\n'; const sortedGroups = Object.keys(groups).sort(); sortedGroups.forEach(groupName => { resultText += `📂 ${groupName}\n`; groups[groupName].forEach(api => { resultText += ` └─ [${api.method}] ${api.path}\n`; resultText += ` ${api.summary}\n`; }); resultText += '\n'; }); resultText += '\n💡 提示:\n'; resultText += ' - 导入新接口时,请参考上述目录结构\n'; resultText += ' - 将相关接口放入对应的目录(使用 tags 字段)\n'; resultText += ' - 保持接口路径命名风格一致\n'; if (!pathFilter) { resultText += ' - 如需查看特定接口详情,使用 pathFilter 参数过滤\n'; resultText += ' - 如需完整规范,使用 mode: "full"\n'; } return { content: [ { type: 'text', text: resultText } ] }; } // Full 模式:返回完整 OpenAPI 规范 let resultText = '✅ OpenAPI 规范导出成功(Full 模式)\n\n'; const pathsCount = Object.keys(filteredPaths).length; const schemasCount = result.components?.schemas ? Object.keys(result.components.schemas).length : 0; resultText += `📊 导出统计:\n`; resultText += ` - OpenAPI 版本: ${result.openapi || result.swagger}\n`; resultText += ` - 项目标题: ${result.info?.title || '未命名'}\n`; resultText += ` - 接口数量: ${pathsCount}\n`; resultText += ` - 数据模型数量: ${schemasCount}\n`; if (pathsCount > 0) { resultText += '\n📋 接口列表:\n'; const paths = Object.keys(filteredPaths); paths.slice(0, 10).forEach(path => { const methods = Object.keys(filteredPaths[path]).filter(m => m !== 'parameters'); resultText += ` - ${path} [${methods.join(', ').toUpperCase()}]\n`; }); if (paths.length > 10) { resultText += ` ... 还有 ${paths.length - 10} 个接口\n`; } } // 如果有过滤,返回过滤后的规范 let exportedSpec = result; if (pathFilter) { exportedSpec = { ...result, paths: filteredPaths }; } return { content: [ { type: 'text', text: resultText }, { type: 'text', text: `\n📄 完整 OpenAPI 规范:\n\`\`\`json\n${JSON.stringify(exportedSpec, null, 2)}\n\`\`\`` } ] }; } default: throw new Error(`未知的工具: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ 操作失败:${errorMessage}` } ], isError: true }; } }); // 启动服务器 const transport = new StdioServerTransport(); await server.connect(transport); console.error('✅ Apifox MCP 服务器已启动'); console.error(`📦 项目 ID: ${config.projectId}`); console.error(`🔗 API 地址: ${config.baseUrl || 'https://api.apifox.com'}`); console.error('\n🚀 可用工具:'); console.error(' - import_openapi: 批量导入 OpenAPI/Swagger 规范到 Apifox'); console.error(' - export_openapi: 从 Apifox 导出 OpenAPI/Swagger 规范'); } // 运行主函数 main().catch((error) => { console.error('服务器启动失败:', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Warren-W/apifox-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server