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

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
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";
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
11
  import { createKubb } from "@kubb/core";
12
+ import { defineTool } from "tmcp/tool";
13
+ import { tool } from "tmcp/utils";
14
+ import * as v from "valibot";
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.6";
28
19
  //#endregion
29
20
  //#region ../../internals/utils/src/errors.ts
30
21
  /**
@@ -162,6 +153,21 @@ function isPromise(result) {
162
153
  return result !== null && result !== void 0 && typeof result["then"] === "function";
163
154
  }
164
155
  //#endregion
156
+ //#region src/schemas/generateSchema.ts
157
+ const generateSchema = v.object({
158
+ 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"))),
159
+ input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
160
+ output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory path (overrides config)"))),
161
+ logLevel: v.optional(v.pipe(v.picklist([
162
+ "silent",
163
+ "error",
164
+ "warn",
165
+ "info",
166
+ "verbose",
167
+ "debug"
168
+ ]), v.description("Log level for build output")), "info")
169
+ });
170
+ //#endregion
165
171
  //#region src/types.ts
166
172
  const NotifyTypes = {
167
173
  INFO: "INFO",
@@ -215,8 +221,6 @@ async function loadModule(filePath) {
215
221
  return mod;
216
222
  }
217
223
  async function loadUserConfig(configPath, { notify }) {
218
- let userConfig;
219
- let cwd;
220
224
  if (configPath) {
221
225
  const ext = path.extname(configPath);
222
226
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -232,41 +236,44 @@ async function loadUserConfig(configPath, { notify }) {
232
236
  await notify(NotifyTypes.CONFIG_ERROR, msg);
233
237
  throw new Error(msg);
234
238
  }
235
- cwd = path.dirname(resolvedConfigPath);
239
+ const cwd = path.dirname(resolvedConfigPath);
236
240
  try {
237
- userConfig = await loadModule(resolvedConfigPath);
241
+ const userConfig = await loadModule(resolvedConfigPath);
238
242
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
243
+ return {
244
+ userConfig,
245
+ cwd
246
+ };
239
247
  } 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 {}
248
+ const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
249
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
250
+ throw new Error(msg);
260
251
  }
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(", ")}`);
252
+ }
253
+ const cwd = process.cwd();
254
+ const configFileNames = [
255
+ "kubb.config.ts",
256
+ "kubb.config.mts",
257
+ "kubb.config.cts",
258
+ "kubb.config.js",
259
+ "kubb.config.cjs"
260
+ ];
261
+ for (const configFileName of configFileNames) {
262
+ const configFilePath = path.resolve(process.cwd(), configFileName);
263
+ if (!existsSync(configFilePath)) continue;
264
+ try {
265
+ const userConfig = await loadModule(configFilePath);
266
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
267
+ return {
268
+ userConfig,
269
+ cwd
270
+ };
271
+ } catch (err) {
272
+ await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
264
273
  }
265
274
  }
266
- return {
267
- userConfig,
268
- cwd
269
- };
275
+ await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
276
+ throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
270
277
  }
271
278
  //#endregion
272
279
  //#region src/utils/resolveCwd.ts
@@ -285,40 +292,27 @@ function resolveCwd(userConfig, cwd) {
285
292
  }
286
293
  //#endregion
287
294
  //#region src/utils/resolveUserConfig.ts
288
- /**
289
- * Resolve the config by handling function configs and returning the final configuration
290
- */
291
295
  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;
296
+ const result = typeof config === "function" ? config({
297
+ logLevel: options.logLevel,
298
+ config: options.configPath
299
+ }) : config;
300
+ const resolved = isPromise(result) ? await result : result;
301
+ return Array.isArray(resolved) ? resolved[0] : resolved;
302
302
  }
303
303
  //#endregion
304
304
  //#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) {
305
+ const generateTool = defineTool({
306
+ name: "generate",
307
+ description: "Generate OpenAPI spec helpers using Kubb configuration",
308
+ schema: generateSchema
309
+ }, async function generate(schema) {
310
310
  const { config: configPath, input, output, logLevel } = schema;
311
311
  try {
312
312
  const hooks = new AsyncEventEmitter();
313
313
  const messages = [];
314
- const notify = async (type, message, data) => {
314
+ const notify = async (type, message, _data) => {
315
315
  messages.push(`${type}: ${message}`);
316
- await handler.sendNotification("kubb/progress", {
317
- type,
318
- message,
319
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
320
- ...data
321
- });
322
316
  };
323
317
  hooks.on("kubb:info", async ({ message }) => {
324
318
  await notify(NotifyTypes.INFO, message);
@@ -327,7 +321,7 @@ async function generate(schema, handler) {
327
321
  await notify(NotifyTypes.SUCCESS, message);
328
322
  });
329
323
  hooks.on("kubb:error", async ({ error }) => {
330
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
324
+ await notify(NotifyTypes.ERROR, error.message);
331
325
  });
332
326
  hooks.on("kubb:warn", async ({ message }) => {
333
327
  await notify(NotifyTypes.WARN, message);
@@ -359,7 +353,7 @@ async function generate(schema, handler) {
359
353
  const configResult = await loadUserConfig(configPath, { notify });
360
354
  userConfig = configResult.userConfig;
361
355
  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.");
356
+ 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
357
  userConfig = await resolveUserConfig(userConfig, {
364
358
  configPath,
365
359
  logLevel
@@ -367,13 +361,7 @@ async function generate(schema, handler) {
367
361
  } catch (error) {
368
362
  const errorMessage = error instanceof Error ? error.message : String(error);
369
363
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
370
- return {
371
- content: [{
372
- type: "text",
373
- text: errorMessage
374
- }],
375
- isError: true
376
- };
364
+ return tool.error(errorMessage);
377
365
  }
378
366
  const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
379
367
  const config = {
@@ -388,7 +376,7 @@ async function generate(schema, handler) {
388
376
  path: output
389
377
  } : userConfig.output
390
378
  };
391
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
379
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
392
380
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
393
381
  const kubb = createKubb(config, { hooks });
394
382
  await kubb.setup();
@@ -398,79 +386,231 @@ async function generate(schema, handler) {
398
386
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
399
387
  if (error || failedPlugins.size > 0) {
400
388
  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
- };
389
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`);
390
+ return tool.error(`Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`);
412
391
  }
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
- }] };
392
+ await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
393
+ return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
418
394
  } 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
- };
395
+ const error = toError(caughtError);
396
+ return tool.error(`Build error: ${error.message}\n${error.stack ?? ""}`);
397
+ }
398
+ });
399
+ //#endregion
400
+ //#region ../../internals/shared/src/constants.ts
401
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
402
+ const availablePlugins = [
403
+ {
404
+ value: "plugin-ts",
405
+ label: "TypeScript",
406
+ hint: "Recommended",
407
+ packageName: "@kubb/plugin-ts",
408
+ importName: "pluginTs",
409
+ category: "types"
410
+ },
411
+ {
412
+ value: "plugin-client",
413
+ label: "Client (Fetch/Axios)",
414
+ packageName: "@kubb/plugin-client",
415
+ importName: "pluginClient",
416
+ category: "client"
417
+ },
418
+ {
419
+ value: "plugin-react-query",
420
+ label: "React Query / TanStack Query",
421
+ packageName: "@kubb/plugin-react-query",
422
+ importName: "pluginReactQuery",
423
+ category: "framework"
424
+ },
425
+ {
426
+ value: "plugin-vue-query",
427
+ label: "Vue Query",
428
+ packageName: "@kubb/plugin-vue-query",
429
+ importName: "pluginVueQuery",
430
+ category: "framework"
431
+ },
432
+ {
433
+ value: "plugin-zod",
434
+ label: "Zod Schemas",
435
+ packageName: "@kubb/plugin-zod",
436
+ importName: "pluginZod",
437
+ category: "validation"
438
+ },
439
+ {
440
+ value: "plugin-faker",
441
+ label: "Faker.js Mocks",
442
+ packageName: "@kubb/plugin-faker",
443
+ importName: "pluginFaker",
444
+ category: "mocks"
445
+ },
446
+ {
447
+ value: "plugin-msw",
448
+ label: "MSW Handlers",
449
+ packageName: "@kubb/plugin-msw",
450
+ importName: "pluginMsw",
451
+ category: "mocks"
452
+ },
453
+ {
454
+ value: "plugin-cypress",
455
+ label: "Cypress Tests",
456
+ packageName: "@kubb/plugin-cypress",
457
+ importName: "pluginCypress",
458
+ category: "testing"
459
+ },
460
+ {
461
+ value: "plugin-mcp",
462
+ label: "MCP Server (AI / Model Context Protocol)",
463
+ packageName: "@kubb/plugin-mcp",
464
+ importName: "pluginMcp",
465
+ category: "ai"
466
+ },
467
+ {
468
+ value: "plugin-redoc",
469
+ label: "ReDoc Documentation",
470
+ packageName: "@kubb/plugin-redoc",
471
+ importName: "pluginRedoc",
472
+ category: "documentation"
433
473
  }
474
+ ];
475
+ const pluginDefaultConfigs = {
476
+ "plugin-ts": `pluginTs({
477
+ output: { path: 'models' },
478
+ })`,
479
+ "plugin-client": `pluginClient({
480
+ output: { path: 'clients' },
481
+ })`,
482
+ "plugin-react-query": `pluginReactQuery({
483
+ output: { path: 'hooks' },
484
+ })`,
485
+ "plugin-vue-query": `pluginVueQuery({
486
+ output: { path: 'hooks' },
487
+ })`,
488
+ "plugin-zod": `pluginZod({
489
+ output: { path: 'zod' },
490
+ })`,
491
+ "plugin-faker": `pluginFaker({
492
+ output: { path: 'mocks' },
493
+ })`,
494
+ "plugin-msw": `pluginMsw({
495
+ output: { path: 'msw' },
496
+ })`,
497
+ "plugin-cypress": `pluginCypress({
498
+ output: { path: 'cypress' },
499
+ })`,
500
+ "plugin-mcp": `pluginMcp({
501
+ output: { path: 'mcp' },
502
+ })`,
503
+ "plugin-redoc": `pluginRedoc({
504
+ output: { path: 'redoc' },
505
+ })`
506
+ };
507
+ //#endregion
508
+ //#region ../../internals/shared/src/init.ts
509
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
510
+ return `import { defineConfig } from 'kubb'
511
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
512
+
513
+ export default defineConfig({
514
+ root: '.',
515
+ input: {
516
+ path: '${inputPath}',
517
+ },
518
+ output: {
519
+ path: '${outputPath}',
520
+ clean: true,
521
+ },
522
+ plugins: [
523
+ ${selectedPlugins.map((plugin) => {
524
+ return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
525
+ }).join("\n")}
526
+ ],
527
+ })
528
+ `;
434
529
  }
435
530
  //#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() {
531
+ //#region src/schemas/initSchema.ts
532
+ const initSchema = v.object({
533
+ input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
534
+ output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory (default: ./src/gen)"))),
535
+ plugins: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
536
+ });
537
+ //#endregion
538
+ //#region src/tools/init.ts
539
+ function resolvePlugins(pluginsFlag) {
540
+ if (!pluginsFlag) return [];
541
+ const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
542
+ return availablePlugins.filter((p) => requested.includes(p.value));
543
+ }
544
+ const initTool = defineTool({
545
+ name: "init",
546
+ description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
547
+ schema: initSchema
548
+ }, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
549
+ const selected = resolvePlugins(plugins);
550
+ const content = generateConfigFile({
551
+ selectedPlugins: selected,
552
+ inputPath: input,
553
+ outputPath: output
554
+ });
555
+ const dest = path.join(process$1.cwd(), KUBB_CONFIG_FILENAME);
556
+ if (fs.existsSync(dest)) return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
557
+ fs.writeFileSync(dest, content, "utf-8");
558
+ const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
559
+ return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
560
+ });
561
+ //#endregion
562
+ //#region src/tools/validate.ts
563
+ const validateTool = defineTool({
564
+ name: "validate",
565
+ description: "Validate an OpenAPI/Swagger specification file or URL",
566
+ schema: v.object({ input: v.pipe(v.string(), v.minLength(1), v.description("Path or URL to the OpenAPI/Swagger specification")) })
567
+ }, async ({ input }) => {
568
+ let mod;
443
569
  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);
570
+ mod = await import("@kubb/adapter-oas");
571
+ } catch {
572
+ return tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
573
+ }
574
+ try {
575
+ await mod.adapterOas().validate(input, { throwOnError: true });
576
+ return tool.text(`Validation successful: ${input}`);
577
+ } catch (err) {
578
+ return tool.error(`Validation failed:\n${err instanceof Error ? err.message : String(err)}`);
579
+ }
580
+ });
581
+ //#endregion
582
+ //#region src/server.ts
583
+ function createMcpServer() {
584
+ const server = new McpServer({
585
+ name: "Kubb",
586
+ version
587
+ }, { adapter: new ValibotJsonSchemaAdapter() });
588
+ server.tools([
589
+ generateTool,
590
+ validateTool,
591
+ initTool
592
+ ]);
593
+ return server;
594
+ }
595
+ async function startServer({ port, host = "localhost" } = {}) {
596
+ const server = createMcpServer();
597
+ if (port === void 0) {
598
+ new StdioTransport(server).listen();
599
+ return;
466
600
  }
601
+ const transport = new HttpTransport(server, { path: "/mcp" });
602
+ http.createServer(createRequestListener(async (request) => {
603
+ return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
604
+ })).listen(port, host, () => {
605
+ console.log(`Kubb MCP server on http://${host}:${port}`);
606
+ });
467
607
  }
468
608
  //#endregion
469
609
  //#region src/index.ts
470
- async function run(_argv) {
471
- await startServer();
610
+ async function run(_argv, options) {
611
+ await startServer(options);
472
612
  }
473
613
  //#endregion
474
- export { run };
614
+ export { createMcpServer, run };
475
615
 
476
616
  //# sourceMappingURL=index.js.map