api2ai 1.0.3 → 1.0.4

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.
@@ -1,833 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * OpenAPI to MCP Server Generator (mcp-use framework)
5
- *
6
- * Generates a complete MCP server using the mcp-use framework from any OpenAPI spec.
7
- *
8
- * Usage:
9
- * node generate-mcp-use-server.js <openapi-spec> [output-folder] [options]
10
- *
11
- * Examples:
12
- * node generate-mcp-use-server.js ./petstore.json ./my-mcp-server
13
- * node generate-mcp-use-server.js https://petstore3.swagger.io/api/v3/openapi.json ./petstore-mcp --base-url https://petstore3.swagger.io/api/v3
14
- */
15
-
16
- import fs from 'fs/promises';
17
- import path from 'path';
18
- import { fileURLToPath } from 'url';
19
-
20
- // ============================================================================
21
- // OpenAPI Spec Loading & Parsing
22
- // ============================================================================
23
-
24
- async function loadOpenApiSpec(specPathOrUrl) {
25
- if (specPathOrUrl.startsWith('http://') || specPathOrUrl.startsWith('https://')) {
26
- const response = await fetch(specPathOrUrl);
27
- if (!response.ok) {
28
- throw new Error(`Failed to fetch spec: ${response.status} ${response.statusText}`);
29
- }
30
- return response.json();
31
- }
32
-
33
- const content = await fs.readFile(specPathOrUrl, 'utf-8');
34
-
35
- if (specPathOrUrl.endsWith('.yaml') || specPathOrUrl.endsWith('.yml')) {
36
- const yaml = await import('js-yaml').catch(() => null);
37
- if (yaml) {
38
- return yaml.load(content);
39
- }
40
- throw new Error('YAML spec detected but js-yaml is not installed. Run: npm install js-yaml');
41
- }
42
-
43
- return JSON.parse(content);
44
- }
45
-
46
- // Convert OpenAPI schema to Zod schema string
47
- function schemaToZod(schema, required = false) {
48
- if (!schema) return 'z.unknown()';
49
-
50
- let zodStr;
51
-
52
- switch (schema.type) {
53
- case 'string':
54
- if (schema.enum) {
55
- zodStr = `z.enum([${schema.enum.map(e => `'${e}'`).join(', ')}])`;
56
- } else if (schema.format === 'date-time') {
57
- zodStr = 'z.string().datetime()';
58
- } else if (schema.format === 'date') {
59
- zodStr = 'z.string().date()';
60
- } else if (schema.format === 'email') {
61
- zodStr = 'z.string().email()';
62
- } else if (schema.format === 'uri' || schema.format === 'url') {
63
- zodStr = 'z.string().url()';
64
- } else {
65
- zodStr = 'z.string()';
66
- }
67
- break;
68
-
69
- case 'integer':
70
- zodStr = 'z.number().int()';
71
- break;
72
-
73
- case 'number':
74
- zodStr = 'z.number()';
75
- break;
76
-
77
- case 'boolean':
78
- zodStr = 'z.boolean()';
79
- break;
80
-
81
- case 'array':
82
- zodStr = `z.array(${schemaToZod(schema.items, true)})`;
83
- break;
84
-
85
- case 'object':
86
- if (schema.properties) {
87
- const props = Object.entries(schema.properties)
88
- .map(([key, val]) => {
89
- const isReq = schema.required?.includes(key);
90
- const propZod = schemaToZod(val, isReq);
91
- // Add description if available
92
- const desc = val.description ? `.describe('${val.description.replace(/'/g, "\\'")}')` : '';
93
- return ` ${sanitizePropertyName(key)}: ${propZod}${desc}`;
94
- })
95
- .join(',\n');
96
- zodStr = `z.object({\n${props}\n })`;
97
- } else if (schema.additionalProperties) {
98
- zodStr = `z.record(z.string(), ${schemaToZod(schema.additionalProperties, true)})`;
99
- } else {
100
- zodStr = 'z.record(z.string(), z.unknown())';
101
- }
102
- break;
103
-
104
- default:
105
- // Handle anyOf, oneOf, allOf
106
- if (schema.anyOf) {
107
- const options = schema.anyOf.map(s => schemaToZod(s, true)).join(', ');
108
- zodStr = `z.union([${options}])`;
109
- } else if (schema.oneOf) {
110
- const options = schema.oneOf.map(s => schemaToZod(s, true)).join(', ');
111
- zodStr = `z.union([${options}])`;
112
- } else {
113
- zodStr = 'z.unknown()';
114
- }
115
- }
116
-
117
- // Add optional if not required
118
- if (!required) {
119
- zodStr += '.optional()';
120
- }
121
-
122
- return zodStr;
123
- }
124
-
125
- // Sanitize property name for JS object key
126
- function sanitizePropertyName(name) {
127
- if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
128
- return name;
129
- }
130
- return `'${name}'`;
131
- }
132
-
133
- // Build Zod schema for tool parameters
134
- function buildZodSchema(operation, pathParams) {
135
- const allParams = [...(pathParams || []), ...(operation.parameters || [])];
136
- const properties = [];
137
-
138
- for (const param of allParams) {
139
- const schema = param.schema || { type: 'string' };
140
- const zodType = schemaToZod(schema, param.required);
141
- const desc = param.description ? `.describe('${param.description.replace(/'/g, "\\'")}')` : '';
142
- properties.push(` ${sanitizePropertyName(param.name)}: ${zodType}${desc}`);
143
- }
144
-
145
- // Handle request body
146
- if (operation.requestBody) {
147
- const content = operation.requestBody.content;
148
- const mediaType = content?.['application/json'] || Object.values(content || {})[0];
149
-
150
- if (mediaType?.schema) {
151
- const zodType = schemaToZod(mediaType.schema, operation.requestBody.required);
152
- const desc = operation.requestBody.description
153
- ? `.describe('${operation.requestBody.description.replace(/'/g, "\\'")}')`
154
- : `.describe('Request body')`;
155
- properties.push(` requestBody: ${zodType}${desc}`);
156
- }
157
- }
158
-
159
- if (properties.length === 0) {
160
- return 'z.object({})';
161
- }
162
-
163
- return `z.object({\n${properties.join(',\n')}\n })`;
164
- }
165
-
166
- // Extract tools from OpenAPI spec
167
- function extractTools(spec, options = {}) {
168
- const tools = [];
169
- const { baseUrl: overrideBaseUrl, excludeOperationIds = [], filterFn } = options;
170
-
171
- let baseUrl = overrideBaseUrl;
172
- if (!baseUrl && spec.servers && spec.servers.length > 0) {
173
- baseUrl = spec.servers[0].url;
174
- }
175
-
176
- for (const [pathTemplate, pathItem] of Object.entries(spec.paths || {})) {
177
- const pathParams = pathItem.parameters || [];
178
-
179
- for (const method of ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']) {
180
- const operation = pathItem[method];
181
- if (!operation) continue;
182
-
183
- const operationId = operation.operationId ||
184
- `${method}_${pathTemplate.replace(/[^a-zA-Z0-9]/g, '_')}`;
185
-
186
- if (excludeOperationIds.includes(operationId)) continue;
187
-
188
- // Build tool name (sanitized)
189
- const name = operationId
190
- .replace(/[^a-zA-Z0-9_-]/g, '_')
191
- .replace(/_+/g, '_')
192
- .replace(/^_|_$/g, '');
193
-
194
- const description = operation.summary ||
195
- operation.description ||
196
- `${method.toUpperCase()} ${pathTemplate}`;
197
-
198
- // Build Zod schema string
199
- const zodSchema = buildZodSchema(operation, pathParams);
200
-
201
- // Extract execution parameters
202
- const allParams = [...pathParams, ...(operation.parameters || [])];
203
- const executionParameters = allParams.map(p => ({
204
- name: p.name,
205
- in: p.in,
206
- }));
207
-
208
- let requestBodyContentType;
209
- if (operation.requestBody?.content) {
210
- requestBodyContentType = Object.keys(operation.requestBody.content)[0];
211
- }
212
-
213
- const tool = {
214
- name,
215
- description: description.substring(0, 1024),
216
- zodSchema,
217
- method,
218
- pathTemplate,
219
- executionParameters,
220
- requestBodyContentType,
221
- operationId,
222
- baseUrl,
223
- };
224
-
225
- if (filterFn && !filterFn(tool)) continue;
226
-
227
- tools.push(tool);
228
- }
229
- }
230
-
231
- return tools;
232
- }
233
-
234
- // ============================================================================
235
- // Code Generation
236
- // ============================================================================
237
-
238
- function generatePackageJson(serverName, tools, port) {
239
- return JSON.stringify({
240
- name: serverName,
241
- version: '1.0.0',
242
- description: `MCP server generated from OpenAPI spec (${tools.length} tools)`,
243
- type: 'module',
244
- main: 'src/index.js',
245
- scripts: {
246
- start: 'node src/index.js',
247
- dev: 'node --watch src/index.js',
248
- },
249
- dependencies: {
250
- 'mcp-use': '1.11.2',
251
- 'zod': '^3.23.0',
252
- 'dotenv': '^17.2.3',
253
- },
254
- engines: { node: '>=18.0.0' },
255
- }, null, 2);
256
- }
257
-
258
- function generateEnvFile(baseUrl, port) {
259
- return `# Server Configuration
260
- PORT=${port}
261
- NODE_ENV=development
262
-
263
- # API Configuration
264
- API_BASE_URL=${baseUrl || 'https://api.example.com'}
265
-
266
- # Authentication (uncomment and configure as needed)
267
- # API_KEY=your-api-key
268
- # API_AUTH_HEADER=X-Custom-Auth:your-token
269
-
270
- # MCP Server URL (for UI widgets in production)
271
- # MCP_URL=https://your-production-url.com
272
-
273
- # Allowed Origins (comma-separated, for production)
274
- # ALLOWED_ORIGINS=https://app1.com,https://app2.com
275
- `;
276
- }
277
-
278
- function generateEnvExampleFile(baseUrl, port) {
279
- return `# Server Configuration
280
- PORT=${port}
281
- NODE_ENV=development
282
-
283
- # API Configuration
284
- API_BASE_URL=${baseUrl || 'https://api.example.com'}
285
-
286
- # Authentication
287
- API_KEY=your-api-key-here
288
- # API_AUTH_HEADER=Header-Name:header-value
289
-
290
- # MCP Configuration
291
- # MCP_URL=https://your-mcp-server.com
292
- # ALLOWED_ORIGINS=https://allowed-origin.com
293
- `;
294
- }
295
-
296
- function generateHttpClient() {
297
- return `// HTTP client for API requests
298
-
299
- /**
300
- * Build URL with path parameters substituted
301
- */
302
- export function buildUrl(baseUrl, pathTemplate, pathParams = {}) {
303
- let url = pathTemplate;
304
- for (const [key, value] of Object.entries(pathParams)) {
305
- url = url.replace(\`{\${key}}\`, encodeURIComponent(String(value)));
306
- }
307
- return new URL(url, baseUrl).toString();
308
- }
309
-
310
- /**
311
- * Build query string from parameters
312
- */
313
- export function buildQueryString(queryParams = {}) {
314
- const params = new URLSearchParams();
315
- for (const [key, value] of Object.entries(queryParams)) {
316
- if (value !== undefined && value !== null) {
317
- if (Array.isArray(value)) {
318
- value.forEach(v => params.append(key, String(v)));
319
- } else {
320
- params.append(key, String(value));
321
- }
322
- }
323
- }
324
- return params.toString();
325
- }
326
-
327
- /**
328
- * Execute HTTP request for a tool
329
- */
330
- export async function executeRequest(toolConfig, args, config = {}) {
331
- const { baseUrl: configBaseUrl, headers: configHeaders = {} } = config;
332
- const baseUrl = configBaseUrl || toolConfig.baseUrl;
333
-
334
- if (!baseUrl) {
335
- throw new Error(\`No base URL configured for tool: \${toolConfig.name}\`);
336
- }
337
-
338
- // Separate parameters by location
339
- const pathParams = {};
340
- const queryParams = {};
341
- const headerParams = {};
342
- let body;
343
-
344
- for (const param of toolConfig.executionParameters || []) {
345
- const value = args[param.name];
346
- if (value === undefined) continue;
347
-
348
- switch (param.in) {
349
- case 'path':
350
- pathParams[param.name] = value;
351
- break;
352
- case 'query':
353
- queryParams[param.name] = value;
354
- break;
355
- case 'header':
356
- headerParams[param.name] = value;
357
- break;
358
- }
359
- }
360
-
361
- // Handle request body
362
- if (args.requestBody !== undefined) {
363
- body = args.requestBody;
364
- }
365
-
366
- // Build URL
367
- let url = buildUrl(baseUrl, toolConfig.pathTemplate, pathParams);
368
-
369
- // Add query parameters
370
- const queryString = buildQueryString(queryParams);
371
- if (queryString) {
372
- url += (url.includes('?') ? '&' : '?') + queryString;
373
- }
374
-
375
- // Build headers
376
- const headers = {
377
- 'Accept': 'application/json',
378
- ...configHeaders,
379
- ...headerParams,
380
- };
381
-
382
- // Set content type for request body
383
- if (body !== undefined) {
384
- headers['Content-Type'] = toolConfig.requestBodyContentType || 'application/json';
385
- }
386
-
387
- // Build request options
388
- const requestOptions = {
389
- method: toolConfig.method.toUpperCase(),
390
- headers,
391
- };
392
-
393
- if (body !== undefined && ['POST', 'PUT', 'PATCH'].includes(requestOptions.method)) {
394
- requestOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
395
- }
396
-
397
- // Execute request
398
- const response = await fetch(url, requestOptions);
399
-
400
- // Parse response
401
- const contentType = response.headers.get('content-type') || '';
402
- let data;
403
-
404
- if (contentType.includes('application/json')) {
405
- data = await response.json();
406
- } else {
407
- data = await response.text();
408
- }
409
-
410
- return {
411
- status: response.status,
412
- statusText: response.statusText,
413
- data,
414
- ok: response.ok,
415
- };
416
- }
417
- `;
418
- }
419
-
420
- function generateToolsConfig(tools) {
421
- const toolConfigs = tools.map(tool => ({
422
- name: tool.name,
423
- description: tool.description,
424
- method: tool.method,
425
- pathTemplate: tool.pathTemplate,
426
- executionParameters: tool.executionParameters,
427
- requestBodyContentType: tool.requestBodyContentType,
428
- baseUrl: tool.baseUrl,
429
- }));
430
-
431
- return `// Tool configurations extracted from OpenAPI spec
432
- // Generated: ${new Date().toISOString()}
433
-
434
- export const toolConfigs = ${JSON.stringify(toolConfigs, null, 2)};
435
-
436
- // Create a map for quick lookup
437
- export const toolConfigMap = new Map(toolConfigs.map(t => [t.name, t]));
438
- `;
439
- }
440
-
441
- function generateServerIndex(serverName, tools, baseUrl, port) {
442
- // Generate tool registration code
443
- const toolRegistrations = tools.map(tool => {
444
- return `
445
- // ${tool.description}
446
- server.tool('${tool.name}', {
447
- description: '${tool.description.replace(/'/g, "\\'")}',
448
- parameters: ${tool.zodSchema},
449
- execute: async (params) => {
450
- const toolConfig = toolConfigMap.get('${tool.name}');
451
- const result = await executeRequest(toolConfig, params, apiConfig);
452
-
453
- if (result.ok) {
454
- return typeof result.data === 'string'
455
- ? result.data
456
- : JSON.stringify(result.data, null, 2);
457
- } else {
458
- throw new Error(\`API Error (\${result.status}): \${
459
- typeof result.data === 'string' ? result.data : JSON.stringify(result.data)
460
- }\`);
461
- }
462
- },
463
- });`;
464
- }).join('\n');
465
-
466
- return `#!/usr/bin/env node
467
-
468
- /**
469
- * ${serverName} - MCP Server
470
- *
471
- * Features:
472
- * - ${tools.length} API tools available
473
- * - Built-in Inspector at http://localhost:${port}/inspector
474
- */
475
-
476
- import 'dotenv/config';
477
- import { MCPServer } from 'mcp-use/server';
478
- import { z } from 'zod';
479
- import { executeRequest } from './http-client.js';
480
- import { toolConfigMap } from './tools-config.js';
481
-
482
- // ============================================================================
483
- // Configuration
484
- // ============================================================================
485
-
486
- const PORT = parseInt(process.env.PORT || '${port}');
487
- const isDev = process.env.NODE_ENV !== 'production';
488
-
489
- // API configuration
490
- const apiConfig = {
491
- baseUrl: process.env.API_BASE_URL || ${baseUrl ? `'${baseUrl}'` : 'null'},
492
- headers: {},
493
- };
494
-
495
- // Set up authentication headers
496
- if (process.env.API_KEY) {
497
- apiConfig.headers['Authorization'] = \`Bearer \${process.env.API_KEY}\`;
498
- }
499
-
500
- if (process.env.API_AUTH_HEADER) {
501
- const [key, ...valueParts] = process.env.API_AUTH_HEADER.split(':');
502
- const value = valueParts.join(':'); // Handle values with colons
503
- if (key && value) {
504
- apiConfig.headers[key.trim()] = value.trim();
505
- }
506
- }
507
-
508
- // ============================================================================
509
- // Server Setup
510
- // ============================================================================
511
-
512
- const server = new MCPServer({
513
- name: '${serverName}',
514
- version: '1.0.0',
515
- description: 'MCP server generated from OpenAPI specification',
516
- baseUrl: process.env.MCP_URL || \`http://localhost:\${PORT}\`,
517
- allowedOrigins: isDev
518
- ? undefined // Development: allow all origins
519
- : process.env.ALLOWED_ORIGINS?.split(',').map(s => s.trim()) || [],
520
- });
521
-
522
- // ============================================================================
523
- // Tool Registrations
524
- // ============================================================================
525
- ${toolRegistrations}
526
-
527
- // ============================================================================
528
- // Start Server
529
- // ============================================================================
530
-
531
- server.listen(PORT);
532
-
533
- console.log(\`
534
- šŸš€ ${serverName} MCP Server Started
535
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
536
-
537
- šŸ“ Server: http://localhost:\${PORT}
538
- šŸ” Inspector: http://localhost:\${PORT}/inspector
539
- šŸ“” MCP: http://localhost:\${PORT}/mcp
540
- šŸ”„ SSE: http://localhost:\${PORT}/sse
541
-
542
- šŸ› ļø Tools Available: ${tools.length}
543
- ${tools.slice(0, 5).map(t => ` • ${t.name}`).join('\n')}${tools.length > 5 ? `\n ... and ${tools.length - 5} more` : ''}
544
- Environment: \${isDev ? 'Development' : 'Production'}
545
- API Base: \${apiConfig.baseUrl || 'Not configured'}
546
- \`);
547
- `;
548
- }
549
-
550
- function generateReadme(serverName, tools, specPath, baseUrl, port) {
551
- const toolList = tools
552
- .map(t => `| \`${t.name}\` | ${t.method.toUpperCase()} | ${t.pathTemplate} | ${t.description.substring(0, 50)}${t.description.length > 50 ? '...' : ''} |`)
553
- .join('\n');
554
-
555
- return `# ${serverName}
556
-
557
- MCP server auto-generated from OpenAPI specification using the [mcp-use](https://mcp-use.com) framework.
558
-
559
- ## Features
560
-
561
- - šŸ› ļø **${tools.length} API Tools** - All operations from the OpenAPI spec
562
- - šŸ” **Built-in Inspector** - Test tools at \`/inspector\`
563
- - šŸ“” **Streamable HTTP** - Modern MCP transport
564
- - šŸ” **Authentication Support** - Bearer tokens & custom headers
565
- - šŸŽØ **UI Widgets** - Compatible with ChatGPT and MCP-UI
566
-
567
- ## Quick Start
568
-
569
- \`\`\`bash
570
- # Install dependencies
571
- npm install
572
-
573
- # Configure environment
574
- cp .env.example .env
575
- # Edit .env with your API credentials
576
-
577
- # Start the server
578
- npm start
579
-
580
- # Or with hot reload
581
- npm run dev
582
- \`\`\`
583
-
584
- Then open http://localhost:${port}/inspector to test your tools!
585
-
586
- ## Environment Variables
587
-
588
- | Variable | Description | Default |
589
- |----------|-------------|---------|
590
- | \`PORT\` | Server port | ${port} |
591
- | \`NODE_ENV\` | Environment (development/production) | development |
592
- | \`API_BASE_URL\` | Base URL for API requests | ${baseUrl || 'From OpenAPI spec'} |
593
- | \`API_KEY\` | Bearer token for Authorization header | - |
594
- | \`API_AUTH_HEADER\` | Custom auth header (format: \`Header:value\`) | - |
595
- | \`MCP_URL\` | Public MCP server URL (for widgets) | http://localhost:${port} |
596
- | \`ALLOWED_ORIGINS\` | Allowed origins in production (comma-separated) | - |
597
-
598
- ## Connect to Claude Desktop
599
-
600
- Add to your Claude Desktop configuration:
601
-
602
- **macOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
603
- **Windows**: \`%APPDATA%\\Claude\\claude_desktop_config.json\`
604
-
605
- \`\`\`json
606
- {
607
- "mcpServers": {
608
- "${serverName}": {
609
- "url": "http://localhost:${port}/mcp"
610
- }
611
- }
612
- }
613
- \`\`\`
614
-
615
- ## Connect to ChatGPT
616
-
617
- This server supports the OpenAI Apps SDK. Configure your ChatGPT integration to use:
618
-
619
- \`\`\`
620
- http://localhost:${port}/mcp
621
- \`\`\`
622
-
623
- ## Available Tools
624
-
625
- | Tool | Method | Path | Description |
626
- |------|--------|------|-------------|
627
- ${toolList}
628
-
629
- ## API Endpoints
630
-
631
- | Endpoint | Description |
632
- |----------|-------------|
633
- | \`GET /inspector\` | Interactive tool testing UI |
634
- | \`POST /mcp\` | MCP protocol endpoint |
635
- | \`GET /sse\` | Server-Sent Events endpoint |
636
- | \`GET /health\` | Health check endpoint |
637
-
638
- ## Project Structure
639
-
640
- \`\`\`
641
- ${serverName}/
642
- ā”œā”€ā”€ .env # Environment configuration
643
- ā”œā”€ā”€ .env.example # Example environment file
644
- ā”œā”€ā”€ package.json # Dependencies
645
- ā”œā”€ā”€ README.md # This file
646
- └── src/
647
- ā”œā”€ā”€ index.js # Main server with tool registrations
648
- ā”œā”€ā”€ http-client.js # HTTP utilities for API calls
649
- └── tools-config.js # Tool configurations from OpenAPI
650
- \`\`\`
651
-
652
- ## Production Deployment
653
-
654
- ### Docker
655
-
656
- \`\`\`dockerfile
657
- FROM node:20-alpine
658
- WORKDIR /app
659
- COPY package*.json ./
660
- RUN npm ci --only=production
661
- COPY . .
662
- ENV NODE_ENV=production
663
- EXPOSE ${port}
664
- CMD ["npm", "start"]
665
- \`\`\`
666
-
667
- ### PM2
668
-
669
- \`\`\`bash
670
- pm2 start src/index.js --name ${serverName}
671
- \`\`\`
672
-
673
- ## Source
674
-
675
- - **OpenAPI Spec**: \`${specPath}\`
676
- - **Generated**: ${new Date().toISOString()}
677
- - **Framework**: [mcp-use](https://mcp-use.com)
678
-
679
- ## License
680
-
681
- MIT
682
- `;
683
- }
684
-
685
- // ============================================================================
686
- // Main Generator
687
- // ============================================================================
688
-
689
- async function generateMcpServer(specPathOrUrl, outputFolder, options = {}) {
690
- const {
691
- baseUrl,
692
- serverName = 'openapi-mcp-server',
693
- port = 3000,
694
- } = options;
695
-
696
- console.log(`\nšŸ“– Loading OpenAPI spec: ${specPathOrUrl}`);
697
- const spec = await loadOpenApiSpec(specPathOrUrl);
698
-
699
- console.log(` Title: ${spec.info?.title || 'Unknown'}`);
700
- console.log(` Version: ${spec.info?.version || 'Unknown'}`);
701
-
702
- const tools = extractTools(spec, { baseUrl, ...options });
703
- console.log(`āœ… Extracted ${tools.length} tools\n`);
704
-
705
- // Create directory structure
706
- const srcDir = path.join(outputFolder, 'src');
707
- await fs.mkdir(srcDir, { recursive: true });
708
-
709
- const effectiveBaseUrl = baseUrl || tools[0]?.baseUrl;
710
-
711
- // Generate all files
712
- const files = [
713
- {
714
- path: path.join(outputFolder, 'package.json'),
715
- content: generatePackageJson(serverName, tools, port)
716
- },
717
- {
718
- path: path.join(outputFolder, '.env'),
719
- content: generateEnvFile(effectiveBaseUrl, port)
720
- },
721
- {
722
- path: path.join(outputFolder, '.env.example'),
723
- content: generateEnvExampleFile(effectiveBaseUrl, port)
724
- },
725
- {
726
- path: path.join(srcDir, 'http-client.js'),
727
- content: generateHttpClient()
728
- },
729
- {
730
- path: path.join(srcDir, 'tools-config.js'),
731
- content: generateToolsConfig(tools)
732
- },
733
- {
734
- path: path.join(srcDir, 'index.js'),
735
- content: generateServerIndex(serverName, tools, effectiveBaseUrl, port)
736
- },
737
- {
738
- path: path.join(outputFolder, 'README.md'),
739
- content: generateReadme(serverName, tools, specPathOrUrl, effectiveBaseUrl, port)
740
- },
741
- {
742
- path: path.join(outputFolder, '.gitignore'),
743
- content: 'node_modules/\n.env\n*.log\n'
744
- },
745
- ];
746
-
747
- for (const file of files) {
748
- await fs.writeFile(file.path, file.content);
749
- console.log(` āœ“ ${path.relative(outputFolder, file.path)}`);
750
- }
751
-
752
- console.log(`
753
- šŸŽ‰ MCP-Use Server Generated
754
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
755
-
756
-
757
- cd ${outputFolder}
758
- npm install
759
- npm start
760
-
761
- Then open http://localhost:${port}/inspector to test your tools!
762
- `);
763
-
764
- return {
765
- outputFolder,
766
- toolCount: tools.length,
767
- tools: tools.map(t => t.name),
768
- port,
769
- };
770
- }
771
-
772
- // ============================================================================
773
- // Exports & CLI
774
- // ============================================================================
775
-
776
- export { generateMcpServer, extractTools, loadOpenApiSpec };
777
-
778
- // CLI entry point
779
- const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
780
-
781
- if (isMainModule) {
782
- const args = process.argv.slice(2);
783
-
784
- if (args.length < 1 || args.includes('--help') || args.includes('-h')) {
785
- console.log(`
786
- OpenAPI to MCP Server Generator (mcp-use framework)
787
-
788
- Usage:
789
- npx api2ai <openapi-spec> [output-folder] [options]
790
-
791
- Arguments:
792
- openapi-spec Path to local file or URL to remote OpenAPI spec
793
- output-folder Directory to create the server in (default: ./mcp-server)
794
-
795
- Options:
796
- --name <name> Server name (default: openapi-mcp-server)
797
- --base-url <url> Override API base URL from the spec
798
- --port <port> Server port (default: 3000)
799
- --help, -h Show this help message
800
-
801
- Examples:
802
- npx api2ai ./petstore.json ./my-server
803
- npx api2ai https://petstore3.swagger.io/api/v3/openapi.json ./petstore-mcp \\
804
- --name petstore-api --port 8080
805
- `);
806
- process.exit(0);
807
- }
808
-
809
- const options = {
810
- specPath: args[0],
811
- outputFolder: './mcp-server',
812
- baseUrl: null,
813
- serverName: 'api-mcp-server',
814
- port: 3000,
815
- };
816
-
817
- for (let i = 1; i < args.length; i++) {
818
- if (args[i] === '--base-url' && args[i + 1]) {
819
- options.baseUrl = args[++i];
820
- } else if (args[i] === '--name' && args[i + 1]) {
821
- options.serverName = args[++i];
822
- } else if (args[i] === '--port' && args[i + 1]) {
823
- options.port = parseInt(args[++i]);
824
- } else if (!args[i].startsWith('--')) {
825
- options.outputFolder = args[i];
826
- }
827
- }
828
-
829
- generateMcpServer(options.specPath, options.outputFolder, options).catch(e => {
830
- console.error('āŒ Error:', e.message);
831
- process.exit(1);
832
- });
833
- }