@leeguoo/yapi-mcp 0.2.3 → 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,18 +98,39 @@ 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
- this.initializeCache();
104
+ if (this.isStdioMode) {
105
+ this.loadCacheFromDiskOnly();
106
+ }
107
+ else {
108
+ this.initializeCache();
109
+ }
110
+ }
111
+ loadCacheFromDiskOnly() {
112
+ try {
113
+ const cachedProjectInfo = this.projectInfoCache.loadFromCache();
114
+ if (cachedProjectInfo.size === 0)
115
+ return;
116
+ cachedProjectInfo.forEach((info, id) => {
117
+ this.yapiService.getProjectInfoCache().set(id, info);
118
+ });
119
+ this.logger.info(`stdio 模式:已从缓存加载 ${cachedProjectInfo.size} 个项目信息(未做预热请求)`);
120
+ }
121
+ catch (e) {
122
+ this.logger.warn(`stdio 模式:读取缓存失败(忽略):${e}`);
123
+ }
105
124
  }
106
125
  async initializeCache() {
107
126
  try {
108
127
  if (this.projectInfoCache.isCacheExpired()) {
109
128
  this.logger.info('缓存已过期,将异步更新缓存数据');
110
- this.asyncUpdateCache().catch(error => {
111
- this.logger.error('异步更新缓存失败:', error);
112
- });
129
+ setTimeout(() => {
130
+ this.asyncUpdateCache().catch(error => {
131
+ this.logger.error('异步更新缓存失败:', error);
132
+ });
133
+ }, 0);
113
134
  }
114
135
  else {
115
136
  const cachedProjectInfo = this.projectInfoCache.loadFromCache();
@@ -121,17 +142,21 @@ class YapiMcpServer {
121
142
  }
122
143
  else {
123
144
  this.logger.info('缓存为空,将异步更新缓存数据');
124
- this.asyncUpdateCache().catch(error => {
125
- this.logger.error('异步更新缓存失败:', error);
126
- });
145
+ setTimeout(() => {
146
+ this.asyncUpdateCache().catch(error => {
147
+ this.logger.error('异步更新缓存失败:', error);
148
+ });
149
+ }, 0);
127
150
  }
128
151
  }
129
152
  }
130
153
  catch (error) {
131
154
  this.logger.error('加载或检查缓存时出错:', error);
132
- this.asyncUpdateCache().catch(err => {
133
- this.logger.error('异步更新缓存失败:', err);
134
- });
155
+ setTimeout(() => {
156
+ this.asyncUpdateCache().catch(err => {
157
+ this.logger.error('异步更新缓存失败:', err);
158
+ });
159
+ }, 0);
135
160
  }
136
161
  }
137
162
  async asyncUpdateCache() {
@@ -150,6 +175,7 @@ class YapiMcpServer {
150
175
  }
151
176
  }
152
177
  registerTools() {
178
+ const isFull = this.toolset === "full";
153
179
  const yapiParamItemSchema = zod_1.z
154
180
  .object({
155
181
  name: zod_1.z.string().describe("参数名"),
@@ -164,16 +190,36 @@ class YapiMcpServer {
164
190
  value: zod_1.z.string().optional().describe("Header 值(如 application/json)"),
165
191
  })
166
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
+ };
167
212
  const buildInterfaceSaveParams = async (input) => {
168
213
  const isUpdate = Boolean(input.id);
169
214
  let current = null;
170
215
  if (isUpdate) {
171
- current = await this.yapiService.getApiInterface(input.projectId, input.id);
216
+ current = await this.yapiService.getApiInterface(normalizeProjectId(input.projectId), String(input.id));
172
217
  }
173
- const finalCatid = input.catid ?? (current ? String(current.catid ?? "") : "");
174
- const finalTitle = input.title ?? (current ? String(current.title ?? "") : "");
175
- const finalPath = input.path ?? (current ? String(current.path ?? "") : "");
176
- 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 ?? "") : ""));
177
223
  if (!finalCatid)
178
224
  return { ok: false, error: "缺少 catid:新增必填;更新可省略但必须能从原接口读取到" };
179
225
  if (!finalTitle)
@@ -183,7 +229,7 @@ class YapiMcpServer {
183
229
  if (!finalMethod)
184
230
  return { ok: false, error: "缺少 method:新增必填;更新可省略但必须能从原接口读取到" };
185
231
  const params = {
186
- project_id: input.projectId,
232
+ project_id: finalProjectId,
187
233
  catid: finalCatid,
188
234
  title: finalTitle,
189
235
  path: finalPath,
@@ -240,7 +286,7 @@ class YapiMcpServer {
240
286
  params.api_opened = input.api_opened ?? (isUpdate ? current?.api_opened : undefined);
241
287
  return { ok: true, params, isUpdate };
242
288
  };
243
- this.server.tool("yapi_update_token", "全局模式:使用用户名/密码登录 YApi,刷新本地缓存的 projectId -> token(用于后续调用开放 API)", {
289
+ this.server.tool("yapi_update_token", "全局模式:使用用户名/密码登录 YApi,刷新本地登录态 Cookie(并可选刷新项目信息缓存)", {
244
290
  forceLogin: zod_1.z.boolean().optional().describe("是否强制重新登录(忽略已缓存的 cookie)"),
245
291
  }, async ({ forceLogin }) => {
246
292
  try {
@@ -254,9 +300,27 @@ class YapiMcpServer {
254
300
  ],
255
301
  };
256
302
  }
257
- const { tokens, groups, projects, cookieHeader } = await this.authService.refreshProjectTokens({ forceLogin });
258
- this.yapiService.setProjectTokens(tokens, { overwrite: true });
303
+ const cookieHeader = await this.authService.getCookieHeaderWithLogin({ forceLogin });
259
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
+ });
260
324
  await this.yapiService.loadAllProjectInfo();
261
325
  this.projectInfoCache.saveToCache(this.yapiService.getProjectInfoCache());
262
326
  await this.yapiService.loadAllCategoryLists();
@@ -264,16 +328,14 @@ class YapiMcpServer {
264
328
  content: [
265
329
  {
266
330
  type: "text",
267
- text: `token 刷新完成:分组 ${groups.length} 个,项目 ${projects.length} 个,已缓存 token ${tokens.size} 个。${tokens.size === 0
268
- ? "(提示:部分 YApi 部署不会在开放 API 返回 token,本工具已尝试从项目设置页抓取;如仍为 0,可能账号权限不足或实例限制展示 token。)"
269
- : ""}`,
331
+ text: `登录态刷新完成:分组 ${groups.length} 个,项目 ${projects.length} 个;已更新 Cookie,并刷新项目信息/分类缓存。`,
270
332
  },
271
333
  ],
272
334
  };
273
335
  }
274
336
  catch (error) {
275
- this.logger.error("刷新 token 失败:", error);
276
- return { content: [{ type: "text", text: `刷新 token 失败: ${error}` }] };
337
+ this.logger.error("刷新登录态失败:", error);
338
+ return { content: [{ type: "text", text: `刷新登录态失败: ${error}` }] };
277
339
  }
278
340
  });
279
341
  this.server.tool("yapi_get_api_desc", "获取YApi中特定接口的详细信息", {
@@ -350,19 +412,21 @@ class YapiMcpServer {
350
412
  return { content: [{ type: "text", text: `搜索项目失败: ${error}` }] };
351
413
  }
352
414
  });
353
- this.server.tool("yapi_interface_get", "获取接口数据(对应 /api/interface/get,返回原始字段)", {
354
- projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
355
- apiId: zod_1.z.string().describe("接口ID"),
356
- }, async ({ projectId, apiId }) => {
357
- try {
358
- const apiInterface = await this.yapiService.getApiInterface(projectId, apiId);
359
- return { content: [{ type: "text", text: JSON.stringify(apiInterface, null, 2) }] };
360
- }
361
- catch (error) {
362
- this.logger.error(`获取接口数据失败:`, error);
363
- return { content: [{ type: "text", text: `获取接口数据失败: ${error}` }] };
364
- }
365
- });
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
+ }
366
430
  this.server.tool("yapi_save_api", "新增或更新YApi中的接口信息", {
367
431
  projectId: zod_1.z.string().describe("YApi项目ID"),
368
432
  catid: zod_1.z.string().optional().describe("接口分类ID;新增接口时必填,更新时可省略(会保留原值)"),
@@ -459,192 +523,194 @@ class YapiMcpServer {
459
523
  return { content: [{ type: "text", text: `新增接口分类失败: ${error}` }] };
460
524
  }
461
525
  });
462
- this.server.tool("yapi_interface_get_cat_menu", "获取菜单列表(对应 /api/interface/getCatMenu)", {
463
- projectId: zod_1.z.string().describe("YApi项目ID"),
464
- }, async ({ projectId }) => {
465
- try {
466
- const list = await this.yapiService.getCategoryList(projectId);
467
- return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
468
- }
469
- catch (error) {
470
- this.logger.error(`获取菜单列表失败:`, error);
471
- return { content: [{ type: "text", text: `获取菜单列表失败: ${error}` }] };
472
- }
473
- });
474
- this.server.tool("yapi_interface_list", "获取接口列表数据(对应 /api/interface/list)", {
475
- projectId: zod_1.z.string().describe("YApi项目ID"),
476
- page: zod_1.z.number().optional().describe("页码,默认 1"),
477
- limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
478
- }, async ({ projectId, page, limit }) => {
479
- try {
480
- const data = await this.yapiService.listInterfaces(projectId, page ?? 1, limit ?? 10);
481
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
482
- }
483
- catch (error) {
484
- this.logger.error(`获取接口列表失败:`, error);
485
- return { content: [{ type: "text", text: `获取接口列表失败: ${error}` }] };
486
- }
487
- });
488
- this.server.tool("yapi_interface_list_cat", "获取某个分类下接口列表(对应 /api/interface/list_cat)", {
489
- projectId: zod_1.z.string().describe("YApi项目ID"),
490
- catId: zod_1.z.string().describe("分类ID"),
491
- page: zod_1.z.number().optional().describe("页码,默认 1"),
492
- limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
493
- }, async ({ projectId, catId, page, limit }) => {
494
- try {
495
- const response = await this.yapiService.listCategoryInterfaces(projectId, catId, page ?? 1, limit ?? 10);
496
- return { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] };
497
- }
498
- catch (error) {
499
- this.logger.error(`获取分类接口列表失败:`, error);
500
- return { content: [{ type: "text", text: `获取分类接口列表失败: ${error}` }] };
501
- }
502
- });
503
- this.server.tool("yapi_interface_list_menu", "获取接口菜单列表(对应 /api/interface/list_menu)", {
504
- projectId: zod_1.z.string().describe("YApi项目ID"),
505
- }, async ({ projectId }) => {
506
- try {
507
- const data = await this.yapiService.getInterfaceMenu(projectId);
508
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
509
- }
510
- catch (error) {
511
- this.logger.error(`获取接口菜单列表失败:`, error);
512
- return { content: [{ type: "text", text: `获取接口菜单列表失败: ${error}` }] };
513
- }
514
- });
515
- this.server.tool("yapi_interface_add", "新增接口(对应 /api/interface/add)", {
516
- projectId: zod_1.z.string().describe("YApi项目ID"),
517
- catid: zod_1.z.string().describe("接口分类ID"),
518
- title: zod_1.z.string().describe("接口标题"),
519
- path: zod_1.z.string().describe("接口路径"),
520
- method: zod_1.z.string().describe("请求方法"),
521
- status: zod_1.z.string().optional().describe("接口状态,默认 undone"),
522
- tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
523
- req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
524
- req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
525
- req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
526
- req_body_type: zod_1.z.string().optional().describe("请求体类型"),
527
- req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
528
- 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("其他请求体"),
529
- req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
530
- res_body_type: zod_1.z.string().optional().describe("响应类型,默认 json"),
531
- 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("响应内容"),
532
- res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
533
- switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
534
- api_opened: zod_1.z.boolean().optional().describe("是否公开"),
535
- message: zod_1.z.string().optional().describe("接口备注信息"),
536
- desc: zod_1.z.string().optional().describe("接口描述"),
537
- markdown: zod_1.z.string().optional().describe("markdown 描述"),
538
- }, async (input) => {
539
- try {
540
- const built = await buildInterfaceSaveParams({ ...input, id: undefined });
541
- if (!built.ok)
542
- return { content: [{ type: "text", text: built.error }] };
543
- const response = await this.yapiService.addInterface(built.params);
544
- return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
545
- }
546
- catch (error) {
547
- this.logger.error(`新增接口失败:`, error);
548
- return { content: [{ type: "text", text: `新增接口失败: ${error}` }] };
549
- }
550
- });
551
- this.server.tool("yapi_interface_up", "更新接口(对应 /api/interface/up)", {
552
- projectId: zod_1.z.string().describe("YApi项目ID"),
553
- id: zod_1.z.string().describe("接口ID"),
554
- catid: zod_1.z.string().optional().describe("分类ID(可省略,会保留原值)"),
555
- title: zod_1.z.string().optional().describe("接口标题(可省略,会保留原值)"),
556
- path: zod_1.z.string().optional().describe("接口路径(可省略,会保留原值)"),
557
- method: zod_1.z.string().optional().describe("请求方法(可省略,会保留原值)"),
558
- status: zod_1.z.string().optional().describe("接口状态"),
559
- tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
560
- req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
561
- req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
562
- req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
563
- req_body_type: zod_1.z.string().optional().describe("请求体类型"),
564
- req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
565
- 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("其他请求体"),
566
- req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
567
- res_body_type: zod_1.z.string().optional().describe("响应类型"),
568
- 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("响应内容"),
569
- res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
570
- switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
571
- api_opened: zod_1.z.boolean().optional().describe("是否公开"),
572
- message: zod_1.z.string().optional().describe("接口备注信息"),
573
- desc: zod_1.z.string().optional().describe("接口描述"),
574
- markdown: zod_1.z.string().optional().describe("markdown 描述"),
575
- }, async (input) => {
576
- try {
577
- const built = await buildInterfaceSaveParams(input);
578
- if (!built.ok)
579
- return { content: [{ type: "text", text: built.error }] };
580
- const response = await this.yapiService.updateInterface(built.params);
581
- return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
582
- }
583
- catch (error) {
584
- this.logger.error(`更新接口失败:`, error);
585
- return { content: [{ type: "text", text: `更新接口失败: ${error}` }] };
586
- }
587
- });
588
- this.server.tool("yapi_interface_save", "新增或者更新接口(对应 /api/interface/save)", {
589
- projectId: zod_1.z.string().describe("YApi项目ID"),
590
- id: zod_1.z.string().optional().describe("接口ID(有则更新,无则新增)"),
591
- catid: zod_1.z.string().optional().describe("接口分类ID"),
592
- title: zod_1.z.string().optional().describe("接口标题"),
593
- path: zod_1.z.string().optional().describe("接口路径"),
594
- method: zod_1.z.string().optional().describe("请求方法"),
595
- status: zod_1.z.string().optional().describe("接口状态"),
596
- tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
597
- req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
598
- req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
599
- req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
600
- req_body_type: zod_1.z.string().optional().describe("请求体类型"),
601
- req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
602
- 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("其他请求体"),
603
- req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
604
- res_body_type: zod_1.z.string().optional().describe("响应类型"),
605
- 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("响应内容"),
606
- res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
607
- switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
608
- api_opened: zod_1.z.boolean().optional().describe("是否公开"),
609
- message: zod_1.z.string().optional().describe("接口备注信息"),
610
- desc: zod_1.z.string().optional().describe("接口描述"),
611
- markdown: zod_1.z.string().optional().describe("markdown 描述"),
612
- }, async (input) => {
613
- try {
614
- const built = await buildInterfaceSaveParams(input);
615
- if (!built.ok)
616
- return { content: [{ type: "text", text: built.error }] };
617
- const response = await this.yapiService.saveInterfaceUnified(built.params);
618
- return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
619
- }
620
- catch (error) {
621
- this.logger.error(`保存接口失败:`, error);
622
- return { content: [{ type: "text", text: `保存接口失败: ${error}` }] };
623
- }
624
- });
625
- this.server.tool("yapi_open_import_data", "服务端数据导入(对应 /api/open/import_data)", {
626
- projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
627
- type: zod_1.z.string().describe("导入方式,如 swagger"),
628
- merge: zod_1.z.enum(["normal", "good", "merge"]).describe("同步模式:normal/good/merge"),
629
- 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(会自动序列化为字符串)"),
630
- url: zod_1.z.string().optional().describe("导入数据 URL(提供后会走 url 方式)"),
631
- }, async ({ projectId, type, merge, json, url }) => {
632
- try {
633
- let jsonString;
634
- if (json !== undefined) {
635
- const normalized = normalizeStringOrJson(json, "json");
636
- if (!normalized.ok)
637
- return { content: [{ type: "text", text: normalized.error }] };
638
- 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) }] };
639
533
  }
640
- const data = await this.yapiService.importData(projectId, { type, merge, json: jsonString, url });
641
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
642
- }
643
- catch (error) {
644
- this.logger.error(`导入数据失败:`, error);
645
- return { content: [{ type: "text", text: `导入数据失败: ${error}` }] };
646
- }
647
- });
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
+ }
648
714
  this.server.tool("yapi_search_apis", "搜索YApi中的接口", {
649
715
  projectKeyword: zod_1.z.string().optional().describe("项目关键字,用于过滤项目"),
650
716
  nameKeyword: zod_1.z.string().optional().describe("接口名称关键字"),
@@ -750,7 +816,7 @@ class YapiMcpServer {
750
816
  项目描述: p?.desc || "无描述",
751
817
  基础路径: p?.basepath || "/",
752
818
  项目分组ID: p?.group_id ?? p?.groupId ?? "",
753
- 已缓存Token: id ? (this.yapiService.hasProjectToken(id) ? "" : "否") : "否",
819
+ 鉴权方式: "Cookie(全局模式)",
754
820
  };
755
821
  });
756
822
  return {