@kubb/mcp 5.0.0-beta.5 → 5.0.0-beta.51

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");
32
+ let _kubb_core = require("@kubb/core");
33
+ let tmcp_tool = require("tmcp/tool");
34
+ let tmcp_utils = require("tmcp/utils");
35
+ let valibot = require("valibot");
36
+ valibot = __toESM(valibot, 1);
30
37
  let node_fs = require("node:fs");
38
+ node_fs = __toESM(node_fs, 1);
31
39
  let node_path = require("node:path");
32
40
  node_path = __toESM(node_path, 1);
33
- let _kubb_core = require("@kubb/core");
34
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.5";
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.51";
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",
@@ -239,8 +286,6 @@ async function loadModule(filePath) {
239
286
  return mod;
240
287
  }
241
288
  async function loadUserConfig(configPath, { notify }) {
242
- let userConfig;
243
- let cwd;
244
289
  if (configPath) {
245
290
  const ext = node_path.default.extname(configPath);
246
291
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -256,41 +301,44 @@ async function loadUserConfig(configPath, { notify }) {
256
301
  await notify(NotifyTypes.CONFIG_ERROR, msg);
257
302
  throw new Error(msg);
258
303
  }
259
- cwd = node_path.default.dirname(resolvedConfigPath);
304
+ const cwd = node_path.default.dirname(resolvedConfigPath);
260
305
  try {
261
- userConfig = await loadModule(resolvedConfigPath);
306
+ const userConfig = await loadModule(resolvedConfigPath);
262
307
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
308
+ return {
309
+ userConfig,
310
+ cwd
311
+ };
263
312
  } catch (error) {
264
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
265
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
266
- }
267
- } else {
268
- cwd = process.cwd();
269
- const configFileNames = [
270
- "kubb.config.ts",
271
- "kubb.config.mts",
272
- "kubb.config.cts",
273
- "kubb.config.js",
274
- "kubb.config.cjs"
275
- ];
276
- for (const configFileName of configFileNames) {
277
- const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
278
- if (!(0, node_fs.existsSync)(configFilePath)) continue;
279
- try {
280
- userConfig = await loadModule(configFilePath);
281
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
282
- break;
283
- } 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);
284
316
  }
285
- if (!userConfig) {
286
- await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
287
- 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)}`);
288
338
  }
289
339
  }
290
- return {
291
- userConfig,
292
- cwd
293
- };
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(", ")}`);
294
342
  }
295
343
  //#endregion
296
344
  //#region src/utils/resolveCwd.ts
@@ -309,40 +357,27 @@ function resolveCwd(userConfig, cwd) {
309
357
  }
310
358
  //#endregion
311
359
  //#region src/utils/resolveUserConfig.ts
312
- /**
313
- * Resolve the config by handling function configs and returning the final configuration
314
- */
315
360
  async function resolveUserConfig(config, options) {
316
- let kubbUserConfig = Promise.resolve(config);
317
- if (typeof config === "function") {
318
- const possiblePromise = config({
319
- logLevel: options.logLevel,
320
- config: options.configPath
321
- });
322
- if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
323
- else kubbUserConfig = Promise.resolve(possiblePromise);
324
- }
325
- 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;
326
367
  }
327
368
  //#endregion
328
369
  //#region src/tools/generate.ts
329
- /**
330
- * Build tool that generates code from OpenAPI specs using Kubb.
331
- * Sends real-time notifications of build progress and events.
332
- */
333
- 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) {
334
375
  const { config: configPath, input, output, logLevel } = schema;
335
376
  try {
336
377
  const hooks = new AsyncEventEmitter();
337
378
  const messages = [];
338
379
  const notify = async (type, message, data) => {
339
- messages.push(`${type}: ${message}`);
340
- await handler.sendNotification("kubb/progress", {
341
- type,
342
- message,
343
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
344
- ...data
345
- });
380
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
346
381
  };
347
382
  hooks.on("kubb:info", async ({ message }) => {
348
383
  await notify(NotifyTypes.INFO, message);
@@ -351,11 +386,14 @@ async function generate(schema, handler) {
351
386
  await notify(NotifyTypes.SUCCESS, message);
352
387
  });
353
388
  hooks.on("kubb:error", async ({ error }) => {
354
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
389
+ await notify(NotifyTypes.ERROR, error.message);
355
390
  });
356
391
  hooks.on("kubb:warn", async ({ message }) => {
357
392
  await notify(NotifyTypes.WARN, message);
358
393
  });
394
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
395
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, _kubb_core.Diagnostics.serialize(diagnostic));
396
+ });
359
397
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
360
398
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
361
399
  });
@@ -365,8 +403,8 @@ async function generate(schema, handler) {
365
403
  hooks.on("kubb:files:processing:start", async () => {
366
404
  await notify(NotifyTypes.FILES_START, "Starting file processing");
367
405
  });
368
- hooks.on("kubb:file:processing:update", async ({ file }) => {
369
- 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`);
370
408
  });
371
409
  hooks.on("kubb:files:processing:end", async () => {
372
410
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -383,7 +421,7 @@ async function generate(schema, handler) {
383
421
  const configResult = await loadUserConfig(configPath, { notify });
384
422
  userConfig = configResult.userConfig;
385
423
  cwd = configResult.cwd;
386
- 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.");
387
425
  userConfig = await resolveUserConfig(userConfig, {
388
426
  configPath,
389
427
  logLevel
@@ -391,13 +429,7 @@ async function generate(schema, handler) {
391
429
  } catch (error) {
392
430
  const errorMessage = error instanceof Error ? error.message : String(error);
393
431
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
394
- return {
395
- content: [{
396
- type: "text",
397
- text: errorMessage
398
- }],
399
- isError: true
400
- };
432
+ return tmcp_utils.tool.error(errorMessage);
401
433
  }
402
434
  const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
403
435
  const config = {
@@ -412,89 +444,259 @@ async function generate(schema, handler) {
412
444
  path: output
413
445
  } : userConfig.output
414
446
  };
415
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
447
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
416
448
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
417
449
  const kubb = (0, _kubb_core.createKubb)(config, { hooks });
418
450
  await kubb.setup();
419
451
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
420
452
  await notify(NotifyTypes.BUILD_START, "Starting build");
421
- const { files, failedPlugins, error } = await kubb.safeBuild();
453
+ const { files, diagnostics } = await kubb.safeBuild();
422
454
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
423
- if (error || failedPlugins.size > 0) {
424
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
425
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
426
- errorCount: allErrors.length,
427
- errors: allErrors.map((err) => err.message)
428
- });
429
- return {
430
- content: [{
431
- type: "text",
432
- text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
433
- }],
434
- isError: true
435
- };
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\`\`\``);
436
461
  }
437
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, { filesCount: files.length });
438
- return { content: [{
439
- type: "text",
440
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
441
- }] };
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")}`);
442
464
  } catch (caughtError) {
443
- const error = caughtError;
444
- await handler.sendNotification("kubb/progress", {
445
- type: NotifyTypes.FATAL_ERROR,
446
- message: error.message,
447
- stack: error.stack,
448
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
449
- });
450
- return {
451
- content: [{
452
- type: "text",
453
- text: `Build error: ${error.message}\n${error.stack || ""}`
454
- }],
455
- isError: true
456
- };
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\`\`\``);
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"
457
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
+ `;
458
599
  }
459
600
  //#endregion
460
- //#region src/server.ts
461
- /**
462
- * Kubb MCP Server
463
- *
464
- * Provides tools for building OpenAPI specifications using Kubb.
465
- */
466
- 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;
467
642
  try {
468
- const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
469
- const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
470
- name: "Kubb",
471
- version
472
- });
473
- server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
474
- return generate(args, { sendNotification: async (method, params) => {
475
- try {
476
- await transport.send({
477
- jsonrpc: "2.0",
478
- method,
479
- params
480
- });
481
- } catch (error) {
482
- console.error("Failed to send notification:", error);
483
- }
484
- } });
485
- });
486
- await server.connect(transport);
487
- } catch (error) {
488
- console.error("Failed to start MCP server:", error);
489
- 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;
490
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
+ });
491
692
  }
492
693
  //#endregion
493
694
  //#region src/index.ts
494
- async function run(_argv) {
495
- await startServer();
695
+ async function run(_argv, options) {
696
+ await startServer(options);
496
697
  }
497
698
  //#endregion
699
+ exports.createMcpServer = createMcpServer;
498
700
  exports.run = run;
499
701
 
500
702
  //# sourceMappingURL=index.cjs.map