@leeguoo/yapi-mcp 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +32 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +174 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.js +847 -0
- package/dist/server.js.map +1 -0
- package/dist/services/yapi/api.d.ts +56 -0
- package/dist/services/yapi/api.js +432 -0
- package/dist/services/yapi/api.js.map +1 -0
- package/dist/services/yapi/auth.d.ts +24 -0
- package/dist/services/yapi/auth.js +239 -0
- package/dist/services/yapi/auth.js.map +1 -0
- package/dist/services/yapi/authCache.d.ts +29 -0
- package/dist/services/yapi/authCache.js +172 -0
- package/dist/services/yapi/authCache.js.map +1 -0
- package/dist/services/yapi/cache.d.ts +14 -0
- package/dist/services/yapi/cache.js +232 -0
- package/dist/services/yapi/cache.js.map +1 -0
- package/dist/services/yapi/logger.d.ts +17 -0
- package/dist/services/yapi/logger.js +61 -0
- package/dist/services/yapi/logger.js.map +1 -0
- package/dist/services/yapi/types.d.ts +102 -0
- package/dist/services/yapi/types.js +3 -0
- package/dist/services/yapi/types.js.map +1 -0
- package/dist/services/yapi.d.ts +28 -0
- package/dist/services/yapi.js +84 -0
- package/dist/services/yapi.js.map +1 -0
- package/dist/skill/install.d.ts +1 -0
- package/dist/skill/install.js +724 -0
- package/dist/skill/install.js.map +1 -0
- package/dist/yapi-cli.d.ts +2 -0
- package/dist/yapi-cli.js +592 -0
- package/dist/yapi-cli.js.map +1 -0
- package/package.json +77 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.YapiMcpServer = void 0;
|
|
7
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const express_1 = __importDefault(require("express"));
|
|
10
|
+
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
11
|
+
const api_1 = require("./services/yapi/api");
|
|
12
|
+
const cache_1 = require("./services/yapi/cache");
|
|
13
|
+
const logger_1 = require("./services/yapi/logger");
|
|
14
|
+
const auth_1 = require("./services/yapi/auth");
|
|
15
|
+
function normalizeJsonArrayInput(value, fieldName) {
|
|
16
|
+
if (value === undefined || value === null)
|
|
17
|
+
return { ok: true, value: [] };
|
|
18
|
+
if (Array.isArray(value))
|
|
19
|
+
return { ok: true, value };
|
|
20
|
+
if (typeof value === "string") {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(value);
|
|
23
|
+
if (!Array.isArray(parsed))
|
|
24
|
+
return { ok: false, error: `${fieldName} 需要是 JSON 数组` };
|
|
25
|
+
return { ok: true, value: parsed };
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return { ok: false, error: `${fieldName} JSON 解析错误: ${e}` };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { ok: false, error: `${fieldName} 需要是 JSON 数组或数组类型` };
|
|
32
|
+
}
|
|
33
|
+
function normalizeStringOrJson(value, fieldName) {
|
|
34
|
+
if (value === undefined || value === null)
|
|
35
|
+
return { ok: true, value: "" };
|
|
36
|
+
if (typeof value === "string")
|
|
37
|
+
return { ok: true, value };
|
|
38
|
+
try {
|
|
39
|
+
return { ok: true, value: JSON.stringify(value, null, 2) };
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return { ok: false, error: `${fieldName} 无法序列化为 JSON 字符串: ${e}` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function normalizeTagInput(value) {
|
|
46
|
+
if (value === undefined || value === null)
|
|
47
|
+
return { ok: true, value: [] };
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
return { ok: true, value: value.map(String).filter(Boolean) };
|
|
50
|
+
if (typeof value === "string") {
|
|
51
|
+
const trimmed = value.trim();
|
|
52
|
+
if (!trimmed)
|
|
53
|
+
return { ok: true, value: [] };
|
|
54
|
+
if (trimmed.startsWith("[")) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(trimmed);
|
|
57
|
+
if (!Array.isArray(parsed))
|
|
58
|
+
return { ok: false, error: "tag 需要是 JSON 数组" };
|
|
59
|
+
return { ok: true, value: parsed.map(String).filter(Boolean) };
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return { ok: false, error: `tag JSON 解析错误: ${e}` };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, value: trimmed.split(/[\s,]+/).map(s => s.trim()).filter(Boolean) };
|
|
66
|
+
}
|
|
67
|
+
return { ok: false, error: "tag 需要是字符串或字符串数组" };
|
|
68
|
+
}
|
|
69
|
+
class YapiMcpServer {
|
|
70
|
+
server;
|
|
71
|
+
yapiService;
|
|
72
|
+
projectInfoCache;
|
|
73
|
+
logger;
|
|
74
|
+
authService;
|
|
75
|
+
authMode;
|
|
76
|
+
sseTransport = null;
|
|
77
|
+
isStdioMode;
|
|
78
|
+
constructor(yapiBaseUrl, yapiToken, yapiLogLevel = "info", yapiCacheTTL = 10, auth) {
|
|
79
|
+
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(yapiBaseUrl, yapiCacheTTL, yapiLogLevel);
|
|
82
|
+
this.authMode = auth?.mode ?? (auth?.email && auth?.password ? "global" : "token");
|
|
83
|
+
this.authService =
|
|
84
|
+
this.authMode === "global" && auth?.email && auth?.password
|
|
85
|
+
? new auth_1.YApiAuthService(yapiBaseUrl, auth.email, auth.password, yapiLogLevel)
|
|
86
|
+
: null;
|
|
87
|
+
if (this.authService) {
|
|
88
|
+
const cachedTokens = this.authService.loadCachedProjectTokens();
|
|
89
|
+
this.yapiService.setProjectTokens(cachedTokens, { overwrite: false });
|
|
90
|
+
this.logger.info(`全局模式已启用:已从本地缓存加载 ${cachedTokens.size} 个项目 token`);
|
|
91
|
+
}
|
|
92
|
+
this.isStdioMode = process.env.NODE_ENV === "cli" || process.argv.includes("--stdio");
|
|
93
|
+
this.logger.info(`YapiMcpServer初始化,日志级别: ${yapiLogLevel}, 缓存TTL: ${yapiCacheTTL}分钟`);
|
|
94
|
+
this.server = new mcp_js_1.McpServer({
|
|
95
|
+
name: "Yapi MCP Server",
|
|
96
|
+
version: "0.2.1",
|
|
97
|
+
});
|
|
98
|
+
this.registerTools();
|
|
99
|
+
this.initializeCache();
|
|
100
|
+
}
|
|
101
|
+
async initializeCache() {
|
|
102
|
+
try {
|
|
103
|
+
if (this.projectInfoCache.isCacheExpired()) {
|
|
104
|
+
this.logger.info('缓存已过期,将异步更新缓存数据');
|
|
105
|
+
this.asyncUpdateCache().catch(error => {
|
|
106
|
+
this.logger.error('异步更新缓存失败:', error);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const cachedProjectInfo = this.projectInfoCache.loadFromCache();
|
|
111
|
+
if (cachedProjectInfo.size > 0) {
|
|
112
|
+
cachedProjectInfo.forEach((info, id) => {
|
|
113
|
+
this.yapiService.getProjectInfoCache().set(id, info);
|
|
114
|
+
});
|
|
115
|
+
this.logger.info(`已从缓存加载 ${cachedProjectInfo.size} 个项目信息`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.logger.info('缓存为空,将异步更新缓存数据');
|
|
119
|
+
this.asyncUpdateCache().catch(error => {
|
|
120
|
+
this.logger.error('异步更新缓存失败:', error);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
this.logger.error('加载或检查缓存时出错:', error);
|
|
127
|
+
this.asyncUpdateCache().catch(err => {
|
|
128
|
+
this.logger.error('异步更新缓存失败:', err);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async asyncUpdateCache() {
|
|
133
|
+
try {
|
|
134
|
+
this.logger.debug('开始异步更新缓存数据');
|
|
135
|
+
await this.yapiService.loadAllProjectInfo();
|
|
136
|
+
this.logger.debug(`已加载 ${this.yapiService.getProjectInfoCache().size} 个项目信息`);
|
|
137
|
+
this.projectInfoCache.saveToCache(this.yapiService.getProjectInfoCache());
|
|
138
|
+
await this.yapiService.loadAllCategoryLists();
|
|
139
|
+
this.logger.debug('已加载所有项目的分类列表');
|
|
140
|
+
this.logger.info('缓存数据已成功更新');
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
this.logger.error('更新缓存数据失败:', error);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
registerTools() {
|
|
148
|
+
const yapiParamItemSchema = zod_1.z
|
|
149
|
+
.object({
|
|
150
|
+
name: zod_1.z.string().describe("参数名"),
|
|
151
|
+
desc: zod_1.z.string().optional().describe("中文备注/说明(建议把枚举、单位、范围等写清楚)"),
|
|
152
|
+
type: zod_1.z.string().optional().describe("类型,如 string/number/boolean/integer/object/array"),
|
|
153
|
+
example: zod_1.z.string().optional().describe("示例值"),
|
|
154
|
+
required: zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean()]).optional().describe("是否必填(YApi 常用 '1'/'0')"),
|
|
155
|
+
})
|
|
156
|
+
.passthrough();
|
|
157
|
+
const yapiHeaderItemSchema = yapiParamItemSchema
|
|
158
|
+
.extend({
|
|
159
|
+
value: zod_1.z.string().optional().describe("Header 值(如 application/json)"),
|
|
160
|
+
})
|
|
161
|
+
.passthrough();
|
|
162
|
+
const buildInterfaceSaveParams = async (input) => {
|
|
163
|
+
const isUpdate = Boolean(input.id);
|
|
164
|
+
let current = null;
|
|
165
|
+
if (isUpdate) {
|
|
166
|
+
current = await this.yapiService.getApiInterface(input.projectId, input.id);
|
|
167
|
+
}
|
|
168
|
+
const finalCatid = input.catid ?? (current ? String(current.catid ?? "") : "");
|
|
169
|
+
const finalTitle = input.title ?? (current ? String(current.title ?? "") : "");
|
|
170
|
+
const finalPath = input.path ?? (current ? String(current.path ?? "") : "");
|
|
171
|
+
const finalMethod = input.method ?? (current ? String(current.method ?? "") : "");
|
|
172
|
+
if (!finalCatid)
|
|
173
|
+
return { ok: false, error: "缺少 catid:新增必填;更新可省略但必须能从原接口读取到" };
|
|
174
|
+
if (!finalTitle)
|
|
175
|
+
return { ok: false, error: "缺少 title:新增必填;更新可省略但必须能从原接口读取到" };
|
|
176
|
+
if (!finalPath)
|
|
177
|
+
return { ok: false, error: "缺少 path:新增必填;更新可省略但必须能从原接口读取到" };
|
|
178
|
+
if (!finalMethod)
|
|
179
|
+
return { ok: false, error: "缺少 method:新增必填;更新可省略但必须能从原接口读取到" };
|
|
180
|
+
const params = {
|
|
181
|
+
project_id: input.projectId,
|
|
182
|
+
catid: finalCatid,
|
|
183
|
+
title: finalTitle,
|
|
184
|
+
path: finalPath,
|
|
185
|
+
method: finalMethod,
|
|
186
|
+
};
|
|
187
|
+
if (isUpdate)
|
|
188
|
+
params.id = input.id;
|
|
189
|
+
const tagRaw = input.tag !== undefined ? input.tag : isUpdate ? current?.tag : undefined;
|
|
190
|
+
const tagResult = normalizeTagInput(tagRaw);
|
|
191
|
+
if (!tagResult.ok)
|
|
192
|
+
return { ok: false, error: tagResult.error };
|
|
193
|
+
params.tag = tagResult.value;
|
|
194
|
+
params.status = input.status ?? (isUpdate ? current?.status : "undone");
|
|
195
|
+
params.desc = input.desc ?? (isUpdate ? current?.desc : "");
|
|
196
|
+
params.markdown = input.markdown ?? (isUpdate ? current?.markdown : "");
|
|
197
|
+
params.message = input.message ?? (isUpdate ? current?.message : "");
|
|
198
|
+
const reqParamsRaw = input.req_params !== undefined ? input.req_params : isUpdate ? current?.req_params : [];
|
|
199
|
+
const reqParamsParsed = normalizeJsonArrayInput(reqParamsRaw, "req_params");
|
|
200
|
+
if (!reqParamsParsed.ok)
|
|
201
|
+
return { ok: false, error: reqParamsParsed.error };
|
|
202
|
+
params.req_params = reqParamsParsed.value;
|
|
203
|
+
const reqQueryRaw = input.req_query !== undefined ? input.req_query : isUpdate ? current?.req_query : [];
|
|
204
|
+
const reqQueryParsed = normalizeJsonArrayInput(reqQueryRaw, "req_query");
|
|
205
|
+
if (!reqQueryParsed.ok)
|
|
206
|
+
return { ok: false, error: reqQueryParsed.error };
|
|
207
|
+
params.req_query = reqQueryParsed.value;
|
|
208
|
+
const reqHeadersRaw = input.req_headers !== undefined ? input.req_headers : isUpdate ? current?.req_headers : [];
|
|
209
|
+
const reqHeadersParsed = normalizeJsonArrayInput(reqHeadersRaw, "req_headers");
|
|
210
|
+
if (!reqHeadersParsed.ok)
|
|
211
|
+
return { ok: false, error: reqHeadersParsed.error };
|
|
212
|
+
params.req_headers = reqHeadersParsed.value;
|
|
213
|
+
params.req_body_type = input.req_body_type ?? (isUpdate ? current?.req_body_type : "");
|
|
214
|
+
const reqBodyFormRaw = input.req_body_form !== undefined ? input.req_body_form : isUpdate ? current?.req_body_form : [];
|
|
215
|
+
const reqBodyFormParsed = normalizeJsonArrayInput(reqBodyFormRaw, "req_body_form");
|
|
216
|
+
if (!reqBodyFormParsed.ok)
|
|
217
|
+
return { ok: false, error: reqBodyFormParsed.error };
|
|
218
|
+
params.req_body_form = reqBodyFormParsed.value;
|
|
219
|
+
const reqBodyOtherRaw = input.req_body_other !== undefined ? input.req_body_other : isUpdate ? current?.req_body_other : "";
|
|
220
|
+
const reqBodyOtherNormalized = normalizeStringOrJson(reqBodyOtherRaw, "req_body_other");
|
|
221
|
+
if (!reqBodyOtherNormalized.ok)
|
|
222
|
+
return { ok: false, error: reqBodyOtherNormalized.error };
|
|
223
|
+
params.req_body_other = reqBodyOtherNormalized.value;
|
|
224
|
+
params.req_body_is_json_schema =
|
|
225
|
+
input.req_body_is_json_schema ?? (isUpdate ? current?.req_body_is_json_schema : undefined);
|
|
226
|
+
params.res_body_type = input.res_body_type ?? (isUpdate ? current?.res_body_type : "json");
|
|
227
|
+
const resBodyRaw = input.res_body !== undefined ? input.res_body : isUpdate ? current?.res_body : "";
|
|
228
|
+
const resBodyNormalized = normalizeStringOrJson(resBodyRaw, "res_body");
|
|
229
|
+
if (!resBodyNormalized.ok)
|
|
230
|
+
return { ok: false, error: resBodyNormalized.error };
|
|
231
|
+
params.res_body = resBodyNormalized.value;
|
|
232
|
+
params.res_body_is_json_schema =
|
|
233
|
+
input.res_body_is_json_schema ?? (isUpdate ? current?.res_body_is_json_schema : undefined);
|
|
234
|
+
params.switch_notice = input.switch_notice ?? (isUpdate ? current?.switch_notice : undefined);
|
|
235
|
+
params.api_opened = input.api_opened ?? (isUpdate ? current?.api_opened : undefined);
|
|
236
|
+
return { ok: true, params, isUpdate };
|
|
237
|
+
};
|
|
238
|
+
this.server.tool("yapi_update_token", "全局模式:使用用户名/密码登录 YApi,刷新本地缓存的 projectId -> token(用于后续调用开放 API)", {
|
|
239
|
+
forceLogin: zod_1.z.boolean().optional().describe("是否强制重新登录(忽略已缓存的 cookie)"),
|
|
240
|
+
}, async ({ forceLogin }) => {
|
|
241
|
+
try {
|
|
242
|
+
if (!this.authService) {
|
|
243
|
+
return {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: "未启用全局模式:请在 MCP 配置中提供 --yapi-auth-mode=global 以及 --yapi-email/--yapi-password(或对应环境变量),然后再调用 yapi_update_token。",
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const { tokens, groups, projects } = await this.authService.refreshProjectTokens({ forceLogin });
|
|
253
|
+
this.yapiService.setProjectTokens(tokens, { overwrite: true });
|
|
254
|
+
await this.yapiService.loadAllProjectInfo();
|
|
255
|
+
this.projectInfoCache.saveToCache(this.yapiService.getProjectInfoCache());
|
|
256
|
+
await this.yapiService.loadAllCategoryLists();
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: `token 刷新完成:分组 ${groups.length} 个,项目 ${projects.length} 个,已缓存 token ${tokens.size} 个。`,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
this.logger.error("刷新 token 失败:", error);
|
|
268
|
+
return { content: [{ type: "text", text: `刷新 token 失败: ${error}` }] };
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
this.server.tool("yapi_get_api_desc", "获取YApi中特定接口的详细信息", {
|
|
272
|
+
projectId: zod_1.z.string().describe("YApi项目ID;如连接/project/28/interface/api/66,则ID为28"),
|
|
273
|
+
apiId: zod_1.z.string().describe("YApi接口的ID;如连接/project/1/interface/api/66,则ID为66")
|
|
274
|
+
}, async ({ projectId, apiId }) => {
|
|
275
|
+
try {
|
|
276
|
+
this.logger.info(`获取API接口: ${apiId}, 项目ID: ${projectId}`);
|
|
277
|
+
const apiInterface = await this.yapiService.getApiInterface(projectId, apiId);
|
|
278
|
+
this.logger.info(`成功获取API接口: ${apiInterface.title || apiId}`);
|
|
279
|
+
const formattedResponse = {
|
|
280
|
+
基本信息: {
|
|
281
|
+
接口ID: apiInterface._id,
|
|
282
|
+
接口名称: apiInterface.title,
|
|
283
|
+
接口路径: apiInterface.path,
|
|
284
|
+
请求方式: apiInterface.method,
|
|
285
|
+
接口描述: apiInterface.desc
|
|
286
|
+
},
|
|
287
|
+
请求参数: {
|
|
288
|
+
URL参数: apiInterface.req_params,
|
|
289
|
+
查询参数: apiInterface.req_query,
|
|
290
|
+
请求头: apiInterface.req_headers,
|
|
291
|
+
请求体类型: apiInterface.req_body_type,
|
|
292
|
+
表单参数: apiInterface.req_body_form,
|
|
293
|
+
Json参数: apiInterface.req_body_other
|
|
294
|
+
},
|
|
295
|
+
响应信息: {
|
|
296
|
+
响应类型: apiInterface.res_body_type,
|
|
297
|
+
响应内容: apiInterface.res_body
|
|
298
|
+
},
|
|
299
|
+
其他信息: {
|
|
300
|
+
接口文档: apiInterface.markdown
|
|
301
|
+
},
|
|
302
|
+
编辑建议: {
|
|
303
|
+
优先更新字段: "req_params / req_query / req_headers / req_body_* / res_body(把枚举值、中文备注、示例尽量放在这些结构字段里)",
|
|
304
|
+
避免滥用字段: "desc 只写简要概述;长文档写 markdown;不要用 desc 替代结构化字段",
|
|
305
|
+
对应开放API工具: "需要原始字段或精确对应开放 API 时,用 yapi_interface_* / yapi_open_import_data / yapi_project_get"
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
return {
|
|
309
|
+
content: [{ type: "text", text: JSON.stringify(formattedResponse, null, 2) }],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
this.logger.error(`获取API接口 ${apiId} 时出错:`, error);
|
|
314
|
+
return {
|
|
315
|
+
content: [{ type: "text", text: `获取API接口出错: ${error}` }],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
this.server.tool("yapi_project_get", "获取 YApi 项目详情(对应 /api/project/get)", {
|
|
320
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
321
|
+
}, async ({ projectId }) => {
|
|
322
|
+
try {
|
|
323
|
+
const projectInfo = await this.yapiService.getProjectInfo(projectId);
|
|
324
|
+
return { content: [{ type: "text", text: JSON.stringify(projectInfo, null, 2) }] };
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
this.logger.error(`获取项目详情失败:`, error);
|
|
328
|
+
return { content: [{ type: "text", text: `获取项目详情失败: ${error}` }] };
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
this.server.tool("yapi_interface_get", "获取接口数据(对应 /api/interface/get,返回原始字段)", {
|
|
332
|
+
projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
|
|
333
|
+
apiId: zod_1.z.string().describe("接口ID"),
|
|
334
|
+
}, async ({ projectId, apiId }) => {
|
|
335
|
+
try {
|
|
336
|
+
const apiInterface = await this.yapiService.getApiInterface(projectId, apiId);
|
|
337
|
+
return { content: [{ type: "text", text: JSON.stringify(apiInterface, null, 2) }] };
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
this.logger.error(`获取接口数据失败:`, error);
|
|
341
|
+
return { content: [{ type: "text", text: `获取接口数据失败: ${error}` }] };
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
this.server.tool("yapi_save_api", "新增或更新YApi中的接口信息", {
|
|
345
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
346
|
+
catid: zod_1.z.string().optional().describe("接口分类ID;新增接口时必填,更新时可省略(会保留原值)"),
|
|
347
|
+
id: zod_1.z.string().optional().describe("接口ID,更新时必填,新增时不需要"),
|
|
348
|
+
title: zod_1.z.string().optional().describe("接口标题;新增接口时必填,更新时可省略(会保留原值)"),
|
|
349
|
+
path: zod_1.z.string().optional().describe("接口路径,如:/api/user;新增接口时必填,更新时可省略(会保留原值)"),
|
|
350
|
+
method: zod_1.z.string().optional().describe("请求方法,如:GET, POST, PUT, DELETE等;新增接口时必填,更新时可省略(会保留原值)"),
|
|
351
|
+
status: zod_1.z.string().optional().describe("接口状态,done代表完成,undone代表未完成;更新时可省略(不覆盖原值)"),
|
|
352
|
+
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("接口标签列表;支持 JSON 数组字符串或字符串数组;更新时可省略(不覆盖原值)"),
|
|
353
|
+
req_params: zod_1.z
|
|
354
|
+
.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)])
|
|
355
|
+
.optional()
|
|
356
|
+
.describe("路径参数;强烈建议填写(枚举值/中文备注放这里);支持 JSON 数组字符串或数组"),
|
|
357
|
+
req_query: zod_1.z
|
|
358
|
+
.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)])
|
|
359
|
+
.optional()
|
|
360
|
+
.describe("查询参数;支持 JSON 数组字符串或数组"),
|
|
361
|
+
req_headers: zod_1.z
|
|
362
|
+
.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)])
|
|
363
|
+
.optional()
|
|
364
|
+
.describe("请求头参数;支持 JSON 数组字符串或数组"),
|
|
365
|
+
req_body_type: zod_1.z.string().optional().describe("请求体类型(常见:raw/form/json/file)"),
|
|
366
|
+
req_body_form: zod_1.z
|
|
367
|
+
.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)])
|
|
368
|
+
.optional()
|
|
369
|
+
.describe("表单请求体;支持 JSON 数组字符串或数组"),
|
|
370
|
+
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("其他请求体(通常是 JSON / JSON Schema);支持字符串或对象/数组"),
|
|
371
|
+
req_body_is_json_schema: zod_1.z.boolean().optional().describe("是否开启JSON Schema,默认false"),
|
|
372
|
+
res_body_type: zod_1.z.string().optional().describe("返回数据类型(常见:json/raw)"),
|
|
373
|
+
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("返回数据;强烈建议填写(枚举值/中文备注放这里);支持字符串或对象/数组(会自动序列化)"),
|
|
374
|
+
res_body_is_json_schema: zod_1.z.boolean().optional().describe("返回数据是否为JSON Schema,默认false"),
|
|
375
|
+
switch_notice: zod_1.z.boolean().optional().describe("开启接口运行通知,默认true"),
|
|
376
|
+
api_opened: zod_1.z.boolean().optional().describe("开启API文档页面,默认true"),
|
|
377
|
+
message: zod_1.z.string().optional().describe("接口备注信息(对应 openapi 示例中的 message 字段)"),
|
|
378
|
+
desc: zod_1.z.string().optional().describe("接口简要描述(不要把请求/响应结构都塞进 desc;结构请写到 req_* / res_body)"),
|
|
379
|
+
markdown: zod_1.z.string().optional().describe("markdown格式的接口描述(补充说明/示例;结构仍建议写到 req_* / res_body)")
|
|
380
|
+
}, async ({ projectId, catid, id, title, path, method, status, tag, req_params, req_query, req_headers, req_body_type, req_body_form, req_body_other, req_body_is_json_schema, res_body_type, res_body, res_body_is_json_schema, switch_notice, api_opened, message, desc, markdown }) => {
|
|
381
|
+
try {
|
|
382
|
+
const built = await buildInterfaceSaveParams({
|
|
383
|
+
projectId,
|
|
384
|
+
catid,
|
|
385
|
+
id,
|
|
386
|
+
title,
|
|
387
|
+
path,
|
|
388
|
+
method,
|
|
389
|
+
status,
|
|
390
|
+
tag,
|
|
391
|
+
req_params,
|
|
392
|
+
req_query,
|
|
393
|
+
req_headers,
|
|
394
|
+
req_body_type,
|
|
395
|
+
req_body_form,
|
|
396
|
+
req_body_other,
|
|
397
|
+
req_body_is_json_schema,
|
|
398
|
+
res_body_type,
|
|
399
|
+
res_body,
|
|
400
|
+
res_body_is_json_schema,
|
|
401
|
+
switch_notice,
|
|
402
|
+
api_opened,
|
|
403
|
+
desc,
|
|
404
|
+
markdown,
|
|
405
|
+
message,
|
|
406
|
+
});
|
|
407
|
+
if (!built.ok) {
|
|
408
|
+
return { content: [{ type: "text", text: built.error }] };
|
|
409
|
+
}
|
|
410
|
+
const response = await this.yapiService.saveInterface(built.params);
|
|
411
|
+
const resultApiId = response.data._id;
|
|
412
|
+
return {
|
|
413
|
+
content: [{
|
|
414
|
+
type: "text",
|
|
415
|
+
text: `接口${id ? '更新' : '新增'}成功!\n接口ID: ${resultApiId}\n接口名称: ${built.params.title}\n请求方法: ${built.params.method}\n接口路径: ${built.params.path}`
|
|
416
|
+
}],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
this.logger.error(`保存API接口时出错:`, error);
|
|
421
|
+
return {
|
|
422
|
+
content: [{ type: "text", text: `保存API接口出错: ${error}` }],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
this.server.tool("yapi_interface_add_cat", "新增接口分类(对应 /api/interface/add_cat)", {
|
|
427
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
428
|
+
name: zod_1.z.string().describe("分类名称"),
|
|
429
|
+
desc: zod_1.z.string().optional().describe("分类描述"),
|
|
430
|
+
}, async ({ projectId, name, desc }) => {
|
|
431
|
+
try {
|
|
432
|
+
const data = await this.yapiService.addCategory(projectId, name, desc || "");
|
|
433
|
+
return { content: [{ type: "text", text: `新增分类成功:\n${JSON.stringify(data, null, 2)}` }] };
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
this.logger.error(`新增接口分类失败:`, error);
|
|
437
|
+
return { content: [{ type: "text", text: `新增接口分类失败: ${error}` }] };
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
this.server.tool("yapi_interface_get_cat_menu", "获取菜单列表(对应 /api/interface/getCatMenu)", {
|
|
441
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
442
|
+
}, async ({ projectId }) => {
|
|
443
|
+
try {
|
|
444
|
+
const list = await this.yapiService.getCategoryList(projectId);
|
|
445
|
+
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
this.logger.error(`获取菜单列表失败:`, error);
|
|
449
|
+
return { content: [{ type: "text", text: `获取菜单列表失败: ${error}` }] };
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
this.server.tool("yapi_interface_list", "获取接口列表数据(对应 /api/interface/list)", {
|
|
453
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
454
|
+
page: zod_1.z.number().optional().describe("页码,默认 1"),
|
|
455
|
+
limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
|
|
456
|
+
}, async ({ projectId, page, limit }) => {
|
|
457
|
+
try {
|
|
458
|
+
const data = await this.yapiService.listInterfaces(projectId, page ?? 1, limit ?? 10);
|
|
459
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
this.logger.error(`获取接口列表失败:`, error);
|
|
463
|
+
return { content: [{ type: "text", text: `获取接口列表失败: ${error}` }] };
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
this.server.tool("yapi_interface_list_cat", "获取某个分类下接口列表(对应 /api/interface/list_cat)", {
|
|
467
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
468
|
+
catId: zod_1.z.string().describe("分类ID"),
|
|
469
|
+
page: zod_1.z.number().optional().describe("页码,默认 1"),
|
|
470
|
+
limit: zod_1.z.number().optional().describe("每页数量,默认 10"),
|
|
471
|
+
}, async ({ projectId, catId, page, limit }) => {
|
|
472
|
+
try {
|
|
473
|
+
const response = await this.yapiService.listCategoryInterfaces(projectId, catId, page ?? 1, limit ?? 10);
|
|
474
|
+
return { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] };
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
this.logger.error(`获取分类接口列表失败:`, error);
|
|
478
|
+
return { content: [{ type: "text", text: `获取分类接口列表失败: ${error}` }] };
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
this.server.tool("yapi_interface_list_menu", "获取接口菜单列表(对应 /api/interface/list_menu)", {
|
|
482
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
483
|
+
}, async ({ projectId }) => {
|
|
484
|
+
try {
|
|
485
|
+
const data = await this.yapiService.getInterfaceMenu(projectId);
|
|
486
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
this.logger.error(`获取接口菜单列表失败:`, error);
|
|
490
|
+
return { content: [{ type: "text", text: `获取接口菜单列表失败: ${error}` }] };
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
this.server.tool("yapi_interface_add", "新增接口(对应 /api/interface/add)", {
|
|
494
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
495
|
+
catid: zod_1.z.string().describe("接口分类ID"),
|
|
496
|
+
title: zod_1.z.string().describe("接口标题"),
|
|
497
|
+
path: zod_1.z.string().describe("接口路径"),
|
|
498
|
+
method: zod_1.z.string().describe("请求方法"),
|
|
499
|
+
status: zod_1.z.string().optional().describe("接口状态,默认 undone"),
|
|
500
|
+
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
501
|
+
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
502
|
+
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
503
|
+
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
504
|
+
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
505
|
+
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
506
|
+
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("其他请求体"),
|
|
507
|
+
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
508
|
+
res_body_type: zod_1.z.string().optional().describe("响应类型,默认 json"),
|
|
509
|
+
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("响应内容"),
|
|
510
|
+
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
511
|
+
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
512
|
+
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
513
|
+
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
514
|
+
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
515
|
+
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
516
|
+
}, async (input) => {
|
|
517
|
+
try {
|
|
518
|
+
const built = await buildInterfaceSaveParams({ ...input, id: undefined });
|
|
519
|
+
if (!built.ok)
|
|
520
|
+
return { content: [{ type: "text", text: built.error }] };
|
|
521
|
+
const response = await this.yapiService.addInterface(built.params);
|
|
522
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
this.logger.error(`新增接口失败:`, error);
|
|
526
|
+
return { content: [{ type: "text", text: `新增接口失败: ${error}` }] };
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
this.server.tool("yapi_interface_up", "更新接口(对应 /api/interface/up)", {
|
|
530
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
531
|
+
id: zod_1.z.string().describe("接口ID"),
|
|
532
|
+
catid: zod_1.z.string().optional().describe("分类ID(可省略,会保留原值)"),
|
|
533
|
+
title: zod_1.z.string().optional().describe("接口标题(可省略,会保留原值)"),
|
|
534
|
+
path: zod_1.z.string().optional().describe("接口路径(可省略,会保留原值)"),
|
|
535
|
+
method: zod_1.z.string().optional().describe("请求方法(可省略,会保留原值)"),
|
|
536
|
+
status: zod_1.z.string().optional().describe("接口状态"),
|
|
537
|
+
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
538
|
+
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
539
|
+
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
540
|
+
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
541
|
+
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
542
|
+
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
543
|
+
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("其他请求体"),
|
|
544
|
+
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
545
|
+
res_body_type: zod_1.z.string().optional().describe("响应类型"),
|
|
546
|
+
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("响应内容"),
|
|
547
|
+
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
548
|
+
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
549
|
+
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
550
|
+
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
551
|
+
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
552
|
+
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
553
|
+
}, async (input) => {
|
|
554
|
+
try {
|
|
555
|
+
const built = await buildInterfaceSaveParams(input);
|
|
556
|
+
if (!built.ok)
|
|
557
|
+
return { content: [{ type: "text", text: built.error }] };
|
|
558
|
+
const response = await this.yapiService.updateInterface(built.params);
|
|
559
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
this.logger.error(`更新接口失败:`, error);
|
|
563
|
+
return { content: [{ type: "text", text: `更新接口失败: ${error}` }] };
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
this.server.tool("yapi_interface_save", "新增或者更新接口(对应 /api/interface/save)", {
|
|
567
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
568
|
+
id: zod_1.z.string().optional().describe("接口ID(有则更新,无则新增)"),
|
|
569
|
+
catid: zod_1.z.string().optional().describe("接口分类ID"),
|
|
570
|
+
title: zod_1.z.string().optional().describe("接口标题"),
|
|
571
|
+
path: zod_1.z.string().optional().describe("接口路径"),
|
|
572
|
+
method: zod_1.z.string().optional().describe("请求方法"),
|
|
573
|
+
status: zod_1.z.string().optional().describe("接口状态"),
|
|
574
|
+
tag: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional().describe("标签"),
|
|
575
|
+
req_params: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("路径参数"),
|
|
576
|
+
req_query: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("查询参数"),
|
|
577
|
+
req_headers: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiHeaderItemSchema)]).optional().describe("请求头"),
|
|
578
|
+
req_body_type: zod_1.z.string().optional().describe("请求体类型"),
|
|
579
|
+
req_body_form: zod_1.z.union([zod_1.z.string(), zod_1.z.array(yapiParamItemSchema)]).optional().describe("表单请求体"),
|
|
580
|
+
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("其他请求体"),
|
|
581
|
+
req_body_is_json_schema: zod_1.z.boolean().optional().describe("请求体是否 JSON Schema"),
|
|
582
|
+
res_body_type: zod_1.z.string().optional().describe("响应类型"),
|
|
583
|
+
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("响应内容"),
|
|
584
|
+
res_body_is_json_schema: zod_1.z.boolean().optional().describe("响应是否 JSON Schema"),
|
|
585
|
+
switch_notice: zod_1.z.boolean().optional().describe("是否通知"),
|
|
586
|
+
api_opened: zod_1.z.boolean().optional().describe("是否公开"),
|
|
587
|
+
message: zod_1.z.string().optional().describe("接口备注信息"),
|
|
588
|
+
desc: zod_1.z.string().optional().describe("接口描述"),
|
|
589
|
+
markdown: zod_1.z.string().optional().describe("markdown 描述"),
|
|
590
|
+
}, async (input) => {
|
|
591
|
+
try {
|
|
592
|
+
const built = await buildInterfaceSaveParams(input);
|
|
593
|
+
if (!built.ok)
|
|
594
|
+
return { content: [{ type: "text", text: built.error }] };
|
|
595
|
+
const response = await this.yapiService.saveInterfaceUnified(built.params);
|
|
596
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
this.logger.error(`保存接口失败:`, error);
|
|
600
|
+
return { content: [{ type: "text", text: `保存接口失败: ${error}` }] };
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
this.server.tool("yapi_open_import_data", "服务端数据导入(对应 /api/open/import_data)", {
|
|
604
|
+
projectId: zod_1.z.string().describe("YApi项目ID(用于选择 token)"),
|
|
605
|
+
type: zod_1.z.string().describe("导入方式,如 swagger"),
|
|
606
|
+
merge: zod_1.z.enum(["normal", "good", "merge"]).describe("同步模式:normal/good/merge"),
|
|
607
|
+
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(会自动序列化为字符串)"),
|
|
608
|
+
url: zod_1.z.string().optional().describe("导入数据 URL(提供后会走 url 方式)"),
|
|
609
|
+
}, async ({ projectId, type, merge, json, url }) => {
|
|
610
|
+
try {
|
|
611
|
+
let jsonString;
|
|
612
|
+
if (json !== undefined) {
|
|
613
|
+
const normalized = normalizeStringOrJson(json, "json");
|
|
614
|
+
if (!normalized.ok)
|
|
615
|
+
return { content: [{ type: "text", text: normalized.error }] };
|
|
616
|
+
jsonString = normalized.value;
|
|
617
|
+
}
|
|
618
|
+
const data = await this.yapiService.importData(projectId, { type, merge, json: jsonString, url });
|
|
619
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
this.logger.error(`导入数据失败:`, error);
|
|
623
|
+
return { content: [{ type: "text", text: `导入数据失败: ${error}` }] };
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
this.server.tool("yapi_search_apis", "搜索YApi中的接口", {
|
|
627
|
+
projectKeyword: zod_1.z.string().optional().describe("项目关键字,用于过滤项目"),
|
|
628
|
+
nameKeyword: zod_1.z.string().optional().describe("接口名称关键字"),
|
|
629
|
+
pathKeyword: zod_1.z.string().optional().describe("接口路径关键字"),
|
|
630
|
+
tagKeyword: zod_1.z.string().optional().describe("接口标签关键字"),
|
|
631
|
+
limit: zod_1.z.number().optional().describe("返回结果数量限制,默认20")
|
|
632
|
+
}, async ({ projectKeyword, nameKeyword, pathKeyword, tagKeyword, limit }) => {
|
|
633
|
+
try {
|
|
634
|
+
const searchOptions = {
|
|
635
|
+
projectKeyword,
|
|
636
|
+
nameKeyword: nameKeyword ? nameKeyword.split(/[\s,]+/) : undefined,
|
|
637
|
+
pathKeyword: pathKeyword ? pathKeyword.split(/[\s,]+/) : undefined,
|
|
638
|
+
tagKeyword: tagKeyword ? tagKeyword.split(/[\s,]+/) : undefined,
|
|
639
|
+
limit: limit || 20
|
|
640
|
+
};
|
|
641
|
+
this.logger.info(`搜索API接口: ${JSON.stringify(searchOptions)}`);
|
|
642
|
+
const searchResults = await this.yapiService.searchApis(searchOptions);
|
|
643
|
+
const apisByProject = {};
|
|
644
|
+
searchResults.list.forEach(api => {
|
|
645
|
+
const projectId = String(api.project_id);
|
|
646
|
+
const projectName = api.project_name || `未知项目(${projectId})`;
|
|
647
|
+
if (!apisByProject[projectId]) {
|
|
648
|
+
apisByProject[projectId] = {
|
|
649
|
+
projectName,
|
|
650
|
+
apis: []
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
apisByProject[projectId].apis.push({
|
|
654
|
+
id: api._id,
|
|
655
|
+
title: api.title,
|
|
656
|
+
path: api.path,
|
|
657
|
+
method: api.method,
|
|
658
|
+
catName: api.cat_name || '未知分类',
|
|
659
|
+
createTime: new Date(api.add_time).toLocaleString(),
|
|
660
|
+
updateTime: new Date(api.up_time).toLocaleString()
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
let responseContent = `共找到 ${searchResults.total} 个符合条件的接口(已限制显示 ${searchResults.list.length} 个)\n\n`;
|
|
664
|
+
responseContent += "搜索条件:\n";
|
|
665
|
+
if (projectKeyword)
|
|
666
|
+
responseContent += `- 项目关键字: ${projectKeyword}\n`;
|
|
667
|
+
if (nameKeyword)
|
|
668
|
+
responseContent += `- 接口名称关键字: ${nameKeyword}\n`;
|
|
669
|
+
if (pathKeyword)
|
|
670
|
+
responseContent += `- API路径关键字: ${pathKeyword}\n`;
|
|
671
|
+
if (tagKeyword)
|
|
672
|
+
responseContent += `- 标签关键字: ${tagKeyword}\n\n`;
|
|
673
|
+
Object.values(apisByProject).forEach(projectGroup => {
|
|
674
|
+
responseContent += `## 项目: ${projectGroup.projectName} (${projectGroup.apis.length}个接口)\n\n`;
|
|
675
|
+
if (projectGroup.apis.length <= 10) {
|
|
676
|
+
projectGroup.apis.forEach(api => {
|
|
677
|
+
responseContent += `### ${api.title} (${api.method} ${api.path})\n\n`;
|
|
678
|
+
responseContent += `- 接口ID: ${api.id}\n`;
|
|
679
|
+
responseContent += `- 所属分类: ${api.catName}\n`;
|
|
680
|
+
responseContent += `- 更新时间: ${api.updateTime}\n\n`;
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
responseContent += "| 接口ID | 接口名称 | 请求方式 | 接口路径 | 所属分类 |\n";
|
|
685
|
+
responseContent += "| ------ | -------- | -------- | -------- | -------- |\n";
|
|
686
|
+
projectGroup.apis.forEach(api => {
|
|
687
|
+
responseContent += `| ${api.id} | ${api.title} | ${api.method} | ${api.path} | ${api.catName} |\n`;
|
|
688
|
+
});
|
|
689
|
+
responseContent += "\n";
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
responseContent += "\n提示: 可以使用 `get_api_desc` 工具获取接口的详细信息,例如: `get_api_desc projectId=228 apiId=8570`";
|
|
693
|
+
return {
|
|
694
|
+
content: [{ type: "text", text: responseContent }],
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
this.logger.error(`搜索接口时出错:`, error);
|
|
699
|
+
let errorMsg = "搜索接口时发生错误";
|
|
700
|
+
if (error instanceof Error) {
|
|
701
|
+
errorMsg += `: ${error.message}`;
|
|
702
|
+
}
|
|
703
|
+
else if (typeof error === 'object' && error !== null) {
|
|
704
|
+
errorMsg += `: ${JSON.stringify(error)}`;
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
content: [{ type: "text", text: errorMsg }],
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
this.server.tool("yapi_list_projects", "列出YApi的项目ID(projectId)和项目名称", {}, async () => {
|
|
712
|
+
try {
|
|
713
|
+
const projectInfoCache = this.yapiService.getProjectInfoCache();
|
|
714
|
+
if (projectInfoCache.size === 0) {
|
|
715
|
+
return {
|
|
716
|
+
content: [{ type: "text", text: "没有找到任何项目信息,请检查配置的token是否正确" }],
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
const projectsList = Array.from(projectInfoCache.entries()).map(([id, info]) => ({
|
|
720
|
+
项目ID: id,
|
|
721
|
+
项目名称: info.name,
|
|
722
|
+
项目描述: info.desc || '无描述',
|
|
723
|
+
基础路径: info.basepath || '/',
|
|
724
|
+
项目分组ID: info.group_id
|
|
725
|
+
}));
|
|
726
|
+
return {
|
|
727
|
+
content: [{
|
|
728
|
+
type: "text",
|
|
729
|
+
text: `已配置 ${projectInfoCache.size} 个YApi项目:\n\n${JSON.stringify(projectsList, null, 2)}`
|
|
730
|
+
}],
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
this.logger.error(`获取项目信息列表时出错:`, error);
|
|
735
|
+
return {
|
|
736
|
+
content: [{ type: "text", text: `获取项目信息列表出错: ${error}` }],
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
this.server.tool("yapi_get_categories", "获取YApi项目下的接口分类列表,以及每个分类下的接口信息", {
|
|
741
|
+
projectId: zod_1.z.string().describe("YApi项目ID"),
|
|
742
|
+
includeApis: zod_1.z.boolean().optional().describe("是否包含分类下接口列表,默认 true"),
|
|
743
|
+
limitPerCategory: zod_1.z.number().optional().describe("每个分类最多返回多少接口(默认 100)")
|
|
744
|
+
}, async ({ projectId, includeApis, limitPerCategory }) => {
|
|
745
|
+
try {
|
|
746
|
+
const shouldIncludeApis = includeApis ?? true;
|
|
747
|
+
const perCatLimit = limitPerCategory ?? 100;
|
|
748
|
+
const projectInfo = await this.yapiService.getProjectInfo(projectId);
|
|
749
|
+
const categoryList = await this.yapiService.getCategoryList(projectId);
|
|
750
|
+
if (!categoryList || categoryList.length === 0) {
|
|
751
|
+
return {
|
|
752
|
+
content: [{ type: "text", text: `项目 "${projectInfo.name}" (ID: ${projectId}) 下没有找到任何接口分类` }],
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
if (!shouldIncludeApis) {
|
|
756
|
+
const simplified = categoryList.map(cat => ({
|
|
757
|
+
分类ID: cat._id,
|
|
758
|
+
分类名称: cat.name,
|
|
759
|
+
分类描述: cat.desc || "无描述",
|
|
760
|
+
创建时间: new Date(cat.add_time).toLocaleString(),
|
|
761
|
+
更新时间: new Date(cat.up_time).toLocaleString(),
|
|
762
|
+
}));
|
|
763
|
+
return {
|
|
764
|
+
content: [
|
|
765
|
+
{
|
|
766
|
+
type: "text",
|
|
767
|
+
text: `项目 "${projectInfo.name}" (ID: ${projectId}) 下共有 ${categoryList.length} 个接口分类:\n\n${JSON.stringify(simplified, null, 2)}`,
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const categoriesWithApisPromises = categoryList.map(async (cat) => {
|
|
773
|
+
try {
|
|
774
|
+
const apisResponse = await this.yapiService.listCategoryInterfaces(projectId, cat._id, 1, perCatLimit);
|
|
775
|
+
const apis = apisResponse.data.list;
|
|
776
|
+
const simplifiedApis = apis?.map(api => ({
|
|
777
|
+
接口ID: api._id,
|
|
778
|
+
接口名称: api.title,
|
|
779
|
+
接口路径: api.path,
|
|
780
|
+
请求方法: api.method
|
|
781
|
+
})) || [];
|
|
782
|
+
return {
|
|
783
|
+
分类ID: cat._id,
|
|
784
|
+
分类名称: cat.name,
|
|
785
|
+
分类描述: cat.desc || '无描述',
|
|
786
|
+
创建时间: new Date(cat.add_time).toLocaleString(),
|
|
787
|
+
更新时间: new Date(cat.up_time).toLocaleString(),
|
|
788
|
+
接口列表: simplifiedApis
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
this.logger.error(`获取分类 ${cat._id} 下的接口列表失败:`, error);
|
|
793
|
+
return {
|
|
794
|
+
分类ID: cat._id,
|
|
795
|
+
分类名称: cat.name,
|
|
796
|
+
分类描述: cat.desc || '无描述',
|
|
797
|
+
创建时间: new Date(cat.add_time).toLocaleString(),
|
|
798
|
+
更新时间: new Date(cat.up_time).toLocaleString(),
|
|
799
|
+
接口列表: [],
|
|
800
|
+
错误: `获取接口列表失败: ${error}`
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
const categoriesWithApis = await Promise.all(categoriesWithApisPromises);
|
|
805
|
+
return {
|
|
806
|
+
content: [{
|
|
807
|
+
type: "text",
|
|
808
|
+
text: `项目 "${projectInfo.name}" (ID: ${projectId}) 下共有 ${categoryList.length} 个接口分类:\n\n${JSON.stringify(categoriesWithApis, null, 2)}`
|
|
809
|
+
}],
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
this.logger.error(`获取接口分类列表时出错:`, error);
|
|
814
|
+
return {
|
|
815
|
+
content: [{ type: "text", text: `获取接口分类列表出错: ${error}` }],
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
async connect(transport) {
|
|
821
|
+
this.logger.info("连接到传输层...");
|
|
822
|
+
await this.server.connect(transport);
|
|
823
|
+
this.logger.info("服务器已连接,准备处理请求");
|
|
824
|
+
}
|
|
825
|
+
async startHttpServer(port) {
|
|
826
|
+
const app = (0, express_1.default)();
|
|
827
|
+
app.get("/sse", async (req, res) => {
|
|
828
|
+
this.logger.info("建立新的SSE连接");
|
|
829
|
+
this.sseTransport = new sse_js_1.SSEServerTransport("/messages", res);
|
|
830
|
+
await this.server.connect(this.sseTransport);
|
|
831
|
+
});
|
|
832
|
+
app.post("/messages", async (req, res) => {
|
|
833
|
+
if (!this.sseTransport) {
|
|
834
|
+
res.sendStatus(400);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
await this.sseTransport.handlePostMessage(req, res);
|
|
838
|
+
});
|
|
839
|
+
app.listen(port, () => {
|
|
840
|
+
this.logger.info(`HTTP服务器监听端口 ${port}`);
|
|
841
|
+
this.logger.info(`SSE端点: http://localhost:${port}/sse`);
|
|
842
|
+
this.logger.info(`消息端点: http://localhost:${port}/messages`);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
exports.YapiMcpServer = YapiMcpServer;
|
|
847
|
+
//# sourceMappingURL=server.js.map
|