# 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:
--------------------------------------------------------------------------------
```
.cursor/
.history/
node_modules/
.env
build/
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
###
# @Descripttion:
# @version:
# @Author: wangmin
# @Date: 2025-03-21 11:25:26
# @LastEditors: wangmin
# @LastEditTime: 2025-03-21 11:26:18
###
# your apifox api key
APIFOX_API_KEY=your_apifox_api_key
# your apifox project id
PROJECT_ID=your_apifox_project_id
# your apifox server
PORT=your_apifox_server_port
```
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
```
{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"plugins": ["@typescript-eslint"],
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module"
},
"rules": {
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
],
"@typescript-eslint/no-explicit-any": "warn"
}
}
```
--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { resolve } from "path";
import { config } from "dotenv";
import { startServer } from "./index.js";
// Load .env from the current working directory
config({ path: resolve(process.cwd(), ".env") });
startServer().catch((error: unknown) => {
if (error instanceof Error) {
console.error("Failed to start server:", error.message);
} else {
console.error("Failed to start server with unknown error:", error);
}
process.exit(1);
});
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022", "DOM"],
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
},
"preserveSymlinks": true,
"allowJs": true,
"removeComments": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build"]
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
/*
* @Descripttion:
* @version:
* @Author: wangmin
* @Date: 2025-03-20 14:39:11
* @LastEditors: wangmin
* @LastEditTime: 2025-04-03 09:33:34
*/
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { getServerConfig } from "./config.js";
import { ApiFoxServer } from "./server.js";
// // 创建 MCP 服务器
// 启动服务器
export async function startServer(): Promise<void> {
const isLocalMode =
process.env.NODE_ENV === "cli" || process.argv.includes("--local");
const config = getServerConfig();
const server = new ApiFoxServer(config.apifoxApiKey, config.projectId);
if (isLocalMode) {
const transport = new StdioServerTransport();
await server.connect(transport);
} else {
console.error(
`初始化HTTP模式下的ApiFox MCP Server服务器在端口 ${config.port}`
);
await server.startHttpServer(config.port);
}
}
startServer().catch((error) => {
console.error("启动接口信息服务器失败:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@wangmhaha/apifox-mcp-server",
"version": "1.6.8",
"main": "build/index.js",
"type": "module",
"bin": {
"@wangmhaha/apifox-mcp-server": "build/cli.js"
},
"scripts": {
"build": "tsc && tsc-alias",
"start": "node build/index.js",
"start:cli": "cross-env NODE_ENV=cli node build/index.js",
"start:http": "node build/index.js"
},
"files": [
"build",
"README.md"
],
"repository": {
"type": "git",
"url": "git+https://github.com/wangmhaha/apifox-mcp-server"
},
"keywords": [],
"author": "wangmhaha",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/express": "^5.0.1",
"@types/express-serve-static-core": "^5.0.6",
"@types/node": "^22.10.0",
"tsc-alias": "^1.8.11",
"tsx": "^4.19.3",
"typescript": "^5.7.2"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.1",
"@types/yargs": "^17.0.33",
"@typescript-eslint/eslint-plugin": "^8.27.0",
"@typescript-eslint/parser": "^8.27.0",
"cross-env": "^7.0.3",
"dotenv": "^16.4.7",
"eslint": "^9.22.0",
"express": "^4.21.2",
"yargs": "^17.7.2",
"zod": "^3.24.2"
}
}
```
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
```typescript
/*
* @Descripttion:
* @version:
* @Author: wangmin
* @Date: 2025-03-20 17:26:55
* @LastEditors: wangmin
* @LastEditTime: 2025-03-21 09:16:43
*/
import { config } from "dotenv";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
config();
interface ServerConfig {
port: number;
apifoxApiKey: string;
projectId: string;
}
interface CliArgs {
"apifox-api-key"?: string;
port?: number;
"project-id"?: string;
}
export function getServerConfig(): ServerConfig {
const argv = yargs(hideBin(process.argv))
.options({
"apifox-api-key": {
type: "string",
describe: "apifox api key",
},
"project-id": {
type: "string",
describe: "apifox project id",
},
port: {
type: "number",
describe: "Prot to run the server on",
},
})
.help()
.parseSync() as CliArgs;
const config: ServerConfig = {
apifoxApiKey: "",
projectId: "",
port: 3000,
};
if (argv["apifox-api-key"]) {
config.apifoxApiKey = argv["apifox-api-key"];
} else if (process.env.APIFOX_API_KEY) {
config.apifoxApiKey = process.env.APIFOX_API_KEY;
}
if (argv["project-id"]) {
config.projectId = argv["project-id"];
} else if (process.env.PROJECT_ID) {
config.projectId = process.env.PROJECT_ID;
}
if (argv.port) {
config.port = argv.port;
} else if (process.env.PORT) {
config.port = parseInt(process.env.PORT, 10);
}
if (!config.apifoxApiKey) {
console.error("请提供 apifox api key");
process.exit(1);
}
if (!config.projectId) {
console.error("请提供 apifox project id");
process.exit(1);
}
return config;
}
```
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
```typescript
/*
* @Descripttion:
* @version:
* @Author: wangmin
* @Date: 2025-03-20 17:49:38
* @LastEditors: wangmin
* @LastEditTime: 2025-04-03 14:30:44
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import { IncomingMessage, ServerResponse } from "http";
import express from "express";
import { Response, Request } from "express-serve-static-core";
import { z } from "zod";
export class ApiFoxServer {
private readonly server: McpServer;
private sseTransport: SSEServerTransport | null = null;
constructor(apifoxApiKey: string, projectId: string) {
this.server = new McpServer({
name: "ApiFox MCP Server",
version: "1.0.0",
capabilities: {
notifications: true,
},
});
this.registerTools(apifoxApiKey, projectId);
}
// 注册工具
private registerTools(key: string, projectId: string): void {
this.server.tool(
"get-interface",
"获取apiFox接口信息",
{
moduleId: z.string().describe("要查询模块id"),
moduleName: z.string().describe("要查询模块名称"),
},
async (args: { moduleId: string; moduleName?: string }) => {
try {
const response = await fetch(
`https://api.apifox.com/v1/projects/${projectId}/export-openapi`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${key}`,
"X-Apifox-Api-Version": "2024-03-28",
},
body: JSON.stringify({
scope: {
type: "SELECTED_FOLDERS",
selectedFolderIds: [args.moduleId],
excludedByTags: ["pet"],
},
options: {
includeApifoxExtensionProperties: false,
addFoldersToTags: true,
},
oasVersion: "3.1",
exportFormat: "JSON",
}),
}
);
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 解析响应数据
const data = await response.json();
if (!data) {
return {
content: [
{
type: "text",
text: `无法找到${args.moduleName}的接口信息`,
},
],
};
}
return {
content: [
{
type: "text",
text: `基于openapi3.1.0的规范,${
args.moduleName
}的接口信息如下: ${JSON.stringify(data)}`,
},
],
};
} catch (error) {
console.error("获取接口信息失败:", error);
return {
content: [
{
type: "text",
text: `获取接口信息失败: ${error}`,
},
],
};
}
}
);
}
async connect(transport: Transport): Promise<void> {
await this.server.connect(transport);
console.error("服务器已连接并准备处理请求");
}
async startHttpServer(port: number): Promise<void> {
const app = express();
app.get("/sse", async (req: Request, res: Response) => {
console.error("SSE连接建立");
this.sseTransport = new SSEServerTransport(
"/messages",
res as unknown as ServerResponse<IncomingMessage>
);
await this.connect(this.sseTransport);
});
app.post("/messages", async (req: Request, res: Response) => {
if (!this.sseTransport) {
res.status(400).send();
return;
}
await this.sseTransport.handlePostMessage(
req as unknown as IncomingMessage,
res as unknown as ServerResponse<IncomingMessage>
);
});
app.listen(port, () => {
console.error(`HTTP服务器监听端口 ${port}`);
console.error(`SSE 端点可用于 http://localhost:${port}/sse`);
console.error(
`消息端点可在以下位置访问: http://localhost:${port}/messages`
);
});
}
}
```