@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.js CHANGED
@@ -1,30 +1,18 @@
1
- import "./chunk--u3MIqq1.js";
2
- import process$1 from "node:process";
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { z } from "zod";
1
+ import "./rolldown-runtime-C0LytTxp.js";
2
+ import { ValibotJsonSchemaAdapter } from "@tmcp/adapter-valibot";
3
+ import { StdioTransport } from "@tmcp/transport-stdio";
4
+ import { McpServer } from "tmcp";
6
5
  import { EventEmitter } from "node:events";
7
- import { existsSync } from "node:fs";
6
+ import { Diagnostics, createKubb } from "@kubb/core";
7
+ import { defineTool } from "tmcp/tool";
8
+ import { tool } from "tmcp/utils";
9
+ import * as v from "valibot";
10
+ import fs, { existsSync } from "node:fs";
8
11
  import path from "node:path";
9
- import { createKubb } from "@kubb/core";
10
- import { unrun } from "unrun";
12
+ import { createJiti } from "jiti";
13
+ import process$1 from "node:process";
11
14
  //#region package.json
12
- var version = "5.0.0-beta.75";
13
- //#endregion
14
- //#region src/schemas/generateSchema.ts
15
- const generateSchema = z.object({
16
- config: 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"),
17
- input: z.string().optional().describe("Path to OpenAPI/Swagger spec file (overrides config)"),
18
- output: z.string().optional().describe("Output directory path (overrides config)"),
19
- logLevel: z.enum([
20
- "silent",
21
- "error",
22
- "warn",
23
- "info",
24
- "verbose",
25
- "debug"
26
- ]).optional().default("info").describe("Log level for build output")
27
- });
15
+ var version = "5.0.0-beta.76";
28
16
  //#endregion
29
17
  //#region ../../internals/utils/src/errors.ts
30
18
  /**
@@ -72,9 +60,12 @@ var AsyncEventEmitter = class {
72
60
  * await emitter.emit('build', 'petstore')
73
61
  * ```
74
62
  */
75
- async emit(eventName, ...eventArgs) {
63
+ emit(eventName, ...eventArgs) {
76
64
  const listeners = this.#emitter.listeners(eventName);
77
65
  if (listeners.length === 0) return;
66
+ return this.#emitAll(eventName, listeners, eventArgs);
67
+ }
68
+ async #emitAll(eventName, listeners, eventArgs) {
78
69
  for (const listener of listeners) try {
79
70
  await listener(...eventArgs);
80
71
  } catch (err) {
@@ -99,21 +90,6 @@ var AsyncEventEmitter = class {
99
90
  this.#emitter.on(eventName, handler);
100
91
  }
101
92
  /**
102
- * Registers a one-shot listener that removes itself after the first invocation.
103
- *
104
- * @example
105
- * ```ts
106
- * emitter.onOnce('build', async (name) => { console.log(name) })
107
- * ```
108
- */
109
- onOnce(eventName, handler) {
110
- const wrapper = (...args) => {
111
- this.off(eventName, wrapper);
112
- return handler(...args);
113
- };
114
- this.on(eventName, wrapper);
115
- }
116
- /**
117
93
  * Removes a previously registered listener.
118
94
  *
119
95
  * @example
@@ -137,6 +113,18 @@ var AsyncEventEmitter = class {
137
113
  return this.#emitter.listenerCount(eventName);
138
114
  }
139
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
+ /**
140
128
  * Removes all listeners from every event channel.
141
129
  *
142
130
  * @example
@@ -162,16 +150,33 @@ function isPromise(result) {
162
150
  return result !== null && result !== void 0 && typeof result["then"] === "function";
163
151
  }
164
152
  //#endregion
165
- //#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 = 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
+ */
166
170
  const NotifyTypes = {
167
171
  INFO: "INFO",
168
172
  SUCCESS: "SUCCESS",
169
173
  ERROR: "ERROR",
170
174
  WARN: "WARN",
175
+ DIAGNOSTIC: "DIAGNOSTIC",
171
176
  PLUGIN_START: "PLUGIN_START",
172
177
  PLUGIN_END: "PLUGIN_END",
173
178
  FILES_START: "FILES_START",
174
- FILE_UPDATE: "FILE_UPDATE",
179
+ FILES_UPDATE: "FILES_UPDATE",
175
180
  FILES_END: "FILES_END",
176
181
  GENERATION_START: "GENERATION_START",
177
182
  GENERATION_END: "GENERATION_END",
@@ -183,33 +188,191 @@ const NotifyTypes = {
183
188
  BUILD_START: "BUILD_START",
184
189
  BUILD_END: "BUILD_END",
185
190
  BUILD_FAILED: "BUILD_FAILED",
186
- BUILD_SUCCESS: "BUILD_SUCCESS",
187
- FATAL_ERROR: "FATAL_ERROR"
191
+ BUILD_SUCCESS: "BUILD_SUCCESS"
188
192
  };
189
193
  //#endregion
190
- //#region src/constants.ts
191
- const ALLOWED_CONFIG_EXTENSIONS = new Set([
192
- ".ts",
193
- ".mts",
194
- ".cts",
195
- ".js",
196
- ".mjs",
197
- ".cjs"
198
- ]);
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
+ ];
199
288
  //#endregion
200
- //#region src/utils/loadUserConfig.ts
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 = {
316
+ jsx: {
317
+ runtime: "automatic",
318
+ importSource: "@kubb/renderer-jsx"
319
+ },
320
+ moduleCache: false
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();
201
358
  const loadedModules = /* @__PURE__ */ new Map();
202
359
  async function loadModule(filePath) {
203
360
  const ext = path.extname(filePath);
204
361
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
205
362
  if (loadedModules.has(filePath)) return loadedModules.get(filePath);
206
- const { module } = await unrun({ path: filePath });
207
- loadedModules.set(filePath, module);
208
- return module;
363
+ const mod = await loader.load(filePath, { default: true });
364
+ loadedModules.set(filePath, mod);
365
+ return mod;
209
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
+ */
210
375
  async function loadUserConfig(configPath, { notify }) {
211
- let userConfig;
212
- let cwd;
213
376
  if (configPath) {
214
377
  const ext = path.extname(configPath);
215
378
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -225,44 +388,45 @@ async function loadUserConfig(configPath, { notify }) {
225
388
  await notify(NotifyTypes.CONFIG_ERROR, msg);
226
389
  throw new Error(msg);
227
390
  }
228
- cwd = path.dirname(resolvedConfigPath);
391
+ const cwd = path.dirname(resolvedConfigPath);
229
392
  try {
230
- userConfig = await loadModule(resolvedConfigPath);
393
+ const userConfig = await loadModule(resolvedConfigPath);
231
394
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
395
+ return {
396
+ userConfig,
397
+ cwd
398
+ };
232
399
  } catch (error) {
233
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
234
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
235
- }
236
- } else {
237
- cwd = process.cwd();
238
- const configFileNames = [
239
- "kubb.config.ts",
240
- "kubb.config.mts",
241
- "kubb.config.cts",
242
- "kubb.config.js",
243
- "kubb.config.cjs"
244
- ];
245
- for (const configFileName of configFileNames) {
246
- const configFilePath = path.resolve(process.cwd(), configFileName);
247
- if (!existsSync(configFilePath)) continue;
248
- try {
249
- userConfig = await loadModule(configFilePath);
250
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
251
- break;
252
- } catch {}
400
+ const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
401
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
402
+ throw new Error(msg);
253
403
  }
254
- if (!userConfig) {
255
- await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
256
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
404
+ }
405
+ const cwd = process.cwd();
406
+ const configFileNames = [
407
+ "kubb.config.ts",
408
+ "kubb.config.mts",
409
+ "kubb.config.cts",
410
+ "kubb.config.js",
411
+ "kubb.config.cjs"
412
+ ];
413
+ for (const configFileName of configFileNames) {
414
+ const configFilePath = path.resolve(process.cwd(), configFileName);
415
+ if (!existsSync(configFilePath)) continue;
416
+ try {
417
+ const userConfig = await loadModule(configFilePath);
418
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
419
+ return {
420
+ userConfig,
421
+ cwd
422
+ };
423
+ } catch (err) {
424
+ await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
257
425
  }
258
426
  }
259
- return {
260
- userConfig,
261
- cwd
262
- };
427
+ await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
428
+ throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
263
429
  }
264
- //#endregion
265
- //#region src/utils/resolveCwd.ts
266
430
  /**
267
431
  * Determine the root directory based on userConfig.root and resolvedConfigDir
268
432
  * 1. If userConfig.root exists and is absolute, use it as-is
@@ -276,42 +440,33 @@ function resolveCwd(userConfig, cwd) {
276
440
  }
277
441
  return cwd;
278
442
  }
279
- //#endregion
280
- //#region src/utils/resolveUserConfig.ts
281
443
  /**
282
- * Resolve the config by handling function configs and returning the final configuration
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.
283
448
  */
284
449
  async function resolveUserConfig(config, options) {
285
- let kubbUserConfig = Promise.resolve(config);
286
- if (typeof config === "function") {
287
- const possiblePromise = config({
288
- logLevel: options.logLevel,
289
- config: options.configPath
290
- });
291
- if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
292
- else kubbUserConfig = Promise.resolve(possiblePromise);
293
- }
294
- return await kubbUserConfig;
450
+ const result = typeof config === "function" ? config({
451
+ logLevel: options.logLevel,
452
+ config: options.configPath
453
+ }) : config;
454
+ const resolved = isPromise(result) ? await result : result;
455
+ return Array.isArray(resolved) ? resolved[0] : resolved;
295
456
  }
296
457
  //#endregion
297
458
  //#region src/tools/generate.ts
298
- /**
299
- * Build tool that generates code from OpenAPI specs using Kubb.
300
- * Sends real-time notifications of build progress and events.
301
- */
302
- async function generate(schema, handler) {
459
+ const generateTool = defineTool({
460
+ name: "generate",
461
+ description: "Generate OpenAPI spec helpers using Kubb configuration",
462
+ schema: generateSchema
463
+ }, async function generate(schema) {
303
464
  const { config: configPath, input, output, logLevel } = schema;
304
465
  try {
305
466
  const hooks = new AsyncEventEmitter();
306
467
  const messages = [];
307
468
  const notify = async (type, message, data) => {
308
- messages.push(`${type}: ${message}`);
309
- await handler.sendNotification("kubb/progress", {
310
- type,
311
- message,
312
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
313
- ...data
314
- });
469
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
315
470
  };
316
471
  hooks.on("kubb:info", async ({ message }) => {
317
472
  await notify(NotifyTypes.INFO, message);
@@ -320,11 +475,14 @@ async function generate(schema, handler) {
320
475
  await notify(NotifyTypes.SUCCESS, message);
321
476
  });
322
477
  hooks.on("kubb:error", async ({ error }) => {
323
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
478
+ await notify(NotifyTypes.ERROR, error.message);
324
479
  });
325
480
  hooks.on("kubb:warn", async ({ message }) => {
326
481
  await notify(NotifyTypes.WARN, message);
327
482
  });
483
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
484
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
485
+ });
328
486
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
329
487
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
330
488
  });
@@ -334,8 +492,8 @@ async function generate(schema, handler) {
334
492
  hooks.on("kubb:files:processing:start", async () => {
335
493
  await notify(NotifyTypes.FILES_START, "Starting file processing");
336
494
  });
337
- hooks.on("kubb:file:processing:update", async ({ file }) => {
338
- 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`);
339
497
  });
340
498
  hooks.on("kubb:files:processing:end", async () => {
341
499
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -352,7 +510,7 @@ async function generate(schema, handler) {
352
510
  const configResult = await loadUserConfig(configPath, { notify });
353
511
  userConfig = configResult.userConfig;
354
512
  cwd = configResult.cwd;
355
- 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.");
513
+ 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.");
356
514
  userConfig = await resolveUserConfig(userConfig, {
357
515
  configPath,
358
516
  logLevel
@@ -360,15 +518,9 @@ async function generate(schema, handler) {
360
518
  } catch (error) {
361
519
  const errorMessage = error instanceof Error ? error.message : String(error);
362
520
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
363
- return {
364
- content: [{
365
- type: "text",
366
- text: errorMessage
367
- }],
368
- isError: true
369
- };
521
+ return tool.error(errorMessage);
370
522
  }
371
- const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
523
+ const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
372
524
  const config = {
373
525
  ...userConfig,
374
526
  root: resolveCwd(userConfig, cwd),
@@ -381,89 +533,124 @@ async function generate(schema, handler) {
381
533
  path: output
382
534
  } : userConfig.output
383
535
  };
384
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
536
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
385
537
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
386
538
  const kubb = createKubb(config, { hooks });
387
539
  await kubb.setup();
388
540
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
389
541
  await notify(NotifyTypes.BUILD_START, "Starting build");
390
- const { files, failedPlugins, error } = await kubb.safeBuild();
542
+ const { files, diagnostics } = await kubb.safeBuild();
391
543
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
392
- if (error || failedPlugins.size > 0) {
393
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
394
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
395
- errorCount: allErrors.length,
396
- errors: allErrors.map((err) => err.message)
397
- });
398
- return {
399
- content: [{
400
- type: "text",
401
- text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
402
- }],
403
- isError: true
404
- };
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\`\`\``);
405
550
  }
406
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, { filesCount: files.length });
407
- return { content: [{
408
- type: "text",
409
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
410
- }] };
551
+ await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
552
+ return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
411
553
  } catch (caughtError) {
412
- const error = caughtError;
413
- await handler.sendNotification("kubb/progress", {
414
- type: NotifyTypes.FATAL_ERROR,
415
- message: error.message,
416
- stack: error.stack,
417
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
418
- });
419
- return {
420
- content: [{
421
- type: "text",
422
- text: `Build error: ${error.message}\n${error.stack || ""}`
423
- }],
424
- isError: true
425
- };
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\`\`\``);
426
556
  }
557
+ });
558
+ //#endregion
559
+ //#region src/schemas/initSchema.ts
560
+ const initSchema = v.object({
561
+ input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
562
+ output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory (default: ./src/gen)"))),
563
+ plugins: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
564
+ });
565
+ //#endregion
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
+ */
571
+ function resolvePlugins(pluginsFlag) {
572
+ if (!pluginsFlag) return [];
573
+ const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
574
+ return availablePlugins.filter((p) => requested.includes(p.value));
427
575
  }
576
+ const initTool = defineTool({
577
+ name: "init",
578
+ description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
579
+ schema: initSchema
580
+ }, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
581
+ const selected = resolvePlugins(plugins);
582
+ const content = generateConfigFile({
583
+ selectedPlugins: selected,
584
+ inputPath: input,
585
+ outputPath: output
586
+ });
587
+ const dest = path.join(process$1.cwd(), KUBB_CONFIG_FILENAME);
588
+ if (fs.existsSync(dest)) return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
589
+ fs.writeFileSync(dest, content, "utf-8");
590
+ const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
591
+ return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
592
+ });
593
+ //#endregion
594
+ //#region src/tools/validate.ts
595
+ const validateTool = defineTool({
596
+ name: "validate",
597
+ description: "Validate an OpenAPI/Swagger specification file or URL",
598
+ schema: v.object({ input: v.pipe(v.string(), v.minLength(1), v.description("Path or URL to the OpenAPI/Swagger specification")) })
599
+ }, async ({ input }) => {
600
+ let mod;
601
+ try {
602
+ mod = await import("@kubb/adapter-oas");
603
+ } catch {
604
+ return tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
605
+ }
606
+ try {
607
+ await mod.adapterOas().validate(input, { throwOnError: true });
608
+ return tool.text(`Validation successful: ${input}`);
609
+ } catch (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\`\`\``);
612
+ }
613
+ });
428
614
  //#endregion
429
615
  //#region src/server.ts
430
616
  /**
431
- * Kubb MCP Server
617
+ * Builds the Kubb MCP server with the generate, validate, and init tools registered.
432
618
  *
433
- * Provides tools for building OpenAPI specifications using Kubb.
619
+ * @example
620
+ * `const server = createMcpServer()`
621
+ */
622
+ function createMcpServer() {
623
+ const server = new McpServer({
624
+ name: "Kubb",
625
+ version
626
+ }, {
627
+ adapter: new ValibotJsonSchemaAdapter(),
628
+ capabilities: { tools: {} }
629
+ });
630
+ server.tools([
631
+ generateTool,
632
+ validateTool,
633
+ initTool
634
+ ]);
635
+ return server;
636
+ }
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.
434
640
  */
435
641
  async function startServer() {
436
- try {
437
- const transport = new StdioServerTransport();
438
- const server = new McpServer({
439
- name: "Kubb",
440
- version
441
- });
442
- server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
443
- return generate(args, { sendNotification: async (method, params) => {
444
- try {
445
- await transport.send({
446
- jsonrpc: "2.0",
447
- method,
448
- params
449
- });
450
- } catch (error) {
451
- console.error("Failed to send notification:", error);
452
- }
453
- } });
454
- });
455
- await server.connect(transport);
456
- } catch (error) {
457
- console.error("Failed to start MCP server:", error);
458
- process$1.exit(1);
459
- }
642
+ new StdioTransport(createMcpServer()).listen();
460
643
  }
461
644
  //#endregion
462
645
  //#region src/index.ts
646
+ /**
647
+ * Entry point that starts the MCP server over stdio. The argument is accepted
648
+ * for CLI parity but ignored.
649
+ */
463
650
  async function run(_argv) {
464
651
  await startServer();
465
652
  }
466
653
  //#endregion
467
- export { run };
654
+ export { createMcpServer, run };
468
655
 
469
656
  //# sourceMappingURL=index.js.map