@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.js CHANGED
@@ -1,30 +1,21 @@
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 "./chunk-C0LytTxp.js";
2
+ import http from "node:http";
3
+ import { createRequestListener } from "@remix-run/node-fetch-server";
4
+ import { ValibotJsonSchemaAdapter } from "@tmcp/adapter-valibot";
5
+ import { HttpTransport } from "@tmcp/transport-http";
6
+ import { StdioTransport } from "@tmcp/transport-stdio";
7
+ import { McpServer } from "tmcp";
6
8
  import { EventEmitter } from "node:events";
7
- import { existsSync } from "node:fs";
9
+ import { Diagnostics, createKubb } from "@kubb/core";
10
+ import { defineTool } from "tmcp/tool";
11
+ import { tool } from "tmcp/utils";
12
+ import * as v from "valibot";
13
+ import fs, { existsSync } from "node:fs";
8
14
  import path from "node:path";
9
- import { createKubb } from "@kubb/core";
10
15
  import { createJiti } from "jiti";
16
+ import process$1 from "node:process";
11
17
  //#region package.json
12
- var version = "5.0.0-beta.5";
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
- });
18
+ var version = "5.0.0-beta.51";
28
19
  //#endregion
29
20
  //#region ../../internals/utils/src/errors.ts
30
21
  /**
@@ -72,9 +63,12 @@ var AsyncEventEmitter = class {
72
63
  * await emitter.emit('build', 'petstore')
73
64
  * ```
74
65
  */
75
- async emit(eventName, ...eventArgs) {
66
+ emit(eventName, ...eventArgs) {
76
67
  const listeners = this.#emitter.listeners(eventName);
77
68
  if (listeners.length === 0) return;
69
+ return this.#emitAll(eventName, listeners, eventArgs);
70
+ }
71
+ async #emitAll(eventName, listeners, eventArgs) {
78
72
  for (const listener of listeners) try {
79
73
  await listener(...eventArgs);
80
74
  } catch (err) {
@@ -137,6 +131,24 @@ var AsyncEventEmitter = class {
137
131
  return this.#emitter.listenerCount(eventName);
138
132
  }
139
133
  /**
134
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
135
+ * Set this above the expected listener count when many listeners attach by design.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * emitter.setMaxListeners(40)
140
+ * ```
141
+ */
142
+ setMaxListeners(max) {
143
+ this.#emitter.setMaxListeners(max);
144
+ }
145
+ /**
146
+ * Returns the current per-event listener ceiling.
147
+ */
148
+ getMaxListeners() {
149
+ return this.#emitter.getMaxListeners();
150
+ }
151
+ /**
140
152
  * Removes all listeners from every event channel.
141
153
  *
142
154
  * @example
@@ -162,16 +174,48 @@ function isPromise(result) {
162
174
  return result !== null && result !== void 0 && typeof result["then"] === "function";
163
175
  }
164
176
  //#endregion
177
+ //#region src/schemas/generateSchema.ts
178
+ const generateSchema = v.object({
179
+ 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"))),
180
+ input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
181
+ output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory path (overrides config)"))),
182
+ logLevel: v.optional(v.pipe(v.picklist([
183
+ "silent",
184
+ "info",
185
+ "verbose"
186
+ ]), v.description("Log level for build output")), "info")
187
+ });
188
+ //#endregion
189
+ //#region src/utils/formatDiagnostics.ts
190
+ /**
191
+ * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
192
+ * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
193
+ * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
194
+ * unlike the CLI renderer.
195
+ */
196
+ function formatDiagnostics(diagnostics) {
197
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
198
+ }
199
+ function formatDiagnostic(diagnostic) {
200
+ const { code, severity, message, location, help, plugin, docsUrl } = diagnostic;
201
+ const lines = [`${severity} ${plugin ? `${plugin}(${code})` : code}: ${message}`];
202
+ if (location && "pointer" in location) lines.push(` at ${location.pointer}`);
203
+ if (help) lines.push(` help: ${help}`);
204
+ if (docsUrl) lines.push(` docs: ${docsUrl}`);
205
+ return lines.join("\n");
206
+ }
207
+ //#endregion
165
208
  //#region src/types.ts
166
209
  const NotifyTypes = {
167
210
  INFO: "INFO",
168
211
  SUCCESS: "SUCCESS",
169
212
  ERROR: "ERROR",
170
213
  WARN: "WARN",
214
+ DIAGNOSTIC: "DIAGNOSTIC",
171
215
  PLUGIN_START: "PLUGIN_START",
172
216
  PLUGIN_END: "PLUGIN_END",
173
217
  FILES_START: "FILES_START",
174
- FILE_UPDATE: "FILE_UPDATE",
218
+ FILES_UPDATE: "FILES_UPDATE",
175
219
  FILES_END: "FILES_END",
176
220
  GENERATION_START: "GENERATION_START",
177
221
  GENERATION_END: "GENERATION_END",
@@ -215,8 +259,6 @@ async function loadModule(filePath) {
215
259
  return mod;
216
260
  }
217
261
  async function loadUserConfig(configPath, { notify }) {
218
- let userConfig;
219
- let cwd;
220
262
  if (configPath) {
221
263
  const ext = path.extname(configPath);
222
264
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -232,41 +274,44 @@ async function loadUserConfig(configPath, { notify }) {
232
274
  await notify(NotifyTypes.CONFIG_ERROR, msg);
233
275
  throw new Error(msg);
234
276
  }
235
- cwd = path.dirname(resolvedConfigPath);
277
+ const cwd = path.dirname(resolvedConfigPath);
236
278
  try {
237
- userConfig = await loadModule(resolvedConfigPath);
279
+ const userConfig = await loadModule(resolvedConfigPath);
238
280
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
281
+ return {
282
+ userConfig,
283
+ cwd
284
+ };
239
285
  } catch (error) {
240
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
241
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
242
- }
243
- } else {
244
- cwd = process.cwd();
245
- const configFileNames = [
246
- "kubb.config.ts",
247
- "kubb.config.mts",
248
- "kubb.config.cts",
249
- "kubb.config.js",
250
- "kubb.config.cjs"
251
- ];
252
- for (const configFileName of configFileNames) {
253
- const configFilePath = path.resolve(process.cwd(), configFileName);
254
- if (!existsSync(configFilePath)) continue;
255
- try {
256
- userConfig = await loadModule(configFilePath);
257
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
258
- break;
259
- } catch {}
286
+ const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
287
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
288
+ throw new Error(msg);
260
289
  }
261
- if (!userConfig) {
262
- await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
263
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
290
+ }
291
+ const cwd = process.cwd();
292
+ const configFileNames = [
293
+ "kubb.config.ts",
294
+ "kubb.config.mts",
295
+ "kubb.config.cts",
296
+ "kubb.config.js",
297
+ "kubb.config.cjs"
298
+ ];
299
+ for (const configFileName of configFileNames) {
300
+ const configFilePath = path.resolve(process.cwd(), configFileName);
301
+ if (!existsSync(configFilePath)) continue;
302
+ try {
303
+ const userConfig = await loadModule(configFilePath);
304
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
305
+ return {
306
+ userConfig,
307
+ cwd
308
+ };
309
+ } catch (err) {
310
+ await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
264
311
  }
265
312
  }
266
- return {
267
- userConfig,
268
- cwd
269
- };
313
+ await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
314
+ throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
270
315
  }
271
316
  //#endregion
272
317
  //#region src/utils/resolveCwd.ts
@@ -285,40 +330,27 @@ function resolveCwd(userConfig, cwd) {
285
330
  }
286
331
  //#endregion
287
332
  //#region src/utils/resolveUserConfig.ts
288
- /**
289
- * Resolve the config by handling function configs and returning the final configuration
290
- */
291
333
  async function resolveUserConfig(config, options) {
292
- let kubbUserConfig = Promise.resolve(config);
293
- if (typeof config === "function") {
294
- const possiblePromise = config({
295
- logLevel: options.logLevel,
296
- config: options.configPath
297
- });
298
- if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
299
- else kubbUserConfig = Promise.resolve(possiblePromise);
300
- }
301
- return await kubbUserConfig;
334
+ const result = typeof config === "function" ? config({
335
+ logLevel: options.logLevel,
336
+ config: options.configPath
337
+ }) : config;
338
+ const resolved = isPromise(result) ? await result : result;
339
+ return Array.isArray(resolved) ? resolved[0] : resolved;
302
340
  }
303
341
  //#endregion
304
342
  //#region src/tools/generate.ts
305
- /**
306
- * Build tool that generates code from OpenAPI specs using Kubb.
307
- * Sends real-time notifications of build progress and events.
308
- */
309
- async function generate(schema, handler) {
343
+ const generateTool = defineTool({
344
+ name: "generate",
345
+ description: "Generate OpenAPI spec helpers using Kubb configuration",
346
+ schema: generateSchema
347
+ }, async function generate(schema) {
310
348
  const { config: configPath, input, output, logLevel } = schema;
311
349
  try {
312
350
  const hooks = new AsyncEventEmitter();
313
351
  const messages = [];
314
352
  const notify = async (type, message, data) => {
315
- messages.push(`${type}: ${message}`);
316
- await handler.sendNotification("kubb/progress", {
317
- type,
318
- message,
319
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
320
- ...data
321
- });
353
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
322
354
  };
323
355
  hooks.on("kubb:info", async ({ message }) => {
324
356
  await notify(NotifyTypes.INFO, message);
@@ -327,11 +359,14 @@ async function generate(schema, handler) {
327
359
  await notify(NotifyTypes.SUCCESS, message);
328
360
  });
329
361
  hooks.on("kubb:error", async ({ error }) => {
330
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
362
+ await notify(NotifyTypes.ERROR, error.message);
331
363
  });
332
364
  hooks.on("kubb:warn", async ({ message }) => {
333
365
  await notify(NotifyTypes.WARN, message);
334
366
  });
367
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
368
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
369
+ });
335
370
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
336
371
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
337
372
  });
@@ -341,8 +376,8 @@ async function generate(schema, handler) {
341
376
  hooks.on("kubb:files:processing:start", async () => {
342
377
  await notify(NotifyTypes.FILES_START, "Starting file processing");
343
378
  });
344
- hooks.on("kubb:file:processing:update", async ({ file }) => {
345
- await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
379
+ hooks.on("kubb:files:processing:update", async ({ files }) => {
380
+ await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
346
381
  });
347
382
  hooks.on("kubb:files:processing:end", async () => {
348
383
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -359,7 +394,7 @@ async function generate(schema, handler) {
359
394
  const configResult = await loadUserConfig(configPath, { notify });
360
395
  userConfig = configResult.userConfig;
361
396
  cwd = configResult.cwd;
362
- 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.");
397
+ 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.");
363
398
  userConfig = await resolveUserConfig(userConfig, {
364
399
  configPath,
365
400
  logLevel
@@ -367,13 +402,7 @@ async function generate(schema, handler) {
367
402
  } catch (error) {
368
403
  const errorMessage = error instanceof Error ? error.message : String(error);
369
404
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
370
- return {
371
- content: [{
372
- type: "text",
373
- text: errorMessage
374
- }],
375
- isError: true
376
- };
405
+ return tool.error(errorMessage);
377
406
  }
378
407
  const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
379
408
  const config = {
@@ -388,89 +417,255 @@ async function generate(schema, handler) {
388
417
  path: output
389
418
  } : userConfig.output
390
419
  };
391
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
420
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
392
421
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
393
422
  const kubb = createKubb(config, { hooks });
394
423
  await kubb.setup();
395
424
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
396
425
  await notify(NotifyTypes.BUILD_START, "Starting build");
397
- const { files, failedPlugins, error } = await kubb.safeBuild();
426
+ const { files, diagnostics } = await kubb.safeBuild();
398
427
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
399
- if (error || failedPlugins.size > 0) {
400
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
401
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
402
- errorCount: allErrors.length,
403
- errors: allErrors.map((err) => err.message)
404
- });
405
- return {
406
- content: [{
407
- type: "text",
408
- text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
409
- }],
410
- isError: true
411
- };
428
+ const problems = diagnostics.filter(Diagnostics.isProblem);
429
+ const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
430
+ if (errors.length > 0) {
431
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
432
+ const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic));
433
+ return tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
412
434
  }
413
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, { filesCount: files.length });
414
- return { content: [{
415
- type: "text",
416
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
417
- }] };
435
+ await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
436
+ return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
418
437
  } catch (caughtError) {
419
- const error = caughtError;
420
- await handler.sendNotification("kubb/progress", {
421
- type: NotifyTypes.FATAL_ERROR,
422
- message: error.message,
423
- stack: error.stack,
424
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
425
- });
426
- return {
427
- content: [{
428
- type: "text",
429
- text: `Build error: ${error.message}\n${error.stack || ""}`
430
- }],
431
- isError: true
432
- };
438
+ const serialized = Diagnostics.serialize(Diagnostics.from(caughtError));
439
+ return tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
440
+ }
441
+ });
442
+ //#endregion
443
+ //#region ../../internals/shared/src/constants.ts
444
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
445
+ const availablePlugins = [
446
+ {
447
+ value: "plugin-ts",
448
+ label: "TypeScript",
449
+ hint: "Recommended",
450
+ packageName: "@kubb/plugin-ts",
451
+ importName: "pluginTs",
452
+ category: "types"
453
+ },
454
+ {
455
+ value: "plugin-client",
456
+ label: "Client (Fetch/Axios)",
457
+ packageName: "@kubb/plugin-client",
458
+ importName: "pluginClient",
459
+ category: "client"
460
+ },
461
+ {
462
+ value: "plugin-react-query",
463
+ label: "React Query / TanStack Query",
464
+ packageName: "@kubb/plugin-react-query",
465
+ importName: "pluginReactQuery",
466
+ category: "framework"
467
+ },
468
+ {
469
+ value: "plugin-vue-query",
470
+ label: "Vue Query",
471
+ packageName: "@kubb/plugin-vue-query",
472
+ importName: "pluginVueQuery",
473
+ category: "framework"
474
+ },
475
+ {
476
+ value: "plugin-zod",
477
+ label: "Zod Schemas",
478
+ packageName: "@kubb/plugin-zod",
479
+ importName: "pluginZod",
480
+ category: "validation"
481
+ },
482
+ {
483
+ value: "plugin-faker",
484
+ label: "Faker.js Mocks",
485
+ packageName: "@kubb/plugin-faker",
486
+ importName: "pluginFaker",
487
+ category: "mocks"
488
+ },
489
+ {
490
+ value: "plugin-msw",
491
+ label: "MSW Handlers",
492
+ packageName: "@kubb/plugin-msw",
493
+ importName: "pluginMsw",
494
+ category: "mocks"
495
+ },
496
+ {
497
+ value: "plugin-cypress",
498
+ label: "Cypress Tests",
499
+ packageName: "@kubb/plugin-cypress",
500
+ importName: "pluginCypress",
501
+ category: "testing"
502
+ },
503
+ {
504
+ value: "plugin-mcp",
505
+ label: "MCP Server (AI / Model Context Protocol)",
506
+ packageName: "@kubb/plugin-mcp",
507
+ importName: "pluginMcp",
508
+ category: "ai"
509
+ },
510
+ {
511
+ value: "plugin-redoc",
512
+ label: "ReDoc Documentation",
513
+ packageName: "@kubb/plugin-redoc",
514
+ importName: "pluginRedoc",
515
+ category: "documentation"
433
516
  }
517
+ ];
518
+ const pluginDefaultConfigs = {
519
+ "plugin-ts": `pluginTs({
520
+ output: { path: 'models' },
521
+ })`,
522
+ "plugin-client": `pluginClient({
523
+ output: { path: 'clients' },
524
+ })`,
525
+ "plugin-react-query": `pluginReactQuery({
526
+ output: { path: 'hooks' },
527
+ })`,
528
+ "plugin-vue-query": `pluginVueQuery({
529
+ output: { path: 'hooks' },
530
+ })`,
531
+ "plugin-zod": `pluginZod({
532
+ output: { path: 'zod' },
533
+ })`,
534
+ "plugin-faker": `pluginFaker({
535
+ output: { path: 'mocks' },
536
+ })`,
537
+ "plugin-msw": `pluginMsw({
538
+ output: { path: 'msw' },
539
+ })`,
540
+ "plugin-cypress": `pluginCypress({
541
+ output: { path: 'cypress' },
542
+ })`,
543
+ "plugin-mcp": `pluginMcp({
544
+ output: { path: 'mcp' },
545
+ })`,
546
+ "plugin-redoc": `pluginRedoc({
547
+ output: { path: 'redoc' },
548
+ })`
549
+ };
550
+ //#endregion
551
+ //#region ../../internals/shared/src/init.ts
552
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
553
+ return `import { defineConfig } from 'kubb'
554
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
555
+
556
+ export default defineConfig({
557
+ root: '.',
558
+ input: {
559
+ path: '${inputPath}',
560
+ },
561
+ output: {
562
+ path: '${outputPath}',
563
+ clean: true,
564
+ },
565
+ plugins: [
566
+ ${selectedPlugins.map((plugin) => {
567
+ return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
568
+ }).join("\n")}
569
+ ],
570
+ })
571
+ `;
434
572
  }
435
573
  //#endregion
436
- //#region src/server.ts
437
- /**
438
- * Kubb MCP Server
439
- *
440
- * Provides tools for building OpenAPI specifications using Kubb.
441
- */
442
- async function startServer() {
574
+ //#region src/schemas/initSchema.ts
575
+ const initSchema = v.object({
576
+ input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
577
+ output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory (default: ./src/gen)"))),
578
+ plugins: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
579
+ });
580
+ //#endregion
581
+ //#region src/tools/init.ts
582
+ function resolvePlugins(pluginsFlag) {
583
+ if (!pluginsFlag) return [];
584
+ const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
585
+ return availablePlugins.filter((p) => requested.includes(p.value));
586
+ }
587
+ const initTool = defineTool({
588
+ name: "init",
589
+ description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
590
+ schema: initSchema
591
+ }, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
592
+ const selected = resolvePlugins(plugins);
593
+ const content = generateConfigFile({
594
+ selectedPlugins: selected,
595
+ inputPath: input,
596
+ outputPath: output
597
+ });
598
+ const dest = path.join(process$1.cwd(), KUBB_CONFIG_FILENAME);
599
+ if (fs.existsSync(dest)) return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
600
+ fs.writeFileSync(dest, content, "utf-8");
601
+ const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
602
+ return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
603
+ });
604
+ //#endregion
605
+ //#region src/tools/validate.ts
606
+ const validateTool = defineTool({
607
+ name: "validate",
608
+ description: "Validate an OpenAPI/Swagger specification file or URL",
609
+ schema: v.object({ input: v.pipe(v.string(), v.minLength(1), v.description("Path or URL to the OpenAPI/Swagger specification")) })
610
+ }, async ({ input }) => {
611
+ let mod;
443
612
  try {
444
- const transport = new StdioServerTransport();
445
- const server = new McpServer({
446
- name: "Kubb",
447
- version
448
- });
449
- server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
450
- return generate(args, { sendNotification: async (method, params) => {
451
- try {
452
- await transport.send({
453
- jsonrpc: "2.0",
454
- method,
455
- params
456
- });
457
- } catch (error) {
458
- console.error("Failed to send notification:", error);
459
- }
460
- } });
461
- });
462
- await server.connect(transport);
463
- } catch (error) {
464
- console.error("Failed to start MCP server:", error);
465
- process$1.exit(1);
613
+ mod = await import("@kubb/adapter-oas");
614
+ } catch {
615
+ return tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
616
+ }
617
+ try {
618
+ await mod.adapterOas().validate(input, { throwOnError: true });
619
+ return tool.text(`Validation successful: ${input}`);
620
+ } catch (err) {
621
+ const serialized = Diagnostics.serialize(Diagnostics.from(err));
622
+ return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
623
+ }
624
+ });
625
+ //#endregion
626
+ //#region src/server.ts
627
+ function createMcpServer() {
628
+ const server = new McpServer({
629
+ name: "Kubb",
630
+ version
631
+ }, {
632
+ adapter: new ValibotJsonSchemaAdapter(),
633
+ capabilities: { tools: {} }
634
+ });
635
+ server.tools([
636
+ generateTool,
637
+ validateTool,
638
+ initTool
639
+ ]);
640
+ return server;
641
+ }
642
+ async function startServer({ port, host = "localhost" } = {}) {
643
+ const server = createMcpServer();
644
+ if (port === void 0) {
645
+ new StdioTransport(server).listen();
646
+ return;
466
647
  }
648
+ const transport = new HttpTransport(server, { path: "/mcp" });
649
+ const httpServer = http.createServer(createRequestListener(async (request) => {
650
+ return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
651
+ }));
652
+ httpServer.listen(port, host, () => {
653
+ console.log(`Kubb MCP server on http://${host}:${port}`);
654
+ });
655
+ const closeServer = () => httpServer.close();
656
+ process.once("SIGINT", closeServer);
657
+ process.once("SIGTERM", closeServer);
658
+ httpServer.once("close", () => {
659
+ process.off("SIGINT", closeServer);
660
+ process.off("SIGTERM", closeServer);
661
+ });
467
662
  }
468
663
  //#endregion
469
664
  //#region src/index.ts
470
- async function run(_argv) {
471
- await startServer();
665
+ async function run(_argv, options) {
666
+ await startServer(options);
472
667
  }
473
668
  //#endregion
474
- export { run };
669
+ export { createMcpServer, run };
475
670
 
476
671
  //# sourceMappingURL=index.js.map