@kubb/mcp 5.0.0-beta.75 → 5.0.0-beta.76

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,34 +21,24 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- let node_process = require("node:process");
25
- node_process = __toESM(node_process, 1);
26
- let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
27
- let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
28
- let zod = require("zod");
24
+ let _tmcp_adapter_valibot = require("@tmcp/adapter-valibot");
25
+ let _tmcp_transport_stdio = require("@tmcp/transport-stdio");
26
+ let tmcp = require("tmcp");
29
27
  let node_events = require("node:events");
28
+ let _kubb_core = require("@kubb/core");
29
+ let tmcp_tool = require("tmcp/tool");
30
+ let tmcp_utils = require("tmcp/utils");
31
+ let valibot = require("valibot");
32
+ valibot = __toESM(valibot, 1);
30
33
  let node_fs = require("node:fs");
34
+ node_fs = __toESM(node_fs, 1);
31
35
  let node_path = require("node:path");
32
36
  node_path = __toESM(node_path, 1);
33
- let _kubb_core = require("@kubb/core");
34
- let unrun = require("unrun");
37
+ let jiti = require("jiti");
38
+ let node_process = require("node:process");
39
+ node_process = __toESM(node_process, 1);
35
40
  //#region package.json
36
- var version = "5.0.0-beta.75";
37
- //#endregion
38
- //#region src/schemas/generateSchema.ts
39
- const generateSchema = zod.z.object({
40
- config: zod.z.string().optional().describe("Path to kubb.config file (supports .ts, .js, .cjs). If not provided, will look for kubb.config.{ts,js,cjs} in current directory"),
41
- input: zod.z.string().optional().describe("Path to OpenAPI/Swagger spec file (overrides config)"),
42
- output: zod.z.string().optional().describe("Output directory path (overrides config)"),
43
- logLevel: zod.z.enum([
44
- "silent",
45
- "error",
46
- "warn",
47
- "info",
48
- "verbose",
49
- "debug"
50
- ]).optional().default("info").describe("Log level for build output")
51
- });
41
+ var version = "5.0.0-beta.76";
52
42
  //#endregion
53
43
  //#region ../../internals/utils/src/errors.ts
54
44
  /**
@@ -96,9 +86,12 @@ var AsyncEventEmitter = class {
96
86
  * await emitter.emit('build', 'petstore')
97
87
  * ```
98
88
  */
99
- async emit(eventName, ...eventArgs) {
89
+ emit(eventName, ...eventArgs) {
100
90
  const listeners = this.#emitter.listeners(eventName);
101
91
  if (listeners.length === 0) return;
92
+ return this.#emitAll(eventName, listeners, eventArgs);
93
+ }
94
+ async #emitAll(eventName, listeners, eventArgs) {
102
95
  for (const listener of listeners) try {
103
96
  await listener(...eventArgs);
104
97
  } catch (err) {
@@ -123,21 +116,6 @@ var AsyncEventEmitter = class {
123
116
  this.#emitter.on(eventName, handler);
124
117
  }
125
118
  /**
126
- * Registers a one-shot listener that removes itself after the first invocation.
127
- *
128
- * @example
129
- * ```ts
130
- * emitter.onOnce('build', async (name) => { console.log(name) })
131
- * ```
132
- */
133
- onOnce(eventName, handler) {
134
- const wrapper = (...args) => {
135
- this.off(eventName, wrapper);
136
- return handler(...args);
137
- };
138
- this.on(eventName, wrapper);
139
- }
140
- /**
141
119
  * Removes a previously registered listener.
142
120
  *
143
121
  * @example
@@ -161,6 +139,18 @@ var AsyncEventEmitter = class {
161
139
  return this.#emitter.listenerCount(eventName);
162
140
  }
163
141
  /**
142
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
143
+ * Set this above the expected listener count when many listeners attach by design.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * emitter.setMaxListeners(40)
148
+ * ```
149
+ */
150
+ setMaxListeners(max) {
151
+ this.#emitter.setMaxListeners(max);
152
+ }
153
+ /**
164
154
  * Removes all listeners from every event channel.
165
155
  *
166
156
  * @example
@@ -186,16 +176,33 @@ function isPromise(result) {
186
176
  return result !== null && result !== void 0 && typeof result["then"] === "function";
187
177
  }
188
178
  //#endregion
189
- //#region src/types.ts
179
+ //#region src/constants.ts
180
+ /**
181
+ * File extensions a Kubb config is allowed to use. A config path with any other
182
+ * extension is rejected before it is loaded.
183
+ */
184
+ const ALLOWED_CONFIG_EXTENSIONS = new Set([
185
+ ".ts",
186
+ ".mts",
187
+ ".cts",
188
+ ".js",
189
+ ".mjs",
190
+ ".cjs"
191
+ ]);
192
+ /**
193
+ * Notification kinds reported back to the MCP client while loading config and
194
+ * running generation, covering progress, results, and errors.
195
+ */
190
196
  const NotifyTypes = {
191
197
  INFO: "INFO",
192
198
  SUCCESS: "SUCCESS",
193
199
  ERROR: "ERROR",
194
200
  WARN: "WARN",
201
+ DIAGNOSTIC: "DIAGNOSTIC",
195
202
  PLUGIN_START: "PLUGIN_START",
196
203
  PLUGIN_END: "PLUGIN_END",
197
204
  FILES_START: "FILES_START",
198
- FILE_UPDATE: "FILE_UPDATE",
205
+ FILES_UPDATE: "FILES_UPDATE",
199
206
  FILES_END: "FILES_END",
200
207
  GENERATION_START: "GENERATION_START",
201
208
  GENERATION_END: "GENERATION_END",
@@ -207,33 +214,191 @@ const NotifyTypes = {
207
214
  BUILD_START: "BUILD_START",
208
215
  BUILD_END: "BUILD_END",
209
216
  BUILD_FAILED: "BUILD_FAILED",
210
- BUILD_SUCCESS: "BUILD_SUCCESS",
211
- FATAL_ERROR: "FATAL_ERROR"
217
+ BUILD_SUCCESS: "BUILD_SUCCESS"
212
218
  };
213
219
  //#endregion
214
- //#region src/constants.ts
215
- const ALLOWED_CONFIG_EXTENSIONS = new Set([
216
- ".ts",
217
- ".mts",
218
- ".cts",
219
- ".js",
220
- ".mjs",
221
- ".cjs"
222
- ]);
220
+ //#region src/schemas/generateSchema.ts
221
+ const generateSchema = valibot.object({
222
+ 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"))),
223
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
224
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
225
+ logLevel: valibot.optional(valibot.pipe(valibot.picklist([
226
+ "silent",
227
+ "info",
228
+ "verbose"
229
+ ]), valibot.description("Log level for build output")), "info")
230
+ });
231
+ //#endregion
232
+ //#region ../../internals/shared/src/constants.ts
233
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
234
+ const availablePlugins = [
235
+ {
236
+ value: "plugin-ts",
237
+ label: "TypeScript",
238
+ hint: "Recommended",
239
+ packageName: "@kubb/plugin-ts",
240
+ importName: "pluginTs",
241
+ category: "types"
242
+ },
243
+ {
244
+ value: "plugin-axios",
245
+ label: "Axios Client",
246
+ packageName: "@kubb/plugin-axios",
247
+ importName: "pluginAxios",
248
+ category: "client"
249
+ },
250
+ {
251
+ value: "plugin-fetch",
252
+ label: "Fetch Client",
253
+ packageName: "@kubb/plugin-fetch",
254
+ importName: "pluginFetch",
255
+ category: "client"
256
+ },
257
+ {
258
+ value: "plugin-react-query",
259
+ label: "React Query / TanStack Query",
260
+ packageName: "@kubb/plugin-react-query",
261
+ importName: "pluginReactQuery",
262
+ category: "framework"
263
+ },
264
+ {
265
+ value: "plugin-vue-query",
266
+ label: "Vue Query",
267
+ packageName: "@kubb/plugin-vue-query",
268
+ importName: "pluginVueQuery",
269
+ category: "framework"
270
+ },
271
+ {
272
+ value: "plugin-zod",
273
+ label: "Zod Schemas",
274
+ packageName: "@kubb/plugin-zod",
275
+ importName: "pluginZod",
276
+ category: "validation"
277
+ },
278
+ {
279
+ value: "plugin-faker",
280
+ label: "Faker.js Mocks",
281
+ packageName: "@kubb/plugin-faker",
282
+ importName: "pluginFaker",
283
+ category: "mocks"
284
+ },
285
+ {
286
+ value: "plugin-msw",
287
+ label: "MSW Handlers",
288
+ packageName: "@kubb/plugin-msw",
289
+ importName: "pluginMsw",
290
+ category: "mocks"
291
+ },
292
+ {
293
+ value: "plugin-cypress",
294
+ label: "Cypress Tests",
295
+ packageName: "@kubb/plugin-cypress",
296
+ importName: "pluginCypress",
297
+ category: "testing"
298
+ },
299
+ {
300
+ value: "plugin-mcp",
301
+ label: "MCP Server (AI / Model Context Protocol)",
302
+ packageName: "@kubb/plugin-mcp",
303
+ importName: "pluginMcp",
304
+ category: "ai"
305
+ },
306
+ {
307
+ value: "plugin-redoc",
308
+ label: "ReDoc Documentation",
309
+ packageName: "@kubb/plugin-redoc",
310
+ importName: "pluginRedoc",
311
+ category: "documentation"
312
+ }
313
+ ];
314
+ //#endregion
315
+ //#region ../../internals/shared/src/init.ts
316
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
317
+ return `import { defineConfig } from 'kubb'
318
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
319
+
320
+ export default defineConfig({
321
+ root: '.',
322
+ input: {
323
+ path: '${inputPath}',
324
+ },
325
+ output: {
326
+ path: '${outputPath}',
327
+ clean: true,
328
+ },
329
+ plugins: [
330
+ ${selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join("\n")}
331
+ ],
332
+ })
333
+ `;
334
+ }
335
+ //#endregion
336
+ //#region ../../internals/shared/src/loader.ts
337
+ /**
338
+ * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
339
+ * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
340
+ */
341
+ const JITI_OPTIONS = {
342
+ jsx: {
343
+ runtime: "automatic",
344
+ importSource: "@kubb/renderer-jsx"
345
+ },
346
+ moduleCache: false
347
+ };
348
+ /**
349
+ * Creates a jiti-based loader for Kubb's TypeScript and JavaScript config modules.
350
+ *
351
+ * jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly.
352
+ *
353
+ * @example
354
+ * ```ts
355
+ * const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
356
+ * ```
357
+ */
358
+ function createModuleLoader() {
359
+ const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, JITI_OPTIONS);
360
+ return { load(filePath, options) {
361
+ return options?.default ? jiti$1.import(filePath, { default: true }) : jiti$1.import(filePath);
362
+ } };
363
+ }
223
364
  //#endregion
224
- //#region src/utils/loadUserConfig.ts
365
+ //#region src/utils.ts
366
+ /**
367
+ * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
368
+ * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
369
+ * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
370
+ * unlike the CLI renderer.
371
+ */
372
+ function formatDiagnostics(diagnostics) {
373
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
374
+ }
375
+ function formatDiagnostic(diagnostic) {
376
+ const { code, message, location, help, plugin, docsUrl } = diagnostic;
377
+ const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`];
378
+ if (location && "pointer" in location) lines.push(` at: ${location.pointer}`);
379
+ if (help) lines.push(` fix: ${help}`);
380
+ if (docsUrl) lines.push(` see: ${docsUrl}`);
381
+ return lines.join("\n");
382
+ }
383
+ const loader = createModuleLoader();
225
384
  const loadedModules = /* @__PURE__ */ new Map();
226
385
  async function loadModule(filePath) {
227
386
  const ext = node_path.default.extname(filePath);
228
387
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
229
388
  if (loadedModules.has(filePath)) return loadedModules.get(filePath);
230
- const { module } = await (0, unrun.unrun)({ path: filePath });
231
- loadedModules.set(filePath, module);
232
- return module;
389
+ const mod = await loader.load(filePath, { default: true });
390
+ loadedModules.set(filePath, mod);
391
+ return mod;
233
392
  }
393
+ /**
394
+ * Loads the user's Kubb config and returns it with the directory it was found in.
395
+ *
396
+ * When `configPath` is given it must use an allowed extension and resolve inside
397
+ * the current working directory, otherwise loading throws. When omitted, the
398
+ * known `kubb.config.*` file names are tried in the current directory. Every
399
+ * outcome is reported through `notify` before the function returns or throws.
400
+ */
234
401
  async function loadUserConfig(configPath, { notify }) {
235
- let userConfig;
236
- let cwd;
237
402
  if (configPath) {
238
403
  const ext = node_path.default.extname(configPath);
239
404
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -249,44 +414,45 @@ async function loadUserConfig(configPath, { notify }) {
249
414
  await notify(NotifyTypes.CONFIG_ERROR, msg);
250
415
  throw new Error(msg);
251
416
  }
252
- cwd = node_path.default.dirname(resolvedConfigPath);
417
+ const cwd = node_path.default.dirname(resolvedConfigPath);
253
418
  try {
254
- userConfig = await loadModule(resolvedConfigPath);
419
+ const userConfig = await loadModule(resolvedConfigPath);
255
420
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
421
+ return {
422
+ userConfig,
423
+ cwd
424
+ };
256
425
  } catch (error) {
257
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
258
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
259
- }
260
- } else {
261
- cwd = process.cwd();
262
- const configFileNames = [
263
- "kubb.config.ts",
264
- "kubb.config.mts",
265
- "kubb.config.cts",
266
- "kubb.config.js",
267
- "kubb.config.cjs"
268
- ];
269
- for (const configFileName of configFileNames) {
270
- const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
271
- if (!(0, node_fs.existsSync)(configFilePath)) continue;
272
- try {
273
- userConfig = await loadModule(configFilePath);
274
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
275
- break;
276
- } catch {}
426
+ const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
427
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
428
+ throw new Error(msg);
277
429
  }
278
- if (!userConfig) {
279
- await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
280
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
430
+ }
431
+ const cwd = process.cwd();
432
+ const configFileNames = [
433
+ "kubb.config.ts",
434
+ "kubb.config.mts",
435
+ "kubb.config.cts",
436
+ "kubb.config.js",
437
+ "kubb.config.cjs"
438
+ ];
439
+ for (const configFileName of configFileNames) {
440
+ const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
441
+ if (!(0, node_fs.existsSync)(configFilePath)) continue;
442
+ try {
443
+ const userConfig = await loadModule(configFilePath);
444
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
445
+ return {
446
+ userConfig,
447
+ cwd
448
+ };
449
+ } catch (err) {
450
+ await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
281
451
  }
282
452
  }
283
- return {
284
- userConfig,
285
- cwd
286
- };
453
+ await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
454
+ throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
287
455
  }
288
- //#endregion
289
- //#region src/utils/resolveCwd.ts
290
456
  /**
291
457
  * Determine the root directory based on userConfig.root and resolvedConfigDir
292
458
  * 1. If userConfig.root exists and is absolute, use it as-is
@@ -300,42 +466,33 @@ function resolveCwd(userConfig, cwd) {
300
466
  }
301
467
  return cwd;
302
468
  }
303
- //#endregion
304
- //#region src/utils/resolveUserConfig.ts
305
469
  /**
306
- * Resolve the config by handling function configs and returning the final configuration
470
+ * Normalizes a possible config into a single resolved `Config`.
471
+ *
472
+ * Calls the config when it is a function, awaits it when it is a promise, and
473
+ * picks the first entry when it resolves to an array.
307
474
  */
308
475
  async function resolveUserConfig(config, options) {
309
- let kubbUserConfig = Promise.resolve(config);
310
- if (typeof config === "function") {
311
- const possiblePromise = config({
312
- logLevel: options.logLevel,
313
- config: options.configPath
314
- });
315
- if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
316
- else kubbUserConfig = Promise.resolve(possiblePromise);
317
- }
318
- return await kubbUserConfig;
476
+ const result = typeof config === "function" ? config({
477
+ logLevel: options.logLevel,
478
+ config: options.configPath
479
+ }) : config;
480
+ const resolved = isPromise(result) ? await result : result;
481
+ return Array.isArray(resolved) ? resolved[0] : resolved;
319
482
  }
320
483
  //#endregion
321
484
  //#region src/tools/generate.ts
322
- /**
323
- * Build tool that generates code from OpenAPI specs using Kubb.
324
- * Sends real-time notifications of build progress and events.
325
- */
326
- async function generate(schema, handler) {
485
+ const generateTool = (0, tmcp_tool.defineTool)({
486
+ name: "generate",
487
+ description: "Generate OpenAPI spec helpers using Kubb configuration",
488
+ schema: generateSchema
489
+ }, async function generate(schema) {
327
490
  const { config: configPath, input, output, logLevel } = schema;
328
491
  try {
329
492
  const hooks = new AsyncEventEmitter();
330
493
  const messages = [];
331
494
  const notify = async (type, message, data) => {
332
- messages.push(`${type}: ${message}`);
333
- await handler.sendNotification("kubb/progress", {
334
- type,
335
- message,
336
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
337
- ...data
338
- });
495
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
339
496
  };
340
497
  hooks.on("kubb:info", async ({ message }) => {
341
498
  await notify(NotifyTypes.INFO, message);
@@ -344,11 +501,14 @@ async function generate(schema, handler) {
344
501
  await notify(NotifyTypes.SUCCESS, message);
345
502
  });
346
503
  hooks.on("kubb:error", async ({ error }) => {
347
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
504
+ await notify(NotifyTypes.ERROR, error.message);
348
505
  });
349
506
  hooks.on("kubb:warn", async ({ message }) => {
350
507
  await notify(NotifyTypes.WARN, message);
351
508
  });
509
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
510
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, _kubb_core.Diagnostics.serialize(diagnostic));
511
+ });
352
512
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
353
513
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
354
514
  });
@@ -358,8 +518,8 @@ async function generate(schema, handler) {
358
518
  hooks.on("kubb:files:processing:start", async () => {
359
519
  await notify(NotifyTypes.FILES_START, "Starting file processing");
360
520
  });
361
- hooks.on("kubb:file:processing:update", async ({ file }) => {
362
- await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
521
+ hooks.on("kubb:files:processing:update", async ({ files }) => {
522
+ await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
363
523
  });
364
524
  hooks.on("kubb:files:processing:end", async () => {
365
525
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -376,7 +536,7 @@ async function generate(schema, handler) {
376
536
  const configResult = await loadUserConfig(configPath, { notify });
377
537
  userConfig = configResult.userConfig;
378
538
  cwd = configResult.cwd;
379
- if (Array.isArray(userConfig) && userConfig.length) throw new Error("Array type in kubb.config.ts is not supported in this tool. Please provide a single configuration object.");
539
+ if (Array.isArray(userConfig)) throw new Error("Array type in kubb.config.ts is not supported in this tool. Please provide a single configuration object.");
380
540
  userConfig = await resolveUserConfig(userConfig, {
381
541
  configPath,
382
542
  logLevel
@@ -384,15 +544,9 @@ async function generate(schema, handler) {
384
544
  } catch (error) {
385
545
  const errorMessage = error instanceof Error ? error.message : String(error);
386
546
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
387
- return {
388
- content: [{
389
- type: "text",
390
- text: errorMessage
391
- }],
392
- isError: true
393
- };
547
+ return tmcp_utils.tool.error(errorMessage);
394
548
  }
395
- const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
549
+ const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
396
550
  const config = {
397
551
  ...userConfig,
398
552
  root: resolveCwd(userConfig, cwd),
@@ -405,89 +559,128 @@ async function generate(schema, handler) {
405
559
  path: output
406
560
  } : userConfig.output
407
561
  };
408
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
562
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
409
563
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
410
564
  const kubb = (0, _kubb_core.createKubb)(config, { hooks });
411
565
  await kubb.setup();
412
566
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
413
567
  await notify(NotifyTypes.BUILD_START, "Starting build");
414
- const { files, failedPlugins, error } = await kubb.safeBuild();
568
+ const { files, diagnostics } = await kubb.safeBuild();
415
569
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
416
- if (error || failedPlugins.size > 0) {
417
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
418
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
419
- errorCount: allErrors.length,
420
- errors: allErrors.map((err) => err.message)
421
- });
422
- return {
423
- content: [{
424
- type: "text",
425
- text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
426
- }],
427
- isError: true
428
- };
570
+ const problems = diagnostics.filter(_kubb_core.Diagnostics.isProblem);
571
+ const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
572
+ if (errors.length > 0) {
573
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
574
+ const serialized = problems.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic));
575
+ return tmcp_utils.tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
429
576
  }
430
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, { filesCount: files.length });
431
- return { content: [{
432
- type: "text",
433
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
434
- }] };
577
+ await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
578
+ return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
435
579
  } catch (caughtError) {
436
- const error = caughtError;
437
- await handler.sendNotification("kubb/progress", {
438
- type: NotifyTypes.FATAL_ERROR,
439
- message: error.message,
440
- stack: error.stack,
441
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
442
- });
443
- return {
444
- content: [{
445
- type: "text",
446
- text: `Build error: ${error.message}\n${error.stack || ""}`
447
- }],
448
- isError: true
449
- };
580
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(caughtError));
581
+ return tmcp_utils.tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
450
582
  }
583
+ });
584
+ //#endregion
585
+ //#region src/schemas/initSchema.ts
586
+ const initSchema = valibot.object({
587
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
588
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory (default: ./src/gen)"))),
589
+ plugins: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
590
+ });
591
+ //#endregion
592
+ //#region src/tools/init.ts
593
+ /**
594
+ * Resolves a comma-separated plugin flag into the matching known plugin options.
595
+ * Unrecognized names are dropped, and a missing flag yields an empty list.
596
+ */
597
+ function resolvePlugins(pluginsFlag) {
598
+ if (!pluginsFlag) return [];
599
+ const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
600
+ return availablePlugins.filter((p) => requested.includes(p.value));
451
601
  }
602
+ const initTool = (0, tmcp_tool.defineTool)({
603
+ name: "init",
604
+ description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
605
+ schema: initSchema
606
+ }, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
607
+ const selected = resolvePlugins(plugins);
608
+ const content = generateConfigFile({
609
+ selectedPlugins: selected,
610
+ inputPath: input,
611
+ outputPath: output
612
+ });
613
+ const dest = node_path.default.join(node_process.default.cwd(), KUBB_CONFIG_FILENAME);
614
+ if (node_fs.default.existsSync(dest)) return tmcp_utils.tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
615
+ node_fs.default.writeFileSync(dest, content, "utf-8");
616
+ const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
617
+ return tmcp_utils.tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
618
+ });
619
+ //#endregion
620
+ //#region src/schemas/validateSchema.ts
621
+ const validateSchema = valibot.object({ input: valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path or URL to the OpenAPI/Swagger specification")) });
622
+ //#endregion
623
+ //#region src/tools/validate.ts
624
+ const validateTool = (0, tmcp_tool.defineTool)({
625
+ name: "validate",
626
+ description: "Validate an OpenAPI/Swagger specification file or URL",
627
+ schema: validateSchema
628
+ }, async ({ input }) => {
629
+ let mod;
630
+ try {
631
+ mod = await import("@kubb/adapter-oas");
632
+ } catch {
633
+ return tmcp_utils.tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
634
+ }
635
+ try {
636
+ await mod.adapterOas().validate(input, { throwOnError: true });
637
+ return tmcp_utils.tool.text(`Validation successful: ${input}`);
638
+ } catch (err) {
639
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(err));
640
+ return tmcp_utils.tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
641
+ }
642
+ });
452
643
  //#endregion
453
644
  //#region src/server.ts
454
645
  /**
455
- * Kubb MCP Server
646
+ * Builds the Kubb MCP server with the generate, validate, and init tools registered.
456
647
  *
457
- * Provides tools for building OpenAPI specifications using Kubb.
648
+ * @example
649
+ * `const server = createMcpServer()`
650
+ */
651
+ function createMcpServer() {
652
+ const server = new tmcp.McpServer({
653
+ name: "Kubb",
654
+ version
655
+ }, {
656
+ adapter: new _tmcp_adapter_valibot.ValibotJsonSchemaAdapter(),
657
+ capabilities: { tools: {} }
658
+ });
659
+ server.tools([
660
+ generateTool,
661
+ validateTool,
662
+ initTool
663
+ ]);
664
+ return server;
665
+ }
666
+ /**
667
+ * Starts the Kubb MCP server over stdio, the transport every local MCP client
668
+ * (Claude, Copilot, editors) uses when it launches the server as a subprocess.
458
669
  */
459
670
  async function startServer() {
460
- try {
461
- const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
462
- const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
463
- name: "Kubb",
464
- version
465
- });
466
- server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
467
- return generate(args, { sendNotification: async (method, params) => {
468
- try {
469
- await transport.send({
470
- jsonrpc: "2.0",
471
- method,
472
- params
473
- });
474
- } catch (error) {
475
- console.error("Failed to send notification:", error);
476
- }
477
- } });
478
- });
479
- await server.connect(transport);
480
- } catch (error) {
481
- console.error("Failed to start MCP server:", error);
482
- node_process.default.exit(1);
483
- }
671
+ new _tmcp_transport_stdio.StdioTransport(createMcpServer()).listen();
484
672
  }
485
673
  //#endregion
486
674
  //#region src/index.ts
675
+ /**
676
+ * Entry point that starts the MCP server over stdio. The argument is accepted
677
+ * for CLI parity but ignored.
678
+ */
487
679
  async function run(_argv) {
488
680
  await startServer();
489
681
  }
490
682
  //#endregion
683
+ exports.createMcpServer = createMcpServer;
491
684
  exports.run = run;
492
685
 
493
686
  //# sourceMappingURL=index.cjs.map