api2mcp 0.0.1 → 0.2.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/README.md +138 -1
- package/bin/api2mcp.js +2 -0
- package/dist/chunk-CVWZJCLP.mjs +992 -0
- package/dist/chunk-CVWZJCLP.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1042 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +47 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +378 -0
- package/dist/index.d.ts +378 -0
- package/dist/index.js +1049 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +41 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +72 -9
package/dist/index.js
ADDED
|
@@ -0,0 +1,1049 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Api2McpError: () => Api2McpError,
|
|
34
|
+
ConfigurationError: () => ConfigurationError,
|
|
35
|
+
HttpError: () => HttpError,
|
|
36
|
+
OpenApiParseError: () => OpenApiParseError,
|
|
37
|
+
ToolExecutionError: () => ToolExecutionError,
|
|
38
|
+
ToolManager: () => ToolManager,
|
|
39
|
+
convertSchema: () => convertSchema,
|
|
40
|
+
createRefResolver: () => createRefResolver,
|
|
41
|
+
createServer: () => createServer,
|
|
42
|
+
executeRequest: () => executeRequest,
|
|
43
|
+
formatResponse: () => formatResponse,
|
|
44
|
+
generateTool: () => generateTool,
|
|
45
|
+
generateTools: () => generateTools,
|
|
46
|
+
getBaseUrl: () => getBaseUrl,
|
|
47
|
+
loadConfig: () => loadConfig,
|
|
48
|
+
logger: () => logger,
|
|
49
|
+
parseOpenApi: () => parseOpenApi,
|
|
50
|
+
startServer: () => startServer
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(src_exports);
|
|
53
|
+
|
|
54
|
+
// src/config/loader.ts
|
|
55
|
+
var import_node_fs = require("fs");
|
|
56
|
+
var import_node_path = require("path");
|
|
57
|
+
|
|
58
|
+
// src/utils/error.ts
|
|
59
|
+
var Api2McpError = class extends Error {
|
|
60
|
+
constructor(message, code, cause) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.code = code;
|
|
63
|
+
this.cause = cause;
|
|
64
|
+
this.name = "Api2McpError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var ConfigurationError = class extends Api2McpError {
|
|
68
|
+
constructor(message, cause) {
|
|
69
|
+
super(message, "CONFIGURATION_ERROR", cause);
|
|
70
|
+
this.name = "ConfigurationError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var OpenApiParseError = class extends Api2McpError {
|
|
74
|
+
constructor(message, cause) {
|
|
75
|
+
super(message, "OPENAPI_PARSE_ERROR", cause);
|
|
76
|
+
this.name = "OpenApiParseError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var ToolExecutionError = class extends Api2McpError {
|
|
80
|
+
constructor(message, toolName, cause) {
|
|
81
|
+
super(message, "TOOL_EXECUTION_ERROR", cause);
|
|
82
|
+
this.toolName = toolName;
|
|
83
|
+
this.name = "ToolExecutionError";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var HttpError = class extends Api2McpError {
|
|
87
|
+
constructor(message, statusCode, responseBody, cause) {
|
|
88
|
+
super(message, "HTTP_ERROR", cause);
|
|
89
|
+
this.statusCode = statusCode;
|
|
90
|
+
this.responseBody = responseBody;
|
|
91
|
+
this.name = "HttpError";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/utils/logger.ts
|
|
96
|
+
var LOG_LEVELS = {
|
|
97
|
+
debug: 0,
|
|
98
|
+
info: 1,
|
|
99
|
+
warn: 2,
|
|
100
|
+
error: 3
|
|
101
|
+
};
|
|
102
|
+
var currentLevel = "info";
|
|
103
|
+
function shouldLog(level) {
|
|
104
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
105
|
+
}
|
|
106
|
+
function formatMessage(level, message) {
|
|
107
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
108
|
+
return `[${timestamp}] [${level.toUpperCase()}] ${message}`;
|
|
109
|
+
}
|
|
110
|
+
var logger = {
|
|
111
|
+
debug(message, ...args) {
|
|
112
|
+
if (shouldLog("debug")) {
|
|
113
|
+
console.error(formatMessage("debug", message), ...args);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
info(message, ...args) {
|
|
117
|
+
if (shouldLog("info")) {
|
|
118
|
+
console.error(formatMessage("info", message), ...args);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
warn(message, ...args) {
|
|
122
|
+
if (shouldLog("warn")) {
|
|
123
|
+
console.error(formatMessage("warn", message), ...args);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
error(message, ...args) {
|
|
127
|
+
if (shouldLog("error")) {
|
|
128
|
+
console.error(formatMessage("error", message), ...args);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
setLevel(level) {
|
|
132
|
+
currentLevel = level;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/config/loader.ts
|
|
137
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
138
|
+
var CONFIG_FILE_NAMES = ["api2mcp.json", "api2mcp.config.json", ".api2mcp.json"];
|
|
139
|
+
function parseHeaders(headersStr) {
|
|
140
|
+
if (!headersStr) return void 0;
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(headersStr);
|
|
143
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
144
|
+
return parsed;
|
|
145
|
+
}
|
|
146
|
+
throw new Error("Headers must be a JSON object");
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new ConfigurationError(
|
|
149
|
+
`Invalid headers JSON: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function loadFromEnv(env) {
|
|
154
|
+
const config = {};
|
|
155
|
+
if (env.OPENAPI_URL) {
|
|
156
|
+
config.openapiUrl = env.OPENAPI_URL;
|
|
157
|
+
}
|
|
158
|
+
if (env.API_BASE_URL) {
|
|
159
|
+
config.baseUrl = env.API_BASE_URL;
|
|
160
|
+
}
|
|
161
|
+
if (env.API_TIMEOUT) {
|
|
162
|
+
const timeout = parseInt(env.API_TIMEOUT, 10);
|
|
163
|
+
if (!Number.isNaN(timeout) && timeout > 0) {
|
|
164
|
+
config.timeout = timeout;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (env.API_HEADERS) {
|
|
168
|
+
config.headers = parseHeaders(env.API_HEADERS);
|
|
169
|
+
}
|
|
170
|
+
if (env.DEBUG) {
|
|
171
|
+
config.debug = env.DEBUG === "true" || env.DEBUG === "1";
|
|
172
|
+
}
|
|
173
|
+
return config;
|
|
174
|
+
}
|
|
175
|
+
function loadFromFile(workingDir = process.cwd()) {
|
|
176
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
177
|
+
const filePath = (0, import_node_path.resolve)(workingDir, fileName);
|
|
178
|
+
if ((0, import_node_fs.existsSync)(filePath)) {
|
|
179
|
+
try {
|
|
180
|
+
const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
|
|
181
|
+
const parsed = JSON.parse(content);
|
|
182
|
+
logger.info(`Loaded configuration from ${filePath}`);
|
|
183
|
+
return {
|
|
184
|
+
openapiUrl: parsed.openapiUrl,
|
|
185
|
+
baseUrl: parsed.baseUrl,
|
|
186
|
+
timeout: parsed.timeout,
|
|
187
|
+
headers: parsed.headers,
|
|
188
|
+
toolPrefix: parsed.toolPrefix,
|
|
189
|
+
debug: parsed.debug
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
throw new ConfigurationError(
|
|
193
|
+
`Failed to load config file ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
function loadFromCli(args) {
|
|
201
|
+
const config = {};
|
|
202
|
+
if (args.url) {
|
|
203
|
+
config.openapiUrl = args.url;
|
|
204
|
+
}
|
|
205
|
+
if (args.baseUrl) {
|
|
206
|
+
config.baseUrl = args.baseUrl;
|
|
207
|
+
}
|
|
208
|
+
if (args.timeout) {
|
|
209
|
+
config.timeout = args.timeout;
|
|
210
|
+
}
|
|
211
|
+
if (args.headers) {
|
|
212
|
+
config.headers = parseHeaders(args.headers);
|
|
213
|
+
}
|
|
214
|
+
if (args.prefix) {
|
|
215
|
+
config.toolPrefix = args.prefix;
|
|
216
|
+
}
|
|
217
|
+
if (args.debug !== void 0) {
|
|
218
|
+
config.debug = args.debug;
|
|
219
|
+
}
|
|
220
|
+
return config;
|
|
221
|
+
}
|
|
222
|
+
function mergeConfigs(...configs) {
|
|
223
|
+
const merged = {
|
|
224
|
+
openapiUrl: "",
|
|
225
|
+
debug: false
|
|
226
|
+
};
|
|
227
|
+
for (const config of configs) {
|
|
228
|
+
if (config.openapiUrl !== void 0) merged.openapiUrl = config.openapiUrl;
|
|
229
|
+
if (config.baseUrl !== void 0) merged.baseUrl = config.baseUrl;
|
|
230
|
+
if (config.timeout !== void 0) merged.timeout = config.timeout;
|
|
231
|
+
if (config.headers !== void 0) merged.headers = config.headers;
|
|
232
|
+
if (config.toolPrefix !== void 0) merged.toolPrefix = config.toolPrefix;
|
|
233
|
+
if (config.debug !== void 0) merged.debug = config.debug;
|
|
234
|
+
}
|
|
235
|
+
if (merged.timeout === void 0) {
|
|
236
|
+
merged.timeout = DEFAULT_TIMEOUT;
|
|
237
|
+
}
|
|
238
|
+
return merged;
|
|
239
|
+
}
|
|
240
|
+
function loadConfig(cliArgs = {}, env = process.env) {
|
|
241
|
+
const fileConfig = loadFromFile() ?? {};
|
|
242
|
+
const envConfig = loadFromEnv(env);
|
|
243
|
+
const cliConfig = loadFromCli(cliArgs);
|
|
244
|
+
const config = mergeConfigs(cliConfig, envConfig, fileConfig);
|
|
245
|
+
if (!config.openapiUrl) {
|
|
246
|
+
throw new ConfigurationError(
|
|
247
|
+
"OpenAPI URL is required. Provide it via --url, OPENAPI_URL env, or config file."
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (config.debug) {
|
|
251
|
+
logger.setLevel("debug");
|
|
252
|
+
}
|
|
253
|
+
logger.debug("Loaded configuration:", {
|
|
254
|
+
openapiUrl: config.openapiUrl,
|
|
255
|
+
baseUrl: config.baseUrl,
|
|
256
|
+
timeout: config.timeout,
|
|
257
|
+
toolPrefix: config.toolPrefix,
|
|
258
|
+
debug: config.debug
|
|
259
|
+
});
|
|
260
|
+
return config;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/converter/schema-converter.ts
|
|
264
|
+
var import_zod = require("zod");
|
|
265
|
+
var defaultRefResolver = () => void 0;
|
|
266
|
+
function convertSchema(schema, refResolver = defaultRefResolver) {
|
|
267
|
+
if (!schema) {
|
|
268
|
+
return import_zod.z.unknown();
|
|
269
|
+
}
|
|
270
|
+
if (schema.$ref) {
|
|
271
|
+
const resolved = refResolver(schema.$ref);
|
|
272
|
+
if (resolved) {
|
|
273
|
+
return convertSchema(resolved, refResolver);
|
|
274
|
+
}
|
|
275
|
+
logger.warn(`Unresolved $ref: ${schema.$ref}`);
|
|
276
|
+
return import_zod.z.unknown();
|
|
277
|
+
}
|
|
278
|
+
if (schema.allOf && schema.allOf.length > 0) {
|
|
279
|
+
const schemas = schema.allOf.map((s) => convertSchema(s, refResolver));
|
|
280
|
+
if (schemas.length === 1) {
|
|
281
|
+
return schemas[0];
|
|
282
|
+
}
|
|
283
|
+
return schemas.reduce((acc, s) => acc.and(s));
|
|
284
|
+
}
|
|
285
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
286
|
+
const schemas = schema.oneOf.map((s) => convertSchema(s, refResolver));
|
|
287
|
+
return import_zod.z.union([schemas[0], schemas[1] || schemas[0], ...schemas.slice(2)]);
|
|
288
|
+
}
|
|
289
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
290
|
+
const schemas = schema.anyOf.map((s) => convertSchema(s, refResolver));
|
|
291
|
+
if (schemas.length === 1) {
|
|
292
|
+
return schemas[0].optional();
|
|
293
|
+
}
|
|
294
|
+
return import_zod.z.union([schemas[0], schemas[1] || schemas[0], ...schemas.slice(2)]);
|
|
295
|
+
}
|
|
296
|
+
const nullable = schema.nullable === true;
|
|
297
|
+
let zodSchema;
|
|
298
|
+
switch (schema.type) {
|
|
299
|
+
case "string":
|
|
300
|
+
zodSchema = convertStringSchema(schema);
|
|
301
|
+
break;
|
|
302
|
+
case "number":
|
|
303
|
+
case "integer":
|
|
304
|
+
zodSchema = convertNumberSchema(schema);
|
|
305
|
+
break;
|
|
306
|
+
case "boolean":
|
|
307
|
+
zodSchema = import_zod.z.boolean();
|
|
308
|
+
break;
|
|
309
|
+
case "array":
|
|
310
|
+
zodSchema = convertArraySchema(schema, refResolver);
|
|
311
|
+
break;
|
|
312
|
+
case "object":
|
|
313
|
+
zodSchema = convertObjectSchema(schema, refResolver);
|
|
314
|
+
break;
|
|
315
|
+
default:
|
|
316
|
+
if (schema.properties) {
|
|
317
|
+
zodSchema = convertObjectSchema(schema, refResolver);
|
|
318
|
+
} else {
|
|
319
|
+
zodSchema = import_zod.z.unknown();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (nullable) {
|
|
323
|
+
zodSchema = zodSchema.nullable();
|
|
324
|
+
}
|
|
325
|
+
if (schema.default !== void 0) {
|
|
326
|
+
zodSchema = zodSchema.default(schema.default);
|
|
327
|
+
}
|
|
328
|
+
if (schema.description) {
|
|
329
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
330
|
+
}
|
|
331
|
+
return zodSchema;
|
|
332
|
+
}
|
|
333
|
+
function convertStringSchema(schema) {
|
|
334
|
+
let zodSchema = import_zod.z.string();
|
|
335
|
+
if (schema.minLength !== void 0) {
|
|
336
|
+
zodSchema = zodSchema.min(schema.minLength);
|
|
337
|
+
}
|
|
338
|
+
if (schema.maxLength !== void 0) {
|
|
339
|
+
zodSchema = zodSchema.max(schema.maxLength);
|
|
340
|
+
}
|
|
341
|
+
if (schema.pattern) {
|
|
342
|
+
zodSchema = zodSchema.regex(new RegExp(schema.pattern));
|
|
343
|
+
}
|
|
344
|
+
if (schema.format) {
|
|
345
|
+
switch (schema.format) {
|
|
346
|
+
case "email":
|
|
347
|
+
zodSchema = zodSchema.email();
|
|
348
|
+
break;
|
|
349
|
+
case "uri":
|
|
350
|
+
case "url":
|
|
351
|
+
zodSchema = zodSchema.url();
|
|
352
|
+
break;
|
|
353
|
+
case "uuid":
|
|
354
|
+
zodSchema = zodSchema.uuid();
|
|
355
|
+
break;
|
|
356
|
+
case "date":
|
|
357
|
+
zodSchema = zodSchema.date();
|
|
358
|
+
break;
|
|
359
|
+
case "date-time":
|
|
360
|
+
zodSchema = zodSchema.datetime();
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (schema.enum) {
|
|
365
|
+
const enumValues = schema.enum;
|
|
366
|
+
zodSchema = import_zod.z.enum(enumValues);
|
|
367
|
+
}
|
|
368
|
+
return zodSchema;
|
|
369
|
+
}
|
|
370
|
+
function convertNumberSchema(schema) {
|
|
371
|
+
let zodSchema = schema.type === "integer" ? import_zod.z.number().int() : import_zod.z.number();
|
|
372
|
+
if (schema.minimum !== void 0) {
|
|
373
|
+
zodSchema = zodSchema.min(schema.minimum);
|
|
374
|
+
}
|
|
375
|
+
if (schema.maximum !== void 0) {
|
|
376
|
+
zodSchema = zodSchema.max(schema.maximum);
|
|
377
|
+
}
|
|
378
|
+
if (schema.exclusiveMinimum === true && schema.minimum !== void 0) {
|
|
379
|
+
zodSchema = zodSchema.min(schema.minimum + (schema.type === "integer" ? 1 : Number.MIN_VALUE));
|
|
380
|
+
} else if (typeof schema.exclusiveMinimum === "number") {
|
|
381
|
+
zodSchema = zodSchema.min(
|
|
382
|
+
schema.exclusiveMinimum + (schema.type === "integer" ? 1 : Number.MIN_VALUE)
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (schema.exclusiveMaximum === true && schema.maximum !== void 0) {
|
|
386
|
+
zodSchema = zodSchema.max(schema.maximum - (schema.type === "integer" ? 1 : Number.MIN_VALUE));
|
|
387
|
+
} else if (typeof schema.exclusiveMaximum === "number") {
|
|
388
|
+
zodSchema = zodSchema.max(
|
|
389
|
+
schema.exclusiveMaximum - (schema.type === "integer" ? 1 : Number.MIN_VALUE)
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (schema.enum) {
|
|
393
|
+
const enumValues = schema.enum;
|
|
394
|
+
zodSchema = import_zod.z.enum(enumValues.map(String));
|
|
395
|
+
}
|
|
396
|
+
return zodSchema;
|
|
397
|
+
}
|
|
398
|
+
function convertArraySchema(schema, refResolver) {
|
|
399
|
+
const itemSchema = schema.items ? convertSchema(schema.items, refResolver) : import_zod.z.unknown();
|
|
400
|
+
let zodSchema = import_zod.z.array(itemSchema);
|
|
401
|
+
if (schema.minItems !== void 0) {
|
|
402
|
+
zodSchema = zodSchema.min(schema.minItems);
|
|
403
|
+
}
|
|
404
|
+
if (schema.maxItems !== void 0) {
|
|
405
|
+
zodSchema = zodSchema.max(schema.maxItems);
|
|
406
|
+
}
|
|
407
|
+
return zodSchema;
|
|
408
|
+
}
|
|
409
|
+
function convertObjectSchema(schema, refResolver) {
|
|
410
|
+
const properties = schema.properties || {};
|
|
411
|
+
const required = new Set(schema.required || []);
|
|
412
|
+
if (Object.keys(properties).length === 0) {
|
|
413
|
+
if (schema.additionalProperties) {
|
|
414
|
+
if (typeof schema.additionalProperties === "boolean") {
|
|
415
|
+
return import_zod.z.record(import_zod.z.unknown());
|
|
416
|
+
}
|
|
417
|
+
return import_zod.z.record(convertSchema(schema.additionalProperties, refResolver));
|
|
418
|
+
}
|
|
419
|
+
return import_zod.z.record(import_zod.z.unknown());
|
|
420
|
+
}
|
|
421
|
+
const zodProperties = {};
|
|
422
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
423
|
+
let propSchema = convertSchema(prop, refResolver);
|
|
424
|
+
if (!required.has(name)) {
|
|
425
|
+
propSchema = propSchema.optional();
|
|
426
|
+
}
|
|
427
|
+
zodProperties[name] = propSchema;
|
|
428
|
+
}
|
|
429
|
+
let zodSchema = import_zod.z.object(zodProperties);
|
|
430
|
+
if (schema.additionalProperties) {
|
|
431
|
+
if (typeof schema.additionalProperties === "boolean") {
|
|
432
|
+
zodSchema = import_zod.z.object(zodProperties).passthrough();
|
|
433
|
+
} else {
|
|
434
|
+
logger.debug("Typed additionalProperties is not fully supported, using passthrough");
|
|
435
|
+
zodSchema = import_zod.z.object(zodProperties).passthrough();
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
zodSchema = import_zod.z.object(zodProperties).strict();
|
|
439
|
+
}
|
|
440
|
+
return zodSchema;
|
|
441
|
+
}
|
|
442
|
+
function createRefResolver(components) {
|
|
443
|
+
return (ref) => {
|
|
444
|
+
const match = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
445
|
+
if (match && components) {
|
|
446
|
+
return components[match[1]];
|
|
447
|
+
}
|
|
448
|
+
return void 0;
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/converter/tool-generator.ts
|
|
453
|
+
var import_zod2 = require("zod");
|
|
454
|
+
function generateToolName(operation, prefix) {
|
|
455
|
+
if (operation.operationId) {
|
|
456
|
+
const name2 = sanitizeToolName(operation.operationId);
|
|
457
|
+
return prefix ? `${prefix}_${name2}` : name2;
|
|
458
|
+
}
|
|
459
|
+
const pathParts = operation.path.split("/").filter(Boolean).map((part) => part.replace(/[{}]/g, ""));
|
|
460
|
+
const name = sanitizeToolName(`${operation.method.toLowerCase()}_${pathParts.join("_")}`);
|
|
461
|
+
return prefix ? `${prefix}_${name}` : name;
|
|
462
|
+
}
|
|
463
|
+
function sanitizeToolName(name) {
|
|
464
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toLowerCase();
|
|
465
|
+
}
|
|
466
|
+
function generateToolDescription(operation) {
|
|
467
|
+
const parts = [];
|
|
468
|
+
if (operation.summary) {
|
|
469
|
+
parts.push(operation.summary);
|
|
470
|
+
}
|
|
471
|
+
if (operation.description) {
|
|
472
|
+
parts.push(operation.description);
|
|
473
|
+
}
|
|
474
|
+
if (operation.deprecated) {
|
|
475
|
+
parts.push("[DEPRECATED]");
|
|
476
|
+
}
|
|
477
|
+
parts.push(`
|
|
478
|
+
|
|
479
|
+
HTTP ${operation.method} ${operation.path}`);
|
|
480
|
+
if (operation.tags && operation.tags.length > 0) {
|
|
481
|
+
parts.push(`
|
|
482
|
+
Tags: ${operation.tags.join(", ")}`);
|
|
483
|
+
}
|
|
484
|
+
return parts.join("\n");
|
|
485
|
+
}
|
|
486
|
+
function buildParametersSchema(operation, refResolver) {
|
|
487
|
+
const shape = {};
|
|
488
|
+
if (operation.parameters) {
|
|
489
|
+
for (const param of operation.parameters) {
|
|
490
|
+
const paramName = param.name;
|
|
491
|
+
let paramSchema = convertSchema(param.schema, refResolver);
|
|
492
|
+
if (param.description) {
|
|
493
|
+
paramSchema = paramSchema.describe(param.description);
|
|
494
|
+
}
|
|
495
|
+
if (!param.required) {
|
|
496
|
+
paramSchema = paramSchema.optional();
|
|
497
|
+
}
|
|
498
|
+
const key = paramName;
|
|
499
|
+
shape[key] = paramSchema;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (operation.requestBody) {
|
|
503
|
+
const jsonContent = operation.requestBody.content["application/json"];
|
|
504
|
+
if (jsonContent?.schema) {
|
|
505
|
+
const bodySchema = convertSchema(jsonContent.schema, refResolver);
|
|
506
|
+
if (operation.requestBody.description) {
|
|
507
|
+
shape.body = bodySchema.describe(operation.requestBody.description);
|
|
508
|
+
} else {
|
|
509
|
+
shape.body = bodySchema.describe("Request body");
|
|
510
|
+
}
|
|
511
|
+
if (!operation.requestBody.required) {
|
|
512
|
+
shape.body = shape.body.optional();
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
const contentTypes = Object.keys(operation.requestBody.content);
|
|
516
|
+
if (contentTypes.length > 0) {
|
|
517
|
+
const firstContent = operation.requestBody.content[contentTypes[0]];
|
|
518
|
+
if (firstContent?.schema) {
|
|
519
|
+
const bodySchema = convertSchema(firstContent.schema, refResolver);
|
|
520
|
+
shape.body = bodySchema.describe(`Request body (${contentTypes[0]})`);
|
|
521
|
+
if (!operation.requestBody.required) {
|
|
522
|
+
shape.body = shape.body.optional();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return shape;
|
|
529
|
+
}
|
|
530
|
+
function generateTool(operation, components, toolPrefix) {
|
|
531
|
+
const refResolver = createRefResolver(components);
|
|
532
|
+
const name = generateToolName(operation, toolPrefix);
|
|
533
|
+
const description = generateToolDescription(operation);
|
|
534
|
+
const parametersShape = buildParametersSchema(operation, refResolver);
|
|
535
|
+
const inputSchema = import_zod2.z.object(parametersShape);
|
|
536
|
+
logger.debug(`Generated tool: ${name}`);
|
|
537
|
+
return {
|
|
538
|
+
name,
|
|
539
|
+
description,
|
|
540
|
+
inputSchema,
|
|
541
|
+
operation
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function generateTools(operations, components, toolPrefix) {
|
|
545
|
+
const tools = [];
|
|
546
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
547
|
+
for (const operation of operations) {
|
|
548
|
+
const tool = generateTool(operation, components, toolPrefix);
|
|
549
|
+
if (usedNames.has(tool.name)) {
|
|
550
|
+
let counter = 1;
|
|
551
|
+
let newName = `${tool.name}_${counter}`;
|
|
552
|
+
while (usedNames.has(newName)) {
|
|
553
|
+
counter++;
|
|
554
|
+
newName = `${tool.name}_${counter}`;
|
|
555
|
+
}
|
|
556
|
+
logger.warn(`Tool name conflict: ${tool.name} renamed to ${newName}`);
|
|
557
|
+
tool.name = newName;
|
|
558
|
+
}
|
|
559
|
+
usedNames.add(tool.name);
|
|
560
|
+
tools.push(tool);
|
|
561
|
+
}
|
|
562
|
+
logger.info(`Generated ${tools.length} tools`);
|
|
563
|
+
return tools;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/executor/request-builder.ts
|
|
567
|
+
function groupParametersByLocation(parameters) {
|
|
568
|
+
const groups = {
|
|
569
|
+
path: [],
|
|
570
|
+
query: [],
|
|
571
|
+
header: [],
|
|
572
|
+
cookie: []
|
|
573
|
+
};
|
|
574
|
+
if (parameters) {
|
|
575
|
+
for (const param of parameters) {
|
|
576
|
+
groups[param.in].push(param);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return groups;
|
|
580
|
+
}
|
|
581
|
+
function buildRequest(operation, input, defaultHeaders = {}) {
|
|
582
|
+
const groupedParams = groupParametersByLocation(operation.parameters);
|
|
583
|
+
let path = operation.path;
|
|
584
|
+
for (const param of groupedParams.path) {
|
|
585
|
+
const value = input[param.name];
|
|
586
|
+
if (value !== void 0) {
|
|
587
|
+
path = path.replace(`{${param.name}}`, String(value));
|
|
588
|
+
} else if (param.required) {
|
|
589
|
+
throw new Error(`Missing required path parameter: ${param.name}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const query = {};
|
|
593
|
+
for (const param of groupedParams.query) {
|
|
594
|
+
const value = input[param.name];
|
|
595
|
+
if (value !== void 0) {
|
|
596
|
+
if (Array.isArray(value)) {
|
|
597
|
+
query[param.name] = value.map(String);
|
|
598
|
+
} else {
|
|
599
|
+
query[param.name] = String(value);
|
|
600
|
+
}
|
|
601
|
+
} else if (param.required) {
|
|
602
|
+
throw new Error(`Missing required query parameter: ${param.name}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const headers = { ...defaultHeaders };
|
|
606
|
+
for (const param of groupedParams.header) {
|
|
607
|
+
const value = input[param.name];
|
|
608
|
+
if (value !== void 0) {
|
|
609
|
+
headers[param.name] = String(value);
|
|
610
|
+
} else if (param.required) {
|
|
611
|
+
throw new Error(`Missing required header parameter: ${param.name}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const body = input.body;
|
|
615
|
+
if (body !== void 0 && !headers["Content-Type"]) {
|
|
616
|
+
if (operation.requestBody?.content) {
|
|
617
|
+
const contentTypes = Object.keys(operation.requestBody.content);
|
|
618
|
+
if (contentTypes.length > 0) {
|
|
619
|
+
headers["Content-Type"] = contentTypes[0];
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
headers["Content-Type"] = "application/json";
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
logger.debug(`Built request: ${operation.method} ${path}`, {
|
|
626
|
+
query,
|
|
627
|
+
headers,
|
|
628
|
+
body: body ? "(body present)" : "(no body)"
|
|
629
|
+
});
|
|
630
|
+
return {
|
|
631
|
+
path,
|
|
632
|
+
query,
|
|
633
|
+
headers,
|
|
634
|
+
body
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function appendQueryString(url, query) {
|
|
638
|
+
const params = new URLSearchParams();
|
|
639
|
+
for (const [key, value] of Object.entries(query)) {
|
|
640
|
+
if (Array.isArray(value)) {
|
|
641
|
+
for (const v of value) {
|
|
642
|
+
params.append(key, v);
|
|
643
|
+
}
|
|
644
|
+
} else {
|
|
645
|
+
params.append(key, value);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const queryString = params.toString();
|
|
649
|
+
if (queryString) {
|
|
650
|
+
return `${url}?${queryString}`;
|
|
651
|
+
}
|
|
652
|
+
return url;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/executor/http-client.ts
|
|
656
|
+
async function executeRequest(operation, input, config) {
|
|
657
|
+
const baseUrl = config.baseUrl || "";
|
|
658
|
+
try {
|
|
659
|
+
const built = buildRequest(operation, input, config.headers || {});
|
|
660
|
+
let url = `${baseUrl}${built.path}`;
|
|
661
|
+
url = appendQueryString(url, built.query);
|
|
662
|
+
logger.info(`Executing: ${operation.method} ${url}`);
|
|
663
|
+
const requestInit = {
|
|
664
|
+
method: operation.method,
|
|
665
|
+
headers: built.headers
|
|
666
|
+
};
|
|
667
|
+
if (built.body !== void 0 && !["GET", "HEAD"].includes(operation.method.toUpperCase())) {
|
|
668
|
+
requestInit.body = typeof built.body === "string" ? built.body : JSON.stringify(built.body);
|
|
669
|
+
}
|
|
670
|
+
const controller = new AbortController();
|
|
671
|
+
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
|
|
672
|
+
requestInit.signal = controller.signal;
|
|
673
|
+
const response = await fetch(url, requestInit);
|
|
674
|
+
clearTimeout(timeoutId);
|
|
675
|
+
const responseHeaders = {};
|
|
676
|
+
response.headers.forEach((value, key) => {
|
|
677
|
+
responseHeaders[key] = value;
|
|
678
|
+
});
|
|
679
|
+
let body;
|
|
680
|
+
const contentType = response.headers.get("content-type") || "";
|
|
681
|
+
if (contentType.includes("application/json")) {
|
|
682
|
+
try {
|
|
683
|
+
body = await response.json();
|
|
684
|
+
} catch {
|
|
685
|
+
body = await response.text();
|
|
686
|
+
}
|
|
687
|
+
} else if (contentType.includes("text/")) {
|
|
688
|
+
body = await response.text();
|
|
689
|
+
} else {
|
|
690
|
+
try {
|
|
691
|
+
body = await response.json();
|
|
692
|
+
} catch {
|
|
693
|
+
body = await response.text();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
logger.debug(`Response status: ${response.status}`);
|
|
697
|
+
if (!response.ok) {
|
|
698
|
+
throw new HttpError(
|
|
699
|
+
`HTTP ${response.status} ${response.statusText}`,
|
|
700
|
+
response.status,
|
|
701
|
+
typeof body === "string" ? body : JSON.stringify(body)
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
status: response.status,
|
|
706
|
+
statusText: response.statusText,
|
|
707
|
+
headers: responseHeaders,
|
|
708
|
+
body
|
|
709
|
+
};
|
|
710
|
+
} catch (error) {
|
|
711
|
+
if (error instanceof HttpError) {
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
714
|
+
if (error instanceof Error) {
|
|
715
|
+
if (error.name === "AbortError") {
|
|
716
|
+
throw new ToolExecutionError(
|
|
717
|
+
`Request timeout after ${config.timeout}ms`,
|
|
718
|
+
operation.operationId || operation.path
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
throw new ToolExecutionError(
|
|
722
|
+
`Request failed: ${error.message}`,
|
|
723
|
+
operation.operationId || operation.path,
|
|
724
|
+
error
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
throw new ToolExecutionError(
|
|
728
|
+
"Unknown error during request execution",
|
|
729
|
+
operation.operationId || operation.path
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function formatResponse(response) {
|
|
734
|
+
const lines = [];
|
|
735
|
+
lines.push(`Status: ${response.status} ${response.statusText}`);
|
|
736
|
+
if (Object.keys(response.headers).length > 0) {
|
|
737
|
+
lines.push("Headers:");
|
|
738
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
739
|
+
lines.push(` ${key}: ${value}`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
lines.push("Body:");
|
|
743
|
+
if (typeof response.body === "object" && response.body !== null) {
|
|
744
|
+
lines.push(JSON.stringify(response.body, null, 2));
|
|
745
|
+
} else {
|
|
746
|
+
lines.push(String(response.body));
|
|
747
|
+
}
|
|
748
|
+
return lines.join("\n");
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/parser/swagger.ts
|
|
752
|
+
var import_swagger_parser = __toESM(require("@apidevtools/swagger-parser"));
|
|
753
|
+
function isOpenAPIV3(doc) {
|
|
754
|
+
return "openapi" in doc;
|
|
755
|
+
}
|
|
756
|
+
function convertParameter(param) {
|
|
757
|
+
return {
|
|
758
|
+
name: param.name,
|
|
759
|
+
in: param.in,
|
|
760
|
+
required: param.required,
|
|
761
|
+
description: param.description,
|
|
762
|
+
schema: param.schema,
|
|
763
|
+
deprecated: param.deprecated
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function convertSchema2(schema) {
|
|
767
|
+
if (!schema) return void 0;
|
|
768
|
+
return schema;
|
|
769
|
+
}
|
|
770
|
+
function convertRequestBody(requestBody) {
|
|
771
|
+
if (!requestBody) return void 0;
|
|
772
|
+
if ("$ref" in requestBody) {
|
|
773
|
+
return void 0;
|
|
774
|
+
}
|
|
775
|
+
const content = {};
|
|
776
|
+
for (const [contentType, mediaType] of Object.entries(requestBody.content || {})) {
|
|
777
|
+
const mt = mediaType;
|
|
778
|
+
content[contentType] = {
|
|
779
|
+
schema: convertSchema2(mt.schema),
|
|
780
|
+
example: mt.example
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
return {
|
|
784
|
+
description: requestBody.description,
|
|
785
|
+
required: requestBody.required,
|
|
786
|
+
content
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function extractOperations(doc) {
|
|
790
|
+
const operations = [];
|
|
791
|
+
if (!doc.paths) return operations;
|
|
792
|
+
const methods = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
|
|
793
|
+
for (const [path, pathItem] of Object.entries(doc.paths)) {
|
|
794
|
+
if (!pathItem) continue;
|
|
795
|
+
for (const method of methods) {
|
|
796
|
+
const operation = pathItem[method];
|
|
797
|
+
if (!operation) continue;
|
|
798
|
+
const parameters = [];
|
|
799
|
+
if (pathItem.parameters) {
|
|
800
|
+
for (const param of pathItem.parameters) {
|
|
801
|
+
if (!("$ref" in param)) {
|
|
802
|
+
parameters.push(convertParameter(param));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (operation.parameters) {
|
|
807
|
+
for (const param of operation.parameters) {
|
|
808
|
+
if (!("$ref" in param)) {
|
|
809
|
+
parameters.push(convertParameter(param));
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const op = {
|
|
814
|
+
method: method.toUpperCase(),
|
|
815
|
+
path,
|
|
816
|
+
operationId: operation.operationId,
|
|
817
|
+
summary: operation.summary,
|
|
818
|
+
description: operation.description,
|
|
819
|
+
tags: operation.tags,
|
|
820
|
+
parameters: parameters.length > 0 ? parameters : void 0,
|
|
821
|
+
requestBody: convertRequestBody(
|
|
822
|
+
operation.requestBody
|
|
823
|
+
),
|
|
824
|
+
deprecated: operation.deprecated
|
|
825
|
+
};
|
|
826
|
+
operations.push(op);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return operations;
|
|
830
|
+
}
|
|
831
|
+
async function parseOpenApi(source) {
|
|
832
|
+
try {
|
|
833
|
+
logger.info(`Parsing OpenAPI document from: ${source}`);
|
|
834
|
+
const api = await import_swagger_parser.default.validate(source);
|
|
835
|
+
if (!isOpenAPIV3(api)) {
|
|
836
|
+
if ("swagger" in api) {
|
|
837
|
+
throw new OpenApiParseError(
|
|
838
|
+
"OpenAPI v2 (Swagger) is not supported. Please convert to OpenAPI v3 first."
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
throw new OpenApiParseError("Unsupported OpenAPI format");
|
|
842
|
+
}
|
|
843
|
+
const info = {
|
|
844
|
+
title: api.info.title,
|
|
845
|
+
version: api.info.version,
|
|
846
|
+
description: api.info.description
|
|
847
|
+
};
|
|
848
|
+
const servers = api.servers?.map((s) => ({
|
|
849
|
+
url: s.url,
|
|
850
|
+
description: s.description,
|
|
851
|
+
variables: s.variables
|
|
852
|
+
}));
|
|
853
|
+
const operations = extractOperations(api);
|
|
854
|
+
const components = api.components ? {
|
|
855
|
+
schemas: api.components.schemas,
|
|
856
|
+
parameters: api.components.parameters,
|
|
857
|
+
requestBodies: api.components.requestBodies
|
|
858
|
+
} : void 0;
|
|
859
|
+
logger.info(`Parsed ${operations.length} API operations`);
|
|
860
|
+
return {
|
|
861
|
+
openapi: api.openapi,
|
|
862
|
+
info,
|
|
863
|
+
servers,
|
|
864
|
+
operations,
|
|
865
|
+
components
|
|
866
|
+
};
|
|
867
|
+
} catch (error) {
|
|
868
|
+
if (error instanceof OpenApiParseError) {
|
|
869
|
+
throw error;
|
|
870
|
+
}
|
|
871
|
+
throw new OpenApiParseError(
|
|
872
|
+
`Failed to parse OpenAPI document: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
873
|
+
error instanceof Error ? error : void 0
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function getBaseUrl(doc, overrideUrl) {
|
|
878
|
+
if (overrideUrl) {
|
|
879
|
+
logger.debug(`Using override base URL: ${overrideUrl}`);
|
|
880
|
+
return overrideUrl;
|
|
881
|
+
}
|
|
882
|
+
if (doc.servers && doc.servers.length > 0) {
|
|
883
|
+
const server = doc.servers[0];
|
|
884
|
+
let url = server.url;
|
|
885
|
+
if (server.variables) {
|
|
886
|
+
for (const [name, variable] of Object.entries(server.variables)) {
|
|
887
|
+
url = url.replace(`{${name}}`, variable.default);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
logger.debug(`Using base URL from OpenAPI servers: ${url}`);
|
|
891
|
+
return url;
|
|
892
|
+
}
|
|
893
|
+
throw new OpenApiParseError("No base URL found in OpenAPI document. Please specify --base-url.");
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/server/index.ts
|
|
897
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
898
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
899
|
+
|
|
900
|
+
// src/server/tool-manager.ts
|
|
901
|
+
var ToolManager = class {
|
|
902
|
+
tools = /* @__PURE__ */ new Map();
|
|
903
|
+
config;
|
|
904
|
+
server;
|
|
905
|
+
constructor(server, config) {
|
|
906
|
+
this.server = server;
|
|
907
|
+
this.config = config;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* 注册工具
|
|
911
|
+
*/
|
|
912
|
+
registerTool(tool) {
|
|
913
|
+
if (this.tools.has(tool.name)) {
|
|
914
|
+
logger.warn(`Tool already registered: ${tool.name}, overwriting`);
|
|
915
|
+
}
|
|
916
|
+
this.tools.set(tool.name, tool);
|
|
917
|
+
this.server.tool(
|
|
918
|
+
tool.name,
|
|
919
|
+
tool.description,
|
|
920
|
+
tool.inputSchema.shape,
|
|
921
|
+
async (args) => {
|
|
922
|
+
return this.executeTool(tool.name, args);
|
|
923
|
+
}
|
|
924
|
+
);
|
|
925
|
+
logger.debug(`Registered tool: ${tool.name}`);
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* 批量注册工具
|
|
929
|
+
*/
|
|
930
|
+
registerTools(tools) {
|
|
931
|
+
for (const tool of tools) {
|
|
932
|
+
this.registerTool(tool);
|
|
933
|
+
}
|
|
934
|
+
logger.info(`Registered ${tools.length} tools`);
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* 执行工具
|
|
938
|
+
*/
|
|
939
|
+
async executeTool(toolName, args) {
|
|
940
|
+
const tool = this.tools.get(toolName);
|
|
941
|
+
if (!tool) {
|
|
942
|
+
return {
|
|
943
|
+
content: [
|
|
944
|
+
{
|
|
945
|
+
type: "text",
|
|
946
|
+
text: `Error: Tool not found: ${toolName}`
|
|
947
|
+
}
|
|
948
|
+
]
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
logger.debug(`Executing tool: ${toolName}`, args);
|
|
953
|
+
const response = await executeRequest(tool.operation, args, this.config);
|
|
954
|
+
const formattedResponse = formatResponse(response);
|
|
955
|
+
return {
|
|
956
|
+
content: [
|
|
957
|
+
{
|
|
958
|
+
type: "text",
|
|
959
|
+
text: formattedResponse
|
|
960
|
+
}
|
|
961
|
+
]
|
|
962
|
+
};
|
|
963
|
+
} catch (error) {
|
|
964
|
+
const errorMessage = error instanceof ToolExecutionError ? `Error: ${error.message}` : `Error: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
965
|
+
logger.error(`Tool execution failed: ${toolName}`, error);
|
|
966
|
+
return {
|
|
967
|
+
content: [
|
|
968
|
+
{
|
|
969
|
+
type: "text",
|
|
970
|
+
text: errorMessage
|
|
971
|
+
}
|
|
972
|
+
]
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* 获取所有已注册的工具名称
|
|
978
|
+
*/
|
|
979
|
+
getToolNames() {
|
|
980
|
+
return Array.from(this.tools.keys());
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* 获取工具数量
|
|
984
|
+
*/
|
|
985
|
+
getToolCount() {
|
|
986
|
+
return this.tools.size;
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// src/server/index.ts
|
|
991
|
+
async function createServer(config) {
|
|
992
|
+
logger.info("Creating MCP server...");
|
|
993
|
+
const server = new import_mcp.McpServer({
|
|
994
|
+
name: "api2mcp",
|
|
995
|
+
version: "0.1.0"
|
|
996
|
+
});
|
|
997
|
+
const openApiDoc = await parseOpenApi(config.openapiUrl);
|
|
998
|
+
let baseUrl;
|
|
999
|
+
try {
|
|
1000
|
+
baseUrl = getBaseUrl(openApiDoc, config.baseUrl);
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
if (config.baseUrl) {
|
|
1003
|
+
baseUrl = config.baseUrl;
|
|
1004
|
+
} else {
|
|
1005
|
+
throw error;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
const effectiveConfig = {
|
|
1009
|
+
...config,
|
|
1010
|
+
baseUrl
|
|
1011
|
+
};
|
|
1012
|
+
const tools = generateTools(
|
|
1013
|
+
openApiDoc.operations,
|
|
1014
|
+
openApiDoc.components?.schemas,
|
|
1015
|
+
config.toolPrefix
|
|
1016
|
+
);
|
|
1017
|
+
const toolManager = new ToolManager(server, effectiveConfig);
|
|
1018
|
+
toolManager.registerTools(tools);
|
|
1019
|
+
logger.info(`Server ready with ${toolManager.getToolCount()} tools`);
|
|
1020
|
+
return server;
|
|
1021
|
+
}
|
|
1022
|
+
async function startServer(config) {
|
|
1023
|
+
const server = await createServer(config);
|
|
1024
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1025
|
+
await server.connect(transport);
|
|
1026
|
+
logger.info("Server started (stdio mode)");
|
|
1027
|
+
}
|
|
1028
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1029
|
+
0 && (module.exports = {
|
|
1030
|
+
Api2McpError,
|
|
1031
|
+
ConfigurationError,
|
|
1032
|
+
HttpError,
|
|
1033
|
+
OpenApiParseError,
|
|
1034
|
+
ToolExecutionError,
|
|
1035
|
+
ToolManager,
|
|
1036
|
+
convertSchema,
|
|
1037
|
+
createRefResolver,
|
|
1038
|
+
createServer,
|
|
1039
|
+
executeRequest,
|
|
1040
|
+
formatResponse,
|
|
1041
|
+
generateTool,
|
|
1042
|
+
generateTools,
|
|
1043
|
+
getBaseUrl,
|
|
1044
|
+
loadConfig,
|
|
1045
|
+
logger,
|
|
1046
|
+
parseOpenApi,
|
|
1047
|
+
startServer
|
|
1048
|
+
});
|
|
1049
|
+
//# sourceMappingURL=index.js.map
|