lhwapi-mcp-server 1.0.3 → 1.0.4

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/dist/bundle.js ADDED
@@ -0,0 +1,277 @@
1
+ // src/index.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ var APIFOX_BASE_URL = "https://api.apifox.com";
6
+ var args = process.argv.slice(2);
7
+ var projectId;
8
+ for (let i = 0; i < args.length; i++) {
9
+ if (args[i] === "--project-id" && args[i + 1]) {
10
+ projectId = args[i + 1];
11
+ }
12
+ }
13
+ var token = process.env.APIFOX_ACCESS_TOKEN;
14
+ async function fetchApifox(endpoint, authToken, options) {
15
+ const response = await fetch(`${APIFOX_BASE_URL}${endpoint}`, {
16
+ method: options?.method || "GET",
17
+ headers: {
18
+ "Authorization": `Bearer ${authToken}`,
19
+ "Content-Type": "application/json",
20
+ "X-Apifox-Api-Version": "2024-03-28"
21
+ },
22
+ body: options?.body ? JSON.stringify(options.body) : void 0
23
+ });
24
+ if (!response.ok) {
25
+ const error = await response.text();
26
+ throw new Error(`Apifox API error: ${response.status} - ${error}`);
27
+ }
28
+ const data = await response.json();
29
+ return data.data;
30
+ }
31
+ var server = new McpServer({
32
+ name: "lhwapi-mcp-server",
33
+ version: "1.0.0"
34
+ });
35
+ server.registerTool(
36
+ "listProjects",
37
+ {
38
+ description: "\u83B7\u53D6 Apifox \u9879\u76EE\u5217\u8868",
39
+ inputSchema: z.object({
40
+ token: z.string().optional().describe("Apifox \u8BBF\u95EE\u4EE4\u724C (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u73AF\u5883\u53D8\u91CF)")
41
+ }).shape
42
+ },
43
+ async ({ token: inputToken }) => {
44
+ const authToken = inputToken || token;
45
+ if (!authToken) {
46
+ return {
47
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u8BBE\u7F6E APIFOX_ACCESS_TOKEN \u73AF\u5883\u53D8\u91CF\u6216\u4F20\u5165 token \u53C2\u6570" }],
48
+ isError: true
49
+ };
50
+ }
51
+ const projects = await fetchApifox("/v1/projects", authToken);
52
+ const text = projects.map((p) => `\u9879\u76EE: ${p.name} (ID: ${p.id})
53
+ \u63CF\u8FF0: ${p.description || "\u65E0"}`).join("\n\n");
54
+ return {
55
+ content: [{ type: "text", text: text || "\u6682\u65E0\u9879\u76EE" }]
56
+ };
57
+ }
58
+ );
59
+ server.registerTool(
60
+ "listApis",
61
+ {
62
+ description: "\u83B7\u53D6\u6307\u5B9A\u9879\u76EE\u4E0B\u7684 API \u5217\u8868",
63
+ inputSchema: z.object({
64
+ projectId: z.string().optional().describe("\u9879\u76EE ID (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u547D\u4EE4\u884C\u53C2\u6570)"),
65
+ token: z.string().optional().describe("Apifox \u8BBF\u95EE\u4EE4\u724C (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u73AF\u5883\u53D8\u91CF)")
66
+ }).shape
67
+ },
68
+ async ({ projectId: inputProjectId, token: inputToken }) => {
69
+ const authToken = inputToken || token;
70
+ const authProjectId = inputProjectId || projectId;
71
+ if (!authToken) {
72
+ return {
73
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u8BBE\u7F6E APIFOX_ACCESS_TOKEN \u73AF\u5883\u53D8\u91CF\u6216\u4F20\u5165 token \u53C2\u6570" }],
74
+ isError: true
75
+ };
76
+ }
77
+ if (!authProjectId) {
78
+ return {
79
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u901A\u8FC7 --project-id \u53C2\u6570\u6307\u5B9A\u9879\u76EE ID" }],
80
+ isError: true
81
+ };
82
+ }
83
+ const apis = await fetchApifox(
84
+ `/v1/projects/${authProjectId}/apis`,
85
+ authToken
86
+ );
87
+ const text = apis.map((api) => `[${api.method.toUpperCase()}] ${api.path} - ${api.name}`).join("\n");
88
+ return {
89
+ content: [{ type: "text", text: text || "\u6682\u65E0 API" }]
90
+ };
91
+ }
92
+ );
93
+ server.registerTool(
94
+ "getApiDetail",
95
+ {
96
+ description: "\u83B7\u53D6\u5355\u4E2A API \u7684\u8BE6\u7EC6\u4FE1\u606F",
97
+ inputSchema: z.object({
98
+ apiId: z.string().describe("API ID"),
99
+ projectId: z.string().optional().describe("\u9879\u76EE ID (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u547D\u4EE4\u884C\u53C2\u6570)"),
100
+ token: z.string().optional().describe("Apifox \u8BBF\u95EE\u4EE4\u724C (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u73AF\u5883\u53D8\u91CF)")
101
+ }).shape
102
+ },
103
+ async ({ apiId, projectId: inputProjectId, token: inputToken }) => {
104
+ const authToken = inputToken || token;
105
+ const authProjectId = inputProjectId || projectId;
106
+ if (!authToken) {
107
+ return {
108
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u8BBE\u7F6E APIFOX_ACCESS_TOKEN \u73AF\u5883\u53D8\u91CF\u6216\u4F20\u5165 token \u53C2\u6570" }],
109
+ isError: true
110
+ };
111
+ }
112
+ if (!authProjectId) {
113
+ return {
114
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u901A\u8FC7 --project-id \u53C2\u6570\u6307\u5B9A\u9879\u76EE ID" }],
115
+ isError: true
116
+ };
117
+ }
118
+ const api = await fetchApifox(
119
+ `/v1/projects/${authProjectId}/apis/${apiId}`,
120
+ authToken
121
+ );
122
+ let detail = `# ${api.name}
123
+
124
+ `;
125
+ detail += `**\u65B9\u6CD5**: ${api.method.toUpperCase()}
126
+ `;
127
+ detail += `**\u8DEF\u5F84**: ${api.path}
128
+
129
+ `;
130
+ if (api.description) {
131
+ detail += `## \u63CF\u8FF0
132
+
133
+ ${api.description}
134
+
135
+ `;
136
+ }
137
+ if (api.requestParams?.query?.length) {
138
+ detail += `## Query \u53C2\u6570
139
+
140
+ `;
141
+ api.requestParams.query.forEach((param) => {
142
+ detail += `- ${param.name}: ${param.type || "string"} ${param.required ? "(\u5FC5\u586B)" : "(\u53EF\u9009)"}
143
+ `;
144
+ });
145
+ detail += "\n";
146
+ }
147
+ if (api.requestBody?.schema) {
148
+ detail += `## \u8BF7\u6C42\u4F53
149
+
150
+ \`\`\`json
151
+ ${JSON.stringify(api.requestBody.schema, null, 2)}
152
+ \`\`\`
153
+
154
+ `;
155
+ }
156
+ if (api.responseList?.length) {
157
+ detail += `## \u54CD\u5E94
158
+
159
+ `;
160
+ api.responseList.forEach((resp) => {
161
+ detail += `### ${resp.statusCode}
162
+ `;
163
+ if (resp.responseBodySchema) {
164
+ detail += `\`\`\`json
165
+ ${JSON.stringify(resp.responseBodySchema, null, 2)}
166
+ \`\`\`
167
+ `;
168
+ }
169
+ });
170
+ }
171
+ return {
172
+ content: [{ type: "text", text: detail }]
173
+ };
174
+ }
175
+ );
176
+ server.registerTool(
177
+ "searchApis",
178
+ {
179
+ description: "\u641C\u7D22 API",
180
+ inputSchema: z.object({
181
+ keyword: z.string().describe("\u641C\u7D22\u5173\u952E\u8BCD"),
182
+ projectId: z.string().optional().describe("\u9879\u76EE ID (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u547D\u4EE4\u884C\u53C2\u6570)"),
183
+ token: z.string().optional().describe("Apifox \u8BBF\u95EE\u4EE4\u724C (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u73AF\u5883\u53D8\u91CF)")
184
+ }).shape
185
+ },
186
+ async ({ keyword, projectId: inputProjectId, token: inputToken }) => {
187
+ const authToken = inputToken || token;
188
+ const authProjectId = inputProjectId || projectId;
189
+ if (!authToken) {
190
+ return {
191
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u8BBE\u7F6E APIFOX_ACCESS_TOKEN \u73AF\u5883\u53D8\u91CF\u6216\u4F20\u5165 token \u53C2\u6570" }],
192
+ isError: true
193
+ };
194
+ }
195
+ if (!authProjectId) {
196
+ return {
197
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u901A\u8FC7 --project-id \u53C2\u6570\u6307\u5B9A\u9879\u76EE ID" }],
198
+ isError: true
199
+ };
200
+ }
201
+ const apis = await fetchApifox(
202
+ `/v1/projects/${authProjectId}/apis`,
203
+ authToken
204
+ );
205
+ const filtered = apis.filter(
206
+ (api) => api.name.toLowerCase().includes(keyword.toLowerCase()) || api.path.toLowerCase().includes(keyword.toLowerCase())
207
+ );
208
+ const text = filtered.map((api) => `[${api.method.toUpperCase()}] ${api.path} - ${api.name}`).join("\n");
209
+ return {
210
+ content: [{ type: "text", text: text || "\u672A\u627E\u5230\u5339\u914D\u7684 API" }]
211
+ };
212
+ }
213
+ );
214
+ server.registerTool(
215
+ "exportOpenApi",
216
+ {
217
+ description: "\u5BFC\u51FA OpenAPI/Swagger \u683C\u5F0F\u6570\u636E",
218
+ inputSchema: z.object({
219
+ oasVersion: z.string().optional().describe("OpenAPI \u7248\u672C: 3.0, 3.1, 2.0"),
220
+ exportFormat: z.string().optional().describe("\u5BFC\u51FA\u683C\u5F0F: JSON \u6216 YAML"),
221
+ projectId: z.string().optional().describe("\u9879\u76EE ID (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u547D\u4EE4\u884C\u53C2\u6570)"),
222
+ token: z.string().optional().describe("Apifox \u8BBF\u95EE\u4EE4\u724C (\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u73AF\u5883\u53D8\u91CF)")
223
+ }).shape
224
+ },
225
+ async ({ oasVersion, exportFormat, projectId: inputProjectId, token: inputToken }) => {
226
+ const authToken = inputToken || token;
227
+ const authProjectId = inputProjectId || projectId;
228
+ if (!authToken) {
229
+ return {
230
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u8BBE\u7F6E APIFOX_ACCESS_TOKEN \u73AF\u5883\u53D8\u91CF\u6216\u4F20\u5165 token \u53C2\u6570" }],
231
+ isError: true
232
+ };
233
+ }
234
+ if (!authProjectId) {
235
+ return {
236
+ content: [{ type: "text", text: "\u9519\u8BEF: \u8BF7\u901A\u8FC7 --project-id \u53C2\u6570\u6307\u5B9A\u9879\u76EE ID" }],
237
+ isError: true
238
+ };
239
+ }
240
+ const result = await fetchApifox(
241
+ `/v1/projects/${authProjectId}/export-openapi`,
242
+ authToken,
243
+ {
244
+ method: "POST",
245
+ body: {
246
+ scope: { type: "ALL" },
247
+ oasVersion: oasVersion || "3.1",
248
+ exportFormat: exportFormat || "JSON"
249
+ }
250
+ }
251
+ );
252
+ const format = exportFormat || "JSON";
253
+ let output;
254
+ if (format.toUpperCase() === "YAML") {
255
+ output = result.openapi || "";
256
+ } else {
257
+ output = JSON.stringify(result, null, 2);
258
+ }
259
+ return {
260
+ content: [{ type: "text", text: output }]
261
+ };
262
+ }
263
+ );
264
+ async function main() {
265
+ if (!projectId) {
266
+ console.error("\u8B66\u544A: \u672A\u6307\u5B9A --project-id \u53C2\u6570");
267
+ }
268
+ if (!token) {
269
+ console.error("\u8B66\u544A: \u672A\u8BBE\u7F6E APIFOX_ACCESS_TOKEN \u73AF\u5883\u53D8\u91CF");
270
+ }
271
+ const transport = new StdioServerTransport();
272
+ await server.connect(transport);
273
+ }
274
+ main().catch((error) => {
275
+ console.error("Server error:", error);
276
+ process.exit(1);
277
+ });
package/dist/cli.cjs CHANGED
@@ -3,7 +3,7 @@ const { spawn } = require("child_process");
3
3
  const path = require("path");
4
4
 
5
5
  const args = process.argv.slice(2);
6
- const child = spawn("node", [path.join(__dirname, "index.js"), ...args], {
6
+ const child = spawn("node", [path.join(__dirname, "bundle.js"), ...args], {
7
7
  stdio: "inherit",
8
8
  shell: true,
9
9
  });
package/package.json CHANGED
@@ -1,19 +1,23 @@
1
1
  {
2
2
  "name": "lhwapi-mcp-server",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "MCP server for API documentation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "lhwapi-mcp-server": "./dist/cli.cjs"
9
+ "lhwapi-mcp-server": "dist/cli.cjs"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc",
13
- "start": "node dist/index.js",
13
+ "bundle": "node scripts/build.mjs",
14
+ "start": "node dist/bundle.js",
14
15
  "dev": "tsx src/index.ts"
15
16
  },
16
- "keywords": ["mcp", "api-docs"],
17
+ "keywords": [
18
+ "mcp",
19
+ "api-docs"
20
+ ],
17
21
  "author": "",
18
22
  "license": "MIT",
19
23
  "dependencies": {
@@ -22,6 +26,7 @@
22
26
  },
23
27
  "devDependencies": {
24
28
  "@types/node": "^25.3.2",
29
+ "esbuild": "^0.27.3",
25
30
  "tsx": "^4.21.0",
26
31
  "typescript": "^5.9.3"
27
32
  }
@@ -0,0 +1,12 @@
1
+ import * as esbuild from 'esbuild';
2
+
3
+ await esbuild.build({
4
+ entryPoints: ['src/index.ts'],
5
+ bundle: true,
6
+ platform: 'node',
7
+ target: 'node18',
8
+ outfile: 'dist/bundle.js',
9
+ format: 'esm',
10
+ external: [],
11
+ packages: 'external',
12
+ });