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

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