api2ai 1.0.6 → 1.0.7

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