# 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 |
```