@kubb/mcp 5.0.0-beta.8 → 5.0.0-beta.81

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,18 @@
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";
15
12
  import { createJiti } from "jiti";
16
13
  import process$1 from "node:process";
17
14
  //#region package.json
18
- var version = "5.0.0-beta.8";
15
+ var version = "5.0.0-beta.81";
19
16
  //#endregion
20
17
  //#region ../../internals/utils/src/errors.ts
21
18
  /**
@@ -63,9 +60,12 @@ var AsyncEventEmitter = class {
63
60
  * await emitter.emit('build', 'petstore')
64
61
  * ```
65
62
  */
66
- async emit(eventName, ...eventArgs) {
63
+ emit(eventName, ...eventArgs) {
67
64
  const listeners = this.#emitter.listeners(eventName);
68
65
  if (listeners.length === 0) return;
66
+ return this.#emitAll(eventName, listeners, eventArgs);
67
+ }
68
+ async #emitAll(eventName, listeners, eventArgs) {
69
69
  for (const listener of listeners) try {
70
70
  await listener(...eventArgs);
71
71
  } catch (err) {
@@ -90,21 +90,6 @@ var AsyncEventEmitter = class {
90
90
  this.#emitter.on(eventName, handler);
91
91
  }
92
92
  /**
93
- * Registers a one-shot listener that removes itself after the first invocation.
94
- *
95
- * @example
96
- * ```ts
97
- * emitter.onOnce('build', async (name) => { console.log(name) })
98
- * ```
99
- */
100
- onOnce(eventName, handler) {
101
- const wrapper = (...args) => {
102
- this.off(eventName, wrapper);
103
- return handler(...args);
104
- };
105
- this.on(eventName, wrapper);
106
- }
107
- /**
108
93
  * Removes a previously registered listener.
109
94
  *
110
95
  * @example
@@ -128,6 +113,18 @@ var AsyncEventEmitter = class {
128
113
  return this.#emitter.listenerCount(eventName);
129
114
  }
130
115
  /**
116
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
117
+ * Set this above the expected listener count when many listeners attach by design.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * emitter.setMaxListeners(40)
122
+ * ```
123
+ */
124
+ setMaxListeners(max) {
125
+ this.#emitter.setMaxListeners(max);
126
+ }
127
+ /**
131
128
  * Removes all listeners from every event channel.
132
129
  *
133
130
  * @example
@@ -153,31 +150,33 @@ function isPromise(result) {
153
150
  return result !== null && result !== void 0 && typeof result["then"] === "function";
154
151
  }
155
152
  //#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
- });
170
- //#endregion
171
- //#region src/types.ts
153
+ //#region src/constants.ts
154
+ /**
155
+ * File extensions a Kubb config is allowed to use. A config path with any other
156
+ * extension is rejected before it is loaded.
157
+ */
158
+ const ALLOWED_CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([
159
+ ".ts",
160
+ ".mts",
161
+ ".cts",
162
+ ".js",
163
+ ".mjs",
164
+ ".cjs"
165
+ ]);
166
+ /**
167
+ * Notification kinds reported back to the MCP client while loading config and
168
+ * running generation, covering progress, results, and errors.
169
+ */
172
170
  const NotifyTypes = {
173
171
  INFO: "INFO",
174
172
  SUCCESS: "SUCCESS",
175
173
  ERROR: "ERROR",
176
174
  WARN: "WARN",
175
+ DIAGNOSTIC: "DIAGNOSTIC",
177
176
  PLUGIN_START: "PLUGIN_START",
178
177
  PLUGIN_END: "PLUGIN_END",
179
178
  FILES_START: "FILES_START",
180
- FILE_UPDATE: "FILE_UPDATE",
179
+ FILES_UPDATE: "FILES_UPDATE",
181
180
  FILES_END: "FILES_END",
182
181
  GENERATION_START: "GENERATION_START",
183
182
  GENERATION_END: "GENERATION_END",
@@ -189,37 +188,190 @@ const NotifyTypes = {
189
188
  BUILD_START: "BUILD_START",
190
189
  BUILD_END: "BUILD_END",
191
190
  BUILD_FAILED: "BUILD_FAILED",
192
- BUILD_SUCCESS: "BUILD_SUCCESS",
193
- FATAL_ERROR: "FATAL_ERROR"
191
+ BUILD_SUCCESS: "BUILD_SUCCESS"
194
192
  };
195
193
  //#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
- ]);
194
+ //#region src/schemas/generateSchema.ts
195
+ const generateSchema = v.object({
196
+ 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"))),
197
+ input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
198
+ output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory path (overrides config)"))),
199
+ logLevel: v.optional(v.pipe(v.picklist([
200
+ "silent",
201
+ "info",
202
+ "verbose"
203
+ ]), v.description("Log level for build output")), "info")
204
+ });
205
+ //#endregion
206
+ //#region ../../internals/shared/src/constants.ts
207
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
208
+ const availablePlugins = [
209
+ {
210
+ value: "plugin-ts",
211
+ label: "TypeScript",
212
+ hint: "Recommended",
213
+ packageName: "@kubb/plugin-ts",
214
+ importName: "pluginTs",
215
+ category: "types"
216
+ },
217
+ {
218
+ value: "plugin-axios",
219
+ label: "Axios Client",
220
+ packageName: "@kubb/plugin-axios",
221
+ importName: "pluginAxios",
222
+ category: "client"
223
+ },
224
+ {
225
+ value: "plugin-fetch",
226
+ label: "Fetch Client",
227
+ packageName: "@kubb/plugin-fetch",
228
+ importName: "pluginFetch",
229
+ category: "client"
230
+ },
231
+ {
232
+ value: "plugin-react-query",
233
+ label: "React Query / TanStack Query",
234
+ packageName: "@kubb/plugin-react-query",
235
+ importName: "pluginReactQuery",
236
+ category: "framework"
237
+ },
238
+ {
239
+ value: "plugin-vue-query",
240
+ label: "Vue Query",
241
+ packageName: "@kubb/plugin-vue-query",
242
+ importName: "pluginVueQuery",
243
+ category: "framework"
244
+ },
245
+ {
246
+ value: "plugin-zod",
247
+ label: "Zod Schemas",
248
+ packageName: "@kubb/plugin-zod",
249
+ importName: "pluginZod",
250
+ category: "validation"
251
+ },
252
+ {
253
+ value: "plugin-faker",
254
+ label: "Faker.js Mocks",
255
+ packageName: "@kubb/plugin-faker",
256
+ importName: "pluginFaker",
257
+ category: "mocks"
258
+ },
259
+ {
260
+ value: "plugin-msw",
261
+ label: "MSW Handlers",
262
+ packageName: "@kubb/plugin-msw",
263
+ importName: "pluginMsw",
264
+ category: "mocks"
265
+ },
266
+ {
267
+ value: "plugin-cypress",
268
+ label: "Cypress Tests",
269
+ packageName: "@kubb/plugin-cypress",
270
+ importName: "pluginCypress",
271
+ category: "testing"
272
+ },
273
+ {
274
+ value: "plugin-mcp",
275
+ label: "MCP Server (AI / Model Context Protocol)",
276
+ packageName: "@kubb/plugin-mcp",
277
+ importName: "pluginMcp",
278
+ category: "ai"
279
+ },
280
+ {
281
+ value: "plugin-redoc",
282
+ label: "ReDoc Documentation",
283
+ packageName: "@kubb/plugin-redoc",
284
+ importName: "pluginRedoc",
285
+ category: "documentation"
286
+ }
287
+ ];
205
288
  //#endregion
206
- //#region src/utils/loadUserConfig.ts
207
- const jiti = createJiti(import.meta.url, {
289
+ //#region ../../internals/shared/src/init.ts
290
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
291
+ return `import { defineConfig } from 'kubb'
292
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
293
+
294
+ export default defineConfig({
295
+ root: '.',
296
+ input: {
297
+ path: '${inputPath}',
298
+ },
299
+ output: {
300
+ path: '${outputPath}',
301
+ clean: true,
302
+ },
303
+ plugins: [
304
+ ${selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join("\n")}
305
+ ],
306
+ })
307
+ `;
308
+ }
309
+ //#endregion
310
+ //#region ../../internals/shared/src/loader.ts
311
+ /**
312
+ * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
313
+ * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
314
+ */
315
+ const JITI_OPTIONS = {
208
316
  jsx: {
209
317
  runtime: "automatic",
210
318
  importSource: "@kubb/renderer-jsx"
211
319
  },
212
320
  moduleCache: false
213
- });
321
+ };
322
+ /**
323
+ * Creates a jiti-based loader for Kubb's TypeScript and JavaScript config modules.
324
+ *
325
+ * jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly.
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
330
+ * ```
331
+ */
332
+ function createModuleLoader() {
333
+ const jiti = createJiti(import.meta.url, JITI_OPTIONS);
334
+ return { load(filePath, options) {
335
+ return options?.default ? jiti.import(filePath, { default: true }) : jiti.import(filePath);
336
+ } };
337
+ }
338
+ //#endregion
339
+ //#region src/utils.ts
340
+ /**
341
+ * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
342
+ * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
343
+ * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
344
+ * unlike the CLI renderer.
345
+ */
346
+ function formatDiagnostics(diagnostics) {
347
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
348
+ }
349
+ function formatDiagnostic(diagnostic) {
350
+ const { code, message, location, help, plugin, docsUrl } = diagnostic;
351
+ const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`];
352
+ if (location && "pointer" in location) lines.push(` at: ${location.pointer}`);
353
+ if (help) lines.push(` fix: ${help}`);
354
+ if (docsUrl) lines.push(` see: ${docsUrl}`);
355
+ return lines.join("\n");
356
+ }
357
+ const loader = createModuleLoader();
214
358
  const loadedModules = /* @__PURE__ */ new Map();
215
359
  async function loadModule(filePath) {
216
360
  const ext = path.extname(filePath);
217
361
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
218
362
  if (loadedModules.has(filePath)) return loadedModules.get(filePath);
219
- const mod = await jiti.import(filePath, { default: true });
363
+ const mod = await loader.load(filePath, { default: true });
220
364
  loadedModules.set(filePath, mod);
221
365
  return mod;
222
366
  }
367
+ /**
368
+ * Loads the user's Kubb config and returns it with the directory it was found in.
369
+ *
370
+ * When `configPath` is given it must use an allowed extension and resolve inside
371
+ * the current working directory, otherwise loading throws. When omitted, the
372
+ * known `kubb.config.*` file names are tried in the current directory. Every
373
+ * outcome is reported through `notify` before the function returns or throws.
374
+ */
223
375
  async function loadUserConfig(configPath, { notify }) {
224
376
  if (configPath) {
225
377
  const ext = path.extname(configPath);
@@ -275,8 +427,6 @@ async function loadUserConfig(configPath, { notify }) {
275
427
  await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
276
428
  throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
277
429
  }
278
- //#endregion
279
- //#region src/utils/resolveCwd.ts
280
430
  /**
281
431
  * Determine the root directory based on userConfig.root and resolvedConfigDir
282
432
  * 1. If userConfig.root exists and is absolute, use it as-is
@@ -290,8 +440,12 @@ function resolveCwd(userConfig, cwd) {
290
440
  }
291
441
  return cwd;
292
442
  }
293
- //#endregion
294
- //#region src/utils/resolveUserConfig.ts
443
+ /**
444
+ * Normalizes a possible config into a single resolved `Config`.
445
+ *
446
+ * Calls the config when it is a function, awaits it when it is a promise, and
447
+ * picks the first entry when it resolves to an array.
448
+ */
295
449
  async function resolveUserConfig(config, options) {
296
450
  const result = typeof config === "function" ? config({
297
451
  logLevel: options.logLevel,
@@ -311,8 +465,8 @@ const generateTool = defineTool({
311
465
  try {
312
466
  const hooks = new AsyncEventEmitter();
313
467
  const messages = [];
314
- const notify = async (type, message, _data) => {
315
- messages.push(`${type}: ${message}`);
468
+ const notify = async (type, message, data) => {
469
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
316
470
  };
317
471
  hooks.on("kubb:info", async ({ message }) => {
318
472
  await notify(NotifyTypes.INFO, message);
@@ -326,6 +480,9 @@ const generateTool = defineTool({
326
480
  hooks.on("kubb:warn", async ({ message }) => {
327
481
  await notify(NotifyTypes.WARN, message);
328
482
  });
483
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
484
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
485
+ });
329
486
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
330
487
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
331
488
  });
@@ -335,8 +492,8 @@ const generateTool = defineTool({
335
492
  hooks.on("kubb:files:processing:start", async () => {
336
493
  await notify(NotifyTypes.FILES_START, "Starting file processing");
337
494
  });
338
- hooks.on("kubb:file:processing:update", async ({ file }) => {
339
- await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
495
+ hooks.on("kubb:files:processing:update", async ({ files }) => {
496
+ await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
340
497
  });
341
498
  hooks.on("kubb:files:processing:end", async () => {
342
499
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -382,152 +539,23 @@ const generateTool = defineTool({
382
539
  await kubb.setup();
383
540
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
384
541
  await notify(NotifyTypes.BUILD_START, "Starting build");
385
- const { files, failedPlugins, error } = await kubb.safeBuild();
542
+ const { files, diagnostics } = await kubb.safeBuild();
386
543
  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")}`);
544
+ const problems = diagnostics.filter(Diagnostics.isProblem);
545
+ const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
546
+ if (errors.length > 0) {
547
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
548
+ const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic));
549
+ return tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
391
550
  }
392
551
  await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
393
552
  return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
394
553
  } catch (caughtError) {
395
- const error = toError(caughtError);
396
- return tool.error(`Build error: ${error.message}\n${error.stack ?? ""}`);
554
+ const serialized = Diagnostics.serialize(Diagnostics.from(caughtError));
555
+ return tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
397
556
  }
398
557
  });
399
558
  //#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
559
  //#region src/schemas/initSchema.ts
532
560
  const initSchema = v.object({
533
561
  input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
@@ -536,6 +564,10 @@ const initSchema = v.object({
536
564
  });
537
565
  //#endregion
538
566
  //#region src/tools/init.ts
567
+ /**
568
+ * Resolves a comma-separated plugin flag into the matching known plugin options.
569
+ * Unrecognized names are dropped, and a missing flag yields an empty list.
570
+ */
539
571
  function resolvePlugins(pluginsFlag) {
540
572
  if (!pluginsFlag) return [];
541
573
  const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
@@ -575,11 +607,18 @@ const validateTool = defineTool({
575
607
  await mod.adapterOas().validate(input, { throwOnError: true });
576
608
  return tool.text(`Validation successful: ${input}`);
577
609
  } catch (err) {
578
- return tool.error(`Validation failed:\n${err instanceof Error ? err.message : String(err)}`);
610
+ const serialized = Diagnostics.serialize(Diagnostics.from(err));
611
+ return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
579
612
  }
580
613
  });
581
614
  //#endregion
582
615
  //#region src/server.ts
616
+ /**
617
+ * Builds the Kubb MCP server with the generate, validate, and init tools registered.
618
+ *
619
+ * @example
620
+ * `const server = createMcpServer()`
621
+ */
583
622
  function createMcpServer() {
584
623
  const server = new McpServer({
585
624
  name: "Kubb",
@@ -595,23 +634,21 @@ function createMcpServer() {
595
634
  ]);
596
635
  return server;
597
636
  }
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
- });
637
+ /**
638
+ * Starts the Kubb MCP server over stdio, the transport every local MCP client
639
+ * (Claude, Copilot, editors) uses when it launches the server as a subprocess.
640
+ */
641
+ async function startServer() {
642
+ new StdioTransport(createMcpServer()).listen();
610
643
  }
611
644
  //#endregion
612
645
  //#region src/index.ts
613
- async function run(_argv, options) {
614
- await startServer(options);
646
+ /**
647
+ * Entry point that starts the MCP server over stdio. The argument is accepted
648
+ * for CLI parity but ignored.
649
+ */
650
+ async function run(_argv) {
651
+ await startServer();
615
652
  }
616
653
  //#endregion
617
654
  export { createMcpServer, run };