api2mcp 0.3.2 → 0.4.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/index.js CHANGED
@@ -31,14 +31,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  Api2McpError: () => Api2McpError,
34
+ ApiRegistry: () => ApiRegistry,
34
35
  ConfigurationError: () => ConfigurationError,
35
36
  HttpError: () => HttpError,
36
37
  OpenApiParseError: () => OpenApiParseError,
37
38
  ToolExecutionError: () => ToolExecutionError,
38
39
  ToolManager: () => ToolManager,
40
+ apiDetailSchema: () => apiDetailSchema,
41
+ apiDetailTool: () => apiDetailTool,
42
+ apiExecuteSchema: () => apiExecuteSchema,
43
+ apiExecuteTool: () => apiExecuteTool,
44
+ apiListSchema: () => apiListSchema,
45
+ apiListTool: () => apiListTool,
46
+ apiSearchSchema: () => apiSearchSchema,
47
+ apiSearchTool: () => apiSearchTool,
39
48
  convertSchema: () => convertSchema,
40
49
  createRefResolver: () => createRefResolver,
41
50
  createServer: () => createServer,
51
+ executeApiDetail: () => executeApiDetail,
52
+ executeApiExecute: () => executeApiExecute,
53
+ executeApiList: () => executeApiList,
54
+ executeApiSearch: () => executeApiSearch,
42
55
  executeRequest: () => executeRequest,
43
56
  formatResponse: () => formatResponse,
44
57
  generateTool: () => generateTool,
@@ -186,7 +199,8 @@ function loadFromFile(workingDir = process.cwd()) {
186
199
  timeout: parsed.timeout,
187
200
  headers: parsed.headers,
188
201
  toolPrefix: parsed.toolPrefix,
189
- debug: parsed.debug
202
+ debug: parsed.debug,
203
+ mode: parsed.mode
190
204
  };
191
205
  } catch (error) {
192
206
  throw new ConfigurationError(
@@ -217,6 +231,9 @@ function loadFromCli(args) {
217
231
  if (args.debug !== void 0) {
218
232
  config.debug = args.debug;
219
233
  }
234
+ if (args.mode) {
235
+ config.mode = args.mode;
236
+ }
220
237
  return config;
221
238
  }
222
239
  function mergeConfigs(...configs) {
@@ -231,6 +248,7 @@ function mergeConfigs(...configs) {
231
248
  if (config.headers !== void 0) merged.headers = config.headers;
232
249
  if (config.toolPrefix !== void 0) merged.toolPrefix = config.toolPrefix;
233
250
  if (config.debug !== void 0) merged.debug = config.debug;
251
+ if (config.mode !== void 0) merged.mode = config.mode;
234
252
  }
235
253
  if (merged.timeout === void 0) {
236
254
  merged.timeout = DEFAULT_TIMEOUT;
@@ -255,6 +273,7 @@ function loadConfig(cliArgs = {}, env = process.env) {
255
273
  baseUrl: config.baseUrl,
256
274
  timeout: config.timeout,
257
275
  toolPrefix: config.toolPrefix,
276
+ mode: config.mode,
258
277
  debug: config.debug
259
278
  });
260
279
  return config;
@@ -903,10 +922,577 @@ function getBaseUrl(doc, overrideUrl) {
903
922
  return void 0;
904
923
  }
905
924
 
925
+ // src/registry/api-registry.ts
926
+ var DEFAULT_PAGE_SIZE = 20;
927
+ var DEFAULT_SEARCH_LIMIT = 50;
928
+ var ApiRegistry = class {
929
+ apis = /* @__PURE__ */ new Map();
930
+ nameIndex = /* @__PURE__ */ new Map();
931
+ // name -> id
932
+ tagIndex = /* @__PURE__ */ new Map();
933
+ // tag -> set of ids
934
+ /**
935
+ * 注册 API
936
+ */
937
+ register(entry) {
938
+ if (this.apis.has(entry.id)) {
939
+ logger.warn(`API already registered: ${entry.id}, overwriting`);
940
+ }
941
+ this.apis.set(entry.id, entry);
942
+ this.nameIndex.set(entry.name, entry.id);
943
+ if (entry.tags) {
944
+ for (const tag of entry.tags) {
945
+ if (!this.tagIndex.has(tag)) {
946
+ this.tagIndex.set(tag, /* @__PURE__ */ new Set());
947
+ }
948
+ this.tagIndex.get(tag)?.add(entry.id);
949
+ }
950
+ }
951
+ logger.debug(`Registered API: ${entry.id}`);
952
+ }
953
+ /**
954
+ * 批量注册 API
955
+ */
956
+ registerAll(entries) {
957
+ for (const entry of entries) {
958
+ this.register(entry);
959
+ }
960
+ logger.info(`Registered ${entries.length} APIs in registry`);
961
+ }
962
+ /**
963
+ * 获取单个 API
964
+ */
965
+ get(id) {
966
+ return this.apis.get(id);
967
+ }
968
+ /**
969
+ * 通过名称获取 API
970
+ */
971
+ getByName(name) {
972
+ const id = this.nameIndex.get(name);
973
+ return id ? this.apis.get(id) : void 0;
974
+ }
975
+ /**
976
+ * 检查 API 是否存在
977
+ */
978
+ has(id) {
979
+ return this.apis.has(id);
980
+ }
981
+ /**
982
+ * 搜索 API
983
+ */
984
+ search(options) {
985
+ const {
986
+ query,
987
+ searchIn = ["name", "summary", "description", "path"],
988
+ limit = DEFAULT_SEARCH_LIMIT
989
+ } = options;
990
+ const normalizedQuery = query.toLowerCase().trim();
991
+ const results = [];
992
+ for (const entry of this.apis.values()) {
993
+ const matchedFields = [];
994
+ let score = 0;
995
+ if (searchIn.includes("name") && entry.name) {
996
+ const nameLower = entry.name.toLowerCase();
997
+ if (nameLower.includes(normalizedQuery)) {
998
+ matchedFields.push("name");
999
+ score += nameLower === normalizedQuery ? 1 : 0.8;
1000
+ }
1001
+ }
1002
+ if (searchIn.includes("summary") && entry.summary) {
1003
+ const summaryLower = entry.summary.toLowerCase();
1004
+ if (summaryLower.includes(normalizedQuery)) {
1005
+ matchedFields.push("summary");
1006
+ score += 0.6;
1007
+ }
1008
+ }
1009
+ if (searchIn.includes("description") && entry.description) {
1010
+ const descLower = entry.description.toLowerCase();
1011
+ if (descLower.includes(normalizedQuery)) {
1012
+ matchedFields.push("description");
1013
+ score += 0.4;
1014
+ }
1015
+ }
1016
+ if (searchIn.includes("path") && entry.path) {
1017
+ const pathLower = entry.path.toLowerCase();
1018
+ if (pathLower.includes(normalizedQuery)) {
1019
+ matchedFields.push("path");
1020
+ score += 0.5;
1021
+ }
1022
+ }
1023
+ if (matchedFields.length > 0) {
1024
+ results.push({
1025
+ id: entry.id,
1026
+ name: entry.name,
1027
+ method: entry.method,
1028
+ path: entry.path,
1029
+ summary: entry.summary,
1030
+ matchedFields,
1031
+ score: Math.min(score, 1)
1032
+ });
1033
+ }
1034
+ }
1035
+ results.sort((a, b) => b.score - a.score);
1036
+ return results.slice(0, limit);
1037
+ }
1038
+ /**
1039
+ * 分页列出 API
1040
+ */
1041
+ list(options = {}) {
1042
+ const { page = 1, pageSize = DEFAULT_PAGE_SIZE, tag } = options;
1043
+ let entries;
1044
+ if (tag) {
1045
+ const ids = this.tagIndex.get(tag);
1046
+ entries = ids ? Array.from(ids).map((id) => this.apis.get(id)).filter((entry) => entry !== void 0) : [];
1047
+ } else {
1048
+ entries = Array.from(this.apis.values());
1049
+ }
1050
+ const total = entries.length;
1051
+ const totalPages = Math.ceil(total / pageSize);
1052
+ const startIndex = (page - 1) * pageSize;
1053
+ const endIndex = startIndex + pageSize;
1054
+ const pageEntries = entries.slice(startIndex, endIndex);
1055
+ const items = pageEntries.map((entry) => ({
1056
+ id: entry.id,
1057
+ name: entry.name,
1058
+ method: entry.method,
1059
+ path: entry.path,
1060
+ summary: entry.summary,
1061
+ tags: entry.tags,
1062
+ deprecated: entry.deprecated
1063
+ }));
1064
+ return {
1065
+ page,
1066
+ pageSize,
1067
+ total,
1068
+ totalPages,
1069
+ items
1070
+ };
1071
+ }
1072
+ /**
1073
+ * 获取所有标签
1074
+ */
1075
+ getTags() {
1076
+ return Array.from(this.tagIndex.keys()).sort();
1077
+ }
1078
+ /**
1079
+ * 获取 API 详情
1080
+ */
1081
+ getDetail(id) {
1082
+ const entry = this.apis.get(id);
1083
+ if (!entry) {
1084
+ return void 0;
1085
+ }
1086
+ return {
1087
+ ...entry,
1088
+ parameterSchema: this.buildParameterSchema(entry),
1089
+ requestBodySchema: this.buildRequestBodySchema(entry),
1090
+ responseSchemas: this.buildResponseSchemas(entry)
1091
+ };
1092
+ }
1093
+ /**
1094
+ * 获取统计信息
1095
+ */
1096
+ getStats() {
1097
+ const byMethod = {};
1098
+ const byTag = {};
1099
+ for (const entry of this.apis.values()) {
1100
+ const method = entry.method.toUpperCase();
1101
+ byMethod[method] = (byMethod[method] || 0) + 1;
1102
+ if (entry.tags) {
1103
+ for (const tag of entry.tags) {
1104
+ byTag[tag] = (byTag[tag] || 0) + 1;
1105
+ }
1106
+ }
1107
+ }
1108
+ return {
1109
+ totalApis: this.apis.size,
1110
+ tags: this.getTags(),
1111
+ byMethod,
1112
+ byTag
1113
+ };
1114
+ }
1115
+ /**
1116
+ * 获取 API 数量
1117
+ */
1118
+ get size() {
1119
+ return this.apis.size;
1120
+ }
1121
+ /**
1122
+ * 构建参数 Schema
1123
+ */
1124
+ buildParameterSchema(entry) {
1125
+ const { operation } = entry;
1126
+ if (!operation.parameters || operation.parameters.length === 0) {
1127
+ return void 0;
1128
+ }
1129
+ const properties = {};
1130
+ const required = [];
1131
+ for (const param of operation.parameters) {
1132
+ const paramName = param.name;
1133
+ properties[paramName] = {
1134
+ ...param.schema,
1135
+ description: param.description,
1136
+ in: param.in
1137
+ };
1138
+ if (param.required) {
1139
+ required.push(paramName);
1140
+ }
1141
+ }
1142
+ return {
1143
+ type: "object",
1144
+ properties,
1145
+ required: required.length > 0 ? required : void 0
1146
+ };
1147
+ }
1148
+ /**
1149
+ * 构建请求体 Schema
1150
+ */
1151
+ buildRequestBodySchema(entry) {
1152
+ const { operation } = entry;
1153
+ if (!operation.requestBody) {
1154
+ return void 0;
1155
+ }
1156
+ const jsonContent = operation.requestBody.content["application/json"];
1157
+ if (!jsonContent?.schema) {
1158
+ return void 0;
1159
+ }
1160
+ return {
1161
+ ...jsonContent.schema,
1162
+ description: operation.requestBody.description,
1163
+ bodyRequired: operation.requestBody.required
1164
+ };
1165
+ }
1166
+ /**
1167
+ * 构建响应 Schema
1168
+ */
1169
+ buildResponseSchemas(entry) {
1170
+ const { operation } = entry;
1171
+ if (!operation.responses) {
1172
+ return void 0;
1173
+ }
1174
+ const schemas = {};
1175
+ for (const [status, response] of Object.entries(operation.responses)) {
1176
+ const jsonContent = response.content?.["application/json"];
1177
+ schemas[status] = {
1178
+ description: response.description,
1179
+ schema: jsonContent?.schema
1180
+ };
1181
+ }
1182
+ return schemas;
1183
+ }
1184
+ };
1185
+
906
1186
  // src/server/index.ts
907
1187
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
908
1188
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
909
1189
 
1190
+ // src/tools/discovery/api-detail.ts
1191
+ var import_zod3 = require("zod");
1192
+ var apiDetailSchema = import_zod3.z.object({
1193
+ id: import_zod3.z.string().min(1).describe("API ID\uFF08operationId \u6216\u5DE5\u5177\u540D\u79F0\uFF09")
1194
+ });
1195
+ var apiDetailTool = {
1196
+ name: "api_detail",
1197
+ description: `\u83B7\u53D6 API \u7684\u8BE6\u7EC6\u4FE1\u606F\u3002
1198
+
1199
+ \u4F7F\u7528\u573A\u666F\uFF1A
1200
+ - \u67E5\u770B\u67D0\u4E2A API \u7684\u5B8C\u6574\u53C2\u6570\u5B9A\u4E49
1201
+ - \u4E86\u89E3\u8BF7\u6C42\u4F53\u548C\u54CD\u5E94\u7684\u7ED3\u6784
1202
+ - \u5728\u8C03\u7528 API \u524D\u4E86\u89E3\u9700\u8981\u54EA\u4E9B\u53C2\u6570
1203
+
1204
+ \u8FD4\u56DE\u5185\u5BB9\u5305\u62EC\uFF1A
1205
+ - API \u57FA\u672C\u4FE1\u606F\uFF08\u65B9\u6CD5\u3001\u8DEF\u5F84\u3001\u63CF\u8FF0\uFF09
1206
+ - \u53C2\u6570 Schema\uFF08\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u5934\u53C2\u6570\uFF09
1207
+ - \u8BF7\u6C42\u4F53 Schema
1208
+ - \u54CD\u5E94 Schema`,
1209
+ inputSchema: apiDetailSchema
1210
+ };
1211
+ function formatSchema(schema, indent = 0) {
1212
+ if (!schema) return "\u65E0";
1213
+ const spaces = " ".repeat(indent);
1214
+ const lines = [];
1215
+ if (schema.type) {
1216
+ lines.push(`${spaces}\u7C7B\u578B: ${schema.type}`);
1217
+ }
1218
+ if (schema.description) {
1219
+ lines.push(`${spaces}\u63CF\u8FF0: ${schema.description}`);
1220
+ }
1221
+ if (schema.properties) {
1222
+ lines.push(`${spaces}\u5C5E\u6027:`);
1223
+ const props = schema.properties;
1224
+ const required = schema.required || [];
1225
+ for (const [name, prop] of Object.entries(props)) {
1226
+ const isRequired = required.includes(name);
1227
+ const reqTag = isRequired ? " (\u5FC5\u586B)" : " (\u53EF\u9009)";
1228
+ const propType = prop.type || "unknown";
1229
+ const propDesc = prop.description ? ` - ${prop.description}` : "";
1230
+ lines.push(`${spaces} - ${name}${reqTag}: ${propType}${propDesc}`);
1231
+ if (prop.properties) {
1232
+ lines.push(formatSchema(prop, indent + 2));
1233
+ }
1234
+ }
1235
+ }
1236
+ if (schema.enum) {
1237
+ lines.push(`${spaces}\u679A\u4E3E\u503C: ${schema.enum.join(", ")}`);
1238
+ }
1239
+ if (schema.example !== void 0) {
1240
+ lines.push(`${spaces}\u793A\u4F8B: ${JSON.stringify(schema.example)}`);
1241
+ }
1242
+ return lines.join("\n");
1243
+ }
1244
+ function executeApiDetail(registry, input) {
1245
+ const { id } = input;
1246
+ logger.debug(`Executing api_detail: id=${id}`);
1247
+ const detail = registry.getDetail(id);
1248
+ if (!detail) {
1249
+ const byName = registry.getByName(id);
1250
+ if (byName) {
1251
+ return executeApiDetail(registry, { id: byName.id });
1252
+ }
1253
+ return `\u9519\u8BEF: \u627E\u4E0D\u5230 API "${id}"
1254
+
1255
+ \u8BF7\u4F7F\u7528 api_search \u641C\u7D22\u53EF\u7528\u7684 API\u3002`;
1256
+ }
1257
+ const lines = [];
1258
+ lines.push(`## API: ${detail.name}`);
1259
+ lines.push("");
1260
+ const methodBadge = `[${detail.method.toUpperCase()}]`;
1261
+ const deprecatedTag = detail.deprecated ? " \u26A0\uFE0F \u5DF2\u5E9F\u5F03" : "";
1262
+ lines.push(`**${methodBadge}** \`${detail.path}\`${deprecatedTag}`);
1263
+ lines.push("");
1264
+ if (detail.summary) {
1265
+ lines.push(`**\u6458\u8981**: ${detail.summary}`);
1266
+ lines.push("");
1267
+ }
1268
+ if (detail.description) {
1269
+ lines.push(`**\u63CF\u8FF0**: ${detail.description}`);
1270
+ lines.push("");
1271
+ }
1272
+ if (detail.tags && detail.tags.length > 0) {
1273
+ lines.push(`**\u6807\u7B7E**: ${detail.tags.map((t) => `\`${t}\``).join(", ")}`);
1274
+ lines.push("");
1275
+ }
1276
+ lines.push("### \u53C2\u6570");
1277
+ lines.push("");
1278
+ if (detail.parameterSchema) {
1279
+ lines.push(formatSchema(detail.parameterSchema));
1280
+ } else {
1281
+ lines.push("\u65E0\u53C2\u6570");
1282
+ }
1283
+ lines.push("");
1284
+ if (detail.requestBodySchema) {
1285
+ const bodyRequired = detail.requestBodySchema.bodyRequired;
1286
+ lines.push("### \u8BF7\u6C42\u4F53");
1287
+ lines.push("");
1288
+ if (bodyRequired) {
1289
+ lines.push("**\u5FC5\u586B**: \u662F");
1290
+ lines.push("");
1291
+ }
1292
+ lines.push(formatSchema(detail.requestBodySchema));
1293
+ lines.push("");
1294
+ }
1295
+ if (detail.responseSchemas && Object.keys(detail.responseSchemas).length > 0) {
1296
+ lines.push("### \u54CD\u5E94");
1297
+ lines.push("");
1298
+ for (const [status, resp] of Object.entries(detail.responseSchemas)) {
1299
+ lines.push(`#### \u72B6\u6001\u7801: ${status}`);
1300
+ if (resp.description) {
1301
+ lines.push(`${resp.description}`);
1302
+ }
1303
+ if (resp.schema) {
1304
+ lines.push(formatSchema(resp.schema, 1));
1305
+ }
1306
+ lines.push("");
1307
+ }
1308
+ }
1309
+ lines.push("---");
1310
+ lines.push("### \u8C03\u7528\u65B9\u5F0F");
1311
+ lines.push("");
1312
+ lines.push("\u4F7F\u7528 api_execute \u5DE5\u5177\u8C03\u7528\u6B64 API:");
1313
+ lines.push("```");
1314
+ lines.push(`api_execute(operationId="${detail.id}", parameters={...})`);
1315
+ lines.push("```");
1316
+ return lines.join("\n");
1317
+ }
1318
+
1319
+ // src/tools/discovery/api-execute.ts
1320
+ var import_zod4 = require("zod");
1321
+ var apiExecuteSchema = import_zod4.z.object({
1322
+ operationId: import_zod4.z.string().min(1).describe("API ID\uFF08operationId \u6216\u5DE5\u5177\u540D\u79F0\uFF09"),
1323
+ parameters: import_zod4.z.record(import_zod4.z.unknown()).optional().describe("API \u53C2\u6570\uFF08\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u8BF7\u6C42\u4F53\u7B49\uFF09"),
1324
+ _baseUrl: import_zod4.z.string().url().optional().describe("API base URL\uFF08\u53EF\u9009\uFF0C\u8986\u76D6\u9ED8\u8BA4\u914D\u7F6E\uFF09")
1325
+ });
1326
+ var apiExecuteTool = {
1327
+ name: "api_execute",
1328
+ description: `\u6267\u884C API \u8C03\u7528\u3002
1329
+
1330
+ \u4F7F\u7528\u573A\u666F\uFF1A
1331
+ - \u76F4\u63A5\u8C03\u7528\u5DF2\u77E5\u7684 API
1332
+ - \u4F7F\u7528 api_search \u6216 api_list \u627E\u5230 API \u540E\u6267\u884C\u8C03\u7528
1333
+
1334
+ \u4F7F\u7528\u6B65\u9AA4\uFF1A
1335
+ 1. \u5148\u4F7F\u7528 api_search \u6216 api_list \u627E\u5230\u9700\u8981\u7684 API
1336
+ 2. \u4F7F\u7528 api_detail \u67E5\u770B\u53C2\u6570\u8981\u6C42
1337
+ 3. \u4F7F\u7528 api_execute \u6267\u884C\u8C03\u7528
1338
+
1339
+ \u53C2\u6570\u8BF4\u660E\uFF1A
1340
+ - operationId: API \u7684\u552F\u4E00\u6807\u8BC6\u7B26
1341
+ - parameters: \u5305\u542B\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u8BF7\u6C42\u4F53\u7B49
1342
+ - \u8DEF\u5F84\u53C2\u6570: URL \u8DEF\u5F84\u4E2D\u7684\u53C2\u6570 (\u5982 /users/{id} \u4E2D\u7684 id)
1343
+ - \u67E5\u8BE2\u53C2\u6570: URL \u95EE\u53F7\u540E\u7684\u53C2\u6570
1344
+ - body: \u8BF7\u6C42\u4F53\uFF08JSON \u5BF9\u8C61\uFF09`,
1345
+ inputSchema: apiExecuteSchema
1346
+ };
1347
+ async function executeApiExecute(registry, config, input) {
1348
+ const { operationId, parameters = {}, _baseUrl } = input;
1349
+ logger.debug(`Executing api_execute: operationId=${operationId}`);
1350
+ let apiEntry = registry.get(operationId);
1351
+ if (!apiEntry) {
1352
+ apiEntry = registry.getByName(operationId);
1353
+ }
1354
+ if (!apiEntry) {
1355
+ return `\u9519\u8BEF: \u627E\u4E0D\u5230 API "${operationId}"
1356
+
1357
+ \u8BF7\u4F7F\u7528 api_search \u641C\u7D22\u53EF\u7528\u7684 API\u3002`;
1358
+ }
1359
+ if (apiEntry.deprecated) {
1360
+ logger.warn(`API ${operationId} is deprecated`);
1361
+ }
1362
+ try {
1363
+ const executionConfig = {
1364
+ ...config,
1365
+ baseUrl: _baseUrl || config.baseUrl
1366
+ };
1367
+ if (!executionConfig.baseUrl) {
1368
+ return `\u9519\u8BEF: \u6CA1\u6709\u914D\u7F6E base URL
1369
+
1370
+ \u8BF7\u901A\u8FC7\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00\u63D0\u4F9B base URL:
1371
+ 1. \u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E baseUrl
1372
+ 2. \u542F\u52A8\u65F6\u4F7F\u7528 --base-url \u53C2\u6570
1373
+ 3. \u8C03\u7528\u65F6\u63D0\u4F9B _baseUrl \u53C2\u6570`;
1374
+ }
1375
+ logger.info(`Executing API: ${apiEntry.method} ${apiEntry.path}`);
1376
+ const response = await executeRequest(apiEntry.operation, parameters, executionConfig);
1377
+ const formattedResponse = formatResponse(response);
1378
+ return formattedResponse;
1379
+ } catch (error) {
1380
+ const errorMessage = error instanceof ToolExecutionError ? `\u9519\u8BEF: ${error.message}` : `\u9519\u8BEF: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`;
1381
+ logger.error(`API execution failed: ${operationId}`, error);
1382
+ return errorMessage;
1383
+ }
1384
+ }
1385
+
1386
+ // src/tools/discovery/api-list.ts
1387
+ var import_zod5 = require("zod");
1388
+ var apiListSchema = import_zod5.z.object({
1389
+ page: import_zod5.z.number().int().min(1).default(1).describe("\u9875\u7801\uFF08\u4ECE 1 \u5F00\u59CB\uFF09"),
1390
+ pageSize: import_zod5.z.number().int().min(1).max(100).default(20).describe("\u6BCF\u9875\u6570\u91CF\uFF081-100\uFF09"),
1391
+ tag: import_zod5.z.string().optional().describe("\u6309\u6807\u7B7E\u8FC7\u6EE4")
1392
+ });
1393
+ var apiListTool = {
1394
+ name: "api_list",
1395
+ description: `\u5206\u9875\u6D4F\u89C8\u6240\u6709\u53EF\u7528\u7684 API\u3002
1396
+
1397
+ \u4F7F\u7528\u573A\u666F\uFF1A
1398
+ - \u67E5\u770B\u6709\u54EA\u4E9B API \u53EF\u7528
1399
+ - \u6309\u6807\u7B7E\u8FC7\u6EE4 API
1400
+ - \u6D4F\u89C8 API \u5217\u8868\u4EE5\u627E\u5230\u9700\u8981\u7684\u63A5\u53E3
1401
+
1402
+ \u8FD4\u56DE\u5185\u5BB9\u5305\u62EC\uFF1AAPI ID\u3001\u540D\u79F0\u3001HTTP \u65B9\u6CD5\u3001\u8DEF\u5F84\u3001\u6458\u8981\u3001\u6807\u7B7E\u7B49\u3002`,
1403
+ inputSchema: apiListSchema
1404
+ };
1405
+ function executeApiList(registry, input) {
1406
+ const { page, pageSize, tag } = input;
1407
+ logger.debug(`Executing api_list: page=${page}, pageSize=${pageSize}, tag=${tag}`);
1408
+ const result = registry.list({ page, pageSize, tag });
1409
+ const lines = [];
1410
+ lines.push(`## API \u5217\u8868 (${result.total} \u4E2A API)`);
1411
+ lines.push(`\u9875\u7801: ${result.page}/${result.totalPages}`);
1412
+ if (tag) {
1413
+ lines.push(`\u6807\u7B7E\u8FC7\u6EE4: ${tag}`);
1414
+ }
1415
+ lines.push("");
1416
+ if (result.items.length === 0) {
1417
+ lines.push("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 API\u3002");
1418
+ return lines.join("\n");
1419
+ }
1420
+ for (const item of result.items) {
1421
+ const methodBadge = `[${item.method.toUpperCase().padEnd(6)}]`;
1422
+ const deprecatedTag = item.deprecated ? " [\u5DF2\u5E9F\u5F03]" : "";
1423
+ const tags = item.tags ? ` (${item.tags.join(", ")})` : "";
1424
+ lines.push(`### ${item.id}`);
1425
+ lines.push(`${methodBadge} ${item.path}${deprecatedTag}${tags}`);
1426
+ if (item.summary) {
1427
+ lines.push(`${item.summary}`);
1428
+ }
1429
+ lines.push("");
1430
+ }
1431
+ if (result.totalPages > 1) {
1432
+ lines.push("---");
1433
+ if (result.page < result.totalPages) {
1434
+ lines.push(`\u4F7F\u7528 page=${result.page + 1} \u67E5\u770B\u4E0B\u4E00\u9875`);
1435
+ }
1436
+ }
1437
+ return lines.join("\n");
1438
+ }
1439
+
1440
+ // src/tools/discovery/api-search.ts
1441
+ var import_zod6 = require("zod");
1442
+ var apiSearchSchema = import_zod6.z.object({
1443
+ query: import_zod6.z.string().min(1).describe("\u641C\u7D22\u5173\u952E\u8BCD"),
1444
+ searchIn: import_zod6.z.array(import_zod6.z.enum(["name", "summary", "description", "path"])).optional().default(["name", "summary", "description", "path"]).describe("\u641C\u7D22\u8303\u56F4\uFF08\u9ED8\u8BA4\u641C\u7D22\u6240\u6709\u5B57\u6BB5\uFF09"),
1445
+ limit: import_zod6.z.number().int().min(1).max(100).optional().default(20).describe("\u6700\u5927\u8FD4\u56DE\u6570\u91CF\uFF081-100\uFF09")
1446
+ });
1447
+ var apiSearchTool = {
1448
+ name: "api_search",
1449
+ description: `\u641C\u7D22 API\u3002
1450
+
1451
+ \u4F7F\u7528\u573A\u666F\uFF1A
1452
+ - \u6839\u636E\u5173\u952E\u8BCD\u5FEB\u901F\u627E\u5230\u76F8\u5173 API
1453
+ - \u641C\u7D22\u7279\u5B9A\u529F\u80FD\u6216\u8D44\u6E90\u7684\u63A5\u53E3
1454
+ - \u67E5\u627E\u5305\u542B\u7279\u5B9A\u8DEF\u5F84\u6BB5\u7684 API
1455
+
1456
+ \u641C\u7D22\u8303\u56F4\u5305\u62EC\uFF1AAPI \u540D\u79F0\u3001\u6458\u8981\u3001\u63CF\u8FF0\u3001\u8DEF\u5F84\u3002
1457
+ \u7ED3\u679C\u6309\u5339\u914D\u5EA6\u6392\u5E8F\uFF0C\u6700\u5339\u914D\u7684\u6392\u5728\u524D\u9762\u3002`,
1458
+ inputSchema: apiSearchSchema
1459
+ };
1460
+ function executeApiSearch(registry, input) {
1461
+ const { query, searchIn, limit } = input;
1462
+ logger.debug(
1463
+ `Executing api_search: query="${query}", searchIn=${searchIn?.join(",")}, limit=${limit}`
1464
+ );
1465
+ const results = registry.search({ query, searchIn, limit });
1466
+ const lines = [];
1467
+ lines.push(`## \u641C\u7D22\u7ED3\u679C: "${query}"`);
1468
+ lines.push(`\u627E\u5230 ${results.length} \u4E2A\u5339\u914D\u7684 API`);
1469
+ lines.push("");
1470
+ if (results.length === 0) {
1471
+ lines.push("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 API\u3002");
1472
+ lines.push("");
1473
+ lines.push("\u5EFA\u8BAE\uFF1A");
1474
+ lines.push("- \u5C1D\u8BD5\u4F7F\u7528\u4E0D\u540C\u7684\u5173\u952E\u8BCD");
1475
+ lines.push("- \u68C0\u67E5\u62FC\u5199\u662F\u5426\u6B63\u786E");
1476
+ lines.push("- \u4F7F\u7528\u66F4\u901A\u7528\u7684\u641C\u7D22\u8BCD");
1477
+ return lines.join("\n");
1478
+ }
1479
+ for (const item of results) {
1480
+ const methodBadge = `[${item.method.toUpperCase().padEnd(6)}]`;
1481
+ const matchInfo = `\u5339\u914D\u5B57\u6BB5: ${item.matchedFields.join(", ")}`;
1482
+ lines.push(`### ${item.id}`);
1483
+ lines.push(`${methodBadge} ${item.path}`);
1484
+ if (item.summary) {
1485
+ lines.push(`${item.summary}`);
1486
+ }
1487
+ lines.push(`_${matchInfo}_ (\u76F8\u5173\u5EA6: ${Math.round(item.score * 100)}%)`);
1488
+ lines.push("");
1489
+ }
1490
+ lines.push("---");
1491
+ lines.push("\u4F7F\u7528 api_detail <id> \u67E5\u770B API \u8BE6\u60C5");
1492
+ lines.push("\u4F7F\u7528 api_execute <id> <parameters> \u6267\u884C API");
1493
+ return lines.join("\n");
1494
+ }
1495
+
910
1496
  // src/server/tool-manager.ts
911
1497
  var ToolManager = class {
912
1498
  tools = /* @__PURE__ */ new Map();
@@ -943,6 +1529,23 @@ var ToolManager = class {
943
1529
  }
944
1530
  logger.info(`Registered ${tools.length} tools`);
945
1531
  }
1532
+ /**
1533
+ * 获取工具
1534
+ */
1535
+ getTool(name) {
1536
+ return this.tools.get(name);
1537
+ }
1538
+ /**
1539
+ * 通过 operationId 获取工具
1540
+ */
1541
+ getToolByOperationId(operationId) {
1542
+ for (const tool of this.tools.values()) {
1543
+ if (tool.operation.operationId === operationId) {
1544
+ return tool;
1545
+ }
1546
+ }
1547
+ return void 0;
1548
+ }
946
1549
  /**
947
1550
  * 执行工具
948
1551
  */
@@ -988,6 +1591,40 @@ var ToolManager = class {
988
1591
  };
989
1592
  }
990
1593
  }
1594
+ /**
1595
+ * 通过 operation 直接执行(用于 ondemand 模式)
1596
+ */
1597
+ async executeByOperation(operation, args) {
1598
+ try {
1599
+ logger.debug(`Executing operation: ${operation.operationId || operation.path}`, args);
1600
+ const { _baseUrl, ...restArgs } = args;
1601
+ const executionConfig = {
1602
+ ...this.config,
1603
+ baseUrl: typeof _baseUrl === "string" ? _baseUrl : this.config.baseUrl
1604
+ };
1605
+ const response = await executeRequest(operation, restArgs, executionConfig);
1606
+ const formattedResponse = formatResponse(response);
1607
+ return {
1608
+ content: [
1609
+ {
1610
+ type: "text",
1611
+ text: formattedResponse
1612
+ }
1613
+ ]
1614
+ };
1615
+ } catch (error) {
1616
+ const errorMessage = error instanceof ToolExecutionError ? `Error: ${error.message}` : `Error: ${error instanceof Error ? error.message : "Unknown error"}`;
1617
+ logger.error(`Operation execution failed: ${operation.operationId || operation.path}`, error);
1618
+ return {
1619
+ content: [
1620
+ {
1621
+ type: "text",
1622
+ text: errorMessage
1623
+ }
1624
+ ]
1625
+ };
1626
+ }
1627
+ }
991
1628
  /**
992
1629
  * 获取所有已注册的工具名称
993
1630
  */
@@ -1000,11 +1637,80 @@ var ToolManager = class {
1000
1637
  getToolCount() {
1001
1638
  return this.tools.size;
1002
1639
  }
1640
+ /**
1641
+ * 获取配置
1642
+ */
1643
+ getConfig() {
1644
+ return this.config;
1645
+ }
1003
1646
  };
1004
1647
 
1005
1648
  // src/server/index.ts
1649
+ function createRegistry(operations, components) {
1650
+ const registry = new ApiRegistry();
1651
+ for (const tool of operations) {
1652
+ const entry = {
1653
+ id: tool.operation.operationId || tool.name,
1654
+ name: tool.name,
1655
+ method: tool.operation.method,
1656
+ path: tool.operation.path,
1657
+ summary: tool.operation.summary,
1658
+ description: tool.operation.description,
1659
+ tags: tool.operation.tags,
1660
+ deprecated: tool.operation.deprecated,
1661
+ operation: tool.operation,
1662
+ components
1663
+ };
1664
+ registry.register(entry);
1665
+ }
1666
+ return registry;
1667
+ }
1668
+ function registerOndemandTools(server, registry, config) {
1669
+ server.tool(
1670
+ apiListTool.name,
1671
+ apiListTool.description,
1672
+ apiListSchema.shape,
1673
+ async (args) => {
1674
+ const result = executeApiList(registry, args);
1675
+ return { content: [{ type: "text", text: result }] };
1676
+ }
1677
+ );
1678
+ server.tool(
1679
+ apiSearchTool.name,
1680
+ apiSearchTool.description,
1681
+ apiSearchSchema.shape,
1682
+ async (args) => {
1683
+ const result = executeApiSearch(registry, args);
1684
+ return { content: [{ type: "text", text: result }] };
1685
+ }
1686
+ );
1687
+ server.tool(
1688
+ apiDetailTool.name,
1689
+ apiDetailTool.description,
1690
+ apiDetailSchema.shape,
1691
+ async (args) => {
1692
+ const result = executeApiDetail(registry, args);
1693
+ return { content: [{ type: "text", text: result }] };
1694
+ }
1695
+ );
1696
+ server.tool(
1697
+ apiExecuteTool.name,
1698
+ apiExecuteTool.description,
1699
+ apiExecuteSchema.shape,
1700
+ async (args) => {
1701
+ const result = await executeApiExecute(
1702
+ registry,
1703
+ config,
1704
+ args
1705
+ );
1706
+ return { content: [{ type: "text", text: result }] };
1707
+ }
1708
+ );
1709
+ logger.info("Registered 4 discovery tools (ondemand mode)");
1710
+ }
1006
1711
  async function createServer(config) {
1007
1712
  logger.info("Creating MCP server...");
1713
+ logger.info(`Mode: ${config.mode || "default"}`);
1008
1714
  const server = new import_mcp.McpServer({
1009
1715
  name: "api2mcp",
1010
1716
  version: "0.1.0"
@@ -1020,9 +1726,17 @@ async function createServer(config) {
1020
1726
  openApiDoc.components?.schemas,
1021
1727
  config.toolPrefix
1022
1728
  );
1023
- const toolManager = new ToolManager(server, effectiveConfig);
1024
- toolManager.registerTools(tools);
1025
- logger.info(`Server ready with ${toolManager.getToolCount()} tools`);
1729
+ if (config.mode === "ondemand") {
1730
+ const registry = createRegistry(tools, openApiDoc.components?.schemas);
1731
+ registerOndemandTools(server, registry, effectiveConfig);
1732
+ const stats = registry.getStats();
1733
+ logger.info(`Server ready with ${stats.totalApis} APIs in registry`);
1734
+ logger.info(`Tags: ${stats.tags.slice(0, 5).join(", ")}${stats.tags.length > 5 ? "..." : ""}`);
1735
+ } else {
1736
+ const toolManager = new ToolManager(server, effectiveConfig);
1737
+ toolManager.registerTools(tools);
1738
+ logger.info(`Server ready with ${toolManager.getToolCount()} tools`);
1739
+ }
1026
1740
  return server;
1027
1741
  }
1028
1742
  async function startServer(config) {
@@ -1034,14 +1748,27 @@ async function startServer(config) {
1034
1748
  // Annotate the CommonJS export names for ESM import in node:
1035
1749
  0 && (module.exports = {
1036
1750
  Api2McpError,
1751
+ ApiRegistry,
1037
1752
  ConfigurationError,
1038
1753
  HttpError,
1039
1754
  OpenApiParseError,
1040
1755
  ToolExecutionError,
1041
1756
  ToolManager,
1757
+ apiDetailSchema,
1758
+ apiDetailTool,
1759
+ apiExecuteSchema,
1760
+ apiExecuteTool,
1761
+ apiListSchema,
1762
+ apiListTool,
1763
+ apiSearchSchema,
1764
+ apiSearchTool,
1042
1765
  convertSchema,
1043
1766
  createRefResolver,
1044
1767
  createServer,
1768
+ executeApiDetail,
1769
+ executeApiExecute,
1770
+ executeApiList,
1771
+ executeApiSearch,
1045
1772
  executeRequest,
1046
1773
  formatResponse,
1047
1774
  generateTool,