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

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 fs, { existsSync } from "node:fs";
8
10
  import path from "node:path";
9
- import { createKubb } from "@kubb/core";
10
- import { unrun } from "unrun";
11
+ import { Diagnostics, createKubb } from "@kubb/core";
12
+ import { defineTool } from "tmcp/tool";
13
+ import { tool } from "tmcp/utils";
14
+ import * as v from "valibot";
15
+ import { createJiti } from "jiti";
16
+ import process$1 from "node:process";
11
17
  //#region package.json
12
- var version = "5.0.0-beta.4";
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.41";
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",
@@ -198,18 +242,23 @@ const ALLOWED_CONFIG_EXTENSIONS = new Set([
198
242
  ]);
199
243
  //#endregion
200
244
  //#region src/utils/loadUserConfig.ts
245
+ const jiti = createJiti(import.meta.url, {
246
+ jsx: {
247
+ runtime: "automatic",
248
+ importSource: "@kubb/renderer-jsx"
249
+ },
250
+ moduleCache: false
251
+ });
201
252
  const loadedModules = /* @__PURE__ */ new Map();
202
253
  async function loadModule(filePath) {
203
254
  const ext = path.extname(filePath);
204
255
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
205
256
  if (loadedModules.has(filePath)) return loadedModules.get(filePath);
206
- const { module } = await unrun({ path: filePath });
207
- loadedModules.set(filePath, module);
208
- return module;
257
+ const mod = await jiti.import(filePath, { default: true });
258
+ loadedModules.set(filePath, mod);
259
+ return mod;
209
260
  }
210
261
  async function loadUserConfig(configPath, { notify }) {
211
- let userConfig;
212
- let cwd;
213
262
  if (configPath) {
214
263
  const ext = path.extname(configPath);
215
264
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -225,41 +274,44 @@ async function loadUserConfig(configPath, { notify }) {
225
274
  await notify(NotifyTypes.CONFIG_ERROR, msg);
226
275
  throw new Error(msg);
227
276
  }
228
- cwd = path.dirname(resolvedConfigPath);
277
+ const cwd = path.dirname(resolvedConfigPath);
229
278
  try {
230
- userConfig = await loadModule(resolvedConfigPath);
279
+ const userConfig = await loadModule(resolvedConfigPath);
231
280
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
281
+ return {
282
+ userConfig,
283
+ cwd
284
+ };
232
285
  } 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 {}
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);
253
289
  }
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(", ")}`);
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)}`);
257
311
  }
258
312
  }
259
- return {
260
- userConfig,
261
- cwd
262
- };
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(", ")}`);
263
315
  }
264
316
  //#endregion
265
317
  //#region src/utils/resolveCwd.ts
@@ -278,40 +330,27 @@ function resolveCwd(userConfig, cwd) {
278
330
  }
279
331
  //#endregion
280
332
  //#region src/utils/resolveUserConfig.ts
281
- /**
282
- * Resolve the config by handling function configs and returning the final configuration
283
- */
284
333
  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;
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;
295
340
  }
296
341
  //#endregion
297
342
  //#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) {
343
+ const generateTool = defineTool({
344
+ name: "generate",
345
+ description: "Generate OpenAPI spec helpers using Kubb configuration",
346
+ schema: generateSchema
347
+ }, async function generate(schema) {
303
348
  const { config: configPath, input, output, logLevel } = schema;
304
349
  try {
305
350
  const hooks = new AsyncEventEmitter();
306
351
  const messages = [];
307
352
  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
- });
353
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
315
354
  };
316
355
  hooks.on("kubb:info", async ({ message }) => {
317
356
  await notify(NotifyTypes.INFO, message);
@@ -320,11 +359,14 @@ async function generate(schema, handler) {
320
359
  await notify(NotifyTypes.SUCCESS, message);
321
360
  });
322
361
  hooks.on("kubb:error", async ({ error }) => {
323
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
362
+ await notify(NotifyTypes.ERROR, error.message);
324
363
  });
325
364
  hooks.on("kubb:warn", async ({ message }) => {
326
365
  await notify(NotifyTypes.WARN, message);
327
366
  });
367
+ hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
368
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
369
+ });
328
370
  hooks.on("kubb:plugin:start", async ({ plugin }) => {
329
371
  await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
330
372
  });
@@ -334,8 +376,8 @@ async function generate(schema, handler) {
334
376
  hooks.on("kubb:files:processing:start", async () => {
335
377
  await notify(NotifyTypes.FILES_START, "Starting file processing");
336
378
  });
337
- hooks.on("kubb:file:processing:update", async ({ file }) => {
338
- 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`);
339
381
  });
340
382
  hooks.on("kubb:files:processing:end", async () => {
341
383
  await notify(NotifyTypes.FILES_END, "File processing complete");
@@ -352,7 +394,7 @@ async function generate(schema, handler) {
352
394
  const configResult = await loadUserConfig(configPath, { notify });
353
395
  userConfig = configResult.userConfig;
354
396
  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.");
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.");
356
398
  userConfig = await resolveUserConfig(userConfig, {
357
399
  configPath,
358
400
  logLevel
@@ -360,13 +402,7 @@ async function generate(schema, handler) {
360
402
  } catch (error) {
361
403
  const errorMessage = error instanceof Error ? error.message : String(error);
362
404
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
363
- return {
364
- content: [{
365
- type: "text",
366
- text: errorMessage
367
- }],
368
- isError: true
369
- };
405
+ return tool.error(errorMessage);
370
406
  }
371
407
  const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
372
408
  const config = {
@@ -381,89 +417,255 @@ async function generate(schema, handler) {
381
417
  path: output
382
418
  } : userConfig.output
383
419
  };
384
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
420
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
385
421
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
386
422
  const kubb = createKubb(config, { hooks });
387
423
  await kubb.setup();
388
424
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
389
425
  await notify(NotifyTypes.BUILD_START, "Starting build");
390
- const { files, failedPlugins, error } = await kubb.safeBuild();
426
+ const { files, diagnostics } = await kubb.safeBuild();
391
427
  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
- };
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\`\`\``);
405
434
  }
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
- }] };
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")}`);
411
437
  } 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
- };
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\`\`\``);
426
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"
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
+ `;
427
572
  }
428
573
  //#endregion
429
- //#region src/server.ts
430
- /**
431
- * Kubb MCP Server
432
- *
433
- * Provides tools for building OpenAPI specifications using Kubb.
434
- */
435
- 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;
436
612
  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);
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;
459
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
+ });
460
662
  }
461
663
  //#endregion
462
664
  //#region src/index.ts
463
- async function run(_argv) {
464
- await startServer();
665
+ async function run(_argv, options) {
666
+ await startServer(options);
465
667
  }
466
668
  //#endregion
467
- export { run };
669
+ export { createMcpServer, run };
468
670
 
469
671
  //# sourceMappingURL=index.js.map