@kubb/mcp 5.0.0-beta.6 → 5.0.0-beta.61

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
@@ -29,20 +29,21 @@ let _tmcp_transport_http = require("@tmcp/transport-http");
29
29
  let _tmcp_transport_stdio = require("@tmcp/transport-stdio");
30
30
  let tmcp = require("tmcp");
31
31
  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
32
  let _kubb_core = require("@kubb/core");
37
33
  let tmcp_tool = require("tmcp/tool");
38
34
  let tmcp_utils = require("tmcp/utils");
39
35
  let valibot = require("valibot");
40
36
  valibot = __toESM(valibot, 1);
37
+ let node_fs = require("node:fs");
38
+ node_fs = __toESM(node_fs, 1);
39
+ let node_path = require("node:path");
40
+ node_path = __toESM(node_path, 1);
41
+ let node_url = require("node:url");
41
42
  let jiti = require("jiti");
42
43
  let node_process = require("node:process");
43
44
  node_process = __toESM(node_process, 1);
44
45
  //#region package.json
45
- var version = "5.0.0-beta.6";
46
+ var version = "5.0.0-beta.61";
46
47
  //#endregion
47
48
  //#region ../../internals/utils/src/errors.ts
48
49
  /**
@@ -90,9 +91,12 @@ var AsyncEventEmitter = class {
90
91
  * await emitter.emit('build', 'petstore')
91
92
  * ```
92
93
  */
93
- async emit(eventName, ...eventArgs) {
94
+ emit(eventName, ...eventArgs) {
94
95
  const listeners = this.#emitter.listeners(eventName);
95
96
  if (listeners.length === 0) return;
97
+ return this.#emitAll(eventName, listeners, eventArgs);
98
+ }
99
+ async #emitAll(eventName, listeners, eventArgs) {
96
100
  for (const listener of listeners) try {
97
101
  await listener(...eventArgs);
98
102
  } catch (err) {
@@ -155,6 +159,24 @@ var AsyncEventEmitter = class {
155
159
  return this.#emitter.listenerCount(eventName);
156
160
  }
157
161
  /**
162
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
163
+ * Set this above the expected listener count when many listeners attach by design.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * emitter.setMaxListeners(40)
168
+ * ```
169
+ */
170
+ setMaxListeners(max) {
171
+ this.#emitter.setMaxListeners(max);
172
+ }
173
+ /**
174
+ * Returns the current per-event listener ceiling.
175
+ */
176
+ getMaxListeners() {
177
+ return this.#emitter.getMaxListeners();
178
+ }
179
+ /**
158
180
  * Removes all listeners from every event channel.
159
181
  *
160
182
  * @example
@@ -180,31 +202,95 @@ function isPromise(result) {
180
202
  return result !== null && result !== void 0 && typeof result["then"] === "function";
181
203
  }
182
204
  //#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
- });
205
+ //#region ../../internals/utils/src/runtime.ts
206
+ /**
207
+ * Detects the JavaScript runtime executing the current process and exposes its name and version.
208
+ *
209
+ * Prefer the shared {@link runtime} instance over constructing your own.
210
+ */
211
+ var Runtime = class {
212
+ /**
213
+ * `true` when the current process is running under Bun.
214
+ *
215
+ * Detection keys off the global `Bun` object rather than `process.versions`,
216
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
217
+ * otherwise look like Node.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * if (runtime.isBun) {
222
+ * await Bun.write(path, data)
223
+ * }
224
+ * ```
225
+ */
226
+ get isBun() {
227
+ return typeof Bun !== "undefined";
228
+ }
229
+ /**
230
+ * `true` when the current process is running under Deno.
231
+ */
232
+ get isDeno() {
233
+ return typeof globalThis.Deno !== "undefined";
234
+ }
235
+ /**
236
+ * `true` when the current process is running under Node.
237
+ *
238
+ * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
239
+ */
240
+ get isNode() {
241
+ return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
242
+ }
243
+ /**
244
+ * Name of the runtime executing the current process.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
249
+ * ```
250
+ */
251
+ get name() {
252
+ if (this.isBun) return "bun";
253
+ if (this.isDeno) return "deno";
254
+ return "node";
255
+ }
256
+ /**
257
+ * Version of the active runtime, or an empty string when it cannot be read.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * runtime.version // '1.3.11' under Bun, '22.22.2' under Node
262
+ * ```
263
+ */
264
+ get version() {
265
+ if (this.isBun) return process.versions.bun ?? "";
266
+ if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
267
+ return process.versions?.node ?? "";
268
+ }
269
+ };
270
+ /**
271
+ * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
272
+ */
273
+ const runtime = new Runtime();
197
274
  //#endregion
198
- //#region src/types.ts
275
+ //#region src/constants.ts
276
+ const ALLOWED_CONFIG_EXTENSIONS = new Set([
277
+ ".ts",
278
+ ".mts",
279
+ ".cts",
280
+ ".js",
281
+ ".mjs",
282
+ ".cjs"
283
+ ]);
199
284
  const NotifyTypes = {
200
285
  INFO: "INFO",
201
286
  SUCCESS: "SUCCESS",
202
287
  ERROR: "ERROR",
203
288
  WARN: "WARN",
289
+ DIAGNOSTIC: "DIAGNOSTIC",
204
290
  PLUGIN_START: "PLUGIN_START",
205
291
  PLUGIN_END: "PLUGIN_END",
206
292
  FILES_START: "FILES_START",
207
- FILE_UPDATE: "FILE_UPDATE",
293
+ FILES_UPDATE: "FILES_UPDATE",
208
294
  FILES_END: "FILES_END",
209
295
  GENERATION_START: "GENERATION_START",
210
296
  GENERATION_END: "GENERATION_END",
@@ -216,34 +302,201 @@ const NotifyTypes = {
216
302
  BUILD_START: "BUILD_START",
217
303
  BUILD_END: "BUILD_END",
218
304
  BUILD_FAILED: "BUILD_FAILED",
219
- BUILD_SUCCESS: "BUILD_SUCCESS",
220
- FATAL_ERROR: "FATAL_ERROR"
305
+ BUILD_SUCCESS: "BUILD_SUCCESS"
221
306
  };
222
307
  //#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
- ]);
308
+ //#region src/schemas/generateSchema.ts
309
+ const generateSchema = valibot.object({
310
+ 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"))),
311
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
312
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
313
+ logLevel: valibot.optional(valibot.pipe(valibot.picklist([
314
+ "silent",
315
+ "info",
316
+ "verbose"
317
+ ]), valibot.description("Log level for build output")), "info")
318
+ });
232
319
  //#endregion
233
- //#region src/utils/loadUserConfig.ts
234
- const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, {
320
+ //#region ../../internals/shared/src/constants.ts
321
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
322
+ const availablePlugins = [
323
+ {
324
+ value: "plugin-ts",
325
+ label: "TypeScript",
326
+ hint: "Recommended",
327
+ packageName: "@kubb/plugin-ts",
328
+ importName: "pluginTs",
329
+ category: "types"
330
+ },
331
+ {
332
+ value: "plugin-client",
333
+ label: "Client (Fetch/Axios)",
334
+ packageName: "@kubb/plugin-client",
335
+ importName: "pluginClient",
336
+ category: "client"
337
+ },
338
+ {
339
+ value: "plugin-react-query",
340
+ label: "React Query / TanStack Query",
341
+ packageName: "@kubb/plugin-react-query",
342
+ importName: "pluginReactQuery",
343
+ category: "framework"
344
+ },
345
+ {
346
+ value: "plugin-vue-query",
347
+ label: "Vue Query",
348
+ packageName: "@kubb/plugin-vue-query",
349
+ importName: "pluginVueQuery",
350
+ category: "framework"
351
+ },
352
+ {
353
+ value: "plugin-zod",
354
+ label: "Zod Schemas",
355
+ packageName: "@kubb/plugin-zod",
356
+ importName: "pluginZod",
357
+ category: "validation"
358
+ },
359
+ {
360
+ value: "plugin-faker",
361
+ label: "Faker.js Mocks",
362
+ packageName: "@kubb/plugin-faker",
363
+ importName: "pluginFaker",
364
+ category: "mocks"
365
+ },
366
+ {
367
+ value: "plugin-msw",
368
+ label: "MSW Handlers",
369
+ packageName: "@kubb/plugin-msw",
370
+ importName: "pluginMsw",
371
+ category: "mocks"
372
+ },
373
+ {
374
+ value: "plugin-cypress",
375
+ label: "Cypress Tests",
376
+ packageName: "@kubb/plugin-cypress",
377
+ importName: "pluginCypress",
378
+ category: "testing"
379
+ },
380
+ {
381
+ value: "plugin-mcp",
382
+ label: "MCP Server (AI / Model Context Protocol)",
383
+ packageName: "@kubb/plugin-mcp",
384
+ importName: "pluginMcp",
385
+ category: "ai"
386
+ },
387
+ {
388
+ value: "plugin-redoc",
389
+ label: "ReDoc Documentation",
390
+ packageName: "@kubb/plugin-redoc",
391
+ importName: "pluginRedoc",
392
+ category: "documentation"
393
+ }
394
+ ];
395
+ const pluginDefaultConfigs = {
396
+ "plugin-ts": `pluginTs()`,
397
+ "plugin-client": `pluginClient()`,
398
+ "plugin-react-query": `pluginReactQuery()`,
399
+ "plugin-vue-query": `pluginVueQuery()`,
400
+ "plugin-zod": `pluginZod()`,
401
+ "plugin-faker": `pluginFaker()`,
402
+ "plugin-msw": `pluginMsw()`,
403
+ "plugin-cypress": `pluginCypress()`,
404
+ "plugin-mcp": `pluginMcp()`,
405
+ "plugin-redoc": `pluginRedoc()`
406
+ };
407
+ //#endregion
408
+ //#region ../../internals/shared/src/init.ts
409
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
410
+ return `import { defineConfig } from 'kubb'
411
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
412
+
413
+ export default defineConfig({
414
+ root: '.',
415
+ input: {
416
+ path: '${inputPath}',
417
+ },
418
+ output: {
419
+ path: '${outputPath}',
420
+ clean: true,
421
+ },
422
+ plugins: [
423
+ ${selectedPlugins.map((plugin) => {
424
+ return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
425
+ }).join("\n")}
426
+ ],
427
+ })
428
+ `;
429
+ }
430
+ //#endregion
431
+ //#region ../../internals/shared/src/loader.ts
432
+ /**
433
+ * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
434
+ * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
435
+ */
436
+ const JITI_OPTIONS = {
235
437
  jsx: {
236
438
  runtime: "automatic",
237
439
  importSource: "@kubb/renderer-jsx"
238
440
  },
239
441
  moduleCache: false
240
- });
442
+ };
443
+ /**
444
+ * Creates a runtime-aware loader for Kubb's TypeScript and JavaScript config modules.
445
+ *
446
+ * On Bun and Deno it imports the file natively, skipping jiti's transform step, and falls back to
447
+ * jiti only when the native import throws. On Node it always uses jiti, which transpiles TypeScript
448
+ * and the `@kubb/renderer-jsx` JSX runtime on the fly. The jiti instance is created lazily, so the
449
+ * Bun/Deno happy path never pays for it.
450
+ *
451
+ * @example
452
+ * ```ts
453
+ * const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
454
+ * ```
455
+ */
456
+ function createModuleLoader() {
457
+ let jiti$1;
458
+ const getJiti = () => jiti$1 ??= (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, JITI_OPTIONS);
459
+ const viaJiti = (filePath, options) => options?.default ? getJiti().import(filePath, { default: true }) : getJiti().import(filePath);
460
+ const viaNative = async (filePath, options) => {
461
+ const href = (0, node_url.pathToFileURL)(filePath).href;
462
+ const mod = await (options?.bust != null ? import(`${href}?t=${options.bust}`) : import(href));
463
+ return options?.default ? mod.default ?? mod : mod;
464
+ };
465
+ return { async load(filePath, options) {
466
+ if (runtime.isBun || runtime.isDeno) try {
467
+ return await viaNative(filePath, options);
468
+ } catch {
469
+ return viaJiti(filePath, options);
470
+ }
471
+ return viaJiti(filePath, options);
472
+ } };
473
+ }
474
+ //#endregion
475
+ //#region src/utils.ts
476
+ /**
477
+ * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
478
+ * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
479
+ * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
480
+ * unlike the CLI renderer.
481
+ */
482
+ function formatDiagnostics(diagnostics) {
483
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
484
+ }
485
+ function formatDiagnostic(diagnostic) {
486
+ const { code, severity, message, location, help, plugin, docsUrl } = diagnostic;
487
+ const lines = [`${severity} ${plugin ? `${plugin}(${code})` : code}: ${message}`];
488
+ if (location && "pointer" in location) lines.push(` at ${location.pointer}`);
489
+ if (help) lines.push(` help: ${help}`);
490
+ if (docsUrl) lines.push(` docs: ${docsUrl}`);
491
+ return lines.join("\n");
492
+ }
493
+ const loader = createModuleLoader();
241
494
  const loadedModules = /* @__PURE__ */ new Map();
242
495
  async function loadModule(filePath) {
243
496
  const ext = node_path.default.extname(filePath);
244
497
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
245
498
  if (loadedModules.has(filePath)) return loadedModules.get(filePath);
246
- const mod = await jiti$1.import(filePath, { default: true });
499
+ const mod = await loader.load(filePath, { default: true });
247
500
  loadedModules.set(filePath, mod);
248
501
  return mod;
249
502
  }
@@ -302,8 +555,6 @@ async function loadUserConfig(configPath, { notify }) {
302
555
  await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
303
556
  throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
304
557
  }
305
- //#endregion
306
- //#region src/utils/resolveCwd.ts
307
558
  /**
308
559
  * Determine the root directory based on userConfig.root and resolvedConfigDir
309
560
  * 1. If userConfig.root exists and is absolute, use it as-is
@@ -317,8 +568,6 @@ function resolveCwd(userConfig, cwd) {
317
568
  }
318
569
  return cwd;
319
570
  }
320
- //#endregion
321
- //#region src/utils/resolveUserConfig.ts
322
571
  async function resolveUserConfig(config, options) {
323
572
  const result = typeof config === "function" ? config({
324
573
  logLevel: options.logLevel,
@@ -338,8 +587,8 @@ const generateTool = (0, tmcp_tool.defineTool)({
338
587
  try {
339
588
  const hooks = new AsyncEventEmitter();
340
589
  const messages = [];
341
- const notify = async (type, message, _data) => {
342
- messages.push(`${type}: ${message}`);
590
+ const notify = async (type, message, data) => {
591
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
343
592
  };
344
593
  hooks.on("kubb:info", async ({ message }) => {
345
594
  await notify(NotifyTypes.INFO, message);
@@ -353,6 +602,9 @@ const generateTool = (0, tmcp_tool.defineTool)({
353
602
  hooks.on("kubb:warn", async ({ message }) => {
354
603
  await notify(NotifyTypes.WARN, message);
355
604
  });
605
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
606
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, _kubb_core.Diagnostics.serialize(diagnostic));
607
+ });
356
608
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
357
609
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
358
610
  });
@@ -362,8 +614,8 @@ const generateTool = (0, tmcp_tool.defineTool)({
362
614
  hooks.on("kubb:files:processing:start", async () => {
363
615
  await notify(NotifyTypes.FILES_START, "Starting file processing");
364
616
  });
365
- hooks.on("kubb:file:processing:update", async ({ file }) => {
366
- await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
617
+ hooks.on("kubb:files:processing:update", async ({ files }) => {
618
+ await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
367
619
  });
368
620
  hooks.on("kubb:files:processing:end", async () => {
369
621
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -409,152 +661,23 @@ const generateTool = (0, tmcp_tool.defineTool)({
409
661
  await kubb.setup();
410
662
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
411
663
  await notify(NotifyTypes.BUILD_START, "Starting build");
412
- const { files, failedPlugins, error } = await kubb.safeBuild();
664
+ const { files, diagnostics } = await kubb.safeBuild();
413
665
  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")}`);
666
+ const problems = diagnostics.filter(_kubb_core.Diagnostics.isProblem);
667
+ const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
668
+ if (errors.length > 0) {
669
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
670
+ const serialized = problems.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic));
671
+ return tmcp_utils.tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
418
672
  }
419
673
  await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
420
674
  return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
421
675
  } catch (caughtError) {
422
- const error = toError(caughtError);
423
- return tmcp_utils.tool.error(`Build error: ${error.message}\n${error.stack ?? ""}`);
676
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(caughtError));
677
+ return tmcp_utils.tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
424
678
  }
425
679
  });
426
680
  //#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
681
  //#region src/schemas/initSchema.ts
559
682
  const initSchema = valibot.object({
560
683
  input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
@@ -605,7 +728,8 @@ const validateTool = (0, tmcp_tool.defineTool)({
605
728
  await mod.adapterOas().validate(input, { throwOnError: true });
606
729
  return tmcp_utils.tool.text(`Validation successful: ${input}`);
607
730
  } catch (err) {
608
- return tmcp_utils.tool.error(`Validation failed:\n${err instanceof Error ? err.message : String(err)}`);
731
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(err));
732
+ return tmcp_utils.tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
609
733
  }
610
734
  });
611
735
  //#endregion
@@ -614,7 +738,10 @@ function createMcpServer() {
614
738
  const server = new tmcp.McpServer({
615
739
  name: "Kubb",
616
740
  version
617
- }, { adapter: new _tmcp_adapter_valibot.ValibotJsonSchemaAdapter() });
741
+ }, {
742
+ adapter: new _tmcp_adapter_valibot.ValibotJsonSchemaAdapter(),
743
+ capabilities: { tools: {} }
744
+ });
618
745
  server.tools([
619
746
  generateTool,
620
747
  validateTool,
@@ -629,11 +756,19 @@ async function startServer({ port, host = "localhost" } = {}) {
629
756
  return;
630
757
  }
631
758
  const transport = new _tmcp_transport_http.HttpTransport(server, { path: "/mcp" });
632
- node_http.default.createServer((0, _remix_run_node_fetch_server.createRequestListener)(async (request) => {
759
+ const httpServer = node_http.default.createServer((0, _remix_run_node_fetch_server.createRequestListener)(async (request) => {
633
760
  return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
634
- })).listen(port, host, () => {
761
+ }));
762
+ httpServer.listen(port, host, () => {
635
763
  console.log(`Kubb MCP server on http://${host}:${port}`);
636
764
  });
765
+ const closeServer = () => httpServer.close();
766
+ process.once("SIGINT", closeServer);
767
+ process.once("SIGTERM", closeServer);
768
+ httpServer.once("close", () => {
769
+ process.off("SIGINT", closeServer);
770
+ process.off("SIGTERM", closeServer);
771
+ });
637
772
  }
638
773
  //#endregion
639
774
  //#region src/index.ts