@leeguoo/yapi-mcp 0.2.4 → 0.3.0
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/README.md +16 -10
- package/dist/config.d.ts +4 -0
- package/dist/config.js +48 -4
- package/dist/config.js.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +5 -0
- package/dist/server.js +262 -221
- package/dist/server.js.map +1 -1
- package/dist/services/yapi/api.d.ts +9 -1
- package/dist/services/yapi/api.js +55 -13
- package/dist/services/yapi/api.js.map +1 -1
- package/dist/services/yapi/auth.d.ts +13 -1
- package/dist/services/yapi/auth.js +42 -6
- package/dist/services/yapi/auth.js.map +1 -1
- package/dist/services/yapi/authCache.js +49 -4
- package/dist/services/yapi/authCache.js.map +1 -1
- package/dist/services/yapi/cache.d.ts +4 -1
- package/dist/services/yapi/cache.js +104 -15
- package/dist/services/yapi/cache.js.map +1 -1
- package/package.json +4 -4
package/dist/server.js
CHANGED
|
@@ -8,6 +8,7 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
|
8
8
|
const zod_1 = require("zod");
|
|
9
9
|
const express_1 = __importDefault(require("express"));
|
|
10
10
|
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
11
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
11
12
|
const api_1 = require("./services/yapi/api");
|
|
12
13
|
const cache_1 = require("./services/yapi/cache");
|
|
13
14
|
const logger_1 = require("./services/yapi/logger");
|
|
@@ -73,21 +74,20 @@ class YapiMcpServer {
|
|
|
73
74
|
logger;
|
|
74
75
|
authService;
|
|
75
76
|
authMode;
|
|
77
|
+
toolset;
|
|
76
78
|
sseTransport = null;
|
|
77
79
|
isStdioMode;
|
|
78
|
-
constructor(yapiBaseUrl, yapiToken, yapiLogLevel = "info", yapiCacheTTL = 10, auth) {
|
|
80
|
+
constructor(yapiBaseUrl, yapiToken, yapiLogLevel = "info", yapiCacheTTL = 10, auth, http, tools) {
|
|
79
81
|
this.logger = new logger_1.Logger("YapiMCP", yapiLogLevel);
|
|
80
|
-
this.yapiService = new api_1.YApiService(yapiBaseUrl, yapiToken, yapiLogLevel);
|
|
81
|
-
this.projectInfoCache = new cache_1.ProjectInfoCache(yapiCacheTTL);
|
|
82
|
+
this.yapiService = new api_1.YApiService(yapiBaseUrl, yapiToken, yapiLogLevel, { timeoutMs: http?.timeoutMs });
|
|
83
|
+
this.projectInfoCache = new cache_1.ProjectInfoCache(yapiBaseUrl, yapiCacheTTL, yapiLogLevel);
|
|
82
84
|
this.authMode = auth?.mode ?? (auth?.email && auth?.password ? "global" : "token");
|
|
83
85
|
this.authService =
|
|
84
86
|
this.authMode === "global" && auth?.email && auth?.password
|
|
85
|
-
? new auth_1.YApiAuthService(yapiBaseUrl, auth.email, auth.password, yapiLogLevel)
|
|
87
|
+
? new auth_1.YApiAuthService(yapiBaseUrl, auth.email, auth.password, yapiLogLevel, { timeoutMs: http?.timeoutMs })
|
|
86
88
|
: null;
|
|
89
|
+
this.toolset = tools?.toolset ?? "full";
|
|
87
90
|
if (this.authService) {
|
|
88
|
-
const cachedTokens = this.authService.loadCachedProjectTokens();
|
|
89
|
-
this.yapiService.setProjectTokens(cachedTokens, { overwrite: false });
|
|
90
|
-
this.logger.info(`全局模式已启用:已从本地缓存加载 ${cachedTokens.size} 个项目 token`);
|
|
91
91
|
const cachedCookie = this.authService.getCachedCookieHeader();
|
|
92
92
|
if (cachedCookie) {
|
|
93
93
|
this.yapiService.setCookieHeader(cachedCookie);
|
|
@@ -98,7 +98,7 @@ class YapiMcpServer {
|
|
|
98
98
|
this.logger.info(`YapiMcpServer初始化,日志级别: ${yapiLogLevel}, 缓存TTL: ${yapiCacheTTL}分钟`);
|
|
99
99
|
this.server = new mcp_js_1.McpServer({
|
|
100
100
|
name: "Yapi MCP Server",
|
|
101
|
-
version: "0.
|
|
101
|
+
version: String(package_json_1.default?.version ?? "0.0.0"),
|
|
102
102
|
});
|
|
103
103
|
this.registerTools();
|
|
104
104
|
if (this.isStdioMode) {
|
|
@@ -175,6 +175,7 @@ class YapiMcpServer {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
registerTools() {
|
|
178
|
+
const isFull = this.toolset === "full";
|
|
178
179
|
const yapiParamItemSchema = zod_1.z
|
|
179
180
|
.object({
|
|
180
181
|
name: zod_1.z.string().describe("参数名"),
|
|
@@ -189,16 +190,36 @@ class YapiMcpServer {
|
|
|
189
190
|
value: zod_1.z.string().optional().describe("Header 值(如 application/json)"),
|
|
190
191
|
})
|
|
191
192
|
.passthrough();
|
|
193
|
+
const normalizeRequiredString = (value) => String(value ?? "").trim();
|
|
194
|
+
const normalizeProjectId = (value) => {
|
|
195
|
+
const s = normalizeRequiredString(value);
|
|
196
|
+
const m = /(\d+)/.exec(s);
|
|
197
|
+
return m ? m[1] : s;
|
|
198
|
+
};
|
|
199
|
+
const normalizeCatId = (value) => {
|
|
200
|
+
const s = normalizeRequiredString(value);
|
|
201
|
+
const m = /^cat_(\d+)$/i.exec(s);
|
|
202
|
+
return m ? m[1] : s;
|
|
203
|
+
};
|
|
204
|
+
const normalizeHttpMethod = (value) => normalizeRequiredString(value).toUpperCase();
|
|
205
|
+
const normalizeApiPath = (value) => {
|
|
206
|
+
const raw = String(value ?? "");
|
|
207
|
+
const compact = raw.replace(/\s+/g, "");
|
|
208
|
+
if (!compact)
|
|
209
|
+
return "";
|
|
210
|
+
return compact.startsWith("/") ? compact : `/${compact}`;
|
|
211
|
+
};
|
|
192
212
|
const buildInterfaceSaveParams = async (input) => {
|
|
193
213
|
const isUpdate = Boolean(input.id);
|
|
194
214
|
let current = null;
|
|
195
215
|
if (isUpdate) {
|
|
196
|
-
current = await this.yapiService.getApiInterface(input.projectId, input.id);
|
|
216
|
+
current = await this.yapiService.getApiInterface(normalizeProjectId(input.projectId), String(input.id));
|
|
197
217
|
}
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
const
|
|
218
|
+
const finalProjectId = normalizeProjectId(input.projectId);
|
|
219
|
+
const finalCatid = normalizeCatId(input.catid ?? (current ? String(current.catid ?? "") : ""));
|
|
220
|
+
const finalTitle = normalizeRequiredString(input.title ?? (current ? String(current.title ?? "") : ""));
|
|
221
|
+
const finalPath = normalizeApiPath(input.path ?? (current ? String(current.path ?? "") : ""));
|
|
222
|
+
const finalMethod = normalizeHttpMethod(input.method ?? (current ? String(current.method ?? "") : ""));
|
|
202
223
|
if (!finalCatid)
|
|
203
224
|
return { ok: false, error: "缺少 catid:新增必填;更新可省略但必须能从原接口读取到" };
|
|
204
225
|
if (!finalTitle)
|
|
@@ -208,7 +229,7 @@ class YapiMcpServer {
|
|
|
208
229
|
if (!finalMethod)
|
|
209
230
|
return { ok: false, error: "缺少 method:新增必填;更新可省略但必须能从原接口读取到" };
|
|
210
231
|
const params = {
|
|
211
|
-
project_id:
|
|
232
|
+
project_id: finalProjectId,
|
|
212
233
|
catid: finalCatid,
|
|
213
234
|
title: finalTitle,
|
|
214
235
|
path: finalPath,
|
|
@@ -265,7 +286,7 @@ class YapiMcpServer {
|
|
|
265
286
|
params.api_opened = input.api_opened ?? (isUpdate ? current?.api_opened : undefined);
|
|
266
287
|
return { ok: true, params, isUpdate };
|
|
267
288
|
};
|
|
268
|
-
this.server.tool("yapi_update_token", "全局模式:使用用户名/密码登录 YApi
|
|
289
|
+
this.server.tool("yapi_update_token", "全局模式:使用用户名/密码登录 YApi,刷新本地登录态 Cookie(并可选刷新项目信息缓存)", {
|
|
269
290
|
forceLogin: zod_1.z.boolean().optional().describe("是否强制重新登录(忽略已缓存的 cookie)"),
|
|
270
291
|
}, async ({ forceLogin }) => {
|
|
271
292
|
try {
|
|
@@ -279,9 +300,27 @@ class YapiMcpServer {
|
|
|
279
300
|
],
|
|
280
301
|
};
|
|
281
302
|
}
|
|
282
|
-
const
|
|
283
|
-
this.yapiService.setProjectTokens(tokens, { overwrite: true });
|
|
303
|
+
const cookieHeader = await this.authService.getCookieHeaderWithLogin({ forceLogin });
|
|
284
304
|
this.yapiService.setCookieHeader(cookieHeader);
|
|
305
|
+
const { groups, projects } = await this.authService.listAccessibleProjects({ forceLogin });
|
|
306
|
+
projects.forEach((p) => {
|
|
307
|
+
const projectId = String(p?._id ?? p?.id ?? "");
|
|
308
|
+
if (!projectId)
|
|
309
|
+
return;
|
|
310
|
+
const groupIdRaw = p?.group_id ?? p?.groupId ?? 0;
|
|
311
|
+
const uidRaw = p?.uid ?? 0;
|
|
312
|
+
const groupId = Number(groupIdRaw);
|
|
313
|
+
const uid = Number(uidRaw);
|
|
314
|
+
const info = {
|
|
315
|
+
_id: projectId,
|
|
316
|
+
name: p?.name || p?.project_name || p?.title || "",
|
|
317
|
+
desc: p?.desc || "",
|
|
318
|
+
group_id: Number.isFinite(groupId) ? groupId : 0,
|
|
319
|
+
uid: Number.isFinite(uid) ? uid : 0,
|
|
320
|
+
basepath: p?.basepath || "",
|
|
321
|
+
};
|
|
322
|
+
this.yapiService.getProjectInfoCache().set(projectId, info);
|
|
323
|
+
});
|
|
285
324
|
await this.yapiService.loadAllProjectInfo();
|
|
286
325
|
this.projectInfoCache.saveToCache(this.yapiService.getProjectInfoCache());
|
|
287
326
|
await this.yapiService.loadAllCategoryLists();
|
|
@@ -289,16 +328,14 @@ class YapiMcpServer {
|
|
|
289
328
|
content: [
|
|
290
329
|
{
|
|
291
330
|
type: "text",
|
|
292
|
-
text:
|
|
293
|
-
? "(提示:部分 YApi 部署不会在开放 API 返回 token,本工具已尝试从项目设置页抓取;如仍为 0,可能账号权限不足或实例限制展示 token。)"
|
|
294
|
-
: ""}`,
|
|
331
|
+
text: `登录态刷新完成:分组 ${groups.length} 个,项目 ${projects.length} 个;已更新 Cookie,并刷新项目信息/分类缓存。`,
|
|
295
332
|
},
|
|
296
333
|
],
|
|
297
334
|
};
|
|
298
335
|
}
|
|
299
336
|
catch (error) {
|
|
300
|
-
this.logger.error("
|
|
301
|
-
return { content: [{ type: "text", text:
|
|
337
|
+
this.logger.error("刷新登录态失败:", error);
|
|
338
|
+
return { content: [{ type: "text", text: `刷新登录态失败: ${error}` }] };
|
|
302
339
|
}
|
|
303
340
|
});
|
|
304
341
|
this.server.tool("yapi_get_api_desc", "获取YApi中特定接口的详细信息", {
|
|
@@ -375,19 +412,21 @@ class YapiMcpServer {
|
|
|
375
412
|
return { content: [{ type: "text", text: `搜索项目失败: ${error}` }] };
|
|
376
413
|
}
|
|
377
414
|
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
415
|
+
if (isFull) {
|
|
416
|
+
this.server.tool("yapi_interface_get", "获取接口数据(对应 /api/interface/get,返回原始字段)", {
|
|
417
|
+
projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
|
|
418
|
+
apiId: zod_1.z.string().describe("接口ID"),
|
|
419
|
+
}, async ({ projectId, apiId }) => {
|
|
420
|
+
try {
|
|
421
|
+
const apiInterface = await this.yapiService.getApiInterface(projectId, apiId);
|
|
422
|
+
return { content: [{ type: "text", text: JSON.stringify(apiInterface, null, 2) }] };
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
this.logger.error(`获取接口数据失败:`, error);
|
|
426
|
+
return { content: [{ type: "text", text: `获取接口数据失败: ${error}` }] };
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
391
430
|
this.server.tool("yapi_save_api", "新增或更新YApi中的接口信息", {
|
|
392
431
|
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
393
432
|
catid: zod_1.z.string().optional().describe("接口分类ID;新增接口时必填,更新时可省略(会保留原值)"),
|
|
@@ -484,192 +523,194 @@ class YapiMcpServer {
|
|
|
484
523
|
return { content: [{ type: "text", text: `新增接口分类失败: ${error}` }] };
|
|
485
524
|
}
|
|
486
525
|
});
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
catch (error) {
|
|
495
|
-
this.logger.error(`获取菜单列表失败:`, error);
|
|
496
|
-
return { content: [{ type: "text", text: `获取菜单列表失败: ${error}` }] };
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
this.server.tool("yapi_interface_list", "获取接口列表数据(对应 /api/interface/list)", {
|
|
500
|
-
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
501
|
-
page: zod_1.z.number().optional().describe("页码,默认 1"),
|
|
502
|
-
limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
|
|
503
|
-
}, async ({ projectId, page, limit }) => {
|
|
504
|
-
try {
|
|
505
|
-
const data = await this.yapiService.listInterfaces(projectId, page ?? 1, limit ?? 10);
|
|
506
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
507
|
-
}
|
|
508
|
-
catch (error) {
|
|
509
|
-
this.logger.error(`获取接口列表失败:`, error);
|
|
510
|
-
return { content: [{ type: "text", text: `获取接口列表失败: ${error}` }] };
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
this.server.tool("yapi_interface_list_cat", "获取某个分类下接口列表(对应 /api/interface/list_cat)", {
|
|
514
|
-
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
515
|
-
catId: zod_1.z.string().describe("分类ID"),
|
|
516
|
-
page: zod_1.z.number().optional().describe("页码,默认 1"),
|
|
517
|
-
limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
|
|
518
|
-
}, async ({ projectId, catId, page, limit }) => {
|
|
519
|
-
try {
|
|
520
|
-
const response = await this.yapiService.listCategoryInterfaces(projectId, catId, page ?? 1, limit ?? 10);
|
|
521
|
-
return { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] };
|
|
522
|
-
}
|
|
523
|
-
catch (error) {
|
|
524
|
-
this.logger.error(`获取分类接口列表失败:`, error);
|
|
525
|
-
return { content: [{ type: "text", text: `获取分类接口列表失败: ${error}` }] };
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
this.server.tool("yapi_interface_list_menu", "获取接口菜单列表(对应 /api/interface/list_menu)", {
|
|
529
|
-
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
530
|
-
}, async ({ projectId }) => {
|
|
531
|
-
try {
|
|
532
|
-
const data = await this.yapiService.getInterfaceMenu(projectId);
|
|
533
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
534
|
-
}
|
|
535
|
-
catch (error) {
|
|
536
|
-
this.logger.error(`获取接口菜单列表失败:`, error);
|
|
537
|
-
return { content: [{ type: "text", text: `获取接口菜单列表失败: ${error}` }] };
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
this.server.tool("yapi_interface_add", "新增接口(对应 /api/interface/add)", {
|
|
541
|
-
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
542
|
-
catid: zod_1.z.string().describe("接口分类ID"),
|
|
543
|
-
title: zod_1.z.string().describe("接口标题"),
|
|
544
|
-
path: zod_1.z.string().describe("接口路径"),
|
|
545
|
-
method: zod_1.z.string().describe("请求方法"),
|
|
546
|
-
status: zod_1.z.string().optional().describe("接口状态,默认 undone"),
|
|
547
|
-
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
548
|
-
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
549
|
-
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
550
|
-
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
551
|
-
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
552
|
-
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
553
|
-
req_body_other: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("其他请求体"),
|
|
554
|
-
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
555
|
-
res_body_type: zod_1.z.string().optional().describe("响应类型,默认 json"),
|
|
556
|
-
res_body: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("响应内容"),
|
|
557
|
-
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
558
|
-
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
559
|
-
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
560
|
-
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
561
|
-
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
562
|
-
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
563
|
-
}, async (input) => {
|
|
564
|
-
try {
|
|
565
|
-
const built = await buildInterfaceSaveParams({ ...input, id: undefined });
|
|
566
|
-
if (!built.ok)
|
|
567
|
-
return { content: [{ type: "text", text: built.error }] };
|
|
568
|
-
const response = await this.yapiService.addInterface(built.params);
|
|
569
|
-
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
570
|
-
}
|
|
571
|
-
catch (error) {
|
|
572
|
-
this.logger.error(`新增接口失败:`, error);
|
|
573
|
-
return { content: [{ type: "text", text: `新增接口失败: ${error}` }] };
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
this.server.tool("yapi_interface_up", "更新接口(对应 /api/interface/up)", {
|
|
577
|
-
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
578
|
-
id: zod_1.z.string().describe("接口ID"),
|
|
579
|
-
catid: zod_1.z.string().optional().describe("分类ID(可省略,会保留原值)"),
|
|
580
|
-
title: zod_1.z.string().optional().describe("接口标题(可省略,会保留原值)"),
|
|
581
|
-
path: zod_1.z.string().optional().describe("接口路径(可省略,会保留原值)"),
|
|
582
|
-
method: zod_1.z.string().optional().describe("请求方法(可省略,会保留原值)"),
|
|
583
|
-
status: zod_1.z.string().optional().describe("接口状态"),
|
|
584
|
-
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
585
|
-
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
586
|
-
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
587
|
-
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
588
|
-
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
589
|
-
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
590
|
-
req_body_other: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("其他请求体"),
|
|
591
|
-
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
592
|
-
res_body_type: zod_1.z.string().optional().describe("响应类型"),
|
|
593
|
-
res_body: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("响应内容"),
|
|
594
|
-
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
595
|
-
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
596
|
-
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
597
|
-
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
598
|
-
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
599
|
-
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
600
|
-
}, async (input) => {
|
|
601
|
-
try {
|
|
602
|
-
const built = await buildInterfaceSaveParams(input);
|
|
603
|
-
if (!built.ok)
|
|
604
|
-
return { content: [{ type: "text", text: built.error }] };
|
|
605
|
-
const response = await this.yapiService.updateInterface(built.params);
|
|
606
|
-
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
607
|
-
}
|
|
608
|
-
catch (error) {
|
|
609
|
-
this.logger.error(`更新接口失败:`, error);
|
|
610
|
-
return { content: [{ type: "text", text: `更新接口失败: ${error}` }] };
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
this.server.tool("yapi_interface_save", "新增或者更新接口(对应 /api/interface/save)", {
|
|
614
|
-
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
615
|
-
id: zod_1.z.string().optional().describe("接口ID(有则更新,无则新增)"),
|
|
616
|
-
catid: zod_1.z.string().optional().describe("接口分类ID"),
|
|
617
|
-
title: zod_1.z.string().optional().describe("接口标题"),
|
|
618
|
-
path: zod_1.z.string().optional().describe("接口路径"),
|
|
619
|
-
method: zod_1.z.string().optional().describe("请求方法"),
|
|
620
|
-
status: zod_1.z.string().optional().describe("接口状态"),
|
|
621
|
-
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
622
|
-
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
623
|
-
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
624
|
-
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
625
|
-
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
626
|
-
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
627
|
-
req_body_other: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("其他请求体"),
|
|
628
|
-
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
629
|
-
res_body_type: zod_1.z.string().optional().describe("响应类型"),
|
|
630
|
-
res_body: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("响应内容"),
|
|
631
|
-
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
632
|
-
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
633
|
-
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
634
|
-
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
635
|
-
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
636
|
-
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
637
|
-
}, async (input) => {
|
|
638
|
-
try {
|
|
639
|
-
const built = await buildInterfaceSaveParams(input);
|
|
640
|
-
if (!built.ok)
|
|
641
|
-
return { content: [{ type: "text", text: built.error }] };
|
|
642
|
-
const response = await this.yapiService.saveInterfaceUnified(built.params);
|
|
643
|
-
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
644
|
-
}
|
|
645
|
-
catch (error) {
|
|
646
|
-
this.logger.error(`保存接口失败:`, error);
|
|
647
|
-
return { content: [{ type: "text", text: `保存接口失败: ${error}` }] };
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
this.server.tool("yapi_open_import_data", "服务端数据导入(对应 /api/open/import_data)", {
|
|
651
|
-
projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
|
|
652
|
-
type: zod_1.z.string().describe("导入方式,如 swagger"),
|
|
653
|
-
merge: zod_1.z.enum(["normal", "good", "merge"]).describe("同步模式:normal/good/merge"),
|
|
654
|
-
json: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("导入数据 JSON(会自动序列化为字符串)"),
|
|
655
|
-
url: zod_1.z.string().optional().describe("导入数据 URL(提供后会走 url 方式)"),
|
|
656
|
-
}, async ({ projectId, type, merge, json, url }) => {
|
|
657
|
-
try {
|
|
658
|
-
let jsonString;
|
|
659
|
-
if (json !== undefined) {
|
|
660
|
-
const normalized = normalizeStringOrJson(json, "json");
|
|
661
|
-
if (!normalized.ok)
|
|
662
|
-
return { content: [{ type: "text", text: normalized.error }] };
|
|
663
|
-
jsonString = normalized.value;
|
|
526
|
+
if (isFull) {
|
|
527
|
+
this.server.tool("yapi_interface_get_cat_menu", "获取菜单列表(对应 /api/interface/getCatMenu)", {
|
|
528
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
529
|
+
}, async ({ projectId }) => {
|
|
530
|
+
try {
|
|
531
|
+
const list = await this.yapiService.getCategoryList(projectId);
|
|
532
|
+
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
664
533
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
534
|
+
catch (error) {
|
|
535
|
+
this.logger.error(`获取菜单列表失败:`, error);
|
|
536
|
+
return { content: [{ type: "text", text: `获取菜单列表失败: ${error}` }] };
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
this.server.tool("yapi_interface_list", "获取接口列表数据(对应 /api/interface/list)", {
|
|
540
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
541
|
+
page: zod_1.z.number().optional().describe("页码,默认 1"),
|
|
542
|
+
limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
|
|
543
|
+
}, async ({ projectId, page, limit }) => {
|
|
544
|
+
try {
|
|
545
|
+
const data = await this.yapiService.listInterfaces(projectId, page ?? 1, limit ?? 10);
|
|
546
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
this.logger.error(`获取接口列表失败:`, error);
|
|
550
|
+
return { content: [{ type: "text", text: `获取接口列表失败: ${error}` }] };
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
this.server.tool("yapi_interface_list_cat", "获取某个分类下接口列表(对应 /api/interface/list_cat)", {
|
|
554
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
555
|
+
catId: zod_1.z.string().describe("分类ID"),
|
|
556
|
+
page: zod_1.z.number().optional().describe("页码,默认 1"),
|
|
557
|
+
limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
|
|
558
|
+
}, async ({ projectId, catId, page, limit }) => {
|
|
559
|
+
try {
|
|
560
|
+
const response = await this.yapiService.listCategoryInterfaces(projectId, catId, page ?? 1, limit ?? 10);
|
|
561
|
+
return { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] };
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
this.logger.error(`获取分类接口列表失败:`, error);
|
|
565
|
+
return { content: [{ type: "text", text: `获取分类接口列表失败: ${error}` }] };
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
this.server.tool("yapi_interface_list_menu", "获取接口菜单列表(对应 /api/interface/list_menu)", {
|
|
569
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
570
|
+
}, async ({ projectId }) => {
|
|
571
|
+
try {
|
|
572
|
+
const data = await this.yapiService.getInterfaceMenu(projectId);
|
|
573
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
this.logger.error(`获取接口菜单列表失败:`, error);
|
|
577
|
+
return { content: [{ type: "text", text: `获取接口菜单列表失败: ${error}` }] };
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
this.server.tool("yapi_interface_add", "新增接口(对应 /api/interface/add)", {
|
|
581
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
582
|
+
catid: zod_1.z.string().describe("接口分类ID"),
|
|
583
|
+
title: zod_1.z.string().describe("接口标题"),
|
|
584
|
+
path: zod_1.z.string().describe("接口路径"),
|
|
585
|
+
method: zod_1.z.string().describe("请求方法"),
|
|
586
|
+
status: zod_1.z.string().optional().describe("接口状态,默认 undone"),
|
|
587
|
+
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
588
|
+
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
589
|
+
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
590
|
+
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
591
|
+
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
592
|
+
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
593
|
+
req_body_other: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("其他请求体"),
|
|
594
|
+
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
595
|
+
res_body_type: zod_1.z.string().optional().describe("响应类型,默认 json"),
|
|
596
|
+
res_body: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("响应内容"),
|
|
597
|
+
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
598
|
+
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
599
|
+
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
600
|
+
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
601
|
+
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
602
|
+
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
603
|
+
}, async (input) => {
|
|
604
|
+
try {
|
|
605
|
+
const built = await buildInterfaceSaveParams({ ...input, id: undefined });
|
|
606
|
+
if (!built.ok)
|
|
607
|
+
return { content: [{ type: "text", text: built.error }] };
|
|
608
|
+
const response = await this.yapiService.addInterface(built.params);
|
|
609
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
this.logger.error(`新增接口失败:`, error);
|
|
613
|
+
return { content: [{ type: "text", text: `新增接口失败: ${error}` }] };
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
this.server.tool("yapi_interface_up", "更新接口(对应 /api/interface/up)", {
|
|
617
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
618
|
+
id: zod_1.z.string().describe("接口ID"),
|
|
619
|
+
catid: zod_1.z.string().optional().describe("分类ID(可省略,会保留原值)"),
|
|
620
|
+
title: zod_1.z.string().optional().describe("接口标题(可省略,会保留原值)"),
|
|
621
|
+
path: zod_1.z.string().optional().describe("接口路径(可省略,会保留原值)"),
|
|
622
|
+
method: zod_1.z.string().optional().describe("请求方法(可省略,会保留原值)"),
|
|
623
|
+
status: zod_1.z.string().optional().describe("接口状态"),
|
|
624
|
+
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
625
|
+
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
626
|
+
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
627
|
+
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
628
|
+
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
629
|
+
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
630
|
+
req_body_other: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("其他请求体"),
|
|
631
|
+
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
632
|
+
res_body_type: zod_1.z.string().optional().describe("响应类型"),
|
|
633
|
+
res_body: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("响应内容"),
|
|
634
|
+
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
635
|
+
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
636
|
+
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
637
|
+
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
638
|
+
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
639
|
+
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
640
|
+
}, async (input) => {
|
|
641
|
+
try {
|
|
642
|
+
const built = await buildInterfaceSaveParams(input);
|
|
643
|
+
if (!built.ok)
|
|
644
|
+
return { content: [{ type: "text", text: built.error }] };
|
|
645
|
+
const response = await this.yapiService.updateInterface(built.params);
|
|
646
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
this.logger.error(`更新接口失败:`, error);
|
|
650
|
+
return { content: [{ type: "text", text: `更新接口失败: ${error}` }] };
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
this.server.tool("yapi_interface_save", "新增或者更新接口(对应 /api/interface/save)", {
|
|
654
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
655
|
+
id: zod_1.z.string().optional().describe("接口ID(有则更新,无则新增)"),
|
|
656
|
+
catid: zod_1.z.string().optional().describe("接口分类ID"),
|
|
657
|
+
title: zod_1.z.string().optional().describe("接口标题"),
|
|
658
|
+
path: zod_1.z.string().optional().describe("接口路径"),
|
|
659
|
+
method: zod_1.z.string().optional().describe("请求方法"),
|
|
660
|
+
status: zod_1.z.string().optional().describe("接口状态"),
|
|
661
|
+
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
662
|
+
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
663
|
+
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
664
|
+
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
665
|
+
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
666
|
+
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
667
|
+
req_body_other: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("其他请求体"),
|
|
668
|
+
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
669
|
+
res_body_type: zod_1.z.string().optional().describe("响应类型"),
|
|
670
|
+
res_body: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("响应内容"),
|
|
671
|
+
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
672
|
+
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
673
|
+
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
674
|
+
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
675
|
+
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
676
|
+
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
677
|
+
}, async (input) => {
|
|
678
|
+
try {
|
|
679
|
+
const built = await buildInterfaceSaveParams(input);
|
|
680
|
+
if (!built.ok)
|
|
681
|
+
return { content: [{ type: "text", text: built.error }] };
|
|
682
|
+
const response = await this.yapiService.saveInterfaceUnified(built.params);
|
|
683
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
this.logger.error(`保存接口失败:`, error);
|
|
687
|
+
return { content: [{ type: "text", text: `保存接口失败: ${error}` }] };
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
this.server.tool("yapi_open_import_data", "服务端数据导入(对应 /api/open/import_data)", {
|
|
691
|
+
projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
|
|
692
|
+
type: zod_1.z.string().describe("导入方式,如 swagger"),
|
|
693
|
+
merge: zod_1.z.enum(["normal", "good", "merge"]).describe("同步模式:normal/good/merge"),
|
|
694
|
+
json: zod_1.z.union([zod_1.z.string(), zod_1.z.record(zod_1.z.any()), zod_1.z.array(zod_1.z.any())]).optional().describe("导入数据 JSON(会自动序列化为字符串)"),
|
|
695
|
+
url: zod_1.z.string().optional().describe("导入数据 URL(提供后会走 url 方式)"),
|
|
696
|
+
}, async ({ projectId, type, merge, json, url }) => {
|
|
697
|
+
try {
|
|
698
|
+
let jsonString;
|
|
699
|
+
if (json !== undefined) {
|
|
700
|
+
const normalized = normalizeStringOrJson(json, "json");
|
|
701
|
+
if (!normalized.ok)
|
|
702
|
+
return { content: [{ type: "text", text: normalized.error }] };
|
|
703
|
+
jsonString = normalized.value;
|
|
704
|
+
}
|
|
705
|
+
const data = await this.yapiService.importData(projectId, { type, merge, json: jsonString, url });
|
|
706
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
this.logger.error(`导入数据失败:`, error);
|
|
710
|
+
return { content: [{ type: "text", text: `导入数据失败: ${error}` }] };
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
673
714
|
this.server.tool("yapi_search_apis", "搜索YApi中的接口", {
|
|
674
715
|
projectKeyword: zod_1.z.string().optional().describe("项目关键字,用于过滤项目"),
|
|
675
716
|
nameKeyword: zod_1.z.string().optional().describe("接口名称关键字"),
|
|
@@ -775,7 +816,7 @@ class YapiMcpServer {
|
|
|
775
816
|
项目描述: p?.desc || "无描述",
|
|
776
817
|
基础路径: p?.basepath || "/",
|
|
777
818
|
项目分组ID: p?.group_id ?? p?.groupId ?? "",
|
|
778
|
-
|
|
819
|
+
鉴权方式: "Cookie(全局模式)",
|
|
779
820
|
};
|
|
780
821
|
});
|
|
781
822
|
return {
|