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.
@@ -131,7 +131,8 @@ function loadFromFile(workingDir = process.cwd()) {
131
131
  timeout: parsed.timeout,
132
132
  headers: parsed.headers,
133
133
  toolPrefix: parsed.toolPrefix,
134
- debug: parsed.debug
134
+ debug: parsed.debug,
135
+ mode: parsed.mode
135
136
  };
136
137
  } catch (error) {
137
138
  throw new ConfigurationError(
@@ -162,6 +163,9 @@ function loadFromCli(args) {
162
163
  if (args.debug !== void 0) {
163
164
  config.debug = args.debug;
164
165
  }
166
+ if (args.mode) {
167
+ config.mode = args.mode;
168
+ }
165
169
  return config;
166
170
  }
167
171
  function mergeConfigs(...configs) {
@@ -176,6 +180,7 @@ function mergeConfigs(...configs) {
176
180
  if (config.headers !== void 0) merged.headers = config.headers;
177
181
  if (config.toolPrefix !== void 0) merged.toolPrefix = config.toolPrefix;
178
182
  if (config.debug !== void 0) merged.debug = config.debug;
183
+ if (config.mode !== void 0) merged.mode = config.mode;
179
184
  }
180
185
  if (merged.timeout === void 0) {
181
186
  merged.timeout = DEFAULT_TIMEOUT;
@@ -200,6 +205,7 @@ function loadConfig(cliArgs = {}, env = process.env) {
200
205
  baseUrl: config.baseUrl,
201
206
  timeout: config.timeout,
202
207
  toolPrefix: config.toolPrefix,
208
+ mode: config.mode,
203
209
  debug: config.debug
204
210
  });
205
211
  return config;
@@ -848,6 +854,573 @@ function getBaseUrl(doc, overrideUrl) {
848
854
  return void 0;
849
855
  }
850
856
 
857
+ // src/registry/api-registry.ts
858
+ var DEFAULT_PAGE_SIZE = 20;
859
+ var DEFAULT_SEARCH_LIMIT = 50;
860
+ var ApiRegistry = class {
861
+ apis = /* @__PURE__ */ new Map();
862
+ nameIndex = /* @__PURE__ */ new Map();
863
+ // name -> id
864
+ tagIndex = /* @__PURE__ */ new Map();
865
+ // tag -> set of ids
866
+ /**
867
+ * 注册 API
868
+ */
869
+ register(entry) {
870
+ if (this.apis.has(entry.id)) {
871
+ logger.warn(`API already registered: ${entry.id}, overwriting`);
872
+ }
873
+ this.apis.set(entry.id, entry);
874
+ this.nameIndex.set(entry.name, entry.id);
875
+ if (entry.tags) {
876
+ for (const tag of entry.tags) {
877
+ if (!this.tagIndex.has(tag)) {
878
+ this.tagIndex.set(tag, /* @__PURE__ */ new Set());
879
+ }
880
+ this.tagIndex.get(tag)?.add(entry.id);
881
+ }
882
+ }
883
+ logger.debug(`Registered API: ${entry.id}`);
884
+ }
885
+ /**
886
+ * 批量注册 API
887
+ */
888
+ registerAll(entries) {
889
+ for (const entry of entries) {
890
+ this.register(entry);
891
+ }
892
+ logger.info(`Registered ${entries.length} APIs in registry`);
893
+ }
894
+ /**
895
+ * 获取单个 API
896
+ */
897
+ get(id) {
898
+ return this.apis.get(id);
899
+ }
900
+ /**
901
+ * 通过名称获取 API
902
+ */
903
+ getByName(name) {
904
+ const id = this.nameIndex.get(name);
905
+ return id ? this.apis.get(id) : void 0;
906
+ }
907
+ /**
908
+ * 检查 API 是否存在
909
+ */
910
+ has(id) {
911
+ return this.apis.has(id);
912
+ }
913
+ /**
914
+ * 搜索 API
915
+ */
916
+ search(options) {
917
+ const {
918
+ query,
919
+ searchIn = ["name", "summary", "description", "path"],
920
+ limit = DEFAULT_SEARCH_LIMIT
921
+ } = options;
922
+ const normalizedQuery = query.toLowerCase().trim();
923
+ const results = [];
924
+ for (const entry of this.apis.values()) {
925
+ const matchedFields = [];
926
+ let score = 0;
927
+ if (searchIn.includes("name") && entry.name) {
928
+ const nameLower = entry.name.toLowerCase();
929
+ if (nameLower.includes(normalizedQuery)) {
930
+ matchedFields.push("name");
931
+ score += nameLower === normalizedQuery ? 1 : 0.8;
932
+ }
933
+ }
934
+ if (searchIn.includes("summary") && entry.summary) {
935
+ const summaryLower = entry.summary.toLowerCase();
936
+ if (summaryLower.includes(normalizedQuery)) {
937
+ matchedFields.push("summary");
938
+ score += 0.6;
939
+ }
940
+ }
941
+ if (searchIn.includes("description") && entry.description) {
942
+ const descLower = entry.description.toLowerCase();
943
+ if (descLower.includes(normalizedQuery)) {
944
+ matchedFields.push("description");
945
+ score += 0.4;
946
+ }
947
+ }
948
+ if (searchIn.includes("path") && entry.path) {
949
+ const pathLower = entry.path.toLowerCase();
950
+ if (pathLower.includes(normalizedQuery)) {
951
+ matchedFields.push("path");
952
+ score += 0.5;
953
+ }
954
+ }
955
+ if (matchedFields.length > 0) {
956
+ results.push({
957
+ id: entry.id,
958
+ name: entry.name,
959
+ method: entry.method,
960
+ path: entry.path,
961
+ summary: entry.summary,
962
+ matchedFields,
963
+ score: Math.min(score, 1)
964
+ });
965
+ }
966
+ }
967
+ results.sort((a, b) => b.score - a.score);
968
+ return results.slice(0, limit);
969
+ }
970
+ /**
971
+ * 分页列出 API
972
+ */
973
+ list(options = {}) {
974
+ const { page = 1, pageSize = DEFAULT_PAGE_SIZE, tag } = options;
975
+ let entries;
976
+ if (tag) {
977
+ const ids = this.tagIndex.get(tag);
978
+ entries = ids ? Array.from(ids).map((id) => this.apis.get(id)).filter((entry) => entry !== void 0) : [];
979
+ } else {
980
+ entries = Array.from(this.apis.values());
981
+ }
982
+ const total = entries.length;
983
+ const totalPages = Math.ceil(total / pageSize);
984
+ const startIndex = (page - 1) * pageSize;
985
+ const endIndex = startIndex + pageSize;
986
+ const pageEntries = entries.slice(startIndex, endIndex);
987
+ const items = pageEntries.map((entry) => ({
988
+ id: entry.id,
989
+ name: entry.name,
990
+ method: entry.method,
991
+ path: entry.path,
992
+ summary: entry.summary,
993
+ tags: entry.tags,
994
+ deprecated: entry.deprecated
995
+ }));
996
+ return {
997
+ page,
998
+ pageSize,
999
+ total,
1000
+ totalPages,
1001
+ items
1002
+ };
1003
+ }
1004
+ /**
1005
+ * 获取所有标签
1006
+ */
1007
+ getTags() {
1008
+ return Array.from(this.tagIndex.keys()).sort();
1009
+ }
1010
+ /**
1011
+ * 获取 API 详情
1012
+ */
1013
+ getDetail(id) {
1014
+ const entry = this.apis.get(id);
1015
+ if (!entry) {
1016
+ return void 0;
1017
+ }
1018
+ return {
1019
+ ...entry,
1020
+ parameterSchema: this.buildParameterSchema(entry),
1021
+ requestBodySchema: this.buildRequestBodySchema(entry),
1022
+ responseSchemas: this.buildResponseSchemas(entry)
1023
+ };
1024
+ }
1025
+ /**
1026
+ * 获取统计信息
1027
+ */
1028
+ getStats() {
1029
+ const byMethod = {};
1030
+ const byTag = {};
1031
+ for (const entry of this.apis.values()) {
1032
+ const method = entry.method.toUpperCase();
1033
+ byMethod[method] = (byMethod[method] || 0) + 1;
1034
+ if (entry.tags) {
1035
+ for (const tag of entry.tags) {
1036
+ byTag[tag] = (byTag[tag] || 0) + 1;
1037
+ }
1038
+ }
1039
+ }
1040
+ return {
1041
+ totalApis: this.apis.size,
1042
+ tags: this.getTags(),
1043
+ byMethod,
1044
+ byTag
1045
+ };
1046
+ }
1047
+ /**
1048
+ * 获取 API 数量
1049
+ */
1050
+ get size() {
1051
+ return this.apis.size;
1052
+ }
1053
+ /**
1054
+ * 构建参数 Schema
1055
+ */
1056
+ buildParameterSchema(entry) {
1057
+ const { operation } = entry;
1058
+ if (!operation.parameters || operation.parameters.length === 0) {
1059
+ return void 0;
1060
+ }
1061
+ const properties = {};
1062
+ const required = [];
1063
+ for (const param of operation.parameters) {
1064
+ const paramName = param.name;
1065
+ properties[paramName] = {
1066
+ ...param.schema,
1067
+ description: param.description,
1068
+ in: param.in
1069
+ };
1070
+ if (param.required) {
1071
+ required.push(paramName);
1072
+ }
1073
+ }
1074
+ return {
1075
+ type: "object",
1076
+ properties,
1077
+ required: required.length > 0 ? required : void 0
1078
+ };
1079
+ }
1080
+ /**
1081
+ * 构建请求体 Schema
1082
+ */
1083
+ buildRequestBodySchema(entry) {
1084
+ const { operation } = entry;
1085
+ if (!operation.requestBody) {
1086
+ return void 0;
1087
+ }
1088
+ const jsonContent = operation.requestBody.content["application/json"];
1089
+ if (!jsonContent?.schema) {
1090
+ return void 0;
1091
+ }
1092
+ return {
1093
+ ...jsonContent.schema,
1094
+ description: operation.requestBody.description,
1095
+ bodyRequired: operation.requestBody.required
1096
+ };
1097
+ }
1098
+ /**
1099
+ * 构建响应 Schema
1100
+ */
1101
+ buildResponseSchemas(entry) {
1102
+ const { operation } = entry;
1103
+ if (!operation.responses) {
1104
+ return void 0;
1105
+ }
1106
+ const schemas = {};
1107
+ for (const [status, response] of Object.entries(operation.responses)) {
1108
+ const jsonContent = response.content?.["application/json"];
1109
+ schemas[status] = {
1110
+ description: response.description,
1111
+ schema: jsonContent?.schema
1112
+ };
1113
+ }
1114
+ return schemas;
1115
+ }
1116
+ };
1117
+
1118
+ // src/tools/discovery/api-detail.ts
1119
+ import { z as z3 } from "zod";
1120
+ var apiDetailSchema = z3.object({
1121
+ id: z3.string().min(1).describe("API ID\uFF08operationId \u6216\u5DE5\u5177\u540D\u79F0\uFF09")
1122
+ });
1123
+ var apiDetailTool = {
1124
+ name: "api_detail",
1125
+ description: `\u83B7\u53D6 API \u7684\u8BE6\u7EC6\u4FE1\u606F\u3002
1126
+
1127
+ \u4F7F\u7528\u573A\u666F\uFF1A
1128
+ - \u67E5\u770B\u67D0\u4E2A API \u7684\u5B8C\u6574\u53C2\u6570\u5B9A\u4E49
1129
+ - \u4E86\u89E3\u8BF7\u6C42\u4F53\u548C\u54CD\u5E94\u7684\u7ED3\u6784
1130
+ - \u5728\u8C03\u7528 API \u524D\u4E86\u89E3\u9700\u8981\u54EA\u4E9B\u53C2\u6570
1131
+
1132
+ \u8FD4\u56DE\u5185\u5BB9\u5305\u62EC\uFF1A
1133
+ - API \u57FA\u672C\u4FE1\u606F\uFF08\u65B9\u6CD5\u3001\u8DEF\u5F84\u3001\u63CF\u8FF0\uFF09
1134
+ - \u53C2\u6570 Schema\uFF08\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u5934\u53C2\u6570\uFF09
1135
+ - \u8BF7\u6C42\u4F53 Schema
1136
+ - \u54CD\u5E94 Schema`,
1137
+ inputSchema: apiDetailSchema
1138
+ };
1139
+ function formatSchema(schema, indent = 0) {
1140
+ if (!schema) return "\u65E0";
1141
+ const spaces = " ".repeat(indent);
1142
+ const lines = [];
1143
+ if (schema.type) {
1144
+ lines.push(`${spaces}\u7C7B\u578B: ${schema.type}`);
1145
+ }
1146
+ if (schema.description) {
1147
+ lines.push(`${spaces}\u63CF\u8FF0: ${schema.description}`);
1148
+ }
1149
+ if (schema.properties) {
1150
+ lines.push(`${spaces}\u5C5E\u6027:`);
1151
+ const props = schema.properties;
1152
+ const required = schema.required || [];
1153
+ for (const [name, prop] of Object.entries(props)) {
1154
+ const isRequired = required.includes(name);
1155
+ const reqTag = isRequired ? " (\u5FC5\u586B)" : " (\u53EF\u9009)";
1156
+ const propType = prop.type || "unknown";
1157
+ const propDesc = prop.description ? ` - ${prop.description}` : "";
1158
+ lines.push(`${spaces} - ${name}${reqTag}: ${propType}${propDesc}`);
1159
+ if (prop.properties) {
1160
+ lines.push(formatSchema(prop, indent + 2));
1161
+ }
1162
+ }
1163
+ }
1164
+ if (schema.enum) {
1165
+ lines.push(`${spaces}\u679A\u4E3E\u503C: ${schema.enum.join(", ")}`);
1166
+ }
1167
+ if (schema.example !== void 0) {
1168
+ lines.push(`${spaces}\u793A\u4F8B: ${JSON.stringify(schema.example)}`);
1169
+ }
1170
+ return lines.join("\n");
1171
+ }
1172
+ function executeApiDetail(registry, input) {
1173
+ const { id } = input;
1174
+ logger.debug(`Executing api_detail: id=${id}`);
1175
+ const detail = registry.getDetail(id);
1176
+ if (!detail) {
1177
+ const byName = registry.getByName(id);
1178
+ if (byName) {
1179
+ return executeApiDetail(registry, { id: byName.id });
1180
+ }
1181
+ return `\u9519\u8BEF: \u627E\u4E0D\u5230 API "${id}"
1182
+
1183
+ \u8BF7\u4F7F\u7528 api_search \u641C\u7D22\u53EF\u7528\u7684 API\u3002`;
1184
+ }
1185
+ const lines = [];
1186
+ lines.push(`## API: ${detail.name}`);
1187
+ lines.push("");
1188
+ const methodBadge = `[${detail.method.toUpperCase()}]`;
1189
+ const deprecatedTag = detail.deprecated ? " \u26A0\uFE0F \u5DF2\u5E9F\u5F03" : "";
1190
+ lines.push(`**${methodBadge}** \`${detail.path}\`${deprecatedTag}`);
1191
+ lines.push("");
1192
+ if (detail.summary) {
1193
+ lines.push(`**\u6458\u8981**: ${detail.summary}`);
1194
+ lines.push("");
1195
+ }
1196
+ if (detail.description) {
1197
+ lines.push(`**\u63CF\u8FF0**: ${detail.description}`);
1198
+ lines.push("");
1199
+ }
1200
+ if (detail.tags && detail.tags.length > 0) {
1201
+ lines.push(`**\u6807\u7B7E**: ${detail.tags.map((t) => `\`${t}\``).join(", ")}`);
1202
+ lines.push("");
1203
+ }
1204
+ lines.push("### \u53C2\u6570");
1205
+ lines.push("");
1206
+ if (detail.parameterSchema) {
1207
+ lines.push(formatSchema(detail.parameterSchema));
1208
+ } else {
1209
+ lines.push("\u65E0\u53C2\u6570");
1210
+ }
1211
+ lines.push("");
1212
+ if (detail.requestBodySchema) {
1213
+ const bodyRequired = detail.requestBodySchema.bodyRequired;
1214
+ lines.push("### \u8BF7\u6C42\u4F53");
1215
+ lines.push("");
1216
+ if (bodyRequired) {
1217
+ lines.push("**\u5FC5\u586B**: \u662F");
1218
+ lines.push("");
1219
+ }
1220
+ lines.push(formatSchema(detail.requestBodySchema));
1221
+ lines.push("");
1222
+ }
1223
+ if (detail.responseSchemas && Object.keys(detail.responseSchemas).length > 0) {
1224
+ lines.push("### \u54CD\u5E94");
1225
+ lines.push("");
1226
+ for (const [status, resp] of Object.entries(detail.responseSchemas)) {
1227
+ lines.push(`#### \u72B6\u6001\u7801: ${status}`);
1228
+ if (resp.description) {
1229
+ lines.push(`${resp.description}`);
1230
+ }
1231
+ if (resp.schema) {
1232
+ lines.push(formatSchema(resp.schema, 1));
1233
+ }
1234
+ lines.push("");
1235
+ }
1236
+ }
1237
+ lines.push("---");
1238
+ lines.push("### \u8C03\u7528\u65B9\u5F0F");
1239
+ lines.push("");
1240
+ lines.push("\u4F7F\u7528 api_execute \u5DE5\u5177\u8C03\u7528\u6B64 API:");
1241
+ lines.push("```");
1242
+ lines.push(`api_execute(operationId="${detail.id}", parameters={...})`);
1243
+ lines.push("```");
1244
+ return lines.join("\n");
1245
+ }
1246
+
1247
+ // src/tools/discovery/api-execute.ts
1248
+ import { z as z4 } from "zod";
1249
+ var apiExecuteSchema = z4.object({
1250
+ operationId: z4.string().min(1).describe("API ID\uFF08operationId \u6216\u5DE5\u5177\u540D\u79F0\uFF09"),
1251
+ parameters: z4.record(z4.unknown()).optional().describe("API \u53C2\u6570\uFF08\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u8BF7\u6C42\u4F53\u7B49\uFF09"),
1252
+ _baseUrl: z4.string().url().optional().describe("API base URL\uFF08\u53EF\u9009\uFF0C\u8986\u76D6\u9ED8\u8BA4\u914D\u7F6E\uFF09")
1253
+ });
1254
+ var apiExecuteTool = {
1255
+ name: "api_execute",
1256
+ description: `\u6267\u884C API \u8C03\u7528\u3002
1257
+
1258
+ \u4F7F\u7528\u573A\u666F\uFF1A
1259
+ - \u76F4\u63A5\u8C03\u7528\u5DF2\u77E5\u7684 API
1260
+ - \u4F7F\u7528 api_search \u6216 api_list \u627E\u5230 API \u540E\u6267\u884C\u8C03\u7528
1261
+
1262
+ \u4F7F\u7528\u6B65\u9AA4\uFF1A
1263
+ 1. \u5148\u4F7F\u7528 api_search \u6216 api_list \u627E\u5230\u9700\u8981\u7684 API
1264
+ 2. \u4F7F\u7528 api_detail \u67E5\u770B\u53C2\u6570\u8981\u6C42
1265
+ 3. \u4F7F\u7528 api_execute \u6267\u884C\u8C03\u7528
1266
+
1267
+ \u53C2\u6570\u8BF4\u660E\uFF1A
1268
+ - operationId: API \u7684\u552F\u4E00\u6807\u8BC6\u7B26
1269
+ - parameters: \u5305\u542B\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u8BF7\u6C42\u4F53\u7B49
1270
+ - \u8DEF\u5F84\u53C2\u6570: URL \u8DEF\u5F84\u4E2D\u7684\u53C2\u6570 (\u5982 /users/{id} \u4E2D\u7684 id)
1271
+ - \u67E5\u8BE2\u53C2\u6570: URL \u95EE\u53F7\u540E\u7684\u53C2\u6570
1272
+ - body: \u8BF7\u6C42\u4F53\uFF08JSON \u5BF9\u8C61\uFF09`,
1273
+ inputSchema: apiExecuteSchema
1274
+ };
1275
+ async function executeApiExecute(registry, config, input) {
1276
+ const { operationId, parameters = {}, _baseUrl } = input;
1277
+ logger.debug(`Executing api_execute: operationId=${operationId}`);
1278
+ let apiEntry = registry.get(operationId);
1279
+ if (!apiEntry) {
1280
+ apiEntry = registry.getByName(operationId);
1281
+ }
1282
+ if (!apiEntry) {
1283
+ return `\u9519\u8BEF: \u627E\u4E0D\u5230 API "${operationId}"
1284
+
1285
+ \u8BF7\u4F7F\u7528 api_search \u641C\u7D22\u53EF\u7528\u7684 API\u3002`;
1286
+ }
1287
+ if (apiEntry.deprecated) {
1288
+ logger.warn(`API ${operationId} is deprecated`);
1289
+ }
1290
+ try {
1291
+ const executionConfig = {
1292
+ ...config,
1293
+ baseUrl: _baseUrl || config.baseUrl
1294
+ };
1295
+ if (!executionConfig.baseUrl) {
1296
+ return `\u9519\u8BEF: \u6CA1\u6709\u914D\u7F6E base URL
1297
+
1298
+ \u8BF7\u901A\u8FC7\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00\u63D0\u4F9B base URL:
1299
+ 1. \u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E baseUrl
1300
+ 2. \u542F\u52A8\u65F6\u4F7F\u7528 --base-url \u53C2\u6570
1301
+ 3. \u8C03\u7528\u65F6\u63D0\u4F9B _baseUrl \u53C2\u6570`;
1302
+ }
1303
+ logger.info(`Executing API: ${apiEntry.method} ${apiEntry.path}`);
1304
+ const response = await executeRequest(apiEntry.operation, parameters, executionConfig);
1305
+ const formattedResponse = formatResponse(response);
1306
+ return formattedResponse;
1307
+ } catch (error) {
1308
+ const errorMessage = error instanceof ToolExecutionError ? `\u9519\u8BEF: ${error.message}` : `\u9519\u8BEF: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`;
1309
+ logger.error(`API execution failed: ${operationId}`, error);
1310
+ return errorMessage;
1311
+ }
1312
+ }
1313
+
1314
+ // src/tools/discovery/api-list.ts
1315
+ import { z as z5 } from "zod";
1316
+ var apiListSchema = z5.object({
1317
+ page: z5.number().int().min(1).default(1).describe("\u9875\u7801\uFF08\u4ECE 1 \u5F00\u59CB\uFF09"),
1318
+ pageSize: z5.number().int().min(1).max(100).default(20).describe("\u6BCF\u9875\u6570\u91CF\uFF081-100\uFF09"),
1319
+ tag: z5.string().optional().describe("\u6309\u6807\u7B7E\u8FC7\u6EE4")
1320
+ });
1321
+ var apiListTool = {
1322
+ name: "api_list",
1323
+ description: `\u5206\u9875\u6D4F\u89C8\u6240\u6709\u53EF\u7528\u7684 API\u3002
1324
+
1325
+ \u4F7F\u7528\u573A\u666F\uFF1A
1326
+ - \u67E5\u770B\u6709\u54EA\u4E9B API \u53EF\u7528
1327
+ - \u6309\u6807\u7B7E\u8FC7\u6EE4 API
1328
+ - \u6D4F\u89C8 API \u5217\u8868\u4EE5\u627E\u5230\u9700\u8981\u7684\u63A5\u53E3
1329
+
1330
+ \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`,
1331
+ inputSchema: apiListSchema
1332
+ };
1333
+ function executeApiList(registry, input) {
1334
+ const { page, pageSize, tag } = input;
1335
+ logger.debug(`Executing api_list: page=${page}, pageSize=${pageSize}, tag=${tag}`);
1336
+ const result = registry.list({ page, pageSize, tag });
1337
+ const lines = [];
1338
+ lines.push(`## API \u5217\u8868 (${result.total} \u4E2A API)`);
1339
+ lines.push(`\u9875\u7801: ${result.page}/${result.totalPages}`);
1340
+ if (tag) {
1341
+ lines.push(`\u6807\u7B7E\u8FC7\u6EE4: ${tag}`);
1342
+ }
1343
+ lines.push("");
1344
+ if (result.items.length === 0) {
1345
+ lines.push("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 API\u3002");
1346
+ return lines.join("\n");
1347
+ }
1348
+ for (const item of result.items) {
1349
+ const methodBadge = `[${item.method.toUpperCase().padEnd(6)}]`;
1350
+ const deprecatedTag = item.deprecated ? " [\u5DF2\u5E9F\u5F03]" : "";
1351
+ const tags = item.tags ? ` (${item.tags.join(", ")})` : "";
1352
+ lines.push(`### ${item.id}`);
1353
+ lines.push(`${methodBadge} ${item.path}${deprecatedTag}${tags}`);
1354
+ if (item.summary) {
1355
+ lines.push(`${item.summary}`);
1356
+ }
1357
+ lines.push("");
1358
+ }
1359
+ if (result.totalPages > 1) {
1360
+ lines.push("---");
1361
+ if (result.page < result.totalPages) {
1362
+ lines.push(`\u4F7F\u7528 page=${result.page + 1} \u67E5\u770B\u4E0B\u4E00\u9875`);
1363
+ }
1364
+ }
1365
+ return lines.join("\n");
1366
+ }
1367
+
1368
+ // src/tools/discovery/api-search.ts
1369
+ import { z as z6 } from "zod";
1370
+ var apiSearchSchema = z6.object({
1371
+ query: z6.string().min(1).describe("\u641C\u7D22\u5173\u952E\u8BCD"),
1372
+ searchIn: z6.array(z6.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"),
1373
+ limit: z6.number().int().min(1).max(100).optional().default(20).describe("\u6700\u5927\u8FD4\u56DE\u6570\u91CF\uFF081-100\uFF09")
1374
+ });
1375
+ var apiSearchTool = {
1376
+ name: "api_search",
1377
+ description: `\u641C\u7D22 API\u3002
1378
+
1379
+ \u4F7F\u7528\u573A\u666F\uFF1A
1380
+ - \u6839\u636E\u5173\u952E\u8BCD\u5FEB\u901F\u627E\u5230\u76F8\u5173 API
1381
+ - \u641C\u7D22\u7279\u5B9A\u529F\u80FD\u6216\u8D44\u6E90\u7684\u63A5\u53E3
1382
+ - \u67E5\u627E\u5305\u542B\u7279\u5B9A\u8DEF\u5F84\u6BB5\u7684 API
1383
+
1384
+ \u641C\u7D22\u8303\u56F4\u5305\u62EC\uFF1AAPI \u540D\u79F0\u3001\u6458\u8981\u3001\u63CF\u8FF0\u3001\u8DEF\u5F84\u3002
1385
+ \u7ED3\u679C\u6309\u5339\u914D\u5EA6\u6392\u5E8F\uFF0C\u6700\u5339\u914D\u7684\u6392\u5728\u524D\u9762\u3002`,
1386
+ inputSchema: apiSearchSchema
1387
+ };
1388
+ function executeApiSearch(registry, input) {
1389
+ const { query, searchIn, limit } = input;
1390
+ logger.debug(
1391
+ `Executing api_search: query="${query}", searchIn=${searchIn?.join(",")}, limit=${limit}`
1392
+ );
1393
+ const results = registry.search({ query, searchIn, limit });
1394
+ const lines = [];
1395
+ lines.push(`## \u641C\u7D22\u7ED3\u679C: "${query}"`);
1396
+ lines.push(`\u627E\u5230 ${results.length} \u4E2A\u5339\u914D\u7684 API`);
1397
+ lines.push("");
1398
+ if (results.length === 0) {
1399
+ lines.push("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 API\u3002");
1400
+ lines.push("");
1401
+ lines.push("\u5EFA\u8BAE\uFF1A");
1402
+ lines.push("- \u5C1D\u8BD5\u4F7F\u7528\u4E0D\u540C\u7684\u5173\u952E\u8BCD");
1403
+ lines.push("- \u68C0\u67E5\u62FC\u5199\u662F\u5426\u6B63\u786E");
1404
+ lines.push("- \u4F7F\u7528\u66F4\u901A\u7528\u7684\u641C\u7D22\u8BCD");
1405
+ return lines.join("\n");
1406
+ }
1407
+ for (const item of results) {
1408
+ const methodBadge = `[${item.method.toUpperCase().padEnd(6)}]`;
1409
+ const matchInfo = `\u5339\u914D\u5B57\u6BB5: ${item.matchedFields.join(", ")}`;
1410
+ lines.push(`### ${item.id}`);
1411
+ lines.push(`${methodBadge} ${item.path}`);
1412
+ if (item.summary) {
1413
+ lines.push(`${item.summary}`);
1414
+ }
1415
+ lines.push(`_${matchInfo}_ (\u76F8\u5173\u5EA6: ${Math.round(item.score * 100)}%)`);
1416
+ lines.push("");
1417
+ }
1418
+ lines.push("---");
1419
+ lines.push("\u4F7F\u7528 api_detail <id> \u67E5\u770B API \u8BE6\u60C5");
1420
+ lines.push("\u4F7F\u7528 api_execute <id> <parameters> \u6267\u884C API");
1421
+ return lines.join("\n");
1422
+ }
1423
+
851
1424
  // src/server/tool-manager.ts
852
1425
  var ToolManager = class {
853
1426
  tools = /* @__PURE__ */ new Map();
@@ -884,6 +1457,23 @@ var ToolManager = class {
884
1457
  }
885
1458
  logger.info(`Registered ${tools.length} tools`);
886
1459
  }
1460
+ /**
1461
+ * 获取工具
1462
+ */
1463
+ getTool(name) {
1464
+ return this.tools.get(name);
1465
+ }
1466
+ /**
1467
+ * 通过 operationId 获取工具
1468
+ */
1469
+ getToolByOperationId(operationId) {
1470
+ for (const tool of this.tools.values()) {
1471
+ if (tool.operation.operationId === operationId) {
1472
+ return tool;
1473
+ }
1474
+ }
1475
+ return void 0;
1476
+ }
887
1477
  /**
888
1478
  * 执行工具
889
1479
  */
@@ -929,6 +1519,40 @@ var ToolManager = class {
929
1519
  };
930
1520
  }
931
1521
  }
1522
+ /**
1523
+ * 通过 operation 直接执行(用于 ondemand 模式)
1524
+ */
1525
+ async executeByOperation(operation, args) {
1526
+ try {
1527
+ logger.debug(`Executing operation: ${operation.operationId || operation.path}`, args);
1528
+ const { _baseUrl, ...restArgs } = args;
1529
+ const executionConfig = {
1530
+ ...this.config,
1531
+ baseUrl: typeof _baseUrl === "string" ? _baseUrl : this.config.baseUrl
1532
+ };
1533
+ const response = await executeRequest(operation, restArgs, executionConfig);
1534
+ const formattedResponse = formatResponse(response);
1535
+ return {
1536
+ content: [
1537
+ {
1538
+ type: "text",
1539
+ text: formattedResponse
1540
+ }
1541
+ ]
1542
+ };
1543
+ } catch (error) {
1544
+ const errorMessage = error instanceof ToolExecutionError ? `Error: ${error.message}` : `Error: ${error instanceof Error ? error.message : "Unknown error"}`;
1545
+ logger.error(`Operation execution failed: ${operation.operationId || operation.path}`, error);
1546
+ return {
1547
+ content: [
1548
+ {
1549
+ type: "text",
1550
+ text: errorMessage
1551
+ }
1552
+ ]
1553
+ };
1554
+ }
1555
+ }
932
1556
  /**
933
1557
  * 获取所有已注册的工具名称
934
1558
  */
@@ -941,13 +1565,82 @@ var ToolManager = class {
941
1565
  getToolCount() {
942
1566
  return this.tools.size;
943
1567
  }
1568
+ /**
1569
+ * 获取配置
1570
+ */
1571
+ getConfig() {
1572
+ return this.config;
1573
+ }
944
1574
  };
945
1575
 
946
1576
  // src/server/index.ts
947
1577
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
948
1578
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1579
+ function createRegistry(operations, components) {
1580
+ const registry = new ApiRegistry();
1581
+ for (const tool of operations) {
1582
+ const entry = {
1583
+ id: tool.operation.operationId || tool.name,
1584
+ name: tool.name,
1585
+ method: tool.operation.method,
1586
+ path: tool.operation.path,
1587
+ summary: tool.operation.summary,
1588
+ description: tool.operation.description,
1589
+ tags: tool.operation.tags,
1590
+ deprecated: tool.operation.deprecated,
1591
+ operation: tool.operation,
1592
+ components
1593
+ };
1594
+ registry.register(entry);
1595
+ }
1596
+ return registry;
1597
+ }
1598
+ function registerOndemandTools(server, registry, config) {
1599
+ server.tool(
1600
+ apiListTool.name,
1601
+ apiListTool.description,
1602
+ apiListSchema.shape,
1603
+ async (args) => {
1604
+ const result = executeApiList(registry, args);
1605
+ return { content: [{ type: "text", text: result }] };
1606
+ }
1607
+ );
1608
+ server.tool(
1609
+ apiSearchTool.name,
1610
+ apiSearchTool.description,
1611
+ apiSearchSchema.shape,
1612
+ async (args) => {
1613
+ const result = executeApiSearch(registry, args);
1614
+ return { content: [{ type: "text", text: result }] };
1615
+ }
1616
+ );
1617
+ server.tool(
1618
+ apiDetailTool.name,
1619
+ apiDetailTool.description,
1620
+ apiDetailSchema.shape,
1621
+ async (args) => {
1622
+ const result = executeApiDetail(registry, args);
1623
+ return { content: [{ type: "text", text: result }] };
1624
+ }
1625
+ );
1626
+ server.tool(
1627
+ apiExecuteTool.name,
1628
+ apiExecuteTool.description,
1629
+ apiExecuteSchema.shape,
1630
+ async (args) => {
1631
+ const result = await executeApiExecute(
1632
+ registry,
1633
+ config,
1634
+ args
1635
+ );
1636
+ return { content: [{ type: "text", text: result }] };
1637
+ }
1638
+ );
1639
+ logger.info("Registered 4 discovery tools (ondemand mode)");
1640
+ }
949
1641
  async function createServer(config) {
950
1642
  logger.info("Creating MCP server...");
1643
+ logger.info(`Mode: ${config.mode || "default"}`);
951
1644
  const server = new McpServer({
952
1645
  name: "api2mcp",
953
1646
  version: "0.1.0"
@@ -963,9 +1656,17 @@ async function createServer(config) {
963
1656
  openApiDoc.components?.schemas,
964
1657
  config.toolPrefix
965
1658
  );
966
- const toolManager = new ToolManager(server, effectiveConfig);
967
- toolManager.registerTools(tools);
968
- logger.info(`Server ready with ${toolManager.getToolCount()} tools`);
1659
+ if (config.mode === "ondemand") {
1660
+ const registry = createRegistry(tools, openApiDoc.components?.schemas);
1661
+ registerOndemandTools(server, registry, effectiveConfig);
1662
+ const stats = registry.getStats();
1663
+ logger.info(`Server ready with ${stats.totalApis} APIs in registry`);
1664
+ logger.info(`Tags: ${stats.tags.slice(0, 5).join(", ")}${stats.tags.length > 5 ? "..." : ""}`);
1665
+ } else {
1666
+ const toolManager = new ToolManager(server, effectiveConfig);
1667
+ toolManager.registerTools(tools);
1668
+ logger.info(`Server ready with ${toolManager.getToolCount()} tools`);
1669
+ }
969
1670
  return server;
970
1671
  }
971
1672
  async function startServer(config) {
@@ -991,8 +1692,21 @@ export {
991
1692
  formatResponse,
992
1693
  parseOpenApi,
993
1694
  getBaseUrl,
1695
+ ApiRegistry,
1696
+ apiDetailSchema,
1697
+ apiDetailTool,
1698
+ executeApiDetail,
1699
+ apiExecuteSchema,
1700
+ apiExecuteTool,
1701
+ executeApiExecute,
1702
+ apiListSchema,
1703
+ apiListTool,
1704
+ executeApiList,
1705
+ apiSearchSchema,
1706
+ apiSearchTool,
1707
+ executeApiSearch,
994
1708
  ToolManager,
995
1709
  createServer,
996
1710
  startServer
997
1711
  };
998
- //# sourceMappingURL=chunk-YPGEM247.mjs.map
1712
+ //# sourceMappingURL=chunk-UQT2XNCH.mjs.map