@kubb/mcp 5.0.0-beta.4 → 5.0.0-beta.40

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,28 @@ 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 node_http = require("node:http");
25
+ node_http = __toESM(node_http, 1);
26
+ let _remix_run_node_fetch_server = require("@remix-run/node-fetch-server");
27
+ let _tmcp_adapter_valibot = require("@tmcp/adapter-valibot");
28
+ let _tmcp_transport_http = require("@tmcp/transport-http");
29
+ let _tmcp_transport_stdio = require("@tmcp/transport-stdio");
30
+ let tmcp = require("tmcp");
29
31
  let node_events = require("node:events");
30
32
  let node_fs = require("node:fs");
33
+ node_fs = __toESM(node_fs, 1);
31
34
  let node_path = require("node:path");
32
35
  node_path = __toESM(node_path, 1);
33
36
  let _kubb_core = require("@kubb/core");
34
- let unrun = require("unrun");
37
+ let tmcp_tool = require("tmcp/tool");
38
+ let tmcp_utils = require("tmcp/utils");
39
+ let valibot = require("valibot");
40
+ valibot = __toESM(valibot, 1);
41
+ let jiti = require("jiti");
42
+ let node_process = require("node:process");
43
+ node_process = __toESM(node_process, 1);
35
44
  //#region package.json
36
- var version = "5.0.0-beta.4";
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
- });
45
+ var version = "5.0.0-beta.40";
52
46
  //#endregion
53
47
  //#region ../../internals/utils/src/errors.ts
54
48
  /**
@@ -96,9 +90,12 @@ var AsyncEventEmitter = class {
96
90
  * await emitter.emit('build', 'petstore')
97
91
  * ```
98
92
  */
99
- async emit(eventName, ...eventArgs) {
93
+ emit(eventName, ...eventArgs) {
100
94
  const listeners = this.#emitter.listeners(eventName);
101
95
  if (listeners.length === 0) return;
96
+ return this.#emitAll(eventName, listeners, eventArgs);
97
+ }
98
+ async #emitAll(eventName, listeners, eventArgs) {
102
99
  for (const listener of listeners) try {
103
100
  await listener(...eventArgs);
104
101
  } catch (err) {
@@ -161,6 +158,24 @@ var AsyncEventEmitter = class {
161
158
  return this.#emitter.listenerCount(eventName);
162
159
  }
163
160
  /**
161
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
162
+ * Set this above the expected listener count when many listeners attach by design.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * emitter.setMaxListeners(40)
167
+ * ```
168
+ */
169
+ setMaxListeners(max) {
170
+ this.#emitter.setMaxListeners(max);
171
+ }
172
+ /**
173
+ * Returns the current per-event listener ceiling.
174
+ */
175
+ getMaxListeners() {
176
+ return this.#emitter.getMaxListeners();
177
+ }
178
+ /**
164
179
  * Removes all listeners from every event channel.
165
180
  *
166
181
  * @example
@@ -186,16 +201,48 @@ function isPromise(result) {
186
201
  return result !== null && result !== void 0 && typeof result["then"] === "function";
187
202
  }
188
203
  //#endregion
204
+ //#region src/schemas/generateSchema.ts
205
+ const generateSchema = valibot.object({
206
+ 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"))),
207
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
208
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
209
+ logLevel: valibot.optional(valibot.pipe(valibot.picklist([
210
+ "silent",
211
+ "info",
212
+ "verbose"
213
+ ]), valibot.description("Log level for build output")), "info")
214
+ });
215
+ //#endregion
216
+ //#region src/utils/formatDiagnostics.ts
217
+ /**
218
+ * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
219
+ * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
220
+ * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
221
+ * unlike the CLI renderer.
222
+ */
223
+ function formatDiagnostics(diagnostics) {
224
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
225
+ }
226
+ function formatDiagnostic(diagnostic) {
227
+ const { code, severity, message, location, help, plugin, docsUrl } = diagnostic;
228
+ const lines = [`${severity} ${plugin ? `${plugin}(${code})` : code}: ${message}`];
229
+ if (location && "pointer" in location) lines.push(` at ${location.pointer}`);
230
+ if (help) lines.push(` help: ${help}`);
231
+ if (docsUrl) lines.push(` docs: ${docsUrl}`);
232
+ return lines.join("\n");
233
+ }
234
+ //#endregion
189
235
  //#region src/types.ts
190
236
  const NotifyTypes = {
191
237
  INFO: "INFO",
192
238
  SUCCESS: "SUCCESS",
193
239
  ERROR: "ERROR",
194
240
  WARN: "WARN",
241
+ DIAGNOSTIC: "DIAGNOSTIC",
195
242
  PLUGIN_START: "PLUGIN_START",
196
243
  PLUGIN_END: "PLUGIN_END",
197
244
  FILES_START: "FILES_START",
198
- FILE_UPDATE: "FILE_UPDATE",
245
+ FILES_UPDATE: "FILES_UPDATE",
199
246
  FILES_END: "FILES_END",
200
247
  GENERATION_START: "GENERATION_START",
201
248
  GENERATION_END: "GENERATION_END",
@@ -222,18 +269,23 @@ const ALLOWED_CONFIG_EXTENSIONS = new Set([
222
269
  ]);
223
270
  //#endregion
224
271
  //#region src/utils/loadUserConfig.ts
272
+ const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, {
273
+ jsx: {
274
+ runtime: "automatic",
275
+ importSource: "@kubb/renderer-jsx"
276
+ },
277
+ moduleCache: false
278
+ });
225
279
  const loadedModules = /* @__PURE__ */ new Map();
226
280
  async function loadModule(filePath) {
227
281
  const ext = node_path.default.extname(filePath);
228
282
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
229
283
  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;
284
+ const mod = await jiti$1.import(filePath, { default: true });
285
+ loadedModules.set(filePath, mod);
286
+ return mod;
233
287
  }
234
288
  async function loadUserConfig(configPath, { notify }) {
235
- let userConfig;
236
- let cwd;
237
289
  if (configPath) {
238
290
  const ext = node_path.default.extname(configPath);
239
291
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -249,41 +301,44 @@ async function loadUserConfig(configPath, { notify }) {
249
301
  await notify(NotifyTypes.CONFIG_ERROR, msg);
250
302
  throw new Error(msg);
251
303
  }
252
- cwd = node_path.default.dirname(resolvedConfigPath);
304
+ const cwd = node_path.default.dirname(resolvedConfigPath);
253
305
  try {
254
- userConfig = await loadModule(resolvedConfigPath);
306
+ const userConfig = await loadModule(resolvedConfigPath);
255
307
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
308
+ return {
309
+ userConfig,
310
+ cwd
311
+ };
256
312
  } 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 {}
313
+ const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
314
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
315
+ throw new Error(msg);
277
316
  }
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(", ")}`);
317
+ }
318
+ const cwd = process.cwd();
319
+ const configFileNames = [
320
+ "kubb.config.ts",
321
+ "kubb.config.mts",
322
+ "kubb.config.cts",
323
+ "kubb.config.js",
324
+ "kubb.config.cjs"
325
+ ];
326
+ for (const configFileName of configFileNames) {
327
+ const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
328
+ if (!(0, node_fs.existsSync)(configFilePath)) continue;
329
+ try {
330
+ const userConfig = await loadModule(configFilePath);
331
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
332
+ return {
333
+ userConfig,
334
+ cwd
335
+ };
336
+ } catch (err) {
337
+ await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
281
338
  }
282
339
  }
283
- return {
284
- userConfig,
285
- cwd
286
- };
340
+ await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
341
+ throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
287
342
  }
288
343
  //#endregion
289
344
  //#region src/utils/resolveCwd.ts
@@ -302,40 +357,27 @@ function resolveCwd(userConfig, cwd) {
302
357
  }
303
358
  //#endregion
304
359
  //#region src/utils/resolveUserConfig.ts
305
- /**
306
- * Resolve the config by handling function configs and returning the final configuration
307
- */
308
360
  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;
361
+ const result = typeof config === "function" ? config({
362
+ logLevel: options.logLevel,
363
+ config: options.configPath
364
+ }) : config;
365
+ const resolved = isPromise(result) ? await result : result;
366
+ return Array.isArray(resolved) ? resolved[0] : resolved;
319
367
  }
320
368
  //#endregion
321
369
  //#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) {
370
+ const generateTool = (0, tmcp_tool.defineTool)({
371
+ name: "generate",
372
+ description: "Generate OpenAPI spec helpers using Kubb configuration",
373
+ schema: generateSchema
374
+ }, async function generate(schema) {
327
375
  const { config: configPath, input, output, logLevel } = schema;
328
376
  try {
329
377
  const hooks = new AsyncEventEmitter();
330
378
  const messages = [];
331
379
  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
- });
380
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
339
381
  };
340
382
  hooks.on("kubb:info", async ({ message }) => {
341
383
  await notify(NotifyTypes.INFO, message);
@@ -344,11 +386,14 @@ async function generate(schema, handler) {
344
386
  await notify(NotifyTypes.SUCCESS, message);
345
387
  });
346
388
  hooks.on("kubb:error", async ({ error }) => {
347
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
389
+ await notify(NotifyTypes.ERROR, error.message);
348
390
  });
349
391
  hooks.on("kubb:warn", async ({ message }) => {
350
392
  await notify(NotifyTypes.WARN, message);
351
393
  });
394
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
395
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, _kubb_core.Diagnostics.serialize(diagnostic));
396
+ });
352
397
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
353
398
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
354
399
  });
@@ -358,8 +403,8 @@ async function generate(schema, handler) {
358
403
  hooks.on("kubb:files:processing:start", async () => {
359
404
  await notify(NotifyTypes.FILES_START, "Starting file processing");
360
405
  });
361
- hooks.on("kubb:file:processing:update", async ({ file }) => {
362
- await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
406
+ hooks.on("kubb:files:processing:update", async ({ files }) => {
407
+ await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
363
408
  });
364
409
  hooks.on("kubb:files:processing:end", async () => {
365
410
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -376,7 +421,7 @@ async function generate(schema, handler) {
376
421
  const configResult = await loadUserConfig(configPath, { notify });
377
422
  userConfig = configResult.userConfig;
378
423
  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.");
424
+ 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
425
  userConfig = await resolveUserConfig(userConfig, {
381
426
  configPath,
382
427
  logLevel
@@ -384,13 +429,7 @@ async function generate(schema, handler) {
384
429
  } catch (error) {
385
430
  const errorMessage = error instanceof Error ? error.message : String(error);
386
431
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
387
- return {
388
- content: [{
389
- type: "text",
390
- text: errorMessage
391
- }],
392
- isError: true
393
- };
432
+ return tmcp_utils.tool.error(errorMessage);
394
433
  }
395
434
  const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
396
435
  const config = {
@@ -405,89 +444,259 @@ async function generate(schema, handler) {
405
444
  path: output
406
445
  } : userConfig.output
407
446
  };
408
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
447
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
409
448
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
410
449
  const kubb = (0, _kubb_core.createKubb)(config, { hooks });
411
450
  await kubb.setup();
412
451
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
413
452
  await notify(NotifyTypes.BUILD_START, "Starting build");
414
- const { files, failedPlugins, error } = await kubb.safeBuild();
453
+ const { files, diagnostics } = await kubb.safeBuild();
415
454
  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
- };
455
+ const problems = diagnostics.filter(_kubb_core.Diagnostics.isProblem);
456
+ const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
457
+ if (errors.length > 0) {
458
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
459
+ const serialized = problems.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic));
460
+ return tmcp_utils.tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
429
461
  }
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
- }] };
462
+ await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
463
+ return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
435
464
  } 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
- };
465
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(caughtError));
466
+ return tmcp_utils.tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
450
467
  }
468
+ });
469
+ //#endregion
470
+ //#region ../../internals/shared/src/constants.ts
471
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
472
+ const availablePlugins = [
473
+ {
474
+ value: "plugin-ts",
475
+ label: "TypeScript",
476
+ hint: "Recommended",
477
+ packageName: "@kubb/plugin-ts",
478
+ importName: "pluginTs",
479
+ category: "types"
480
+ },
481
+ {
482
+ value: "plugin-client",
483
+ label: "Client (Fetch/Axios)",
484
+ packageName: "@kubb/plugin-client",
485
+ importName: "pluginClient",
486
+ category: "client"
487
+ },
488
+ {
489
+ value: "plugin-react-query",
490
+ label: "React Query / TanStack Query",
491
+ packageName: "@kubb/plugin-react-query",
492
+ importName: "pluginReactQuery",
493
+ category: "framework"
494
+ },
495
+ {
496
+ value: "plugin-vue-query",
497
+ label: "Vue Query",
498
+ packageName: "@kubb/plugin-vue-query",
499
+ importName: "pluginVueQuery",
500
+ category: "framework"
501
+ },
502
+ {
503
+ value: "plugin-zod",
504
+ label: "Zod Schemas",
505
+ packageName: "@kubb/plugin-zod",
506
+ importName: "pluginZod",
507
+ category: "validation"
508
+ },
509
+ {
510
+ value: "plugin-faker",
511
+ label: "Faker.js Mocks",
512
+ packageName: "@kubb/plugin-faker",
513
+ importName: "pluginFaker",
514
+ category: "mocks"
515
+ },
516
+ {
517
+ value: "plugin-msw",
518
+ label: "MSW Handlers",
519
+ packageName: "@kubb/plugin-msw",
520
+ importName: "pluginMsw",
521
+ category: "mocks"
522
+ },
523
+ {
524
+ value: "plugin-cypress",
525
+ label: "Cypress Tests",
526
+ packageName: "@kubb/plugin-cypress",
527
+ importName: "pluginCypress",
528
+ category: "testing"
529
+ },
530
+ {
531
+ value: "plugin-mcp",
532
+ label: "MCP Server (AI / Model Context Protocol)",
533
+ packageName: "@kubb/plugin-mcp",
534
+ importName: "pluginMcp",
535
+ category: "ai"
536
+ },
537
+ {
538
+ value: "plugin-redoc",
539
+ label: "ReDoc Documentation",
540
+ packageName: "@kubb/plugin-redoc",
541
+ importName: "pluginRedoc",
542
+ category: "documentation"
543
+ }
544
+ ];
545
+ const pluginDefaultConfigs = {
546
+ "plugin-ts": `pluginTs({
547
+ output: { path: 'models' },
548
+ })`,
549
+ "plugin-client": `pluginClient({
550
+ output: { path: 'clients' },
551
+ })`,
552
+ "plugin-react-query": `pluginReactQuery({
553
+ output: { path: 'hooks' },
554
+ })`,
555
+ "plugin-vue-query": `pluginVueQuery({
556
+ output: { path: 'hooks' },
557
+ })`,
558
+ "plugin-zod": `pluginZod({
559
+ output: { path: 'zod' },
560
+ })`,
561
+ "plugin-faker": `pluginFaker({
562
+ output: { path: 'mocks' },
563
+ })`,
564
+ "plugin-msw": `pluginMsw({
565
+ output: { path: 'msw' },
566
+ })`,
567
+ "plugin-cypress": `pluginCypress({
568
+ output: { path: 'cypress' },
569
+ })`,
570
+ "plugin-mcp": `pluginMcp({
571
+ output: { path: 'mcp' },
572
+ })`,
573
+ "plugin-redoc": `pluginRedoc({
574
+ output: { path: 'redoc' },
575
+ })`
576
+ };
577
+ //#endregion
578
+ //#region ../../internals/shared/src/init.ts
579
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
580
+ return `import { defineConfig } from 'kubb'
581
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
582
+
583
+ export default defineConfig({
584
+ root: '.',
585
+ input: {
586
+ path: '${inputPath}',
587
+ },
588
+ output: {
589
+ path: '${outputPath}',
590
+ clean: true,
591
+ },
592
+ plugins: [
593
+ ${selectedPlugins.map((plugin) => {
594
+ return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
595
+ }).join("\n")}
596
+ ],
597
+ })
598
+ `;
451
599
  }
452
600
  //#endregion
453
- //#region src/server.ts
454
- /**
455
- * Kubb MCP Server
456
- *
457
- * Provides tools for building OpenAPI specifications using Kubb.
458
- */
459
- async function startServer() {
601
+ //#region src/schemas/initSchema.ts
602
+ const initSchema = valibot.object({
603
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
604
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory (default: ./src/gen)"))),
605
+ plugins: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
606
+ });
607
+ //#endregion
608
+ //#region src/tools/init.ts
609
+ function resolvePlugins(pluginsFlag) {
610
+ if (!pluginsFlag) return [];
611
+ const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
612
+ return availablePlugins.filter((p) => requested.includes(p.value));
613
+ }
614
+ const initTool = (0, tmcp_tool.defineTool)({
615
+ name: "init",
616
+ description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
617
+ schema: initSchema
618
+ }, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
619
+ const selected = resolvePlugins(plugins);
620
+ const content = generateConfigFile({
621
+ selectedPlugins: selected,
622
+ inputPath: input,
623
+ outputPath: output
624
+ });
625
+ const dest = node_path.default.join(node_process.default.cwd(), KUBB_CONFIG_FILENAME);
626
+ 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.`);
627
+ node_fs.default.writeFileSync(dest, content, "utf-8");
628
+ const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
629
+ return tmcp_utils.tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
630
+ });
631
+ //#endregion
632
+ //#region src/schemas/validateSchema.ts
633
+ const validateSchema = valibot.object({ input: valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path or URL to the OpenAPI/Swagger specification")) });
634
+ //#endregion
635
+ //#region src/tools/validate.ts
636
+ const validateTool = (0, tmcp_tool.defineTool)({
637
+ name: "validate",
638
+ description: "Validate an OpenAPI/Swagger specification file or URL",
639
+ schema: validateSchema
640
+ }, async ({ input }) => {
641
+ let mod;
460
642
  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);
643
+ mod = await import("@kubb/adapter-oas");
644
+ } catch {
645
+ return tmcp_utils.tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
646
+ }
647
+ try {
648
+ await mod.adapterOas().validate(input, { throwOnError: true });
649
+ return tmcp_utils.tool.text(`Validation successful: ${input}`);
650
+ } catch (err) {
651
+ const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(err));
652
+ return tmcp_utils.tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
653
+ }
654
+ });
655
+ //#endregion
656
+ //#region src/server.ts
657
+ function createMcpServer() {
658
+ const server = new tmcp.McpServer({
659
+ name: "Kubb",
660
+ version
661
+ }, {
662
+ adapter: new _tmcp_adapter_valibot.ValibotJsonSchemaAdapter(),
663
+ capabilities: { tools: {} }
664
+ });
665
+ server.tools([
666
+ generateTool,
667
+ validateTool,
668
+ initTool
669
+ ]);
670
+ return server;
671
+ }
672
+ async function startServer({ port, host = "localhost" } = {}) {
673
+ const server = createMcpServer();
674
+ if (port === void 0) {
675
+ new _tmcp_transport_stdio.StdioTransport(server).listen();
676
+ return;
483
677
  }
678
+ const transport = new _tmcp_transport_http.HttpTransport(server, { path: "/mcp" });
679
+ const httpServer = node_http.default.createServer((0, _remix_run_node_fetch_server.createRequestListener)(async (request) => {
680
+ return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
681
+ }));
682
+ httpServer.listen(port, host, () => {
683
+ console.log(`Kubb MCP server on http://${host}:${port}`);
684
+ });
685
+ const closeServer = () => httpServer.close();
686
+ process.once("SIGINT", closeServer);
687
+ process.once("SIGTERM", closeServer);
688
+ httpServer.once("close", () => {
689
+ process.off("SIGINT", closeServer);
690
+ process.off("SIGTERM", closeServer);
691
+ });
484
692
  }
485
693
  //#endregion
486
694
  //#region src/index.ts
487
- async function run(_argv) {
488
- await startServer();
695
+ async function run(_argv, options) {
696
+ await startServer(options);
489
697
  }
490
698
  //#endregion
699
+ exports.createMcpServer = createMcpServer;
491
700
  exports.run = run;
492
701
 
493
702
  //# sourceMappingURL=index.cjs.map