lhwapi-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Apifox MCP Server
2
+
3
+ MCP 服务器,用于获取 Apifox API 文档。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g apifox-mcp-server
9
+ ```
10
+
11
+ ## 使用方法
12
+
13
+ ### Claude Desktop 配置
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "apifox-api-doc": {
19
+ "command": "npx",
20
+ "args": ["-y", "apifox-mcp-server", "--project-id=你的项目ID"],
21
+ "env": {
22
+ "APIFOX_ACCESS_TOKEN": "<你的token>"
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ### 获取 Apifox Access Token
30
+
31
+ 1. 登录 Apifox
32
+ 2. 点击头像 → 账号设置 → API 访问令牌
33
+ 3. 新建令牌并保存
34
+
35
+ ## 可用工具
36
+
37
+ - **listProjects** - 获取项目列表
38
+ - **listApis** - 获取指定项目下的 API 列表
39
+ - **getApiDetail** - 获取单个 API 的详细信息
40
+ - **searchApis** - 搜索 API
41
+ - **exportOpenApi** - 导出 OpenAPI/Swagger 格式
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ const APIFOX_BASE_URL = "https://api.apifox.com";
5
+ const args = process.argv.slice(2);
6
+ let projectId;
7
+ for (let i = 0; i < args.length; i++) {
8
+ if (args[i] === "--project-id" && args[i + 1]) {
9
+ projectId = args[i + 1];
10
+ }
11
+ }
12
+ const token = process.env.APIFOX_ACCESS_TOKEN;
13
+ async function fetchApifox(endpoint, authToken, options) {
14
+ const response = await fetch(`${APIFOX_BASE_URL}${endpoint}`, {
15
+ method: options?.method || "GET",
16
+ headers: {
17
+ "Authorization": `Bearer ${authToken}`,
18
+ "Content-Type": "application/json",
19
+ "X-Apifox-Api-Version": "2024-03-28",
20
+ },
21
+ body: options?.body ? JSON.stringify(options.body) : undefined,
22
+ });
23
+ if (!response.ok) {
24
+ const error = await response.text();
25
+ throw new Error(`Apifox API error: ${response.status} - ${error}`);
26
+ }
27
+ const data = await response.json();
28
+ return data.data;
29
+ }
30
+ const server = new McpServer({
31
+ name: "lhwapi-mcp-server",
32
+ version: "1.0.0",
33
+ });
34
+ server.registerTool("listProjects", {
35
+ description: "获取 Apifox 项目列表",
36
+ inputSchema: z.object({
37
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
38
+ }).shape,
39
+ }, async ({ token: inputToken }) => {
40
+ const authToken = inputToken || token;
41
+ if (!authToken) {
42
+ return {
43
+ content: [{ type: "text", text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
44
+ isError: true,
45
+ };
46
+ }
47
+ const projects = await fetchApifox("/v1/projects", authToken);
48
+ const text = projects
49
+ .map((p) => `项目: ${p.name} (ID: ${p.id})\n描述: ${p.description || "无"}`)
50
+ .join("\n\n");
51
+ return {
52
+ content: [{ type: "text", text: text || "暂无项目" }],
53
+ };
54
+ });
55
+ server.registerTool("listApis", {
56
+ description: "获取指定项目下的 API 列表",
57
+ inputSchema: z.object({
58
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
59
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
60
+ }).shape,
61
+ }, async ({ projectId: inputProjectId, token: inputToken }) => {
62
+ const authToken = inputToken || token;
63
+ const authProjectId = inputProjectId || projectId;
64
+ if (!authToken) {
65
+ return {
66
+ content: [{ type: "text", text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
67
+ isError: true,
68
+ };
69
+ }
70
+ if (!authProjectId) {
71
+ return {
72
+ content: [{ type: "text", text: "错误: 请通过 --project-id 参数指定项目 ID" }],
73
+ isError: true,
74
+ };
75
+ }
76
+ const apis = await fetchApifox(`/v1/projects/${authProjectId}/apis`, authToken);
77
+ const text = apis
78
+ .map((api) => `[${api.method.toUpperCase()}] ${api.path} - ${api.name}`)
79
+ .join("\n");
80
+ return {
81
+ content: [{ type: "text", text: text || "暂无 API" }],
82
+ };
83
+ });
84
+ server.registerTool("getApiDetail", {
85
+ description: "获取单个 API 的详细信息",
86
+ inputSchema: z.object({
87
+ apiId: z.string().describe("API ID"),
88
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
89
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
90
+ }).shape,
91
+ }, async ({ apiId, projectId: inputProjectId, token: inputToken }) => {
92
+ const authToken = inputToken || token;
93
+ const authProjectId = inputProjectId || projectId;
94
+ if (!authToken) {
95
+ return {
96
+ content: [{ type: "text", text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
97
+ isError: true,
98
+ };
99
+ }
100
+ if (!authProjectId) {
101
+ return {
102
+ content: [{ type: "text", text: "错误: 请通过 --project-id 参数指定项目 ID" }],
103
+ isError: true,
104
+ };
105
+ }
106
+ const api = await fetchApifox(`/v1/projects/${authProjectId}/apis/${apiId}`, authToken);
107
+ let detail = `# ${api.name}\n\n`;
108
+ detail += `**方法**: ${api.method.toUpperCase()}\n`;
109
+ detail += `**路径**: ${api.path}\n\n`;
110
+ if (api.description) {
111
+ detail += `## 描述\n\n${api.description}\n\n`;
112
+ }
113
+ if (api.requestParams?.query?.length) {
114
+ detail += `## Query 参数\n\n`;
115
+ api.requestParams.query.forEach((param) => {
116
+ detail += `- ${param.name}: ${param.type || "string"} ${param.required ? "(必填)" : "(可选)"}\n`;
117
+ });
118
+ detail += "\n";
119
+ }
120
+ if (api.requestBody?.schema) {
121
+ detail += `## 请求体\n\n\`\`\`json\n${JSON.stringify(api.requestBody.schema, null, 2)}\n\`\`\`\n\n`;
122
+ }
123
+ if (api.responseList?.length) {
124
+ detail += `## 响应\n\n`;
125
+ api.responseList.forEach((resp) => {
126
+ detail += `### ${resp.statusCode}\n`;
127
+ if (resp.responseBodySchema) {
128
+ detail += `\`\`\`json\n${JSON.stringify(resp.responseBodySchema, null, 2)}\n\`\`\`\n`;
129
+ }
130
+ });
131
+ }
132
+ return {
133
+ content: [{ type: "text", text: detail }],
134
+ };
135
+ });
136
+ server.registerTool("searchApis", {
137
+ description: "搜索 API",
138
+ inputSchema: z.object({
139
+ keyword: z.string().describe("搜索关键词"),
140
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
141
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
142
+ }).shape,
143
+ }, async ({ keyword, projectId: inputProjectId, token: inputToken }) => {
144
+ const authToken = inputToken || token;
145
+ const authProjectId = inputProjectId || projectId;
146
+ if (!authToken) {
147
+ return {
148
+ content: [{ type: "text", text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
149
+ isError: true,
150
+ };
151
+ }
152
+ if (!authProjectId) {
153
+ return {
154
+ content: [{ type: "text", text: "错误: 请通过 --project-id 参数指定项目 ID" }],
155
+ isError: true,
156
+ };
157
+ }
158
+ const apis = await fetchApifox(`/v1/projects/${authProjectId}/apis`, authToken);
159
+ const filtered = apis.filter((api) => api.name.toLowerCase().includes(keyword.toLowerCase()) ||
160
+ api.path.toLowerCase().includes(keyword.toLowerCase()));
161
+ const text = filtered
162
+ .map((api) => `[${api.method.toUpperCase()}] ${api.path} - ${api.name}`)
163
+ .join("\n");
164
+ return {
165
+ content: [{ type: "text", text: text || "未找到匹配的 API" }],
166
+ };
167
+ });
168
+ server.registerTool("exportOpenApi", {
169
+ description: "导出 OpenAPI/Swagger 格式数据",
170
+ inputSchema: z.object({
171
+ oasVersion: z.string().optional().describe("OpenAPI 版本: 3.0, 3.1, 2.0"),
172
+ exportFormat: z.string().optional().describe("导出格式: JSON 或 YAML"),
173
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
174
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
175
+ }).shape,
176
+ }, async ({ oasVersion, exportFormat, projectId: inputProjectId, token: inputToken }) => {
177
+ const authToken = inputToken || token;
178
+ const authProjectId = inputProjectId || projectId;
179
+ if (!authToken) {
180
+ return {
181
+ content: [{ type: "text", text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
182
+ isError: true,
183
+ };
184
+ }
185
+ if (!authProjectId) {
186
+ return {
187
+ content: [{ type: "text", text: "错误: 请通过 --project-id 参数指定项目 ID" }],
188
+ isError: true,
189
+ };
190
+ }
191
+ const result = await fetchApifox(`/v1/projects/${authProjectId}/export-openapi`, authToken, {
192
+ method: "POST",
193
+ body: {
194
+ scope: { type: "ALL" },
195
+ oasVersion: oasVersion || "3.1",
196
+ exportFormat: exportFormat || "JSON",
197
+ },
198
+ });
199
+ const format = exportFormat || "JSON";
200
+ let output;
201
+ if (format.toUpperCase() === "YAML") {
202
+ output = result.openapi || "";
203
+ }
204
+ else {
205
+ output = JSON.stringify(result, null, 2);
206
+ }
207
+ return {
208
+ content: [{ type: "text", text: output }],
209
+ };
210
+ });
211
+ async function main() {
212
+ if (!projectId) {
213
+ console.error("警告: 未指定 --project-id 参数");
214
+ }
215
+ if (!token) {
216
+ console.error("警告: 未设置 APIFOX_ACCESS_TOKEN 环境变量");
217
+ }
218
+ const transport = new StdioServerTransport();
219
+ await server.connect(transport);
220
+ }
221
+ main().catch((error) => {
222
+ console.error("Server error:", error);
223
+ process.exit(1);
224
+ });
225
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAEjD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,SAA6B,CAAC;AAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;IACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,cAAc,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9C,SAAS,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;AAiC9C,KAAK,UAAU,WAAW,CAAI,QAAgB,EAAE,SAAiB,EAAE,OAGlE;IACC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,eAAe,GAAG,QAAQ,EAAE,EAAE;QAC5D,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK;QAChC,OAAO,EAAE;YACP,eAAe,EAAE,UAAU,SAAS,EAAE;YACtC,cAAc,EAAE,kBAAkB;YAClC,sBAAsB,EAAE,YAAY;SACrC;QACD,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC/D,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,MAAM,KAAK,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAuB,CAAC;IACxD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,mBAAmB;IACzB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;IACE,WAAW,EAAE,gBAAgB;IAC7B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;KACnE,CAAC,CAAC,KAAK;CACT,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAsB,EAAE,EAAE;IAClD,MAAM,SAAS,GAAG,UAAU,IAAI,KAAK,CAAC;IACtC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,8CAA8C,EAAE,CAAC;YAC1F,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAY,cAAc,EAAE,SAAS,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,QAAQ;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,WAAW,IAAI,GAAG,EAAE,CAAC;SACtE,IAAI,CAAC,MAAM,CAAC,CAAC;IAChB,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,IAAI,MAAM,EAAE,CAAC;KAC3D,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,UAAU,EACV;IACE,WAAW,EAAE,iBAAiB;IAC9B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACjE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;KACnE,CAAC,CAAC,KAAK;CACT,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAA0C,EAAE,EAAE;IACjG,MAAM,SAAS,GAAG,UAAU,IAAI,KAAK,CAAC;IACtC,MAAM,aAAa,GAAG,cAAc,IAAI,SAAS,CAAC;IAElD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,8CAA8C,EAAE,CAAC;YAC1F,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gCAAgC,EAAE,CAAC;YAC5E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,WAAW,CAC5B,gBAAgB,aAAa,OAAO,EACpC,SAAS,CACV,CAAC;IACF,MAAM,IAAI,GAAG,IAAI;SACd,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;SACvE,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,IAAI,QAAQ,EAAE,CAAC;KAC7D,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;IACE,WAAW,EAAE,gBAAgB;IAC7B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACpC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACjE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;KACnE,CAAC,CAAC,KAAK;CACT,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAyD,EAAE,EAAE;IACvH,MAAM,SAAS,GAAG,UAAU,IAAI,KAAK,CAAC;IACtC,MAAM,aAAa,GAAG,cAAc,IAAI,SAAS,CAAC;IAElD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,8CAA8C,EAAE,CAAC;YAC1F,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gCAAgC,EAAE,CAAC;YAC5E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,WAAW,CAC3B,gBAAgB,aAAa,SAAS,KAAK,EAAE,EAC7C,SAAS,CACV,CAAC;IAEF,IAAI,MAAM,GAAG,KAAK,GAAG,CAAC,IAAI,MAAM,CAAC;IACjC,MAAM,IAAI,WAAW,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC;IAClD,MAAM,IAAI,WAAW,GAAG,CAAC,IAAI,MAAM,CAAC;IAEpC,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;QACpB,MAAM,IAAI,YAAY,GAAG,CAAC,WAAW,MAAM,CAAC;IAC9C,CAAC;IAED,IAAI,GAAG,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,iBAAiB,CAAC;QAC5B,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,KAAU,EAAE,EAAE;YAC7C,MAAM,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,QAAQ,IAClD,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAC5B,IAAI,CAAC;QACP,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,IAAI,CAAC;IACjB,CAAC;IAED,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,yBAAyB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IACnG,CAAC;IAED,IAAI,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAC7B,MAAM,IAAI,WAAW,CAAC;QACtB,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAChC,MAAM,IAAI,OAAO,IAAI,CAAC,UAAU,IAAI,CAAC;YACrC,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC5B,MAAM,IAAI,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC;YACxF,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;KACnD,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;IACE,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;QACrC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACjE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;KACnE,CAAC,CAAC,KAAK;CACT,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAA2D,EAAE,EAAE;IAC3H,MAAM,SAAS,GAAG,UAAU,IAAI,KAAK,CAAC;IACtC,MAAM,aAAa,GAAG,cAAc,IAAI,SAAS,CAAC;IAElD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,8CAA8C,EAAE,CAAC;YAC1F,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gCAAgC,EAAE,CAAC;YAC5E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,WAAW,CAC5B,gBAAgB,aAAa,OAAO,EACpC,SAAS,CACV,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAC1B,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACtD,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CACzD,CAAC;IAEF,MAAM,IAAI,GAAG,QAAQ;SAClB,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;SACvE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,IAAI,YAAY,EAAE,CAAC;KACjE,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;IACE,WAAW,EAAE,yBAAyB;IACtC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;QACvE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACjE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACjE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;KACnE,CAAC,CAAC,KAAK;CACT,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAK9E,EAAE,EAAE;IACH,MAAM,SAAS,GAAG,UAAU,IAAI,KAAK,CAAC;IACtC,MAAM,aAAa,GAAG,cAAc,IAAI,SAAS,CAAC;IAElD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,8CAA8C,EAAE,CAAC;YAC1F,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gCAAgC,EAAE,CAAC;YAC5E,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,gBAAgB,aAAa,iBAAiB,EAC9C,SAAS,EACT;QACE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE;YACJ,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;YACtB,UAAU,EAAE,UAAU,IAAI,KAAK;YAC/B,YAAY,EAAE,YAAY,IAAI,MAAM;SACrC;KACF,CACF,CAAC;IAEF,MAAM,MAAM,GAAG,YAAY,IAAI,MAAM,CAAC;IACtC,IAAI,MAAc,CAAC;IACnB,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;QACpC,MAAM,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;KACnD,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "lhwapi-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for API documentation",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "lhwapi-mcp-server": "dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "start": "node dist/index.js",
14
+ "dev": "tsx src/index.ts"
15
+ },
16
+ "keywords": ["mcp", "api-docs"],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.27.1",
21
+ "zod": "^4.3.6"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^25.3.2",
25
+ "tsx": "^4.21.0",
26
+ "typescript": "^5.9.3"
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,334 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+
5
+ const APIFOX_BASE_URL = "https://api.apifox.com";
6
+
7
+ const args = process.argv.slice(2);
8
+ let projectId: string | undefined;
9
+ for (let i = 0; i < args.length; i++) {
10
+ if (args[i] === "--project-id" && args[i + 1]) {
11
+ projectId = args[i + 1];
12
+ }
13
+ }
14
+
15
+ const token = process.env.APIFOX_ACCESS_TOKEN;
16
+
17
+ interface ApifoxResponse<T> {
18
+ data: T;
19
+ message?: string;
20
+ }
21
+
22
+ interface Project {
23
+ id: string;
24
+ name: string;
25
+ description?: string;
26
+ }
27
+
28
+ interface ApiDetail {
29
+ id: string;
30
+ name: string;
31
+ path: string;
32
+ method: string;
33
+ description?: string;
34
+ requestParams?: {
35
+ query?: any[];
36
+ path?: any[];
37
+ header?: any[];
38
+ };
39
+ requestBody?: {
40
+ schema?: any;
41
+ };
42
+ responseList?: {
43
+ statusCode: number;
44
+ responseBodySchema?: any;
45
+ }[];
46
+ }
47
+
48
+ async function fetchApifox<T>(endpoint: string, authToken: string, options?: {
49
+ method?: string;
50
+ body?: any;
51
+ }): Promise<T> {
52
+ const response = await fetch(`${APIFOX_BASE_URL}${endpoint}`, {
53
+ method: options?.method || "GET",
54
+ headers: {
55
+ "Authorization": `Bearer ${authToken}`,
56
+ "Content-Type": "application/json",
57
+ "X-Apifox-Api-Version": "2024-03-28",
58
+ },
59
+ body: options?.body ? JSON.stringify(options.body) : undefined,
60
+ });
61
+
62
+ if (!response.ok) {
63
+ const error = await response.text();
64
+ throw new Error(`Apifox API error: ${response.status} - ${error}`);
65
+ }
66
+
67
+ const data = await response.json() as ApifoxResponse<T>;
68
+ return data.data;
69
+ }
70
+
71
+ const server = new McpServer({
72
+ name: "lhwapi-mcp-server",
73
+ version: "1.0.0",
74
+ });
75
+
76
+ server.registerTool(
77
+ "listProjects",
78
+ {
79
+ description: "获取 Apifox 项目列表",
80
+ inputSchema: z.object({
81
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
82
+ }).shape,
83
+ },
84
+ async ({ token: inputToken }: { token?: string }) => {
85
+ const authToken = inputToken || token;
86
+ if (!authToken) {
87
+ return {
88
+ content: [{ type: "text" as const, text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
89
+ isError: true,
90
+ };
91
+ }
92
+ const projects = await fetchApifox<Project[]>("/v1/projects", authToken);
93
+ const text = projects
94
+ .map((p) => `项目: ${p.name} (ID: ${p.id})\n描述: ${p.description || "无"}`)
95
+ .join("\n\n");
96
+ return {
97
+ content: [{ type: "text" as const, text: text || "暂无项目" }],
98
+ };
99
+ }
100
+ );
101
+
102
+ server.registerTool(
103
+ "listApis",
104
+ {
105
+ description: "获取指定项目下的 API 列表",
106
+ inputSchema: z.object({
107
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
108
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
109
+ }).shape,
110
+ },
111
+ async ({ projectId: inputProjectId, token: inputToken }: { projectId?: string; token?: string }) => {
112
+ const authToken = inputToken || token;
113
+ const authProjectId = inputProjectId || projectId;
114
+
115
+ if (!authToken) {
116
+ return {
117
+ content: [{ type: "text" as const, text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
118
+ isError: true,
119
+ };
120
+ }
121
+ if (!authProjectId) {
122
+ return {
123
+ content: [{ type: "text" as const, text: "错误: 请通过 --project-id 参数指定项目 ID" }],
124
+ isError: true,
125
+ };
126
+ }
127
+
128
+ const apis = await fetchApifox<ApiDetail[]>(
129
+ `/v1/projects/${authProjectId}/apis`,
130
+ authToken
131
+ );
132
+ const text = apis
133
+ .map((api) => `[${api.method.toUpperCase()}] ${api.path} - ${api.name}`)
134
+ .join("\n");
135
+ return {
136
+ content: [{ type: "text" as const, text: text || "暂无 API" }],
137
+ };
138
+ }
139
+ );
140
+
141
+ server.registerTool(
142
+ "getApiDetail",
143
+ {
144
+ description: "获取单个 API 的详细信息",
145
+ inputSchema: z.object({
146
+ apiId: z.string().describe("API ID"),
147
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
148
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
149
+ }).shape,
150
+ },
151
+ async ({ apiId, projectId: inputProjectId, token: inputToken }: { apiId: string; projectId?: string; token?: string }) => {
152
+ const authToken = inputToken || token;
153
+ const authProjectId = inputProjectId || projectId;
154
+
155
+ if (!authToken) {
156
+ return {
157
+ content: [{ type: "text" as const, text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
158
+ isError: true,
159
+ };
160
+ }
161
+ if (!authProjectId) {
162
+ return {
163
+ content: [{ type: "text" as const, text: "错误: 请通过 --project-id 参数指定项目 ID" }],
164
+ isError: true,
165
+ };
166
+ }
167
+
168
+ const api = await fetchApifox<ApiDetail>(
169
+ `/v1/projects/${authProjectId}/apis/${apiId}`,
170
+ authToken
171
+ );
172
+
173
+ let detail = `# ${api.name}\n\n`;
174
+ detail += `**方法**: ${api.method.toUpperCase()}\n`;
175
+ detail += `**路径**: ${api.path}\n\n`;
176
+
177
+ if (api.description) {
178
+ detail += `## 描述\n\n${api.description}\n\n`;
179
+ }
180
+
181
+ if (api.requestParams?.query?.length) {
182
+ detail += `## Query 参数\n\n`;
183
+ api.requestParams.query.forEach((param: any) => {
184
+ detail += `- ${param.name}: ${param.type || "string"} ${
185
+ param.required ? "(必填)" : "(可选)"
186
+ }\n`;
187
+ });
188
+ detail += "\n";
189
+ }
190
+
191
+ if (api.requestBody?.schema) {
192
+ detail += `## 请求体\n\n\`\`\`json\n${JSON.stringify(api.requestBody.schema, null, 2)}\n\`\`\`\n\n`;
193
+ }
194
+
195
+ if (api.responseList?.length) {
196
+ detail += `## 响应\n\n`;
197
+ api.responseList.forEach((resp) => {
198
+ detail += `### ${resp.statusCode}\n`;
199
+ if (resp.responseBodySchema) {
200
+ detail += `\`\`\`json\n${JSON.stringify(resp.responseBodySchema, null, 2)}\n\`\`\`\n`;
201
+ }
202
+ });
203
+ }
204
+
205
+ return {
206
+ content: [{ type: "text" as const, text: detail }],
207
+ };
208
+ }
209
+ );
210
+
211
+ server.registerTool(
212
+ "searchApis",
213
+ {
214
+ description: "搜索 API",
215
+ inputSchema: z.object({
216
+ keyword: z.string().describe("搜索关键词"),
217
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
218
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
219
+ }).shape,
220
+ },
221
+ async ({ keyword, projectId: inputProjectId, token: inputToken }: { keyword: string; projectId?: string; token?: string }) => {
222
+ const authToken = inputToken || token;
223
+ const authProjectId = inputProjectId || projectId;
224
+
225
+ if (!authToken) {
226
+ return {
227
+ content: [{ type: "text" as const, text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
228
+ isError: true,
229
+ };
230
+ }
231
+ if (!authProjectId) {
232
+ return {
233
+ content: [{ type: "text" as const, text: "错误: 请通过 --project-id 参数指定项目 ID" }],
234
+ isError: true,
235
+ };
236
+ }
237
+
238
+ const apis = await fetchApifox<ApiDetail[]>(
239
+ `/v1/projects/${authProjectId}/apis`,
240
+ authToken
241
+ );
242
+
243
+ const filtered = apis.filter(
244
+ (api) =>
245
+ api.name.toLowerCase().includes(keyword.toLowerCase()) ||
246
+ api.path.toLowerCase().includes(keyword.toLowerCase())
247
+ );
248
+
249
+ const text = filtered
250
+ .map((api) => `[${api.method.toUpperCase()}] ${api.path} - ${api.name}`)
251
+ .join("\n");
252
+
253
+ return {
254
+ content: [{ type: "text" as const, text: text || "未找到匹配的 API" }],
255
+ };
256
+ }
257
+ );
258
+
259
+ server.registerTool(
260
+ "exportOpenApi",
261
+ {
262
+ description: "导出 OpenAPI/Swagger 格式数据",
263
+ inputSchema: z.object({
264
+ oasVersion: z.string().optional().describe("OpenAPI 版本: 3.0, 3.1, 2.0"),
265
+ exportFormat: z.string().optional().describe("导出格式: JSON 或 YAML"),
266
+ projectId: z.string().optional().describe("项目 ID (可选,默认使用命令行参数)"),
267
+ token: z.string().optional().describe("Apifox 访问令牌 (可选,默认使用环境变量)"),
268
+ }).shape,
269
+ },
270
+ async ({ oasVersion, exportFormat, projectId: inputProjectId, token: inputToken }: {
271
+ oasVersion?: string;
272
+ exportFormat?: string;
273
+ projectId?: string;
274
+ token?: string
275
+ }) => {
276
+ const authToken = inputToken || token;
277
+ const authProjectId = inputProjectId || projectId;
278
+
279
+ if (!authToken) {
280
+ return {
281
+ content: [{ type: "text" as const, text: "错误: 请设置 APIFOX_ACCESS_TOKEN 环境变量或传入 token 参数" }],
282
+ isError: true,
283
+ };
284
+ }
285
+ if (!authProjectId) {
286
+ return {
287
+ content: [{ type: "text" as const, text: "错误: 请通过 --project-id 参数指定项目 ID" }],
288
+ isError: true,
289
+ };
290
+ }
291
+
292
+ const result = await fetchApifox<any>(
293
+ `/v1/projects/${authProjectId}/export-openapi`,
294
+ authToken,
295
+ {
296
+ method: "POST",
297
+ body: {
298
+ scope: { type: "ALL" },
299
+ oasVersion: oasVersion || "3.1",
300
+ exportFormat: exportFormat || "JSON",
301
+ },
302
+ }
303
+ );
304
+
305
+ const format = exportFormat || "JSON";
306
+ let output: string;
307
+ if (format.toUpperCase() === "YAML") {
308
+ output = result.openapi || "";
309
+ } else {
310
+ output = JSON.stringify(result, null, 2);
311
+ }
312
+
313
+ return {
314
+ content: [{ type: "text" as const, text: output }],
315
+ };
316
+ }
317
+ );
318
+
319
+ async function main() {
320
+ if (!projectId) {
321
+ console.error("警告: 未指定 --project-id 参数");
322
+ }
323
+ if (!token) {
324
+ console.error("警告: 未设置 APIFOX_ACCESS_TOKEN 环境变量");
325
+ }
326
+
327
+ const transport = new StdioServerTransport();
328
+ await server.connect(transport);
329
+ }
330
+
331
+ main().catch((error) => {
332
+ console.error("Server error:", error);
333
+ process.exit(1);
334
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }