@christopher_dondici/mcp-gen 2.1.2

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +217 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/README.pt-BR.md +320 -0
  5. package/RELEASE_NOTES.md +98 -0
  6. package/SECURITY.md +75 -0
  7. package/SECURITY.pt-BR.md +77 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +685 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/core/generator.d.ts +4 -0
  13. package/dist/core/generator.d.ts.map +1 -0
  14. package/dist/core/generator.js +275 -0
  15. package/dist/core/generator.js.map +1 -0
  16. package/dist/core/incremental.d.ts +25 -0
  17. package/dist/core/incremental.d.ts.map +1 -0
  18. package/dist/core/incremental.js +91 -0
  19. package/dist/core/incremental.js.map +1 -0
  20. package/dist/core/parser.d.ts +3 -0
  21. package/dist/core/parser.d.ts.map +1 -0
  22. package/dist/core/parser.js +372 -0
  23. package/dist/core/parser.js.map +1 -0
  24. package/dist/core/registry.d.ts +13 -0
  25. package/dist/core/registry.d.ts.map +1 -0
  26. package/dist/core/registry.js +107 -0
  27. package/dist/core/registry.js.map +1 -0
  28. package/dist/core/security-lint.d.ts +53 -0
  29. package/dist/core/security-lint.d.ts.map +1 -0
  30. package/dist/core/security-lint.js +470 -0
  31. package/dist/core/security-lint.js.map +1 -0
  32. package/dist/core/security.d.ts +41 -0
  33. package/dist/core/security.d.ts.map +1 -0
  34. package/dist/core/security.js +150 -0
  35. package/dist/core/security.js.map +1 -0
  36. package/dist/core/templating.d.ts +5 -0
  37. package/dist/core/templating.d.ts.map +1 -0
  38. package/dist/core/templating.js +211 -0
  39. package/dist/core/templating.js.map +1 -0
  40. package/dist/core/types.d.ts +104 -0
  41. package/dist/core/types.d.ts.map +1 -0
  42. package/dist/core/types.js +3 -0
  43. package/dist/core/types.js.map +1 -0
  44. package/dist/index.d.ts +7 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +20 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/templates/go/Dockerfile.hbs +16 -0
  49. package/dist/templates/go/README.md.hbs +77 -0
  50. package/dist/templates/go/ci.yml.hbs +28 -0
  51. package/dist/templates/go/client.go.hbs +86 -0
  52. package/dist/templates/go/go.mod.hbs +5 -0
  53. package/dist/templates/go/models.go.hbs +28 -0
  54. package/dist/templates/go/server.go.hbs +220 -0
  55. package/dist/templates/python/Dockerfile.hbs +12 -0
  56. package/dist/templates/python/README.md.hbs +115 -0
  57. package/dist/templates/python/ci.yml.hbs +24 -0
  58. package/dist/templates/python/models.py.hbs +20 -0
  59. package/dist/templates/python/requirements.txt.hbs +3 -0
  60. package/dist/templates/python/server.py.hbs +195 -0
  61. package/dist/templates/typescript/Dockerfile.hbs +18 -0
  62. package/dist/templates/typescript/README.md.hbs +91 -0
  63. package/dist/templates/typescript/ci.yml.hbs +28 -0
  64. package/dist/templates/typescript/client.hbs +49 -0
  65. package/dist/templates/typescript/models.hbs +23 -0
  66. package/dist/templates/typescript/package.json.hbs +26 -0
  67. package/dist/templates/typescript/server.hbs +337 -0
  68. package/dist/templates/typescript/tsconfig.json.hbs +17 -0
  69. package/examples/petstore.json +130 -0
  70. package/examples/petstore.yaml +131 -0
  71. package/examples/week3.json +47 -0
  72. package/package.json +64 -0
@@ -0,0 +1,685 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const ora_1 = __importDefault(require("ora"));
43
+ const path_1 = __importDefault(require("path"));
44
+ const generator_1 = require("../core/generator");
45
+ const registry_1 = require("../core/registry");
46
+ const fs_1 = __importDefault(require("fs"));
47
+ const inquirer_1 = __importDefault(require("inquirer"));
48
+ const SUPPORTED_LANGS = ["typescript", "python", "go"];
49
+ const SUPPORTED_EXTS = [".json", ".yaml", ".yml"];
50
+ const VERSION = (() => {
51
+ try {
52
+ return require("../../package.json").version;
53
+ }
54
+ catch {
55
+ return "0.0.0";
56
+ }
57
+ })();
58
+ function resolveInput(input) {
59
+ if (input.startsWith("http://") || input.startsWith("https://"))
60
+ return input;
61
+ return path_1.default.resolve(input);
62
+ }
63
+ function validateLang(lang) {
64
+ if (!SUPPORTED_LANGS.includes(lang)) {
65
+ console.error(chalk_1.default.red(`\n ✗ Unsupported language: "${lang}". Choose: ${SUPPORTED_LANGS.join(" | ")}\n`));
66
+ process.exit(1);
67
+ }
68
+ }
69
+ function validateInputExt(input) {
70
+ if (input.startsWith("http"))
71
+ return;
72
+ const ext = path_1.default.extname(input).toLowerCase();
73
+ if (!SUPPORTED_EXTS.includes(ext)) {
74
+ console.error(chalk_1.default.red(`\n ✗ Unsupported file extension: "${ext}". Accepted: ${SUPPORTED_EXTS.join(", ")}\n`));
75
+ process.exit(1);
76
+ }
77
+ }
78
+ const program = new commander_1.Command();
79
+ program
80
+ .name("mcp-gen")
81
+ .description("OpenAPI to MCP Server generator")
82
+ .version(VERSION);
83
+ program
84
+ .command("generate")
85
+ .alias("g")
86
+ .description("Generate an MCP server from an OpenAPI spec (JSON or YAML)")
87
+ .requiredOption("-i, --input <path>", "Path or URL to the OpenAPI spec (.json | .yaml | .yml)")
88
+ .option("-l, --lang <language>", `Target language: ${SUPPORTED_LANGS.join(" | ")}`, "typescript")
89
+ .option("-o, --out <dir>", "Output directory for the generated project", "./mcp-server")
90
+ .option("-f, --force", "Overwrite existing files without prompting", false)
91
+ .option("--incremental", "Preserve custom handler code on re-generation (@@mcp-gen markers)", false)
92
+ .option("--http", "Generate handlers that call the real API over HTTP instead of returning example stubs", false)
93
+ .option("--env-file <path>", "Path to an .env-style file whose TOKEN/BASE_URL are embedded into the generated client")
94
+ .option("--name <name>", "Override the server name")
95
+ .option("--server-version <version>", "Override the server version")
96
+ .option("--plugin <path>", "Path to a plugin module or folder to load", (val, acc) => {
97
+ if (!acc)
98
+ return [val];
99
+ acc.push(val);
100
+ return acc;
101
+ }, [])
102
+ .action(async (opts) => {
103
+ validateLang(opts.lang);
104
+ const input = resolveInput(opts.input);
105
+ validateInputExt(input);
106
+ const plugins = opts.plugin ?? [];
107
+ const options = {
108
+ input,
109
+ lang: opts.lang,
110
+ out: path_1.default.resolve(opts.out),
111
+ force: opts.force,
112
+ incremental: opts.incremental,
113
+ http: opts.http,
114
+ envFile: opts.envFile,
115
+ plugins,
116
+ serverName: opts.name,
117
+ serverVersion: opts.serverVersion,
118
+ };
119
+ console.log(chalk_1.default.bold("\nmcp-gen") + ` v${VERSION} — OpenAPI to MCP Server\n`);
120
+ console.log(` Input: ${chalk_1.default.cyan(options.input)}`);
121
+ console.log(` Language: ${chalk_1.default.cyan(options.lang)}`);
122
+ console.log(` Output: ${chalk_1.default.cyan(options.out)}`);
123
+ if (options.http) {
124
+ console.log(` HTTP mode: ${chalk_1.default.green("on — handlers will call the real API")}`);
125
+ }
126
+ if (options.incremental) {
127
+ console.log(` Incremental: ${chalk_1.default.yellow("on — custom handlers will be preserved")}`);
128
+ }
129
+ console.log();
130
+ const spinner = (0, ora_1.default)("Parsing OpenAPI spec...").start();
131
+ try {
132
+ const result = await (0, generator_1.generate)(options);
133
+ if (result.warnings.length > 0) {
134
+ spinner.warn("Completed with warnings");
135
+ for (const w of result.warnings)
136
+ console.log(chalk_1.default.yellow(` ⚠ ${w}`));
137
+ console.log();
138
+ }
139
+ if (!result.success) {
140
+ spinner.fail("Generation failed");
141
+ for (const err of result.errors)
142
+ console.error(chalk_1.default.red(` ✗ ${err}`));
143
+ process.exit(1);
144
+ }
145
+ spinner.succeed("Generation complete");
146
+ console.log(chalk_1.default.green(`\n ✓ ${result.filesCreated.length} files created\n`));
147
+ for (const f of result.filesCreated) {
148
+ console.log(` ${chalk_1.default.dim(result.outputDir + "/")}${f}`);
149
+ }
150
+ if (result.filesPreserved && result.filesPreserved.length > 0) {
151
+ console.log(chalk_1.default.cyan(`\n ↺ ${result.filesPreserved.length} handler(s) preserved\n`));
152
+ for (const f of result.filesPreserved)
153
+ console.log(` ${chalk_1.default.dim("↺ ")}${f}`);
154
+ }
155
+ const isTs = options.lang === "typescript";
156
+ const isPy = options.lang === "python";
157
+ console.log(chalk_1.default.bold("\nNext steps:\n"));
158
+ console.log(` cd ${opts.out}`);
159
+ if (isTs) {
160
+ console.log(" npm install");
161
+ console.log(" npm run build");
162
+ console.log(" npm start\n");
163
+ }
164
+ else if (isPy) {
165
+ console.log(" pip install -r requirements.txt");
166
+ console.log(" python server.py\n");
167
+ }
168
+ else {
169
+ console.log(" go mod tidy");
170
+ console.log(" go run .\n");
171
+ }
172
+ }
173
+ catch (err) {
174
+ spinner.fail("Unexpected error");
175
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
176
+ process.exit(1);
177
+ }
178
+ });
179
+ program
180
+ .command("validate")
181
+ .alias("v")
182
+ .description("Validate an OpenAPI spec without generating")
183
+ .requiredOption("-i, --input <path>", "Path or URL to the OpenAPI spec")
184
+ .action(async (opts) => {
185
+ const input = resolveInput(opts.input);
186
+ validateInputExt(input);
187
+ const spinner = (0, ora_1.default)("Validating spec...").start();
188
+ try {
189
+ const result = await (0, generator_1.validateSpec)(input);
190
+ if (!result.valid) {
191
+ spinner.fail("Spec is invalid");
192
+ for (const err of result.errors)
193
+ console.error(chalk_1.default.red(` ✗ ${err}`));
194
+ process.exit(1);
195
+ }
196
+ spinner.succeed("Spec is valid");
197
+ console.log(chalk_1.default.dim(`\n Tools: ${result.tools} Models: ${result.models} Base URL: ${result.baseUrl}\n`));
198
+ if (result.warnings.length > 0) {
199
+ console.log(chalk_1.default.yellow(` ${result.warnings.length} warning(s):`));
200
+ for (const w of result.warnings)
201
+ console.log(chalk_1.default.yellow(` ⚠ ${w}`));
202
+ console.log();
203
+ }
204
+ }
205
+ catch (err) {
206
+ spinner.fail("Validation failed");
207
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
208
+ process.exit(1);
209
+ }
210
+ });
211
+ program
212
+ .command("init")
213
+ .description("Initialize a local spec from a known public registry (e.g. stripe, github)")
214
+ .requiredOption("--from <key>", "Registry key to fetch spec from. Use 'list' to show known keys")
215
+ .option("--generate", "Run generation after fetching the spec", false)
216
+ .option("-i, --input <path>", "If provided, use this as the saved filename instead of the registry default")
217
+ .option("-l, --lang <language>", `Target language: ${SUPPORTED_LANGS.join(" | ")}`, "typescript")
218
+ .option("-o, --out <dir>", "Output directory for the generated project", "./mcp-server")
219
+ .action(async (opts) => {
220
+ const key = opts.from;
221
+ try {
222
+ if (key === "list") {
223
+ const specs = (0, registry_1.listKnownSpecs)();
224
+ console.log(chalk_1.default.bold("\nKnown Public Specs:\n"));
225
+ for (const k of specs) {
226
+ const info = (0, registry_1.getSpecInfo)(k);
227
+ console.log(chalk_1.default.cyan(` ${k.padEnd(15)}`), info?.description || "");
228
+ }
229
+ console.log("\n" + chalk_1.default.dim(`Usage: mcp-gen init --from <key> [--generate -o ./output]`));
230
+ console.log(chalk_1.default.dim(`Example: mcp-gen init --from stripe --generate -o ./stripe-mcp\n`));
231
+ return;
232
+ }
233
+ const spinner = (0, ora_1.default)(`Fetching ${key}...`).start();
234
+ const saved = await (0, registry_1.fetchSpecToCwd)(key, opts.input ? resolveInput(opts.input) : undefined);
235
+ spinner.succeed(`Saved spec to ${chalk_1.default.green(path_1.default.basename(saved))}`);
236
+ if (opts.generate) {
237
+ const input = saved;
238
+ validateInputExt(input);
239
+ const options = {
240
+ input,
241
+ lang: opts.lang,
242
+ out: path_1.default.resolve(opts.out),
243
+ force: false,
244
+ incremental: false,
245
+ http: false,
246
+ };
247
+ const result = await (0, generator_1.generate)(options);
248
+ if (!result.success) {
249
+ for (const err of result.errors)
250
+ console.error(chalk_1.default.red(` ✗ ${err}`));
251
+ process.exit(1);
252
+ }
253
+ }
254
+ }
255
+ catch (err) {
256
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
257
+ process.exit(1);
258
+ }
259
+ });
260
+ program
261
+ .command("security")
262
+ .alias("sec")
263
+ .description("Scan a generated MCP project for security and lint issues")
264
+ .requiredOption("-p, --project <path>", "Path to the generated MCP project to scan")
265
+ .option("--fail-on-warn", "Exit with code 1 if warnings are found", false)
266
+ .option("--json", "Output results as JSON", false)
267
+ .action(async (opts) => {
268
+ const projectPath = path_1.default.resolve(opts.project);
269
+ if (!fs_1.default.existsSync(projectPath)) {
270
+ console.error(chalk_1.default.red(`\n ✗ Project not found: ${projectPath}\n`));
271
+ process.exit(1);
272
+ }
273
+ console.log(chalk_1.default.bold("\nmcp-gen security") + ` v${VERSION} — Security & Lint Scan\n`);
274
+ console.log(` Project: ${chalk_1.default.cyan(projectPath)}\n`);
275
+ const { scanProject, formatReport } = await Promise.resolve().then(() => __importStar(require("../core/security-lint")));
276
+ const spinner = (0, ora_1.default)("Scanning project...").start();
277
+ try {
278
+ const report = await scanProject(projectPath);
279
+ if (opts.json) {
280
+ spinner.stop();
281
+ console.log(JSON.stringify(report, null, 2));
282
+ }
283
+ else {
284
+ spinner.succeed("Scan complete");
285
+ console.log(formatReport(report));
286
+ }
287
+ if (!report.passed || (opts.failOnWarn && report.summary.warnings > 0)) {
288
+ process.exit(1);
289
+ }
290
+ }
291
+ catch (err) {
292
+ spinner.fail("Scan failed");
293
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
294
+ process.exit(1);
295
+ }
296
+ });
297
+ program
298
+ .command("watch")
299
+ .description("Watch a spec (file or URL) and regenerate on changes")
300
+ .requiredOption("-i, --input <path>", "Path or URL to the OpenAPI spec to watch")
301
+ .option("-l, --lang <language>", `Target language: ${SUPPORTED_LANGS.join(" | ")}`, "typescript")
302
+ .option("-o, --out <dir>", "Output directory for the generated project", "./mcp-server")
303
+ .option("--once", "Run generation once on first change then exit", false)
304
+ .option("--interval <ms>", "Polling interval for URL inputs (ms)", "30000")
305
+ .option("--plugin <path>", "Path to a plugin module or folder to load", (val, acc) => {
306
+ if (!acc)
307
+ return [val];
308
+ acc.push(val);
309
+ return acc;
310
+ }, [])
311
+ .action(async (opts) => {
312
+ const input = resolveInput(opts.input);
313
+ validateInputExt(input);
314
+ validateLang(opts.lang);
315
+ const commonOptions = {
316
+ lang: opts.lang,
317
+ out: path_1.default.resolve(opts.out),
318
+ force: false,
319
+ incremental: true,
320
+ http: false,
321
+ plugins: opts.plugin,
322
+ };
323
+ const runGenerate = async () => {
324
+ const options = {
325
+ input,
326
+ lang: commonOptions.lang,
327
+ out: commonOptions.out,
328
+ force: false,
329
+ incremental: true,
330
+ http: false,
331
+ plugins: commonOptions.plugins,
332
+ };
333
+ console.log(chalk_1.default.dim(`[watch] regenerating from ${opts.input} → ${options.out}`));
334
+ try {
335
+ const res = await (0, generator_1.generate)(options);
336
+ if (!res.success) {
337
+ console.error(chalk_1.default.red("Generation failed:"));
338
+ for (const e of res.errors)
339
+ console.error(chalk_1.default.red(` ${e}`));
340
+ }
341
+ else {
342
+ console.log(chalk_1.default.green(`[watch] generated ${res.filesCreated.length} files`));
343
+ }
344
+ }
345
+ catch (e) {
346
+ console.error(chalk_1.default.red(String(e)));
347
+ }
348
+ };
349
+ if (opts.input.startsWith("http://") || opts.input.startsWith("https://")) {
350
+ let last = "";
351
+ const interval = Number(opts.interval) || 30000;
352
+ console.log(chalk_1.default.dim(`[watch] polling ${opts.input} every ${interval}ms`));
353
+ const check = async () => {
354
+ try {
355
+ const r = await fetch(opts.input);
356
+ if (!r.ok)
357
+ throw new Error(`HTTP ${r.status}`);
358
+ const body = await r.text();
359
+ if (!last) {
360
+ last = body;
361
+ await runGenerate();
362
+ if (opts.once)
363
+ process.exit(0);
364
+ return;
365
+ }
366
+ if (body !== last) {
367
+ last = body;
368
+ await runGenerate();
369
+ if (opts.once)
370
+ process.exit(0);
371
+ }
372
+ }
373
+ catch (e) {
374
+ console.error(chalk_1.default.red(String(e)));
375
+ }
376
+ };
377
+ await check();
378
+ setInterval(check, interval);
379
+ return;
380
+ }
381
+ const abs = path_1.default.resolve(opts.input);
382
+ if (!fs_1.default.existsSync(abs)) {
383
+ console.error(chalk_1.default.red(`File not found: ${abs}`));
384
+ process.exit(1);
385
+ }
386
+ let timeout = null;
387
+ const watcher = fs_1.default.watch(abs, async () => {
388
+ if (timeout)
389
+ clearTimeout(timeout);
390
+ timeout = setTimeout(async () => {
391
+ await runGenerate();
392
+ if (opts.once) {
393
+ watcher.close();
394
+ process.exit(0);
395
+ }
396
+ }, 200);
397
+ });
398
+ console.log(chalk_1.default.dim(`[watch] watching ${abs}`));
399
+ await runGenerate();
400
+ });
401
+ async function interactive() {
402
+ while (true) {
403
+ const { cmd } = await inquirer_1.default.prompt([
404
+ {
405
+ type: "list",
406
+ name: "cmd",
407
+ message: "Choose an action:",
408
+ choices: [
409
+ { name: "Generate (Generate an MCP server from a spec)", value: "generate" },
410
+ { name: "Validate (Validate an OpenAPI spec)", value: "validate" },
411
+ { name: "Init (Download a known public spec)", value: "init" },
412
+ { name: "Watch (Watch and auto-regenerate on changes)", value: "watch" },
413
+ { name: "Security (Scan project for security/lint issues)", value: "security" },
414
+ { name: "Exit", value: "exit" },
415
+ ],
416
+ },
417
+ ]);
418
+ if (cmd === "exit")
419
+ return;
420
+ if (cmd === "generate") {
421
+ const answers = await inquirer_1.default.prompt([
422
+ { type: "input", name: "input", message: "Path or URL to the OpenAPI spec (.json|.yaml):" },
423
+ { type: "list", name: "lang", message: "Target language:", choices: [...SUPPORTED_LANGS] },
424
+ { type: "input", name: "out", message: "Output directory:", default: "./mcp-server" },
425
+ { type: "confirm", name: "force", message: "Overwrite existing files?", default: false },
426
+ { type: "confirm", name: "incremental", message: "Preserve custom handlers?", default: false },
427
+ { type: "confirm", name: "http", message: "Generate real HTTP handlers?", default: false },
428
+ { type: "input", name: "name", message: "Server name (optional):", default: "" },
429
+ { type: "input", name: "serverVersion", message: "Server version (optional):", default: "" },
430
+ ]);
431
+ const input = resolveInput(answers.input);
432
+ validateInputExt(input);
433
+ validateLang(answers.lang);
434
+ const options = {
435
+ input,
436
+ lang: answers.lang,
437
+ out: path_1.default.resolve(answers.out),
438
+ force: Boolean(answers.force),
439
+ incremental: Boolean(answers.incremental),
440
+ http: Boolean(answers.http),
441
+ plugins: [],
442
+ serverName: answers.name || undefined,
443
+ serverVersion: answers.serverVersion || undefined,
444
+ };
445
+ console.log(chalk_1.default.bold("\nmcp-gen") + " — OpenAPI to MCP Server\n");
446
+ const spinner = (0, ora_1.default)("Parsing OpenAPI spec...").start();
447
+ try {
448
+ const result = await (0, generator_1.generate)(options);
449
+ if (result.warnings.length > 0) {
450
+ spinner.warn("Completed with warnings");
451
+ for (const w of result.warnings)
452
+ console.log(chalk_1.default.yellow(` ⚠ ${w}`));
453
+ console.log();
454
+ }
455
+ if (!result.success) {
456
+ spinner.fail("Generation failed");
457
+ for (const err of result.errors)
458
+ console.error(chalk_1.default.red(` ✗ ${err}`));
459
+ }
460
+ else {
461
+ spinner.succeed("Generation complete");
462
+ console.log(chalk_1.default.green(`\n ✓ ${result.filesCreated.length} files created\n`));
463
+ }
464
+ }
465
+ catch (err) {
466
+ spinner.fail("Unexpected error");
467
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
468
+ }
469
+ }
470
+ if (cmd === "validate") {
471
+ const { input } = await inquirer_1.default.prompt([{ type: "input", name: "input", message: "Path or URL to the OpenAPI spec:" }]);
472
+ const resolved = resolveInput(input);
473
+ validateInputExt(resolved);
474
+ const spinner = (0, ora_1.default)("Validating spec...").start();
475
+ try {
476
+ const { parseOpenAPI } = await Promise.resolve().then(() => __importStar(require("../core/parser")));
477
+ const ast = await parseOpenAPI(resolved);
478
+ spinner.succeed("Spec is valid");
479
+ console.log(chalk_1.default.dim(`\n Tools: ${ast.tools.length} Models: ${ast.models.length} Base URL: ${ast.baseUrl}\n`));
480
+ }
481
+ catch (err) {
482
+ spinner.fail("Validation failed");
483
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
484
+ }
485
+ }
486
+ if (cmd === "init") {
487
+ const specs = (0, registry_1.listKnownSpecs)();
488
+ const { key } = await inquirer_1.default.prompt([
489
+ {
490
+ type: "list",
491
+ name: "key",
492
+ message: "Which public spec?",
493
+ choices: specs.map((k) => ({
494
+ name: `${k.padEnd(15)} — ${(0, registry_1.getSpecInfo)(k)?.description || ""}`,
495
+ value: k,
496
+ })),
497
+ },
498
+ ]);
499
+ try {
500
+ const spinner = (0, ora_1.default)(`Downloading ${key}...`).start();
501
+ const saved = await (0, registry_1.fetchSpecToCwd)(key);
502
+ spinner.succeed(`Saved to ${chalk_1.default.green(path_1.default.basename(saved))}`);
503
+ const { autoGen } = await inquirer_1.default.prompt([
504
+ {
505
+ type: "confirm",
506
+ name: "autoGen",
507
+ message: "Generate MCP server now?",
508
+ default: true,
509
+ },
510
+ ]);
511
+ if (autoGen) {
512
+ const { lang, out } = await inquirer_1.default.prompt([
513
+ {
514
+ type: "list",
515
+ name: "lang",
516
+ message: "Target language:",
517
+ choices: [...SUPPORTED_LANGS],
518
+ },
519
+ {
520
+ type: "input",
521
+ name: "out",
522
+ message: "Output directory:",
523
+ default: `./${key}-mcp`,
524
+ },
525
+ ]);
526
+ const options = {
527
+ input: resolveInput(saved),
528
+ lang: lang,
529
+ out: path_1.default.resolve(out),
530
+ force: false,
531
+ incremental: false,
532
+ http: false,
533
+ };
534
+ const genSpinner = (0, ora_1.default)("Generating MCP server...").start();
535
+ try {
536
+ const result = await (0, generator_1.generate)(options);
537
+ if (result.warnings.length > 0) {
538
+ genSpinner.warn("Generated with warnings");
539
+ for (const w of result.warnings)
540
+ console.log(chalk_1.default.yellow(` ⚠ ${w}`));
541
+ }
542
+ if (!result.success) {
543
+ genSpinner.fail("Generation failed");
544
+ for (const err of result.errors)
545
+ console.error(chalk_1.default.red(` ✗ ${err}`));
546
+ }
547
+ else {
548
+ genSpinner.succeed("MCP server generated!");
549
+ console.log(chalk_1.default.green(`\n ✓ ${result.filesCreated.length} files created\n`));
550
+ console.log(chalk_1.default.bold("Next steps:\n"));
551
+ console.log(` cd ${out}`);
552
+ console.log(lang === "typescript" ? " npm install && npm run build\n" : " pip install -r requirements.txt\n");
553
+ }
554
+ }
555
+ catch (err) {
556
+ genSpinner.fail("Generation error");
557
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
558
+ }
559
+ }
560
+ }
561
+ catch (err) {
562
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
563
+ }
564
+ }
565
+ if (cmd === "watch") {
566
+ const answers = await inquirer_1.default.prompt([
567
+ { type: "input", name: "input", message: "Path or URL to the OpenAPI spec:" },
568
+ { type: "list", name: "lang", message: "Target language:", choices: [...SUPPORTED_LANGS] },
569
+ { type: "input", name: "out", message: "Output directory:", default: "./mcp-server" },
570
+ ]);
571
+ const input = resolveInput(answers.input);
572
+ validateInputExt(input);
573
+ validateLang(answers.lang);
574
+ const commonOptions = {
575
+ lang: answers.lang,
576
+ out: path_1.default.resolve(answers.out),
577
+ force: false,
578
+ incremental: false,
579
+ http: false,
580
+ plugins: [],
581
+ };
582
+ const runGenerate = async () => {
583
+ const options = {
584
+ input: answers.input,
585
+ lang: commonOptions.lang,
586
+ out: commonOptions.out,
587
+ force: false,
588
+ incremental: false,
589
+ http: false,
590
+ plugins: commonOptions.plugins,
591
+ };
592
+ console.log(chalk_1.default.dim(`[watch] regenerating from ${answers.input} → ${options.out}`));
593
+ try {
594
+ const res = await (0, generator_1.generate)(options);
595
+ if (!res.success) {
596
+ console.error(chalk_1.default.red("Generation failed:"));
597
+ for (const e of res.errors)
598
+ console.error(chalk_1.default.red(` ${e}`));
599
+ }
600
+ else {
601
+ console.log(chalk_1.default.green(`[watch] generated ${res.filesCreated.length} files`));
602
+ }
603
+ }
604
+ catch (e) {
605
+ console.error(chalk_1.default.red(String(e)));
606
+ }
607
+ };
608
+ if (answers.input.startsWith("http://") || answers.input.startsWith("https://")) {
609
+ console.log(chalk_1.default.dim(`[watch] polling ${answers.input} every 30000ms`));
610
+ let last = "";
611
+ const check = async () => {
612
+ try {
613
+ const r = await fetch(answers.input);
614
+ if (!r.ok)
615
+ throw new Error(`HTTP ${r.status}`);
616
+ const body = await r.text();
617
+ if (!last) {
618
+ last = body;
619
+ await runGenerate();
620
+ return;
621
+ }
622
+ if (body !== last) {
623
+ last = body;
624
+ await runGenerate();
625
+ }
626
+ }
627
+ catch (e) {
628
+ console.error(chalk_1.default.red(String(e)));
629
+ }
630
+ };
631
+ await check();
632
+ setInterval(check, 30000);
633
+ }
634
+ else {
635
+ const abs = path_1.default.resolve(answers.input);
636
+ if (!fs_1.default.existsSync(abs)) {
637
+ console.error(chalk_1.default.red(`File not found: ${abs}`));
638
+ }
639
+ else {
640
+ let timeout = null;
641
+ const watcher = fs_1.default.watch(abs, async () => {
642
+ if (timeout)
643
+ clearTimeout(timeout);
644
+ timeout = setTimeout(async () => {
645
+ await runGenerate();
646
+ }, 200);
647
+ });
648
+ console.log(chalk_1.default.dim(`[watch] watching ${abs}`));
649
+ await runGenerate();
650
+ }
651
+ }
652
+ }
653
+ if (cmd === "security") {
654
+ const { project } = await inquirer_1.default.prompt([
655
+ { type: "input", name: "project", message: "Path to the generated MCP project to scan:" },
656
+ ]);
657
+ const projectPath = path_1.default.resolve(project);
658
+ if (!fs_1.default.existsSync(projectPath)) {
659
+ console.error(chalk_1.default.red(`\n ✗ Project not found: ${projectPath}\n`));
660
+ continue;
661
+ }
662
+ console.log(chalk_1.default.bold("\nmcp-gen security") + ` v${VERSION} — Security & Lint Scan\n`);
663
+ console.log(` Project: ${chalk_1.default.cyan(projectPath)}\n`);
664
+ const { scanProject, formatReport } = await Promise.resolve().then(() => __importStar(require("../core/security-lint")));
665
+ const spinner = (0, ora_1.default)("Scanning project...").start();
666
+ try {
667
+ const report = await scanProject(projectPath);
668
+ spinner.succeed("Scan complete");
669
+ console.log(formatReport(report));
670
+ }
671
+ catch (err) {
672
+ spinner.fail("Scan failed");
673
+ console.error(chalk_1.default.red(err instanceof Error ? err.message : String(err)));
674
+ }
675
+ }
676
+ }
677
+ }
678
+ (async () => {
679
+ if (process.argv.length <= 2 && process.stdin.isTTY) {
680
+ await interactive();
681
+ process.exit(0);
682
+ }
683
+ program.parse();
684
+ })();
685
+ //# sourceMappingURL=index.js.map