#
tokens: 4001/50000 9/9 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .env.example
├── .eslintrc
├── .gitignore
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── src
│   ├── cli.ts
│   ├── config.ts
│   ├── index.ts
│   └── server.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | .cursor/
2 | .history/
3 | node_modules/
4 | .env
5 | build/
6 | 
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | ###
 2 |  # @Descripttion: 
 3 |  # @version: 
 4 |  # @Author: wangmin
 5 |  # @Date: 2025-03-21 11:25:26
 6 |  # @LastEditors: wangmin
 7 |  # @LastEditTime: 2025-03-21 11:26:18
 8 | ### 
 9 | # your apifox api key
10 | APIFOX_API_KEY=your_apifox_api_key
11 | # your apifox project id
12 | PROJECT_ID=your_apifox_project_id
13 | # your apifox server 
14 | PORT=your_apifox_server_port
```

--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------

```
 1 | {
 2 |   "parser": "@typescript-eslint/parser",
 3 |   "extends": [
 4 |     "eslint:recommended",
 5 |     "plugin:@typescript-eslint/recommended",
 6 |     "prettier"
 7 |   ],
 8 |   "plugins": ["@typescript-eslint"],
 9 |   "parserOptions": {
10 |     "ecmaVersion": 2022,
11 |     "sourceType": "module"
12 |   },
13 |   "rules": {
14 |     "@typescript-eslint/explicit-function-return-type": "warn",
15 |     "@typescript-eslint/no-unused-vars": [
16 |       "error",
17 |       { "argsIgnorePattern": "^_" }
18 |     ],
19 |     "@typescript-eslint/no-explicit-any": "warn"
20 |   }
21 | }
22 | 
```

--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { resolve } from "path";
 4 | import { config } from "dotenv";
 5 | import { startServer } from "./index.js";
 6 | 
 7 | // Load .env from the current working directory
 8 | config({ path: resolve(process.cwd(), ".env") });
 9 | 
10 | startServer().catch((error: unknown) => {
11 |   if (error instanceof Error) {
12 |     console.error("Failed to start server:", error.message);
13 |   } else {
14 |     console.error("Failed to start server with unknown error:", error);
15 |   }
16 |   process.exit(1);
17 | });
18 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "lib": ["ES2022", "DOM"],
 7 |     "outDir": "./build",
 8 |     "rootDir": "./src",
 9 |     "strict": true,
10 |     "esModuleInterop": true,
11 |     "skipLibCheck": true,
12 |     "forceConsistentCasingInFileNames": true,
13 |     "resolveJsonModule": true,
14 |     "declaration": true,
15 |     "sourceMap": true,
16 |     "baseUrl": ".",
17 |     "paths": {
18 |       "~/*": ["src/*"]
19 |     },
20 |     "preserveSymlinks": true,
21 |     "allowJs": true,
22 |     "removeComments": true
23 |   },
24 |   "include": ["src/**/*"],
25 |   "exclude": ["node_modules", "build"]
26 | }
27 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /*
 2 |  * @Descripttion:
 3 |  * @version:
 4 |  * @Author: wangmin
 5 |  * @Date: 2025-03-20 14:39:11
 6 |  * @LastEditors: wangmin
 7 |  * @LastEditTime: 2025-04-03 09:33:34
 8 |  */
 9 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10 | import { getServerConfig } from "./config.js";
11 | import { ApiFoxServer } from "./server.js";
12 | // // 创建 MCP 服务器
13 | 
14 | // 启动服务器
15 | export async function startServer(): Promise<void> {
16 |   const isLocalMode =
17 |     process.env.NODE_ENV === "cli" || process.argv.includes("--local");
18 |   const config = getServerConfig();
19 | 
20 |   const server = new ApiFoxServer(config.apifoxApiKey, config.projectId);
21 | 
22 |   if (isLocalMode) {
23 |     const transport = new StdioServerTransport();
24 |     await server.connect(transport);
25 |   } else {
26 |     console.error(
27 |       `初始化HTTP模式下的ApiFox MCP Server服务器在端口 ${config.port}`
28 |     );
29 |     await server.startHttpServer(config.port);
30 |   }
31 | }
32 | 
33 | startServer().catch((error) => {
34 |   console.error("启动接口信息服务器失败:", error);
35 |   process.exit(1);
36 | });
37 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@wangmhaha/apifox-mcp-server",
 3 |   "version": "1.6.8",
 4 |   "main": "build/index.js",
 5 |   "type": "module",
 6 |   "bin": {
 7 |     "@wangmhaha/apifox-mcp-server": "build/cli.js"
 8 |   },
 9 |   "scripts": {
10 |     "build": "tsc && tsc-alias",
11 |     "start": "node build/index.js",
12 |     "start:cli": "cross-env NODE_ENV=cli node build/index.js",
13 |     "start:http": "node build/index.js"
14 |   },
15 |   "files": [
16 |     "build",
17 |     "README.md"
18 |   ],
19 |   "repository": {
20 |     "type": "git",
21 |     "url": "git+https://github.com/wangmhaha/apifox-mcp-server"
22 |   },
23 |   "keywords": [],
24 |   "author": "wangmhaha",
25 |   "license": "ISC",
26 |   "description": "",
27 |   "devDependencies": {
28 |     "@types/express": "^5.0.1",
29 |     "@types/express-serve-static-core": "^5.0.6",
30 |     "@types/node": "^22.10.0",
31 |     "tsc-alias": "^1.8.11",
32 |     "tsx": "^4.19.3",
33 |     "typescript": "^5.7.2"
34 |   },
35 |   "dependencies": {
36 |     "@modelcontextprotocol/sdk": "^1.6.1",
37 |     "@types/yargs": "^17.0.33",
38 |     "@typescript-eslint/eslint-plugin": "^8.27.0",
39 |     "@typescript-eslint/parser": "^8.27.0",
40 |     "cross-env": "^7.0.3",
41 |     "dotenv": "^16.4.7",
42 |     "eslint": "^9.22.0",
43 |     "express": "^4.21.2",
44 |     "yargs": "^17.7.2",
45 |     "zod": "^3.24.2"
46 |   }
47 | }
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /*
 2 |  * @Descripttion:
 3 |  * @version:
 4 |  * @Author: wangmin
 5 |  * @Date: 2025-03-20 17:26:55
 6 |  * @LastEditors: wangmin
 7 |  * @LastEditTime: 2025-03-21 09:16:43
 8 |  */
 9 | import { config } from "dotenv";
10 | import yargs from "yargs";
11 | import { hideBin } from "yargs/helpers";
12 | 
13 | config();
14 | 
15 | interface ServerConfig {
16 |   port: number;
17 |   apifoxApiKey: string;
18 |   projectId: string;
19 | }
20 | 
21 | interface CliArgs {
22 |   "apifox-api-key"?: string;
23 |   port?: number;
24 |   "project-id"?: string;
25 | }
26 | 
27 | export function getServerConfig(): ServerConfig {
28 |   const argv = yargs(hideBin(process.argv))
29 |     .options({
30 |       "apifox-api-key": {
31 |         type: "string",
32 |         describe: "apifox api key",
33 |       },
34 |       "project-id": {
35 |         type: "string",
36 |         describe: "apifox project id",
37 |       },
38 |       port: {
39 |         type: "number",
40 |         describe: "Prot to run the server on",
41 |       },
42 |     })
43 |     .help()
44 |     .parseSync() as CliArgs;
45 | 
46 |   const config: ServerConfig = {
47 |     apifoxApiKey: "",
48 |     projectId: "",
49 |     port: 3000,
50 |   };
51 | 
52 |   if (argv["apifox-api-key"]) {
53 |     config.apifoxApiKey = argv["apifox-api-key"];
54 |   } else if (process.env.APIFOX_API_KEY) {
55 |     config.apifoxApiKey = process.env.APIFOX_API_KEY;
56 |   }
57 | 
58 |   if (argv["project-id"]) {
59 |     config.projectId = argv["project-id"];
60 |   } else if (process.env.PROJECT_ID) {
61 |     config.projectId = process.env.PROJECT_ID;
62 |   }
63 | 
64 |   if (argv.port) {
65 |     config.port = argv.port;
66 |   } else if (process.env.PORT) {
67 |     config.port = parseInt(process.env.PORT, 10);
68 |   }
69 | 
70 |   if (!config.apifoxApiKey) {
71 |     console.error("请提供 apifox api key");
72 |     process.exit(1);
73 |   }
74 | 
75 |   if (!config.projectId) {
76 |     console.error("请提供 apifox project id");
77 |     process.exit(1);
78 |   }
79 | 
80 |   return config;
81 | }
82 | 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /*
  2 |  * @Descripttion:
  3 |  * @version:
  4 |  * @Author: wangmin
  5 |  * @Date: 2025-03-20 17:49:38
  6 |  * @LastEditors: wangmin
  7 |  * @LastEditTime: 2025-04-03 14:30:44
  8 |  */
  9 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 10 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
 11 | import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
 12 | import { IncomingMessage, ServerResponse } from "http";
 13 | import express from "express";
 14 | import { Response, Request } from "express-serve-static-core";
 15 | import { z } from "zod";
 16 | 
 17 | export class ApiFoxServer {
 18 |   private readonly server: McpServer;
 19 |   private sseTransport: SSEServerTransport | null = null;
 20 | 
 21 |   constructor(apifoxApiKey: string, projectId: string) {
 22 |     this.server = new McpServer({
 23 |       name: "ApiFox MCP Server",
 24 |       version: "1.0.0",
 25 |       capabilities: {
 26 |         notifications: true,
 27 |       },
 28 |     });
 29 | 
 30 |     this.registerTools(apifoxApiKey, projectId);
 31 |   }
 32 | 
 33 |   // 注册工具
 34 |   private registerTools(key: string, projectId: string): void {
 35 |     this.server.tool(
 36 |       "get-interface",
 37 |       "获取apiFox接口信息",
 38 |       {
 39 |         moduleId: z.string().describe("要查询模块id"),
 40 |         moduleName: z.string().describe("要查询模块名称"),
 41 |       },
 42 |       async (args: { moduleId: string; moduleName?: string }) => {
 43 |         try {
 44 |           const response = await fetch(
 45 |             `https://api.apifox.com/v1/projects/${projectId}/export-openapi`,
 46 |             {
 47 |               method: "POST",
 48 |               headers: {
 49 |                 "Content-Type": "application/json",
 50 |                 Authorization: `Bearer ${key}`,
 51 |                 "X-Apifox-Api-Version": "2024-03-28",
 52 |               },
 53 |               body: JSON.stringify({
 54 |                 scope: {
 55 |                   type: "SELECTED_FOLDERS",
 56 |                   selectedFolderIds: [args.moduleId],
 57 |                   excludedByTags: ["pet"],
 58 |                 },
 59 |                 options: {
 60 |                   includeApifoxExtensionProperties: false,
 61 |                   addFoldersToTags: true,
 62 |                 },
 63 |                 oasVersion: "3.1",
 64 |                 exportFormat: "JSON",
 65 |               }),
 66 |             }
 67 |           );
 68 | 
 69 |           // 检查响应状态
 70 |           if (!response.ok) {
 71 |             throw new Error(`HTTP error! status: ${response.status}`);
 72 |           }
 73 | 
 74 |           // 解析响应数据
 75 |           const data = await response.json();
 76 | 
 77 |           if (!data) {
 78 |             return {
 79 |               content: [
 80 |                 {
 81 |                   type: "text",
 82 |                   text: `无法找到${args.moduleName}的接口信息`,
 83 |                 },
 84 |               ],
 85 |             };
 86 |           }
 87 | 
 88 |           return {
 89 |             content: [
 90 |               {
 91 |                 type: "text",
 92 |                 text: `基于openapi3.1.0的规范,${
 93 |                   args.moduleName
 94 |                 }的接口信息如下: ${JSON.stringify(data)}`,
 95 |               },
 96 |             ],
 97 |           };
 98 |         } catch (error) {
 99 |           console.error("获取接口信息失败:", error);
100 |           return {
101 |             content: [
102 |               {
103 |                 type: "text",
104 |                 text: `获取接口信息失败: ${error}`,
105 |               },
106 |             ],
107 |           };
108 |         }
109 |       }
110 |     );
111 |   }
112 | 
113 |   async connect(transport: Transport): Promise<void> {
114 |     await this.server.connect(transport);
115 |     console.error("服务器已连接并准备处理请求");
116 |   }
117 | 
118 |   async startHttpServer(port: number): Promise<void> {
119 |     const app = express();
120 | 
121 |     app.get("/sse", async (req: Request, res: Response) => {
122 |       console.error("SSE连接建立");
123 |       this.sseTransport = new SSEServerTransport(
124 |         "/messages",
125 |         res as unknown as ServerResponse<IncomingMessage>
126 |       );
127 |       await this.connect(this.sseTransport);
128 |     });
129 | 
130 |     app.post("/messages", async (req: Request, res: Response) => {
131 |       if (!this.sseTransport) {
132 |         res.status(400).send();
133 |         return;
134 |       }
135 |       await this.sseTransport.handlePostMessage(
136 |         req as unknown as IncomingMessage,
137 |         res as unknown as ServerResponse<IncomingMessage>
138 |       );
139 |     });
140 | 
141 |     app.listen(port, () => {
142 |       console.error(`HTTP服务器监听端口 ${port}`);
143 |       console.error(`SSE 端点可用于 http://localhost:${port}/sse`);
144 |       console.error(
145 |         `消息端点可在以下位置访问: http://localhost:${port}/messages`
146 |       );
147 |     });
148 |   }
149 | }
150 | 
```