@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/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.2.1",
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 finalCatid = input.catid ?? (current ? String(current.catid ?? "") : "");
199
- const finalTitle = input.title ?? (current ? String(current.title ?? "") : "");
200
- const finalPath = input.path ?? (current ? String(current.path ?? "") : "");
201
- const finalMethod = input.method ?? (current ? String(current.method ?? "") : "");
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: input.projectId,
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,刷新本地缓存的 projectId -> token(用于后续调用开放 API)", {
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 { tokens, groups, projects, cookieHeader } = await this.authService.refreshProjectTokens({ forceLogin });
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: `token 刷新完成:分组 ${groups.length} 个,项目 ${projects.length} 个,已缓存 token ${tokens.size} 个。${tokens.size === 0
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("刷新 token 失败:", error);
301
- return { content: [{ type: "text", text: `刷新 token 失败: ${error}` }] };
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
- this.server.tool("yapi_interface_get", "获取接口数据(对应 /api/interface/get,返回原始字段)", {
379
- projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
380
- apiId: zod_1.z.string().describe("接口ID"),
381
- }, async ({ projectId, apiId }) => {
382
- try {
383
- const apiInterface = await this.yapiService.getApiInterface(projectId, apiId);
384
- return { content: [{ type: "text", text: JSON.stringify(apiInterface, null, 2) }] };
385
- }
386
- catch (error) {
387
- this.logger.error(`获取接口数据失败:`, error);
388
- return { content: [{ type: "text", text: `获取接口数据失败: ${error}` }] };
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
- this.server.tool("yapi_interface_get_cat_menu", "获取菜单列表(对应 /api/interface/getCatMenu)", {
488
- projectId: zod_1.z.string().describe("YApi项目ID"),
489
- }, async ({ projectId }) => {
490
- try {
491
- const list = await this.yapiService.getCategoryList(projectId);
492
- return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
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
- const data = await this.yapiService.importData(projectId, { type, merge, json: jsonString, url });
666
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
667
- }
668
- catch (error) {
669
- this.logger.error(`导入数据失败:`, error);
670
- return { content: [{ type: "text", text: `导入数据失败: ${error}` }] };
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
- 已缓存Token: id ? (this.yapiService.hasProjectToken(id) ? "" : "否") : "否",
819
+ 鉴权方式: "Cookie(全局模式)",
779
820
  };
780
821
  });
781
822
  return {