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

```
├── .github
│   └── workflows
│       └── publish.yml
├── .gitignore
├── .node-version
├── .npmrc
├── .vscode
│   └── settings.json
├── .zed
│   └── settings.json
├── biome.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── src
│   ├── index.ts
│   └── utils
│       ├── __tests__
│       │   └── apifox.test.ts
│       └── apifox.ts
├── tsconfig.json
└── tsup.config.ts
```

# Files

--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------

```
1 | v20.11.0
2 | 
```

--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------

```
1 | # registry=https://registry.npmjs.org/
2 | registry=https://registry.npmmirror.com/
3 | 
```

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

```
 1 | # Logs
 2 | logs
 3 | *.log
 4 | npm-debug.log*
 5 | yarn-debug.log*
 6 | yarn-error.log*
 7 | pnpm-debug.log*
 8 | lerna-debug.log*
 9 | 
10 | node_modules
11 | .output
12 | stats.html
13 | stats-*.json
14 | .wxt
15 | web-ext.config.ts
16 | 
17 | # Editor directories and files
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 | 
27 | .env
28 | 
29 | dist
30 | 
```

--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "cSpell.words": [
3 |     "apifox",
4 |     "modelcontextprotocol"
5 |   ]
6 | }
```

--------------------------------------------------------------------------------
/.zed/settings.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"formatter": {
 3 | 		"language_server": {
 4 | 			"name": "biome"
 5 | 		}
 6 | 	},
 7 | 	"format_on_save": "on",
 8 | 	"code_actions_on_format": {
 9 | 		"source.fixAll.biome": true,
10 | 		"source.organizeImports.biome": true
11 | 	}
12 | }
13 | 
```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish
 2 | on:
 3 |   release:
 4 |     types: [created]
 5 | jobs:
 6 |   build:
 7 |     runs-on: ubuntu-latest
 8 |     steps:
 9 |       - uses: actions/checkout@v3
10 |       - uses: actions/setup-node@v3
11 |         with:
12 |           node-version: '20.x'
13 |           registry-url: 'https://registry.npmjs.org'
14 |       - run: npm install 
15 |       - run: npm run build
16 |       - run: npm run release
17 |         env:
18 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

--------------------------------------------------------------------------------
/src/utils/__tests__/apifox.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { extractProjectIdAndApiIdFromText, fetchApiInfoApi } from "../apifox.js";
 2 | import { describe, it, expect } from 'vitest'
 3 | 
 4 | 
 5 | 
 6 | describe('fetchApiInfoApi', () => {
 7 |   it('should fetch api info', async () => {
 8 |     const result = await fetchApiInfoApi('5333436', '269566330','')
 9 |     expect(result).toMatchInlineSnapshot(`"{"success":false,"errorCode":"403012","errorMessage":"No project maintainer privilege"}"`)
10 |   })
11 | 
12 | 
13 |   it('should extract project id and api id from text', () => {
14 |     const result = extractProjectIdAndApiIdFromText('https://app.apifox.com/link/project/5333436/apis/api-271295333')
15 |     expect(result).toMatchInlineSnapshot(`
16 |       {
17 |         "apiId": "271295333",
18 |         "projectId": "5333436",
19 |       }
20 |     `)
21 |   })
22 | })
23 | 
```

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

```json
 1 | {
 2 |   "name": "mcp-apifox",
 3 |   "version": "1.0.3",
 4 |   "bin": {
 5 | 		"mcp-apifox": "dist/index.js"
 6 | 	},
 7 |   "description": "",
 8 |   "type": "module",
 9 | 	"main": "./dist/index.js",
10 | 	"module": "./dist/index.js",
11 | 	"types": "./dist/index.d.ts",
12 | 	"exports": {
13 | 		".": "./dist/index.js"
14 | 	},
15 | 	"files": [
16 | 		"dist",
17 |     "*.d.ts"
18 | 	],
19 |   "repository": {
20 |     "type": "git",
21 |     "url": "https://github.com/sujianqingfeng/mcp-apifox.git"
22 |   },
23 |   "scripts": {
24 |     "build": "tsup",
25 | 		"release": "npm publish --no-git-checks --access public --registry https://registry.npmjs.org/",
26 | 		"dev": "tsup --watch",
27 | 		"preview": "node dist/index.js",
28 | 		"test": "vitest",
29 | 		"build:tsc": "tsc"
30 |   },
31 |   "keywords": [
32 |     "mcp",
33 |     "apifox"
34 |   ],
35 |   "author": "hens",
36 |   "license": "ISC",
37 |   "devDependencies": {
38 |     "@biomejs/biome": "^1.9.4",
39 |     "@types/node": "^22.13.4",
40 |     "cross-env": "^7.0.3",
41 |     "tsup": "^8.3.6",
42 |     "typescript": "^5.7.3",
43 |     "vitest": "^3.0.6"
44 |   },
45 |   "dependencies": {
46 |     "@modelcontextprotocol/sdk": "^1.5.0",
47 |     "undici": "^7.3.0",
48 |     "zod": "^3.24.2"
49 |   }
50 | }
51 | 
```

--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
 3 |   "organizeImports": {
 4 |     "enabled": true
 5 |   },
 6 |   "linter": {
 7 |     "enabled": true,
 8 |     "rules": {
 9 |       "recommended": true,
10 |       "suspicious": {
11 |         "noExplicitAny": "off"
12 |       },
13 |       "correctness": {
14 |         "noUnusedVariables": "warn",
15 |         "noUnusedImports": "warn"
16 |       },
17 |       "style": {
18 |         "noParameterAssign": "off"
19 |       },
20 |       "a11y": {
21 |         "useKeyWithClickEvents": "off"
22 |       }
23 |     }
24 |   },
25 |   "formatter": {
26 |     "enabled": true,
27 |     "formatWithErrors": false,
28 |     "ignore": [],
29 |     "attributePosition": "auto",
30 |     "indentStyle": "tab",
31 |     "indentWidth": 2,
32 |     "lineWidth": 80,
33 |     "lineEnding": "lf"
34 |   },
35 |   "javascript": {
36 |     "formatter": {
37 |       "arrowParentheses": "always",
38 |       "bracketSameLine": false,
39 |       "bracketSpacing": true,
40 |       "jsxQuoteStyle": "double",
41 |       "quoteProperties": "asNeeded",
42 |       "semicolons": "asNeeded",
43 |       "trailingCommas": "all"
44 |     }
45 |   },
46 |   "json": {
47 |     "formatter": {
48 |       "trailingCommas": "none"
49 |     }
50 |   }
51 | }
52 | 
```

--------------------------------------------------------------------------------
/src/utils/apifox.ts:
--------------------------------------------------------------------------------

```typescript
 1 | 
 2 | import { request } from 'undici';
 3 | 
 4 | export async function fetchApiInfoApi(projectId: string, apiId: string, accessToken: string) {
 5 | 
 6 |   const response = await request(`https://api.apifox.com/v1/projects/${projectId}/export-openapi`, {
 7 |     method: 'POST',
 8 |     headers: {
 9 |       'X-Apifox-Api-Version': '2024-03-28',
10 |       'Authorization': `Bearer ${accessToken}`,
11 |       'Content-Type': 'application/json'
12 |     },
13 |     body: JSON.stringify({
14 |       scope: {
15 |         type: 'SELECTED_ENDPOINTS',
16 |         selectedEndpointIds: [apiId]
17 |       },
18 |       options: {
19 |         includeApifoxExtensionProperties: false,
20 |         addFoldersToTags: false
21 |       },
22 |       oasVersion: '3.1',
23 |       exportFormat: 'JSON'
24 |     })
25 |   });
26 | 
27 |   const result = await response.body.text();
28 |   return result
29 | }
30 | 
31 | 
32 | export function extractProjectIdAndApiIdFromText(text: string) {
33 | 	const urlPattern = /apifox\.com\/link\/project\/(\d+)\/apis\/api-(\d+)/
34 | 	const contentPattern = /project\/(\d+).*api-(\d+)/
35 | 
36 | 		let projectId = ""
37 | 		let apiId = ""
38 | 
39 | 		// 寻找输入中的 URL 或相关内容
40 | 		const lines = text.split("\n")
41 | 		for (const line of lines) {
42 | 			const trimmedLine = line.trim()
43 | 
44 | 			// 尝试匹配完整 URL
45 | 			const urlMatch = trimmedLine.match(urlPattern)
46 | 			if (urlMatch) {
47 | 				projectId = urlMatch[1]
48 | 				apiId = urlMatch[2]
49 | 				break
50 | 			}
51 | 
52 | 			// 尝试匹配部分路径
53 | 			const contentMatch = trimmedLine.match(contentPattern)
54 | 			if (contentMatch) {
55 | 				projectId = contentMatch[1]
56 | 				apiId = contentMatch[2]
57 | 				break
58 | 			}
59 | 		}
60 | 
61 | 
62 |   return {
63 |     projectId,
64 |     apiId
65 |   }
66 | }
67 | 
```

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

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
 5 | import { z } from "zod"
 6 | import { extractProjectIdAndApiIdFromText, fetchApiInfoApi } from "./utils/apifox.js"
 7 | 
 8 | const server = new McpServer({
 9 | 	name: "apifox",
10 | 	version: "0.0.1",
11 | })
12 | 
13 | server.tool(
14 | 	"get_apifox_project_id_and_api_id_from_url",
15 | 	"Get the project ID and API ID of Apifox from the URL.",
16 | 	{
17 | 		text: z
18 | 			.string()
19 | 			.describe("The text to extract the project id and api id from"),
20 | 	},
21 | 	({ text }) => {
22 |     const { projectId, apiId } = extractProjectIdAndApiIdFromText(text)
23 | 
24 | 		return {
25 | 			content: [
26 | 				{
27 | 					type: "text",
28 | 					text: JSON.stringify({ projectId, apiId }),
29 | 				},
30 | 			],
31 | 		}
32 | 	},
33 | )
34 | 
35 | server.tool(
36 | 	"get_apifox_api_info",
37 | 	"Get the info of Apifox API.",
38 | 	{
39 | 		projectId: z.string().describe("The project ID of Apifox"),
40 | 		apiId: z.string().describe("The API ID of Apifox"),
41 | 	},
42 | 	async ({ projectId, apiId }) => {
43 | 		try {
44 |       // Get token from command line arguments or environment variable
45 |       let token = process.env.APIFOX_ACCESS_TOKEN
46 |       
47 |       // Check if token is provided in command line arguments
48 |       // Format: --token=your_token or --apifox-token=your_token
49 |       const args = process.argv.slice(2)
50 |       for (const arg of args) {
51 |         const tokenArg = arg.match(/^--(?:apifox-)?token=(.+)$/)
52 |         if (tokenArg) {
53 |           token = tokenArg[1]
54 |           break
55 |         }
56 |       }
57 |       if (!token) {
58 |         throw new Error("No token provided")
59 |       }
60 |       
61 | 			const result = await fetchApiInfoApi(projectId, apiId, token)
62 | 
63 | 			return {
64 | 				content: [
65 | 					{
66 | 						type: "text",
67 | 						text: result,
68 | 					},
69 | 				],
70 | 			}
71 | 		} catch (error: any) {
72 | 			return {
73 | 				content: [
74 | 					{
75 | 						type: "text",
76 | 						text: `Error fetching API info: ${error.message}`,
77 | 					},
78 | 				],
79 | 				isError: true,
80 | 			}
81 | 		}
82 | 	},
83 | )
84 | 
85 | async function main() {
86 | 	const transport = new StdioServerTransport()
87 | 	await server.connect(transport)
88 | 	console.error("Apifox MCP Server running on stdio")
89 | }
90 | 
91 | main().catch((error) => {
92 | 	console.error("Fatal error in main():", error)
93 | 	process.exit(1)
94 | })
95 | 
```

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

```json
  1 | {
  2 |   "compilerOptions": {
  3 |     /* Visit https://aka.ms/tsconfig to read more about this file */
  4 |     /* Projects */
  5 |     // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
  6 |     // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
  7 |     // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
  8 |     // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
  9 |     // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
 10 |     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 11 |     /* Language and Environment */
 12 |     "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
 13 |     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
 14 |     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
 15 |     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
 16 |     // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
 17 |     // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
 18 |     // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
 19 |     // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
 20 |     // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
 21 |     // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
 22 |     // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
 23 |     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
 24 |     /* Modules */
 25 |     "module": "Node16", /* Specify what module code is generated. */
 26 |     // "rootDir": "./",                                  /* Specify the root folder within your source files. */
 27 |     "moduleResolution": "Node16", /* Specify how TypeScript looks up a file from a given module specifier. */
 28 |     // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
 29 |     // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
 30 |     // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
 31 |     // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
 32 |     // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
 33 |     // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
 34 |     // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
 35 |     // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
 36 |     // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
 37 |     // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
 38 |     // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
 39 |     // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
 40 |     // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
 41 |     // "resolveJsonModule": true,                        /* Enable importing .json files. */
 42 |     // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
 43 |     // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
 44 |     /* JavaScript Support */
 45 |     // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
 46 |     // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
 47 |     // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
 48 |     /* Emit */
 49 |     // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
 50 |     // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
 51 |     // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
 52 |     // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
 53 |     // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
 54 |     // "noEmit": true,                                   /* Disable emitting files from a compilation. */
 55 |     // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
 56 |     // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
 57 |     // "removeComments": true,                           /* Disable emitting comments. */
 58 |     // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
 59 |     // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
 60 |     // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
 61 |     // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
 62 |     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
 63 |     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
 64 |     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
 65 |     // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
 66 |     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
 67 |     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
 68 |     // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
 69 |     // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
 70 |     /* Interop Constraints */
 71 |     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
 72 |     // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
 73 |     // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
 74 |     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
 75 |     "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
 76 |     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
 77 |     "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
 78 |     /* Type Checking */
 79 |     "strict": true, /* Enable all strict type-checking options. */
 80 |     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
 81 |     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
 82 |     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
 83 |     // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
 84 |     // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
 85 |     // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
 86 |     // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
 87 |     // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
 88 |     // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
 89 |     // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
 90 |     // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
 91 |     // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
 92 |     // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
 93 |     // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
 94 |     // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
 95 |     // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
 96 |     // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
 97 |     // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
 98 |     // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
 99 |     /* Completeness */
100 |     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
101 |     "skipLibCheck": true /* Skip type checking all .d.ts files. */
102 |   },
103 |   "include": [
104 |     "src/**/*"
105 |   ],
106 |   "exclude": [
107 |     "node_modules"
108 |   ]
109 | }
110 | 
```