api2mcp 0.0.1 → 0.2.0-beta.1

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