api2ai 1.0.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,832 @@
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': '^0.2.0',
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
442
+ const toolRegistrations = tools.map(tool => {
443
+ return `
444
+ // ${tool.description}
445
+ server.tool('${tool.name}', {
446
+ description: '${tool.description.replace(/'/g, "\\'")}',
447
+ parameters: ${tool.zodSchema},
448
+ execute: async (params) => {
449
+ const toolConfig = toolConfigMap.get('${tool.name}');
450
+ const result = await executeRequest(toolConfig, params, apiConfig);
451
+
452
+ if (result.ok) {
453
+ return typeof result.data === 'string'
454
+ ? result.data
455
+ : JSON.stringify(result.data, null, 2);
456
+ } else {
457
+ throw new Error(\`API Error (\${result.status}): \${
458
+ typeof result.data === 'string' ? result.data : JSON.stringify(result.data)
459
+ }\`);
460
+ }
461
+ },
462
+ });`;
463
+ }).join('\n');
464
+
465
+ return `#!/usr/bin/env node
466
+
467
+ /**
468
+ * ${serverName} - MCP Server
469
+ *
470
+ * Features:
471
+ * - ${tools.length} API tools available
472
+ * - Built-in Inspector at http://localhost:${port}/inspector
473
+ */
474
+
475
+ import 'dotenv/config';
476
+ import { MCPServer } from 'mcp-use/server';
477
+ import { z } from 'zod';
478
+ import { executeRequest } from './http-client.js';
479
+ import { toolConfigMap } from './tools-config.js';
480
+
481
+ // ============================================================================
482
+ // Configuration
483
+ // ============================================================================
484
+
485
+ const PORT = parseInt(process.env.PORT || '${port}');
486
+ const isDev = process.env.NODE_ENV !== 'production';
487
+
488
+ // API configuration
489
+ const apiConfig = {
490
+ baseUrl: process.env.API_BASE_URL || ${baseUrl ? `'${baseUrl}'` : 'null'},
491
+ headers: {},
492
+ };
493
+
494
+ // Set up authentication headers
495
+ if (process.env.API_KEY) {
496
+ apiConfig.headers['Authorization'] = \`Bearer \${process.env.API_KEY}\`;
497
+ }
498
+
499
+ if (process.env.API_AUTH_HEADER) {
500
+ const [key, ...valueParts] = process.env.API_AUTH_HEADER.split(':');
501
+ const value = valueParts.join(':'); // Handle values with colons
502
+ if (key && value) {
503
+ apiConfig.headers[key.trim()] = value.trim();
504
+ }
505
+ }
506
+
507
+ // ============================================================================
508
+ // Server Setup
509
+ // ============================================================================
510
+
511
+ const server = new MCPServer({
512
+ name: '${serverName}',
513
+ version: '1.0.0',
514
+ description: 'MCP server generated from OpenAPI specification',
515
+ baseUrl: process.env.MCP_URL || \`http://localhost:\${PORT}\`,
516
+ allowedOrigins: isDev
517
+ ? undefined // Development: allow all origins
518
+ : process.env.ALLOWED_ORIGINS?.split(',').map(s => s.trim()) || [],
519
+ });
520
+
521
+ // ============================================================================
522
+ // Tool Registrations
523
+ // ============================================================================
524
+ ${toolRegistrations}
525
+
526
+ // ============================================================================
527
+ // Start Server
528
+ // ============================================================================
529
+
530
+ server.listen(PORT);
531
+
532
+ console.log(\`
533
+ šŸš€ ${serverName} MCP Server Started
534
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
535
+
536
+ šŸ“ Server: http://localhost:\${PORT}
537
+ šŸ” Inspector: http://localhost:\${PORT}/inspector
538
+ šŸ“” MCP: http://localhost:\${PORT}/mcp
539
+ šŸ”„ SSE: http://localhost:\${PORT}/sse
540
+
541
+ šŸ› ļø Tools Available: ${tools.length}
542
+ ${tools.slice(0, 5).map(t => ` • ${t.name}`).join('\n')}${tools.length > 5 ? `\n ... and ${tools.length - 5} more` : ''}
543
+ Environment: \${isDev ? 'Development' : 'Production'}
544
+ API Base: \${apiConfig.baseUrl || 'Not configured'}
545
+ \`);
546
+ `;
547
+ }
548
+
549
+ function generateReadme(serverName, tools, specPath, baseUrl, port) {
550
+ const toolList = tools
551
+ .map(t => `| \`${t.name}\` | ${t.method.toUpperCase()} | ${t.pathTemplate} | ${t.description.substring(0, 50)}${t.description.length > 50 ? '...' : ''} |`)
552
+ .join('\n');
553
+
554
+ return `# ${serverName}
555
+
556
+ MCP server auto-generated from OpenAPI specification using the [mcp-use](https://mcp-use.com) framework.
557
+
558
+ ## Features
559
+
560
+ - šŸ› ļø **${tools.length} API Tools** - All operations from the OpenAPI spec
561
+ - šŸ” **Built-in Inspector** - Test tools at \`/inspector\`
562
+ - šŸ“” **Streamable HTTP** - Modern MCP transport
563
+ - šŸ” **Authentication Support** - Bearer tokens & custom headers
564
+ - šŸŽØ **UI Widgets** - Compatible with ChatGPT and MCP-UI
565
+
566
+ ## Quick Start
567
+
568
+ \`\`\`bash
569
+ # Install dependencies
570
+ npm install
571
+
572
+ # Configure environment
573
+ cp .env.example .env
574
+ # Edit .env with your API credentials
575
+
576
+ # Start the server
577
+ npm start
578
+
579
+ # Or with hot reload
580
+ npm run dev
581
+ \`\`\`
582
+
583
+ Then open http://localhost:${port}/inspector to test your tools!
584
+
585
+ ## Environment Variables
586
+
587
+ | Variable | Description | Default |
588
+ |----------|-------------|---------|
589
+ | \`PORT\` | Server port | ${port} |
590
+ | \`NODE_ENV\` | Environment (development/production) | development |
591
+ | \`API_BASE_URL\` | Base URL for API requests | ${baseUrl || 'From OpenAPI spec'} |
592
+ | \`API_KEY\` | Bearer token for Authorization header | - |
593
+ | \`API_AUTH_HEADER\` | Custom auth header (format: \`Header:value\`) | - |
594
+ | \`MCP_URL\` | Public MCP server URL (for widgets) | http://localhost:${port} |
595
+ | \`ALLOWED_ORIGINS\` | Allowed origins in production (comma-separated) | - |
596
+
597
+ ## Connect to Claude Desktop
598
+
599
+ Add to your Claude Desktop configuration:
600
+
601
+ **macOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
602
+ **Windows**: \`%APPDATA%\\Claude\\claude_desktop_config.json\`
603
+
604
+ \`\`\`json
605
+ {
606
+ "mcpServers": {
607
+ "${serverName}": {
608
+ "url": "http://localhost:${port}/mcp"
609
+ }
610
+ }
611
+ }
612
+ \`\`\`
613
+
614
+ ## Connect to ChatGPT
615
+
616
+ This server supports the OpenAI Apps SDK. Configure your ChatGPT integration to use:
617
+
618
+ \`\`\`
619
+ http://localhost:${port}/mcp
620
+ \`\`\`
621
+
622
+ ## Available Tools
623
+
624
+ | Tool | Method | Path | Description |
625
+ |------|--------|------|-------------|
626
+ ${toolList}
627
+
628
+ ## API Endpoints
629
+
630
+ | Endpoint | Description |
631
+ |----------|-------------|
632
+ | \`GET /inspector\` | Interactive tool testing UI |
633
+ | \`POST /mcp\` | MCP protocol endpoint |
634
+ | \`GET /sse\` | Server-Sent Events endpoint |
635
+ | \`GET /health\` | Health check endpoint |
636
+
637
+ ## Project Structure
638
+
639
+ \`\`\`
640
+ ${serverName}/
641
+ ā”œā”€ā”€ .env # Environment configuration
642
+ ā”œā”€ā”€ .env.example # Example environment file
643
+ ā”œā”€ā”€ package.json # Dependencies
644
+ ā”œā”€ā”€ README.md # This file
645
+ └── src/
646
+ ā”œā”€ā”€ index.js # Main server with tool registrations
647
+ ā”œā”€ā”€ http-client.js # HTTP utilities for API calls
648
+ └── tools-config.js # Tool configurations from OpenAPI
649
+ \`\`\`
650
+
651
+ ## Production Deployment
652
+
653
+ ### Docker
654
+
655
+ \`\`\`dockerfile
656
+ FROM node:20-alpine
657
+ WORKDIR /app
658
+ COPY package*.json ./
659
+ RUN npm ci --only=production
660
+ COPY . .
661
+ ENV NODE_ENV=production
662
+ EXPOSE ${port}
663
+ CMD ["npm", "start"]
664
+ \`\`\`
665
+
666
+ ### PM2
667
+
668
+ \`\`\`bash
669
+ pm2 start src/index.js --name ${serverName}
670
+ \`\`\`
671
+
672
+ ## Source
673
+
674
+ - **OpenAPI Spec**: \`${specPath}\`
675
+ - **Generated**: ${new Date().toISOString()}
676
+ - **Framework**: [mcp-use](https://mcp-use.com)
677
+
678
+ ## License
679
+
680
+ MIT
681
+ `;
682
+ }
683
+
684
+ // ============================================================================
685
+ // Main Generator
686
+ // ============================================================================
687
+
688
+ async function generateMcpServer(specPathOrUrl, outputFolder, options = {}) {
689
+ const {
690
+ baseUrl,
691
+ serverName = 'openapi-mcp-server',
692
+ port = 3000,
693
+ } = options;
694
+
695
+ console.log(`\nšŸ“– Loading OpenAPI spec: ${specPathOrUrl}`);
696
+ const spec = await loadOpenApiSpec(specPathOrUrl);
697
+
698
+ console.log(` Title: ${spec.info?.title || 'Unknown'}`);
699
+ console.log(` Version: ${spec.info?.version || 'Unknown'}`);
700
+
701
+ const tools = extractTools(spec, { baseUrl, ...options });
702
+ console.log(`āœ… Extracted ${tools.length} tools\n`);
703
+
704
+ // Create directory structure
705
+ const srcDir = path.join(outputFolder, 'src');
706
+ await fs.mkdir(srcDir, { recursive: true });
707
+
708
+ const effectiveBaseUrl = baseUrl || tools[0]?.baseUrl;
709
+
710
+ // Generate all files
711
+ const files = [
712
+ {
713
+ path: path.join(outputFolder, 'package.json'),
714
+ content: generatePackageJson(serverName, tools, port)
715
+ },
716
+ {
717
+ path: path.join(outputFolder, '.env'),
718
+ content: generateEnvFile(effectiveBaseUrl, port)
719
+ },
720
+ {
721
+ path: path.join(outputFolder, '.env.example'),
722
+ content: generateEnvExampleFile(effectiveBaseUrl, port)
723
+ },
724
+ {
725
+ path: path.join(srcDir, 'http-client.js'),
726
+ content: generateHttpClient()
727
+ },
728
+ {
729
+ path: path.join(srcDir, 'tools-config.js'),
730
+ content: generateToolsConfig(tools)
731
+ },
732
+ {
733
+ path: path.join(srcDir, 'index.js'),
734
+ content: generateServerIndex(serverName, tools, effectiveBaseUrl, port)
735
+ },
736
+ {
737
+ path: path.join(outputFolder, 'README.md'),
738
+ content: generateReadme(serverName, tools, specPathOrUrl, effectiveBaseUrl, port)
739
+ },
740
+ {
741
+ path: path.join(outputFolder, '.gitignore'),
742
+ content: 'node_modules/\n.env\n*.log\n'
743
+ },
744
+ ];
745
+
746
+ for (const file of files) {
747
+ await fs.writeFile(file.path, file.content);
748
+ console.log(` āœ“ ${path.relative(outputFolder, file.path)}`);
749
+ }
750
+
751
+ console.log(`
752
+ šŸŽ‰ MCP-Use Server Generated
753
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
754
+
755
+
756
+ cd ${outputFolder}
757
+ npm install
758
+ npm start
759
+
760
+ Then open http://localhost:${port}/inspector to test your tools!
761
+ `);
762
+
763
+ return {
764
+ outputFolder,
765
+ toolCount: tools.length,
766
+ tools: tools.map(t => t.name),
767
+ port,
768
+ };
769
+ }
770
+
771
+ // ============================================================================
772
+ // Exports & CLI
773
+ // ============================================================================
774
+
775
+ export { generateMcpServer, extractTools, loadOpenApiSpec };
776
+
777
+ // CLI entry point
778
+ const isMainModule = process.argv[1]?.includes('generate-mcp-use-server');
779
+
780
+ if (isMainModule) {
781
+ const args = process.argv.slice(2);
782
+
783
+ if (args.length < 1 || args.includes('--help') || args.includes('-h')) {
784
+ console.log(`
785
+ OpenAPI to MCP Server Generator (mcp-use framework)
786
+
787
+ Usage:
788
+ node generate-mcp-use-server.js <openapi-spec> [output-folder] [options]
789
+
790
+ Arguments:
791
+ openapi-spec Path to local file or URL to remote OpenAPI spec
792
+ output-folder Directory to create the server in (default: ./mcp-server)
793
+
794
+ Options:
795
+ --name <name> Server name (default: openapi-mcp-server)
796
+ --base-url <url> Override API base URL from the spec
797
+ --port <port> Server port (default: 3000)
798
+ --help, -h Show this help message
799
+
800
+ Examples:
801
+ node generate-mcp-use-server.js ./petstore.json ./my-server
802
+ node generate-mcp-use-server.js https://petstore3.swagger.io/api/v3/openapi.json ./petstore-mcp \\
803
+ --name petstore-api --port 8080
804
+ `);
805
+ process.exit(0);
806
+ }
807
+
808
+ const options = {
809
+ specPath: args[0],
810
+ outputFolder: './mcp-server',
811
+ baseUrl: null,
812
+ serverName: 'api-mcp-server',
813
+ port: 3000,
814
+ };
815
+
816
+ for (let i = 1; i < args.length; i++) {
817
+ if (args[i] === '--base-url' && args[i + 1]) {
818
+ options.baseUrl = args[++i];
819
+ } else if (args[i] === '--name' && args[i + 1]) {
820
+ options.serverName = args[++i];
821
+ } else if (args[i] === '--port' && args[i + 1]) {
822
+ options.port = parseInt(args[++i]);
823
+ } else if (!args[i].startsWith('--')) {
824
+ options.outputFolder = args[i];
825
+ }
826
+ }
827
+
828
+ generateMcpServer(options.specPath, options.outputFolder, options).catch(e => {
829
+ console.error('āŒ Error:', e.message);
830
+ process.exit(1);
831
+ });
832
+ }