api2mcp 0.3.2 → 0.4.0-beta.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/cli.js CHANGED
@@ -161,7 +161,8 @@ function loadFromFile(workingDir = process.cwd()) {
161
161
  timeout: parsed.timeout,
162
162
  headers: parsed.headers,
163
163
  toolPrefix: parsed.toolPrefix,
164
- debug: parsed.debug
164
+ debug: parsed.debug,
165
+ mode: parsed.mode
165
166
  };
166
167
  } catch (error) {
167
168
  throw new ConfigurationError(
@@ -192,6 +193,9 @@ function loadFromCli(args) {
192
193
  if (args.debug !== void 0) {
193
194
  config.debug = args.debug;
194
195
  }
196
+ if (args.mode) {
197
+ config.mode = args.mode;
198
+ }
195
199
  return config;
196
200
  }
197
201
  function mergeConfigs(...configs) {
@@ -206,6 +210,7 @@ function mergeConfigs(...configs) {
206
210
  if (config.headers !== void 0) merged.headers = config.headers;
207
211
  if (config.toolPrefix !== void 0) merged.toolPrefix = config.toolPrefix;
208
212
  if (config.debug !== void 0) merged.debug = config.debug;
213
+ if (config.mode !== void 0) merged.mode = config.mode;
209
214
  }
210
215
  if (merged.timeout === void 0) {
211
216
  merged.timeout = DEFAULT_TIMEOUT;
@@ -230,6 +235,7 @@ function loadConfig(cliArgs = {}, env = process.env) {
230
235
  baseUrl: config.baseUrl,
231
236
  timeout: config.timeout,
232
237
  toolPrefix: config.toolPrefix,
238
+ mode: config.mode,
233
239
  debug: config.debug
234
240
  });
235
241
  return config;
@@ -693,6 +699,394 @@ function getBaseUrl(doc, overrideUrl) {
693
699
  return void 0;
694
700
  }
695
701
 
702
+ // src/registry/api-registry.ts
703
+ var DEFAULT_PAGE_SIZE = 20;
704
+ var DEFAULT_SEARCH_LIMIT = 50;
705
+ var ApiRegistry = class {
706
+ apis = /* @__PURE__ */ new Map();
707
+ nameIndex = /* @__PURE__ */ new Map();
708
+ // name -> id
709
+ tagIndex = /* @__PURE__ */ new Map();
710
+ // tag -> set of ids
711
+ /**
712
+ * 注册 API
713
+ */
714
+ register(entry) {
715
+ if (this.apis.has(entry.id)) {
716
+ logger.warn(`API already registered: ${entry.id}, overwriting`);
717
+ }
718
+ this.apis.set(entry.id, entry);
719
+ this.nameIndex.set(entry.name, entry.id);
720
+ if (entry.tags) {
721
+ for (const tag of entry.tags) {
722
+ if (!this.tagIndex.has(tag)) {
723
+ this.tagIndex.set(tag, /* @__PURE__ */ new Set());
724
+ }
725
+ this.tagIndex.get(tag)?.add(entry.id);
726
+ }
727
+ }
728
+ logger.debug(`Registered API: ${entry.id}`);
729
+ }
730
+ /**
731
+ * 批量注册 API
732
+ */
733
+ registerAll(entries) {
734
+ for (const entry of entries) {
735
+ this.register(entry);
736
+ }
737
+ logger.info(`Registered ${entries.length} APIs in registry`);
738
+ }
739
+ /**
740
+ * 获取单个 API
741
+ */
742
+ get(id) {
743
+ return this.apis.get(id);
744
+ }
745
+ /**
746
+ * 通过名称获取 API
747
+ */
748
+ getByName(name) {
749
+ const id = this.nameIndex.get(name);
750
+ return id ? this.apis.get(id) : void 0;
751
+ }
752
+ /**
753
+ * 检查 API 是否存在
754
+ */
755
+ has(id) {
756
+ return this.apis.has(id);
757
+ }
758
+ /**
759
+ * 搜索 API
760
+ */
761
+ search(options) {
762
+ const {
763
+ query,
764
+ searchIn = ["name", "summary", "description", "path"],
765
+ limit = DEFAULT_SEARCH_LIMIT
766
+ } = options;
767
+ const normalizedQuery = query.toLowerCase().trim();
768
+ const results = [];
769
+ for (const entry of this.apis.values()) {
770
+ const matchedFields = [];
771
+ let score = 0;
772
+ if (searchIn.includes("name") && entry.name) {
773
+ const nameLower = entry.name.toLowerCase();
774
+ if (nameLower.includes(normalizedQuery)) {
775
+ matchedFields.push("name");
776
+ score += nameLower === normalizedQuery ? 1 : 0.8;
777
+ }
778
+ }
779
+ if (searchIn.includes("summary") && entry.summary) {
780
+ const summaryLower = entry.summary.toLowerCase();
781
+ if (summaryLower.includes(normalizedQuery)) {
782
+ matchedFields.push("summary");
783
+ score += 0.6;
784
+ }
785
+ }
786
+ if (searchIn.includes("description") && entry.description) {
787
+ const descLower = entry.description.toLowerCase();
788
+ if (descLower.includes(normalizedQuery)) {
789
+ matchedFields.push("description");
790
+ score += 0.4;
791
+ }
792
+ }
793
+ if (searchIn.includes("path") && entry.path) {
794
+ const pathLower = entry.path.toLowerCase();
795
+ if (pathLower.includes(normalizedQuery)) {
796
+ matchedFields.push("path");
797
+ score += 0.5;
798
+ }
799
+ }
800
+ if (matchedFields.length > 0) {
801
+ results.push({
802
+ id: entry.id,
803
+ name: entry.name,
804
+ method: entry.method,
805
+ path: entry.path,
806
+ summary: entry.summary,
807
+ matchedFields,
808
+ score: Math.min(score, 1)
809
+ });
810
+ }
811
+ }
812
+ results.sort((a, b) => b.score - a.score);
813
+ return results.slice(0, limit);
814
+ }
815
+ /**
816
+ * 分页列出 API
817
+ */
818
+ list(options = {}) {
819
+ const { page = 1, pageSize = DEFAULT_PAGE_SIZE, tag } = options;
820
+ let entries;
821
+ if (tag) {
822
+ const ids = this.tagIndex.get(tag);
823
+ entries = ids ? Array.from(ids).map((id) => this.apis.get(id)).filter((entry) => entry !== void 0) : [];
824
+ } else {
825
+ entries = Array.from(this.apis.values());
826
+ }
827
+ const total = entries.length;
828
+ const totalPages = Math.ceil(total / pageSize);
829
+ const startIndex = (page - 1) * pageSize;
830
+ const endIndex = startIndex + pageSize;
831
+ const pageEntries = entries.slice(startIndex, endIndex);
832
+ const items = pageEntries.map((entry) => ({
833
+ id: entry.id,
834
+ name: entry.name,
835
+ method: entry.method,
836
+ path: entry.path,
837
+ summary: entry.summary,
838
+ tags: entry.tags,
839
+ deprecated: entry.deprecated
840
+ }));
841
+ return {
842
+ page,
843
+ pageSize,
844
+ total,
845
+ totalPages,
846
+ items
847
+ };
848
+ }
849
+ /**
850
+ * 获取所有标签
851
+ */
852
+ getTags() {
853
+ return Array.from(this.tagIndex.keys()).sort();
854
+ }
855
+ /**
856
+ * 获取 API 详情
857
+ */
858
+ getDetail(id) {
859
+ const entry = this.apis.get(id);
860
+ if (!entry) {
861
+ return void 0;
862
+ }
863
+ return {
864
+ ...entry,
865
+ parameterSchema: this.buildParameterSchema(entry),
866
+ requestBodySchema: this.buildRequestBodySchema(entry),
867
+ responseSchemas: this.buildResponseSchemas(entry)
868
+ };
869
+ }
870
+ /**
871
+ * 获取统计信息
872
+ */
873
+ getStats() {
874
+ const byMethod = {};
875
+ const byTag = {};
876
+ for (const entry of this.apis.values()) {
877
+ const method = entry.method.toUpperCase();
878
+ byMethod[method] = (byMethod[method] || 0) + 1;
879
+ if (entry.tags) {
880
+ for (const tag of entry.tags) {
881
+ byTag[tag] = (byTag[tag] || 0) + 1;
882
+ }
883
+ }
884
+ }
885
+ return {
886
+ totalApis: this.apis.size,
887
+ tags: this.getTags(),
888
+ byMethod,
889
+ byTag
890
+ };
891
+ }
892
+ /**
893
+ * 获取 API 数量
894
+ */
895
+ get size() {
896
+ return this.apis.size;
897
+ }
898
+ /**
899
+ * 构建参数 Schema
900
+ */
901
+ buildParameterSchema(entry) {
902
+ const { operation } = entry;
903
+ if (!operation.parameters || operation.parameters.length === 0) {
904
+ return void 0;
905
+ }
906
+ const properties = {};
907
+ const required = [];
908
+ for (const param of operation.parameters) {
909
+ const paramName = param.name;
910
+ properties[paramName] = {
911
+ ...param.schema,
912
+ description: param.description,
913
+ in: param.in
914
+ };
915
+ if (param.required) {
916
+ required.push(paramName);
917
+ }
918
+ }
919
+ return {
920
+ type: "object",
921
+ properties,
922
+ required: required.length > 0 ? required : void 0
923
+ };
924
+ }
925
+ /**
926
+ * 构建请求体 Schema
927
+ */
928
+ buildRequestBodySchema(entry) {
929
+ const { operation } = entry;
930
+ if (!operation.requestBody) {
931
+ return void 0;
932
+ }
933
+ const jsonContent = operation.requestBody.content["application/json"];
934
+ if (!jsonContent?.schema) {
935
+ return void 0;
936
+ }
937
+ return {
938
+ ...jsonContent.schema,
939
+ description: operation.requestBody.description,
940
+ required: operation.requestBody.required
941
+ };
942
+ }
943
+ /**
944
+ * 构建响应 Schema
945
+ */
946
+ buildResponseSchemas(entry) {
947
+ const { operation } = entry;
948
+ if (!operation.responses) {
949
+ return void 0;
950
+ }
951
+ const schemas = {};
952
+ for (const [status, response] of Object.entries(operation.responses)) {
953
+ const jsonContent = response.content?.["application/json"];
954
+ schemas[status] = {
955
+ description: response.description,
956
+ schema: jsonContent?.schema
957
+ };
958
+ }
959
+ return schemas;
960
+ }
961
+ };
962
+
963
+ // src/tools/discovery/api-detail.ts
964
+ var import_zod3 = require("zod");
965
+ var apiDetailSchema = import_zod3.z.object({
966
+ id: import_zod3.z.string().min(1).describe("API ID\uFF08operationId \u6216\u5DE5\u5177\u540D\u79F0\uFF09")
967
+ });
968
+ var apiDetailTool = {
969
+ name: "api_detail",
970
+ description: `\u83B7\u53D6 API \u7684\u8BE6\u7EC6\u4FE1\u606F\u3002
971
+
972
+ \u4F7F\u7528\u573A\u666F\uFF1A
973
+ - \u67E5\u770B\u67D0\u4E2A API \u7684\u5B8C\u6574\u53C2\u6570\u5B9A\u4E49
974
+ - \u4E86\u89E3\u8BF7\u6C42\u4F53\u548C\u54CD\u5E94\u7684\u7ED3\u6784
975
+ - \u5728\u8C03\u7528 API \u524D\u4E86\u89E3\u9700\u8981\u54EA\u4E9B\u53C2\u6570
976
+
977
+ \u8FD4\u56DE\u5185\u5BB9\u5305\u62EC\uFF1A
978
+ - API \u57FA\u672C\u4FE1\u606F\uFF08\u65B9\u6CD5\u3001\u8DEF\u5F84\u3001\u63CF\u8FF0\uFF09
979
+ - \u53C2\u6570 Schema\uFF08\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u5934\u53C2\u6570\uFF09
980
+ - \u8BF7\u6C42\u4F53 Schema
981
+ - \u54CD\u5E94 Schema`,
982
+ inputSchema: apiDetailSchema
983
+ };
984
+ function formatSchema(schema, indent = 0) {
985
+ if (!schema) return "\u65E0";
986
+ const spaces = " ".repeat(indent);
987
+ const lines = [];
988
+ if (schema.type) {
989
+ lines.push(`${spaces}\u7C7B\u578B: ${schema.type}`);
990
+ }
991
+ if (schema.description) {
992
+ lines.push(`${spaces}\u63CF\u8FF0: ${schema.description}`);
993
+ }
994
+ if (schema.properties) {
995
+ lines.push(`${spaces}\u5C5E\u6027:`);
996
+ const props = schema.properties;
997
+ const required = schema.required || [];
998
+ for (const [name, prop] of Object.entries(props)) {
999
+ const isRequired = required.includes(name);
1000
+ const reqTag = isRequired ? " (\u5FC5\u586B)" : " (\u53EF\u9009)";
1001
+ const propType = prop.type || "unknown";
1002
+ const propDesc = prop.description ? ` - ${prop.description}` : "";
1003
+ lines.push(`${spaces} - ${name}${reqTag}: ${propType}${propDesc}`);
1004
+ if (prop.properties) {
1005
+ lines.push(formatSchema(prop, indent + 2));
1006
+ }
1007
+ }
1008
+ }
1009
+ if (schema.enum) {
1010
+ lines.push(`${spaces}\u679A\u4E3E\u503C: ${schema.enum.join(", ")}`);
1011
+ }
1012
+ if (schema.example !== void 0) {
1013
+ lines.push(`${spaces}\u793A\u4F8B: ${JSON.stringify(schema.example)}`);
1014
+ }
1015
+ return lines.join("\n");
1016
+ }
1017
+ function executeApiDetail(registry, input) {
1018
+ const { id } = input;
1019
+ logger.debug(`Executing api_detail: id=${id}`);
1020
+ const detail = registry.getDetail(id);
1021
+ if (!detail) {
1022
+ const byName = registry.getByName(id);
1023
+ if (byName) {
1024
+ return executeApiDetail(registry, { id: byName.id });
1025
+ }
1026
+ return `\u9519\u8BEF: \u627E\u4E0D\u5230 API "${id}"
1027
+
1028
+ \u8BF7\u4F7F\u7528 api_search \u641C\u7D22\u53EF\u7528\u7684 API\u3002`;
1029
+ }
1030
+ const lines = [];
1031
+ lines.push(`## API: ${detail.name}`);
1032
+ lines.push("");
1033
+ const methodBadge = `[${detail.method.toUpperCase()}]`;
1034
+ const deprecatedTag = detail.deprecated ? " \u26A0\uFE0F \u5DF2\u5E9F\u5F03" : "";
1035
+ lines.push(`**${methodBadge}** \`${detail.path}\`${deprecatedTag}`);
1036
+ lines.push("");
1037
+ if (detail.summary) {
1038
+ lines.push(`**\u6458\u8981**: ${detail.summary}`);
1039
+ lines.push("");
1040
+ }
1041
+ if (detail.description) {
1042
+ lines.push(`**\u63CF\u8FF0**: ${detail.description}`);
1043
+ lines.push("");
1044
+ }
1045
+ if (detail.tags && detail.tags.length > 0) {
1046
+ lines.push(`**\u6807\u7B7E**: ${detail.tags.map((t) => `\`${t}\``).join(", ")}`);
1047
+ lines.push("");
1048
+ }
1049
+ lines.push("### \u53C2\u6570");
1050
+ lines.push("");
1051
+ if (detail.parameterSchema) {
1052
+ lines.push(formatSchema(detail.parameterSchema));
1053
+ } else {
1054
+ lines.push("\u65E0\u53C2\u6570");
1055
+ }
1056
+ lines.push("");
1057
+ if (detail.requestBodySchema) {
1058
+ lines.push("### \u8BF7\u6C42\u4F53");
1059
+ lines.push("");
1060
+ lines.push(formatSchema(detail.requestBodySchema));
1061
+ lines.push("");
1062
+ }
1063
+ if (detail.responseSchemas && Object.keys(detail.responseSchemas).length > 0) {
1064
+ lines.push("### \u54CD\u5E94");
1065
+ lines.push("");
1066
+ for (const [status, resp] of Object.entries(detail.responseSchemas)) {
1067
+ lines.push(`#### \u72B6\u6001\u7801: ${status}`);
1068
+ if (resp.description) {
1069
+ lines.push(`${resp.description}`);
1070
+ }
1071
+ if (resp.schema) {
1072
+ lines.push(formatSchema(resp.schema, 1));
1073
+ }
1074
+ lines.push("");
1075
+ }
1076
+ }
1077
+ lines.push("---");
1078
+ lines.push("### \u8C03\u7528\u65B9\u5F0F");
1079
+ lines.push("");
1080
+ lines.push("\u4F7F\u7528 api_execute \u5DE5\u5177\u8C03\u7528\u6B64 API:");
1081
+ lines.push("```");
1082
+ lines.push(`api_execute(operationId="${detail.id}", parameters={...})`);
1083
+ lines.push("```");
1084
+ return lines.join("\n");
1085
+ }
1086
+
1087
+ // src/tools/discovery/api-execute.ts
1088
+ var import_zod4 = require("zod");
1089
+
696
1090
  // src/executor/request-builder.ts
697
1091
  function groupParametersByLocation(parameters) {
698
1092
  const groups = {
@@ -884,6 +1278,182 @@ function formatResponse(response) {
884
1278
  return lines.join("\n");
885
1279
  }
886
1280
 
1281
+ // src/tools/discovery/api-execute.ts
1282
+ var apiExecuteSchema = import_zod4.z.object({
1283
+ operationId: import_zod4.z.string().min(1).describe("API ID\uFF08operationId \u6216\u5DE5\u5177\u540D\u79F0\uFF09"),
1284
+ 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"),
1285
+ _baseUrl: import_zod4.z.string().url().optional().describe("API base URL\uFF08\u53EF\u9009\uFF0C\u8986\u76D6\u9ED8\u8BA4\u914D\u7F6E\uFF09")
1286
+ });
1287
+ var apiExecuteTool = {
1288
+ name: "api_execute",
1289
+ description: `\u6267\u884C API \u8C03\u7528\u3002
1290
+
1291
+ \u4F7F\u7528\u573A\u666F\uFF1A
1292
+ - \u76F4\u63A5\u8C03\u7528\u5DF2\u77E5\u7684 API
1293
+ - \u4F7F\u7528 api_search \u6216 api_list \u627E\u5230 API \u540E\u6267\u884C\u8C03\u7528
1294
+
1295
+ \u4F7F\u7528\u6B65\u9AA4\uFF1A
1296
+ 1. \u5148\u4F7F\u7528 api_search \u6216 api_list \u627E\u5230\u9700\u8981\u7684 API
1297
+ 2. \u4F7F\u7528 api_detail \u67E5\u770B\u53C2\u6570\u8981\u6C42
1298
+ 3. \u4F7F\u7528 api_execute \u6267\u884C\u8C03\u7528
1299
+
1300
+ \u53C2\u6570\u8BF4\u660E\uFF1A
1301
+ - operationId: API \u7684\u552F\u4E00\u6807\u8BC6\u7B26
1302
+ - parameters: \u5305\u542B\u8DEF\u5F84\u53C2\u6570\u3001\u67E5\u8BE2\u53C2\u6570\u3001\u8BF7\u6C42\u4F53\u7B49
1303
+ - \u8DEF\u5F84\u53C2\u6570: URL \u8DEF\u5F84\u4E2D\u7684\u53C2\u6570 (\u5982 /users/{id} \u4E2D\u7684 id)
1304
+ - \u67E5\u8BE2\u53C2\u6570: URL \u95EE\u53F7\u540E\u7684\u53C2\u6570
1305
+ - body: \u8BF7\u6C42\u4F53\uFF08JSON \u5BF9\u8C61\uFF09`,
1306
+ inputSchema: apiExecuteSchema
1307
+ };
1308
+ async function executeApiExecute(registry, config, input) {
1309
+ const { operationId, parameters = {}, _baseUrl } = input;
1310
+ logger.debug(`Executing api_execute: operationId=${operationId}`);
1311
+ let apiEntry = registry.get(operationId);
1312
+ if (!apiEntry) {
1313
+ apiEntry = registry.getByName(operationId);
1314
+ }
1315
+ if (!apiEntry) {
1316
+ return `\u9519\u8BEF: \u627E\u4E0D\u5230 API "${operationId}"
1317
+
1318
+ \u8BF7\u4F7F\u7528 api_search \u641C\u7D22\u53EF\u7528\u7684 API\u3002`;
1319
+ }
1320
+ if (apiEntry.deprecated) {
1321
+ logger.warn(`API ${operationId} is deprecated`);
1322
+ }
1323
+ try {
1324
+ const executionConfig = {
1325
+ ...config,
1326
+ baseUrl: _baseUrl || config.baseUrl
1327
+ };
1328
+ if (!executionConfig.baseUrl) {
1329
+ return `\u9519\u8BEF: \u6CA1\u6709\u914D\u7F6E base URL
1330
+
1331
+ \u8BF7\u901A\u8FC7\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00\u63D0\u4F9B base URL:
1332
+ 1. \u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E baseUrl
1333
+ 2. \u542F\u52A8\u65F6\u4F7F\u7528 --base-url \u53C2\u6570
1334
+ 3. \u8C03\u7528\u65F6\u63D0\u4F9B _baseUrl \u53C2\u6570`;
1335
+ }
1336
+ logger.info(`Executing API: ${apiEntry.method} ${apiEntry.path}`);
1337
+ const response = await executeRequest(apiEntry.operation, parameters, executionConfig);
1338
+ const formattedResponse = formatResponse(response);
1339
+ return formattedResponse;
1340
+ } catch (error) {
1341
+ const errorMessage = error instanceof ToolExecutionError ? `\u9519\u8BEF: ${error.message}` : `\u9519\u8BEF: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`;
1342
+ logger.error(`API execution failed: ${operationId}`, error);
1343
+ return errorMessage;
1344
+ }
1345
+ }
1346
+
1347
+ // src/tools/discovery/api-list.ts
1348
+ var import_zod5 = require("zod");
1349
+ var apiListSchema = import_zod5.z.object({
1350
+ page: import_zod5.z.number().int().min(1).default(1).describe("\u9875\u7801\uFF08\u4ECE 1 \u5F00\u59CB\uFF09"),
1351
+ pageSize: import_zod5.z.number().int().min(1).max(100).default(20).describe("\u6BCF\u9875\u6570\u91CF\uFF081-100\uFF09"),
1352
+ tag: import_zod5.z.string().optional().describe("\u6309\u6807\u7B7E\u8FC7\u6EE4")
1353
+ });
1354
+ var apiListTool = {
1355
+ name: "api_list",
1356
+ description: `\u5206\u9875\u6D4F\u89C8\u6240\u6709\u53EF\u7528\u7684 API\u3002
1357
+
1358
+ \u4F7F\u7528\u573A\u666F\uFF1A
1359
+ - \u67E5\u770B\u6709\u54EA\u4E9B API \u53EF\u7528
1360
+ - \u6309\u6807\u7B7E\u8FC7\u6EE4 API
1361
+ - \u6D4F\u89C8 API \u5217\u8868\u4EE5\u627E\u5230\u9700\u8981\u7684\u63A5\u53E3
1362
+
1363
+ \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`,
1364
+ inputSchema: apiListSchema
1365
+ };
1366
+ function executeApiList(registry, input) {
1367
+ const { page, pageSize, tag } = input;
1368
+ logger.debug(`Executing api_list: page=${page}, pageSize=${pageSize}, tag=${tag}`);
1369
+ const result = registry.list({ page, pageSize, tag });
1370
+ const lines = [];
1371
+ lines.push(`## API \u5217\u8868 (${result.total} \u4E2A API)`);
1372
+ lines.push(`\u9875\u7801: ${result.page}/${result.totalPages}`);
1373
+ if (tag) {
1374
+ lines.push(`\u6807\u7B7E\u8FC7\u6EE4: ${tag}`);
1375
+ }
1376
+ lines.push("");
1377
+ if (result.items.length === 0) {
1378
+ lines.push("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 API\u3002");
1379
+ return lines.join("\n");
1380
+ }
1381
+ for (const item of result.items) {
1382
+ const methodBadge = `[${item.method.toUpperCase().padEnd(6)}]`;
1383
+ const deprecatedTag = item.deprecated ? " [\u5DF2\u5E9F\u5F03]" : "";
1384
+ const tags = item.tags ? ` (${item.tags.join(", ")})` : "";
1385
+ lines.push(`### ${item.id}`);
1386
+ lines.push(`${methodBadge} ${item.path}${deprecatedTag}${tags}`);
1387
+ if (item.summary) {
1388
+ lines.push(`${item.summary}`);
1389
+ }
1390
+ lines.push("");
1391
+ }
1392
+ if (result.totalPages > 1) {
1393
+ lines.push("---");
1394
+ if (result.page < result.totalPages) {
1395
+ lines.push(`\u4F7F\u7528 page=${result.page + 1} \u67E5\u770B\u4E0B\u4E00\u9875`);
1396
+ }
1397
+ }
1398
+ return lines.join("\n");
1399
+ }
1400
+
1401
+ // src/tools/discovery/api-search.ts
1402
+ var import_zod6 = require("zod");
1403
+ var apiSearchSchema = import_zod6.z.object({
1404
+ query: import_zod6.z.string().min(1).describe("\u641C\u7D22\u5173\u952E\u8BCD"),
1405
+ 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"),
1406
+ limit: import_zod6.z.number().int().min(1).max(100).optional().default(20).describe("\u6700\u5927\u8FD4\u56DE\u6570\u91CF\uFF081-100\uFF09")
1407
+ });
1408
+ var apiSearchTool = {
1409
+ name: "api_search",
1410
+ description: `\u641C\u7D22 API\u3002
1411
+
1412
+ \u4F7F\u7528\u573A\u666F\uFF1A
1413
+ - \u6839\u636E\u5173\u952E\u8BCD\u5FEB\u901F\u627E\u5230\u76F8\u5173 API
1414
+ - \u641C\u7D22\u7279\u5B9A\u529F\u80FD\u6216\u8D44\u6E90\u7684\u63A5\u53E3
1415
+ - \u67E5\u627E\u5305\u542B\u7279\u5B9A\u8DEF\u5F84\u6BB5\u7684 API
1416
+
1417
+ \u641C\u7D22\u8303\u56F4\u5305\u62EC\uFF1AAPI \u540D\u79F0\u3001\u6458\u8981\u3001\u63CF\u8FF0\u3001\u8DEF\u5F84\u3002
1418
+ \u7ED3\u679C\u6309\u5339\u914D\u5EA6\u6392\u5E8F\uFF0C\u6700\u5339\u914D\u7684\u6392\u5728\u524D\u9762\u3002`,
1419
+ inputSchema: apiSearchSchema
1420
+ };
1421
+ function executeApiSearch(registry, input) {
1422
+ const { query, searchIn, limit } = input;
1423
+ logger.debug(
1424
+ `Executing api_search: query="${query}", searchIn=${searchIn?.join(",")}, limit=${limit}`
1425
+ );
1426
+ const results = registry.search({ query, searchIn, limit });
1427
+ const lines = [];
1428
+ lines.push(`## \u641C\u7D22\u7ED3\u679C: "${query}"`);
1429
+ lines.push(`\u627E\u5230 ${results.length} \u4E2A\u5339\u914D\u7684 API`);
1430
+ lines.push("");
1431
+ if (results.length === 0) {
1432
+ lines.push("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 API\u3002");
1433
+ lines.push("");
1434
+ lines.push("\u5EFA\u8BAE\uFF1A");
1435
+ lines.push("- \u5C1D\u8BD5\u4F7F\u7528\u4E0D\u540C\u7684\u5173\u952E\u8BCD");
1436
+ lines.push("- \u68C0\u67E5\u62FC\u5199\u662F\u5426\u6B63\u786E");
1437
+ lines.push("- \u4F7F\u7528\u66F4\u901A\u7528\u7684\u641C\u7D22\u8BCD");
1438
+ return lines.join("\n");
1439
+ }
1440
+ for (const item of results) {
1441
+ const methodBadge = `[${item.method.toUpperCase().padEnd(6)}]`;
1442
+ const matchInfo = `\u5339\u914D\u5B57\u6BB5: ${item.matchedFields.join(", ")}`;
1443
+ lines.push(`### ${item.id}`);
1444
+ lines.push(`${methodBadge} ${item.path}`);
1445
+ if (item.summary) {
1446
+ lines.push(`${item.summary}`);
1447
+ }
1448
+ lines.push(`_${matchInfo}_ (\u76F8\u5173\u5EA6: ${Math.round(item.score * 100)}%)`);
1449
+ lines.push("");
1450
+ }
1451
+ lines.push("---");
1452
+ lines.push("\u4F7F\u7528 api_detail <id> \u67E5\u770B API \u8BE6\u60C5");
1453
+ lines.push("\u4F7F\u7528 api_execute <id> <parameters> \u6267\u884C API");
1454
+ return lines.join("\n");
1455
+ }
1456
+
887
1457
  // src/server/tool-manager.ts
888
1458
  var ToolManager = class {
889
1459
  tools = /* @__PURE__ */ new Map();
@@ -920,6 +1490,23 @@ var ToolManager = class {
920
1490
  }
921
1491
  logger.info(`Registered ${tools.length} tools`);
922
1492
  }
1493
+ /**
1494
+ * 获取工具
1495
+ */
1496
+ getTool(name) {
1497
+ return this.tools.get(name);
1498
+ }
1499
+ /**
1500
+ * 通过 operationId 获取工具
1501
+ */
1502
+ getToolByOperationId(operationId) {
1503
+ for (const tool of this.tools.values()) {
1504
+ if (tool.operation.operationId === operationId) {
1505
+ return tool;
1506
+ }
1507
+ }
1508
+ return void 0;
1509
+ }
923
1510
  /**
924
1511
  * 执行工具
925
1512
  */
@@ -965,6 +1552,40 @@ var ToolManager = class {
965
1552
  };
966
1553
  }
967
1554
  }
1555
+ /**
1556
+ * 通过 operation 直接执行(用于 ondemand 模式)
1557
+ */
1558
+ async executeByOperation(operation, args) {
1559
+ try {
1560
+ logger.debug(`Executing operation: ${operation.operationId || operation.path}`, args);
1561
+ const { _baseUrl, ...restArgs } = args;
1562
+ const executionConfig = {
1563
+ ...this.config,
1564
+ baseUrl: typeof _baseUrl === "string" ? _baseUrl : this.config.baseUrl
1565
+ };
1566
+ const response = await executeRequest(operation, restArgs, executionConfig);
1567
+ const formattedResponse = formatResponse(response);
1568
+ return {
1569
+ content: [
1570
+ {
1571
+ type: "text",
1572
+ text: formattedResponse
1573
+ }
1574
+ ]
1575
+ };
1576
+ } catch (error) {
1577
+ const errorMessage = error instanceof ToolExecutionError ? `Error: ${error.message}` : `Error: ${error instanceof Error ? error.message : "Unknown error"}`;
1578
+ logger.error(`Operation execution failed: ${operation.operationId || operation.path}`, error);
1579
+ return {
1580
+ content: [
1581
+ {
1582
+ type: "text",
1583
+ text: errorMessage
1584
+ }
1585
+ ]
1586
+ };
1587
+ }
1588
+ }
968
1589
  /**
969
1590
  * 获取所有已注册的工具名称
970
1591
  */
@@ -977,11 +1598,80 @@ var ToolManager = class {
977
1598
  getToolCount() {
978
1599
  return this.tools.size;
979
1600
  }
1601
+ /**
1602
+ * 获取配置
1603
+ */
1604
+ getConfig() {
1605
+ return this.config;
1606
+ }
980
1607
  };
981
1608
 
982
1609
  // src/server/index.ts
1610
+ function createRegistry(operations, components) {
1611
+ const registry = new ApiRegistry();
1612
+ for (const tool of operations) {
1613
+ const entry = {
1614
+ id: tool.operation.operationId || tool.name,
1615
+ name: tool.name,
1616
+ method: tool.operation.method,
1617
+ path: tool.operation.path,
1618
+ summary: tool.operation.summary,
1619
+ description: tool.operation.description,
1620
+ tags: tool.operation.tags,
1621
+ deprecated: tool.operation.deprecated,
1622
+ operation: tool.operation,
1623
+ components
1624
+ };
1625
+ registry.register(entry);
1626
+ }
1627
+ return registry;
1628
+ }
1629
+ function registerOndemandTools(server, registry, config) {
1630
+ server.tool(
1631
+ apiListTool.name,
1632
+ apiListTool.description,
1633
+ apiListSchema.shape,
1634
+ async (args) => {
1635
+ const result = executeApiList(registry, args);
1636
+ return { content: [{ type: "text", text: result }] };
1637
+ }
1638
+ );
1639
+ server.tool(
1640
+ apiSearchTool.name,
1641
+ apiSearchTool.description,
1642
+ apiSearchSchema.shape,
1643
+ async (args) => {
1644
+ const result = executeApiSearch(registry, args);
1645
+ return { content: [{ type: "text", text: result }] };
1646
+ }
1647
+ );
1648
+ server.tool(
1649
+ apiDetailTool.name,
1650
+ apiDetailTool.description,
1651
+ apiDetailSchema.shape,
1652
+ async (args) => {
1653
+ const result = executeApiDetail(registry, args);
1654
+ return { content: [{ type: "text", text: result }] };
1655
+ }
1656
+ );
1657
+ server.tool(
1658
+ apiExecuteTool.name,
1659
+ apiExecuteTool.description,
1660
+ apiExecuteSchema.shape,
1661
+ async (args) => {
1662
+ const result = await executeApiExecute(
1663
+ registry,
1664
+ config,
1665
+ args
1666
+ );
1667
+ return { content: [{ type: "text", text: result }] };
1668
+ }
1669
+ );
1670
+ logger.info("Registered 4 discovery tools (ondemand mode)");
1671
+ }
983
1672
  async function createServer(config) {
984
1673
  logger.info("Creating MCP server...");
1674
+ logger.info(`Mode: ${config.mode || "default"}`);
985
1675
  const server = new import_mcp.McpServer({
986
1676
  name: "api2mcp",
987
1677
  version: "0.1.0"
@@ -997,9 +1687,17 @@ async function createServer(config) {
997
1687
  openApiDoc.components?.schemas,
998
1688
  config.toolPrefix
999
1689
  );
1000
- const toolManager = new ToolManager(server, effectiveConfig);
1001
- toolManager.registerTools(tools);
1002
- logger.info(`Server ready with ${toolManager.getToolCount()} tools`);
1690
+ if (config.mode === "ondemand") {
1691
+ const registry = createRegistry(tools, openApiDoc.components?.schemas);
1692
+ registerOndemandTools(server, registry, effectiveConfig);
1693
+ const stats = registry.getStats();
1694
+ logger.info(`Server ready with ${stats.totalApis} APIs in registry`);
1695
+ logger.info(`Tags: ${stats.tags.slice(0, 5).join(", ")}${stats.tags.length > 5 ? "..." : ""}`);
1696
+ } else {
1697
+ const toolManager = new ToolManager(server, effectiveConfig);
1698
+ toolManager.registerTools(tools);
1699
+ logger.info(`Server ready with ${toolManager.getToolCount()} tools`);
1700
+ }
1003
1701
  return server;
1004
1702
  }
1005
1703
  async function startServer(config) {
@@ -1011,7 +1709,11 @@ async function startServer(config) {
1011
1709
 
1012
1710
  // src/cli.ts
1013
1711
  var program = new import_commander.Command();
1014
- program.name("api2mcp").description("Convert OpenAPI specifications to MCP tools").version("0.1.0").option("-u, --url <url>", "OpenAPI document URL or file path").option("-b, --base-url <url>", "API base URL (overrides OpenAPI servers)").option("-t, --timeout <ms>", "Request timeout in milliseconds", parseInt).option("-h, --headers <json>", "Custom headers as JSON string").option("-p, --prefix <prefix>", "Tool name prefix").option("-d, --debug", "Enable debug mode", false).action(async (options) => {
1712
+ program.name("api2mcp").description("Convert OpenAPI specifications to MCP tools").version("0.1.0").option("-u, --url <url>", "OpenAPI document URL or file path").option("-b, --base-url <url>", "API base URL (overrides OpenAPI servers)").option("-t, --timeout <ms>", "Request timeout in milliseconds", parseInt).option("-h, --headers <json>", "Custom headers as JSON string").option("-p, --prefix <prefix>", "Tool name prefix").option(
1713
+ "-m, --mode <mode>",
1714
+ "Working mode: default (all APIs as tools) or ondemand (discovery tools)",
1715
+ "default"
1716
+ ).option("-d, --debug", "Enable debug mode", false).action(async (options) => {
1015
1717
  let config;
1016
1718
  try {
1017
1719
  config = loadConfig({
@@ -1020,6 +1722,7 @@ program.name("api2mcp").description("Convert OpenAPI specifications to MCP tools
1020
1722
  timeout: options.timeout,
1021
1723
  headers: options.headers,
1022
1724
  prefix: options.prefix,
1725
+ mode: options.mode,
1023
1726
  debug: options.debug
1024
1727
  });
1025
1728
  logger.info(`Starting api2mcp...`);
@@ -1027,6 +1730,7 @@ program.name("api2mcp").description("Convert OpenAPI specifications to MCP tools
1027
1730
  if (config.baseUrl) {
1028
1731
  logger.info(`Base URL: ${config.baseUrl}`);
1029
1732
  }
1733
+ logger.info(`Mode: ${config.mode || "default"}`);
1030
1734
  await startServer(config);
1031
1735
  } catch (error) {
1032
1736
  if (error instanceof ConfigurationError) {