From be63d49f40e9a057e402ce048fa4cee62d0110c4 Mon Sep 17 00:00:00 2001 From: zhangguoyun Date: Tue, 16 Dec 2025 10:32:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20README=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=B9=B6=E5=AE=9E=E7=8E=B0=20HTTP=20=E4=BC=A0?= =?UTF-8?q?=E8=BE=93=E6=A8=A1=E5=BC=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ index.js | 61 +++++++++++++++++++++++++-- package.json | 3 ++ 3 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5dd83dd --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# Apifox MCP Service + +这是一个基于 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 标准实现的 Apifox 服务端。它充当 LLM (如 Claude, Gemini 等) 与 Apifox API 文档之间的桥梁,允许 AI 助手直接检索 Apifox 中的接口定义,并根据定义生成强类型的 API 请求代码。 + +## ✨ 功能特性 + +* **MCP 协议支持**: 支持标准 MCP 协议,可集成到 Claude Desktop, Cursor 等支持 MCP 的客户端。 +* **多传输模式**: 支持 `stdio` (标准输入输出,默认) 和 `http` (SSE 流式传输) 两种模式。 +* **接口检索**: + * `get_api_by_path`: 根据路径获取指定接口的详细定义 (Schema)。 + * `get_apis_by_folder`: 获取指定目录下的所有接口定义。 +* **AI 提示词 (Prompts)**: + * `gen-api-by-path`: 指导 AI 根据接口路径生成 API 代码。 + * `gen-api-by-folder`: 指导 AI 根据目录批量生成 API 代码。 +* **健康检查**: HTTP 模式下提供 `/health` 端点。 + +## 🛠️ 安装与配置 + +### 前置要求 + +* Node.js (v18 或更高版本) +* Apifox 账号及项目访问权限 + +### 环境变量 + +运行此服务需要配置以下环境变量: + +| 变量名 | 描述 | +| :--- | :--- | +| `APIFOX_PROJECT_ID` | 你的 Apifox 项目 ID (数字) | +| `APIFOX_TOKEN` | 你的 Apifox 访问令牌 (Bearer Token) | + +### 安装依赖 + +```bash +npm install +``` + +## 🚀 启动服务 + +### 1. Stdio 模式 (默认) + +适用于 Claude Desktop 或作为子进程运行的场景。 + +```bash +# 需确保环境变量已设置 +export APIFOX_PROJECT_ID=你的项目ID +export APIFOX_TOKEN=你的Token + +node index.js +# 或显式指定 +node index.js --transport stdio +``` + +### 2. HTTP (SSE) 模式 + +适用于远程部署或需要 HTTP 访问的场景。 + +```bash +node index.js --transport http --port 8889 +``` + +* **Endpoint**: `http://localhost:8889/sse` +* **Health Check**: `http://localhost:8889/health` + +## 💻 客户端集成示例 + +### Claude Desktop 配置 + +编辑 Claude Desktop 的配置文件 (通常位于 `%APPDATA%\Claude\claude_desktop_config.json` 或 `~/Library/Application Support/Claude/claude_desktop_config.json`): + +```json +{ + "mcpServers": { + "apifox": { + "command": "node", + "args": [ + "D:/demo/apifox-mcp-service/index.js" + ], + "env": { + "APIFOX_PROJECT_ID": "123456", + "APIFOX_TOKEN": "apifox_pat_xxxxxxxx" + } + } + } +} +``` + +*注意:请将路径替换为你本地的实际绝对路径。* + +## 📚 工具详情 + +### Tools + +#### `get_api_by_path` +根据接口路径获取 Apifox 接口定义数据。 +* 参数: `path` (string), `projectId` (number, 可选) + +#### `get_apis_by_folder` +根据接口目录名称获取目录下所有接口定义。 +* 参数: `folderName` (string), `projectId` (number, 可选) + +### Prompts + +#### `gen-api-by-path` +流程化 Prompt,引导 LLM 获取接口定义并生成符合项目规范的 API 代码。 + +#### `gen-api-by-folder` +流程化 Prompt,引导 LLM 批量获取目录下接口并生成代码。 + +## 📝 License + +ISC diff --git a/index.js b/index.js index 5d6b0bb..87c2ad9 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,9 @@ +#!/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' @@ -162,14 +165,66 @@ server.registerPrompt( +// 启动服务器,使用标准输入/输出(stdio)与客户端通信 + // 启动服务器,使用标准输入/输出(stdio)与客户端通信 async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Apifox MCP 服务器已通过 stdio 启动"); // 日志输出到 stderr + 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); diff --git a/package.json b/package.json index 37761b7..bfa6d50 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,9 @@ "node-cache": "^5.1.2", "zod": "^3.25.76" }, + "bin": { + "apifox-mcp-server": "./index.js" + }, "devDependencies": { "@modelcontextprotocol/inspector": "^0.17.2" }