@kubb/mcp 5.0.0-beta.7 → 5.0.0-beta.70

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.
package/dist/index.cjs CHANGED
@@ -21,28 +21,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- let node_http = require("node:http");
25
- node_http = __toESM(node_http, 1);
26
- let _remix_run_node_fetch_server = require("@remix-run/node-fetch-server");
27
24
  let _tmcp_adapter_valibot = require("@tmcp/adapter-valibot");
28
- let _tmcp_transport_http = require("@tmcp/transport-http");
29
25
  let _tmcp_transport_stdio = require("@tmcp/transport-stdio");
30
26
  let tmcp = require("tmcp");
31
27
  let node_events = require("node:events");
32
- let node_fs = require("node:fs");
33
- node_fs = __toESM(node_fs, 1);
34
- let node_path = require("node:path");
35
- node_path = __toESM(node_path, 1);
36
28
  let _kubb_core = require("@kubb/core");
37
29
  let tmcp_tool = require("tmcp/tool");
38
30
  let tmcp_utils = require("tmcp/utils");
39
31
  let valibot = require("valibot");
40
32
  valibot = __toESM(valibot, 1);
33
+ let node_fs = require("node:fs");
34
+ node_fs = __toESM(node_fs, 1);
35
+ let node_path = require("node:path");
36
+ node_path = __toESM(node_path, 1);
37
+ let node_url = require("node:url");
41
38
  let jiti = require("jiti");
42
39
  let node_process = require("node:process");
43
40
  node_process = __toESM(node_process, 1);
44
41
  //#region package.json
45
- var version = "5.0.0-beta.7";
42
+ var version = "5.0.0-beta.70";
46
43
  //#endregion
47
44
  //#region ../../internals/utils/src/errors.ts
48
45
  /**
@@ -90,9 +87,12 @@ var AsyncEventEmitter = class {
90
87
  * await emitter.emit('build', 'petstore')
91
88
  * ```
92
89
  */
93
- async emit(eventName, ...eventArgs) {
90
+ emit(eventName, ...eventArgs) {
94
91
  const listeners = this.#emitter.listeners(eventName);
95
92
  if (listeners.length === 0) return;
93
+ return this.#emitAll(eventName, listeners, eventArgs);
94
+ }
95
+ async #emitAll(eventName, listeners, eventArgs) {
96
96
  for (const listener of listeners) try {
97
97
  await listener(...eventArgs);
98
98
  } catch (err) {
@@ -155,6 +155,24 @@ var AsyncEventEmitter = class {
155
155
  return this.#emitter.listenerCount(eventName);
156
156
  }
157
157
  /**
158
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
159
+ * Set this above the expected listener count when many listeners attach by design.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * emitter.setMaxListeners(40)
164
+ * ```
165
+ */
166
+ setMaxListeners(max) {
167
+ this.#emitter.setMaxListeners(max);
168
+ }
169
+ /**
170
+ * Returns the current per-event listener ceiling.
171
+ */
172
+ getMaxListeners() {
173
+ return this.#emitter.getMaxListeners();
174
+ }
175
+ /**
158
176
  * Removes all listeners from every event channel.
159
177
  *
160
178
  * @example
@@ -180,31 +198,103 @@ function isPromise(result) {
180
198
  return result !== null && result !== void 0 && typeof result["then"] === "function";
181
199
  }
182
200
  //#endregion
183
- //#region src/schemas/generateSchema.ts
184
- const generateSchema = valibot.object({
185
- config: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to kubb.config file (supports .ts, .js, .cjs). If not provided, will look for kubb.config.{ts,js,cjs} in current directory"))),
186
- input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
187
- output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
188
- logLevel: valibot.optional(valibot.pipe(valibot.picklist([
189
- "silent",
190
- "error",
191
- "warn",
192
- "info",
193
- "verbose",
194
- "debug"
195
- ]), valibot.description("Log level for build output")), "info")
196
- });
201
+ //#region ../../internals/utils/src/runtime.ts
202
+ /**
203
+ * Detects the JavaScript runtime executing the current process and exposes its name and version.
204
+ *
205
+ * Prefer the shared {@link runtime} instance over constructing your own.
206
+ */
207
+ var Runtime = class {
208
+ /**
209
+ * `true` when the current process is running under Bun.
210
+ *
211
+ * Detection keys off the global `Bun` object rather than `process.versions`,
212
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
213
+ * otherwise look like Node.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * if (runtime.isBun) {
218
+ * await Bun.write(path, data)
219
+ * }
220
+ * ```
221
+ */
222
+ get isBun() {
223
+ return typeof Bun !== "undefined";
224
+ }
225
+ /**
226
+ * `true` when the current process is running under Deno.
227
+ */
228
+ get isDeno() {
229
+ return typeof globalThis.Deno !== "undefined";
230
+ }
231
+ /**
232
+ * `true` when the current process is running under Node.
233
+ *
234
+ * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
235
+ */
236
+ get isNode() {
237
+ return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
238
+ }
239
+ /**
240
+ * Name of the runtime executing the current process.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
245
+ * ```
246
+ */
247
+ get name() {
248
+ if (this.isBun) return "bun";
249
+ if (this.isDeno) return "deno";
250
+ return "node";
251
+ }
252
+ /**
253
+ * Version of the active runtime, or an empty string when it cannot be read.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * runtime.version // '1.3.11' under Bun, '22.22.2' under Node
258
+ * ```
259
+ */
260
+ get version() {
261
+ if (this.isBun) return process.versions.bun ?? "";
262
+ if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
263
+ return process.versions?.node ?? "";
264
+ }
265
+ };
266
+ /**
267
+ * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
268
+ */
269
+ const runtime = new Runtime();
197
270
  //#endregion
198
- //#region src/types.ts
271
+ //#region src/constants.ts
272
+ /**
273
+ * File extensions a Kubb config is allowed to use. A config path with any other
274
+ * extension is rejected before it is loaded.
275
+ */
276
+ const ALLOWED_CONFIG_EXTENSIONS = new Set([
277
+ ".ts",
278
+ ".mts",
279
+ ".cts",
280
+ ".js",
281
+ ".mjs",
282
+ ".cjs"
283
+ ]);
284
+ /**
285
+ * Notification kinds reported back to the MCP client while loading config and
286
+ * running generation, covering progress, results, and errors.
287
+ */
199
288
  const NotifyTypes = {
200
289
  INFO: "INFO",
201
290
  SUCCESS: "SUCCESS",
202
291
  ERROR: "ERROR",
203
292
  WARN: "WARN",
293
+ DIAGNOSTIC: "DIAGNOSTIC",
204
294
  PLUGIN_START: "PLUGIN_START",
205
295
  PLUGIN_END: "PLUGIN_END",
206
296
  FILES_START: "FILES_START",
207
- FILE_UPDATE: "FILE_UPDATE",
297
+ FILES_UPDATE: "FILES_UPDATE",
208
298
  FILES_END: "FILES_END",
209
299
  GENERATION_START: "GENERATION_START",
210
300
  GENERATION_END: "GENERATION_END",
@@ -216,37 +306,198 @@ const NotifyTypes = {
216
306
  BUILD_START: "BUILD_START",
217
307
  BUILD_END: "BUILD_END",
218
308
  BUILD_FAILED: "BUILD_FAILED",
219
- BUILD_SUCCESS: "BUILD_SUCCESS",
220
- FATAL_ERROR: "FATAL_ERROR"
309
+ BUILD_SUCCESS: "BUILD_SUCCESS"
221
310
  };
222
311
  //#endregion
223
- //#region src/constants.ts
224
- const ALLOWED_CONFIG_EXTENSIONS = new Set([
225
- ".ts",
226
- ".mts",
227
- ".cts",
228
- ".js",
229
- ".mjs",
230
- ".cjs"
231
- ]);
312
+ //#region src/schemas/generateSchema.ts
313
+ const generateSchema = valibot.object({
314
+ config: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to kubb.config file (supports .ts, .js, .cjs). If not provided, will look for kubb.config.{ts,js,cjs} in current directory"))),
315
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
316
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
317
+ logLevel: valibot.optional(valibot.pipe(valibot.picklist([
318
+ "silent",
319
+ "info",
320
+ "verbose"
321
+ ]), valibot.description("Log level for build output")), "info")
322
+ });
323
+ //#endregion
324
+ //#region ../../internals/shared/src/constants.ts
325
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
326
+ const availablePlugins = [
327
+ {
328
+ value: "plugin-ts",
329
+ label: "TypeScript",
330
+ hint: "Recommended",
331
+ packageName: "@kubb/plugin-ts",
332
+ importName: "pluginTs",
333
+ category: "types"
334
+ },
335
+ {
336
+ value: "plugin-client",
337
+ label: "Client (Fetch/Axios)",
338
+ packageName: "@kubb/plugin-client",
339
+ importName: "pluginClient",
340
+ category: "client"
341
+ },
342
+ {
343
+ value: "plugin-react-query",
344
+ label: "React Query / TanStack Query",
345
+ packageName: "@kubb/plugin-react-query",
346
+ importName: "pluginReactQuery",
347
+ category: "framework"
348
+ },
349
+ {
350
+ value: "plugin-vue-query",
351
+ label: "Vue Query",
352
+ packageName: "@kubb/plugin-vue-query",
353
+ importName: "pluginVueQuery",
354
+ category: "framework"
355
+ },
356
+ {
357
+ value: "plugin-zod",
358
+ label: "Zod Schemas",
359
+ packageName: "@kubb/plugin-zod",
360
+ importName: "pluginZod",
361
+ category: "validation"
362
+ },
363
+ {
364
+ value: "plugin-faker",
365
+ label: "Faker.js Mocks",
366
+ packageName: "@kubb/plugin-faker",
367
+ importName: "pluginFaker",
368
+ category: "mocks"
369
+ },
370
+ {
371
+ value: "plugin-msw",
372
+ label: "MSW Handlers",
373
+ packageName: "@kubb/plugin-msw",
374
+ importName: "pluginMsw",
375
+ category: "mocks"
376
+ },
377
+ {
378
+ value: "plugin-cypress",
379
+ label: "Cypress Tests",
380
+ packageName: "@kubb/plugin-cypress",
381
+ importName: "pluginCypress",
382
+ category: "testing"
383
+ },
384
+ {
385
+ value: "plugin-mcp",
386
+ label: "MCP Server (AI / Model Context Protocol)",
387
+ packageName: "@kubb/plugin-mcp",
388
+ importName: "pluginMcp",
389
+ category: "ai"
390
+ },
391
+ {
392
+ value: "plugin-redoc",
393
+ label: "ReDoc Documentation",
394
+ packageName: "@kubb/plugin-redoc",
395
+ importName: "pluginRedoc",
396
+ category: "documentation"
397
+ }
398
+ ];
232
399
  //#endregion
233
- //#region src/utils/loadUserConfig.ts
234
- const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, {
400
+ //#region ../../internals/shared/src/init.ts
401
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
402
+ return `import { defineConfig } from 'kubb'
403
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
404
+
405
+ export default defineConfig({
406
+ root: '.',
407
+ input: {
408
+ path: '${inputPath}',
409
+ },
410
+ output: {
411
+ path: '${outputPath}',
412
+ clean: true,
413
+ },
414
+ plugins: [
415
+ ${selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join("\n")}
416
+ ],
417
+ })
418
+ `;
419
+ }
420
+ //#endregion
421
+ //#region ../../internals/shared/src/loader.ts
422
+ /**
423
+ * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
424
+ * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
425
+ */
426
+ const JITI_OPTIONS = {
235
427
  jsx: {
236
428
  runtime: "automatic",
237
429
  importSource: "@kubb/renderer-jsx"
238
430
  },
239
431
  moduleCache: false
240
- });
432
+ };
433
+ /**
434
+ * Creates a runtime-aware loader for Kubb's TypeScript and JavaScript config modules.
435
+ *
436
+ * On Bun and Deno it imports the file natively, skipping jiti's transform step, and falls back to
437
+ * jiti only when the native import throws. On Node it always uses jiti, which transpiles TypeScript
438
+ * and the `@kubb/renderer-jsx` JSX runtime on the fly. The jiti instance is created lazily, so the
439
+ * Bun/Deno happy path never pays for it.
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
444
+ * ```
445
+ */
446
+ function createModuleLoader() {
447
+ let jiti$1;
448
+ const getJiti = () => jiti$1 ??= (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, JITI_OPTIONS);
449
+ const viaJiti = (filePath, options) => options?.default ? getJiti().import(filePath, { default: true }) : getJiti().import(filePath);
450
+ const viaNative = async (filePath, options) => {
451
+ const href = (0, node_url.pathToFileURL)(filePath).href;
452
+ const mod = await (options?.bust != null ? import(`${href}?t=${options.bust}`) : import(href));
453
+ return options?.default ? mod.default ?? mod : mod;
454
+ };
455
+ return { async load(filePath, options) {
456
+ if (runtime.isBun || runtime.isDeno) try {
457
+ return await viaNative(filePath, options);
458
+ } catch {
459
+ return viaJiti(filePath, options);
460
+ }
461
+ return viaJiti(filePath, options);
462
+ } };
463
+ }
464
+ //#endregion
465
+ //#region src/utils.ts
466
+ /**
467
+ * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
468
+ * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
469
+ * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
470
+ * unlike the CLI renderer.
471
+ */
472
+ function formatDiagnostics(diagnostics) {
473
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
474
+ }
475
+ function formatDiagnostic(diagnostic) {
476
+ const { code, message, location, help, plugin, docsUrl } = diagnostic;
477
+ const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`];
478
+ if (location && "pointer" in location) lines.push(` at: ${location.pointer}`);
479
+ if (help) lines.push(` fix: ${help}`);
480
+ if (docsUrl) lines.push(` see: ${docsUrl}`);
481
+ return lines.join("\n");
482
+ }
483
+ const loader = createModuleLoader();
241
484
  const loadedModules = /* @__PURE__ */ new Map();
242
485
  async function loadModule(filePath) {
243
486
  const ext = node_path.default.extname(filePath);
244
487
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
245
488
  if (loadedModules.has(filePath)) return loadedModules.get(filePath);
246
- const mod = await jiti$1.import(filePath, { default: true });
489
+ const mod = await loader.load(filePath, { default: true });
247
490
  loadedModules.set(filePath, mod);
248
491
  return mod;
249
492
  }
493
+ /**
494
+ * Loads the user's Kubb config and returns it with the directory it was found in.
495
+ *
496
+ * When `configPath` is given it must use an allowed extension and resolve inside
497
+ * the current working directory, otherwise loading throws. When omitted, the
498
+ * known `kubb.config.*` file names are tried in the current directory. Every
499
+ * outcome is reported through `notify` before the function returns or throws.
500
+ */
250
501
  async function loadUserConfig(configPath, { notify }) {
251
502
  if (configPath) {
252
503
  const ext = node_path.default.extname(configPath);
@@ -302,8 +553,6 @@ async function loadUserConfig(configPath, { notify }) {
302
553
  await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
303
554
  throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
304
555
  }
305
- //#endregion
306
- //#region src/utils/resolveCwd.ts
307
556
  /**
308
557
  * Determine the root directory based on userConfig.root and resolvedConfigDir
309
558
  * 1. If userConfig.root exists and is absolute, use it as-is
@@ -317,8 +566,12 @@ function resolveCwd(userConfig, cwd) {
317
566
  }
318
567
  return cwd;
319
568
  }
320
- //#endregion
321
- //#region src/utils/resolveUserConfig.ts
569
+ /**
570
+ * Normalizes a possible config into a single resolved `Config`.
571
+ *
572
+ * Calls the config when it is a function, awaits it when it is a promise, and
573
+ * picks the first entry when it resolves to an array.
574
+ */
322
575
  async function resolveUserConfig(config, options) {
323
576
  const result = typeof config === "function" ? config({
324
577
  logLevel: options.logLevel,
@@ -338,8 +591,8 @@ const generateTool = (0, tmcp_tool.defineTool)({
338
591
  try {
339
592
  const hooks = new AsyncEventEmitter();
340
593
  const messages = [];
341
- const notify = async (type, message, _data) => {
342
- messages.push(`${type}: ${message}`);
594
+ const notify = async (type, message, data) => {
595
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
343
596
  };
344
597
  hooks.on("kubb:info", async ({ message }) => {
345
598
  await notify(NotifyTypes.INFO, message);
@@ -353,6 +606,9 @@ const generateTool = (0, tmcp_tool.defineTool)({
353
606
  hooks.on("kubb:warn", async ({ message }) => {
354
607
  await notify(NotifyTypes.WARN, message);
355
608
  });
609
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
610
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, _kubb_core.Diagnostics.serialize(diagnostic));
611
+ });
356
612
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
357
613
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
358
614
  });
@@ -362,8 +618,8 @@ const generateTool = (0, tmcp_tool.defineTool)({
362
618
  hooks.on("kubb:files:processing:start", async () => {
363
619
  await notify(NotifyTypes.FILES_START, "Starting file processing");
364
620
  });
365
- hooks.on("kubb:file:processing:update", async ({ file }) => {
366
- await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
621
+ hooks.on("kubb:files:processing:update", async ({ files }) => {
622
+ await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
367
623
  });
368
624
  hooks.on("kubb:files:processing:end", async () => {
369
625
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -409,152 +665,23 @@ const generateTool = (0, tmcp_tool.defineTool)({
409
665
  await kubb.setup();
410
666
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
411
667
  await notify(NotifyTypes.BUILD_START, "Starting build");
412
- const { files, failedPlugins, error } = await kubb.safeBuild();
668
+ const { files, diagnostics } = await kubb.safeBuild();
413
669
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
414
- if (error || failedPlugins.size > 0) {
415
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
416
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`);
417
- return tmcp_utils.tool.error(`Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`);
670
+ const problems = diagnostics.filter(_kubb_core.Diagnostics.isProblem);
671
+ const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
672
+ if (errors.length > 0) {
673
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
674
+ const serialized = problems.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic));
675
+ return tmcp_utils.tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
418
676
  }
419
677
  await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
420
678
  return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
421
679
  } catch (caughtError) {
422
- const error = toError(caughtError);
423
- return tmcp_utils.tool.error(`Build error: ${error.message}\n${error.stack ?? ""}`);
680
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(caughtError));
681
+ return tmcp_utils.tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
424
682
  }
425
683
  });
426
684
  //#endregion
427
- //#region ../../internals/shared/src/constants.ts
428
- const KUBB_CONFIG_FILENAME = "kubb.config.ts";
429
- const availablePlugins = [
430
- {
431
- value: "plugin-ts",
432
- label: "TypeScript",
433
- hint: "Recommended",
434
- packageName: "@kubb/plugin-ts",
435
- importName: "pluginTs",
436
- category: "types"
437
- },
438
- {
439
- value: "plugin-client",
440
- label: "Client (Fetch/Axios)",
441
- packageName: "@kubb/plugin-client",
442
- importName: "pluginClient",
443
- category: "client"
444
- },
445
- {
446
- value: "plugin-react-query",
447
- label: "React Query / TanStack Query",
448
- packageName: "@kubb/plugin-react-query",
449
- importName: "pluginReactQuery",
450
- category: "framework"
451
- },
452
- {
453
- value: "plugin-vue-query",
454
- label: "Vue Query",
455
- packageName: "@kubb/plugin-vue-query",
456
- importName: "pluginVueQuery",
457
- category: "framework"
458
- },
459
- {
460
- value: "plugin-zod",
461
- label: "Zod Schemas",
462
- packageName: "@kubb/plugin-zod",
463
- importName: "pluginZod",
464
- category: "validation"
465
- },
466
- {
467
- value: "plugin-faker",
468
- label: "Faker.js Mocks",
469
- packageName: "@kubb/plugin-faker",
470
- importName: "pluginFaker",
471
- category: "mocks"
472
- },
473
- {
474
- value: "plugin-msw",
475
- label: "MSW Handlers",
476
- packageName: "@kubb/plugin-msw",
477
- importName: "pluginMsw",
478
- category: "mocks"
479
- },
480
- {
481
- value: "plugin-cypress",
482
- label: "Cypress Tests",
483
- packageName: "@kubb/plugin-cypress",
484
- importName: "pluginCypress",
485
- category: "testing"
486
- },
487
- {
488
- value: "plugin-mcp",
489
- label: "MCP Server (AI / Model Context Protocol)",
490
- packageName: "@kubb/plugin-mcp",
491
- importName: "pluginMcp",
492
- category: "ai"
493
- },
494
- {
495
- value: "plugin-redoc",
496
- label: "ReDoc Documentation",
497
- packageName: "@kubb/plugin-redoc",
498
- importName: "pluginRedoc",
499
- category: "documentation"
500
- }
501
- ];
502
- const pluginDefaultConfigs = {
503
- "plugin-ts": `pluginTs({
504
- output: { path: 'models' },
505
- })`,
506
- "plugin-client": `pluginClient({
507
- output: { path: 'clients' },
508
- })`,
509
- "plugin-react-query": `pluginReactQuery({
510
- output: { path: 'hooks' },
511
- })`,
512
- "plugin-vue-query": `pluginVueQuery({
513
- output: { path: 'hooks' },
514
- })`,
515
- "plugin-zod": `pluginZod({
516
- output: { path: 'zod' },
517
- })`,
518
- "plugin-faker": `pluginFaker({
519
- output: { path: 'mocks' },
520
- })`,
521
- "plugin-msw": `pluginMsw({
522
- output: { path: 'msw' },
523
- })`,
524
- "plugin-cypress": `pluginCypress({
525
- output: { path: 'cypress' },
526
- })`,
527
- "plugin-mcp": `pluginMcp({
528
- output: { path: 'mcp' },
529
- })`,
530
- "plugin-redoc": `pluginRedoc({
531
- output: { path: 'redoc' },
532
- })`
533
- };
534
- //#endregion
535
- //#region ../../internals/shared/src/init.ts
536
- function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
537
- return `import { defineConfig } from 'kubb'
538
- ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
539
-
540
- export default defineConfig({
541
- root: '.',
542
- input: {
543
- path: '${inputPath}',
544
- },
545
- output: {
546
- path: '${outputPath}',
547
- clean: true,
548
- },
549
- plugins: [
550
- ${selectedPlugins.map((plugin) => {
551
- return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
552
- }).join("\n")}
553
- ],
554
- })
555
- `;
556
- }
557
- //#endregion
558
685
  //#region src/schemas/initSchema.ts
559
686
  const initSchema = valibot.object({
560
687
  input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
@@ -563,6 +690,10 @@ const initSchema = valibot.object({
563
690
  });
564
691
  //#endregion
565
692
  //#region src/tools/init.ts
693
+ /**
694
+ * Resolves a comma-separated plugin flag into the matching known plugin options.
695
+ * Unrecognized names are dropped, and a missing flag yields an empty list.
696
+ */
566
697
  function resolvePlugins(pluginsFlag) {
567
698
  if (!pluginsFlag) return [];
568
699
  const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
@@ -605,11 +736,18 @@ const validateTool = (0, tmcp_tool.defineTool)({
605
736
  await mod.adapterOas().validate(input, { throwOnError: true });
606
737
  return tmcp_utils.tool.text(`Validation successful: ${input}`);
607
738
  } catch (err) {
608
- return tmcp_utils.tool.error(`Validation failed:\n${err instanceof Error ? err.message : String(err)}`);
739
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(err));
740
+ return tmcp_utils.tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
609
741
  }
610
742
  });
611
743
  //#endregion
612
744
  //#region src/server.ts
745
+ /**
746
+ * Builds the Kubb MCP server with the generate, validate, and init tools registered.
747
+ *
748
+ * @example
749
+ * `const server = createMcpServer()`
750
+ */
613
751
  function createMcpServer() {
614
752
  const server = new tmcp.McpServer({
615
753
  name: "Kubb",
@@ -625,23 +763,21 @@ function createMcpServer() {
625
763
  ]);
626
764
  return server;
627
765
  }
628
- async function startServer({ port, host = "localhost" } = {}) {
629
- const server = createMcpServer();
630
- if (port === void 0) {
631
- new _tmcp_transport_stdio.StdioTransport(server).listen();
632
- return;
633
- }
634
- const transport = new _tmcp_transport_http.HttpTransport(server, { path: "/mcp" });
635
- node_http.default.createServer((0, _remix_run_node_fetch_server.createRequestListener)(async (request) => {
636
- return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
637
- })).listen(port, host, () => {
638
- console.log(`Kubb MCP server on http://${host}:${port}`);
639
- });
766
+ /**
767
+ * Starts the Kubb MCP server over stdio, the transport every local MCP client
768
+ * (Claude, Copilot, editors) uses when it launches the server as a subprocess.
769
+ */
770
+ async function startServer() {
771
+ new _tmcp_transport_stdio.StdioTransport(createMcpServer()).listen();
640
772
  }
641
773
  //#endregion
642
774
  //#region src/index.ts
643
- async function run(_argv, options) {
644
- await startServer(options);
775
+ /**
776
+ * Entry point that starts the MCP server over stdio. The argument is accepted
777
+ * for CLI parity but ignored.
778
+ */
779
+ async function run(_argv) {
780
+ await startServer();
645
781
  }
646
782
  //#endregion
647
783
  exports.createMcpServer = createMcpServer;