@kubb/mcp 5.0.0-beta.1 → 5.0.0-beta.11

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/README.md CHANGED
@@ -1,26 +1,38 @@
1
- # @kubb/mcp
2
-
3
- Model Context Protocol (MCP) server for Kubb.
4
-
5
- ## Overview
6
-
7
- This package provides an MCP server that exposes Kubb's code generation functionality through the [Model Context Protocol](https://modelcontextprotocol.io), allowing AI assistants like [Claude](https://claude.ai), [Cursor](https://cursor.sh), and other MCP-compatible clients to generate code from OpenAPI specifications using natural language.
8
-
9
- The server acts as a bridge between MCP clients (like [Claude Desktop](https://claude.ai/download)) and Kubb's build system, enabling conversational code generation workflows.
10
-
11
- ## Features
12
-
13
- - **Generate Tool**: Generate TypeScript types, API clients, and more from OpenAPI specs using your `kubb.config.ts`
14
- - **Real-time Progress Notifications**: Stream build events and progress updates to the MCP client
15
- - Uses `@kubb/core` build functionality directly
16
- - Lightweight and focused on code generation
1
+ <div align="center">
2
+ <h1>@kubb/mcp</h1>
3
+ <a href="https://kubb.dev" target="_blank" rel="noopener noreferrer">
4
+ <img width="180" src="https://raw.githubusercontent.com/kubb-labs/kubb/main/assets/logo.png" alt="Kubb logo">
5
+ </a>
6
+
7
+ [![npm version][npm-version-src]][npm-version-href]
8
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
9
+ [![Coverage][coverage-src]][coverage-href]
10
+ [![License][license-src]][license-href]
11
+ [![Sponsors][sponsors-src]][sponsors-href]
12
+
13
+ <h4>
14
+ <a href="https://kubb.dev/" target="_blank">Documentation</a>
15
+ <span> · </span>
16
+ <a href="https://github.com/kubb-labs/kubb/issues/" target="_blank">Report Bug</a>
17
+ <span> · </span>
18
+ <a href="https://github.com/kubb-labs/kubb/issues/" target="_blank">Request Feature</a>
19
+ </h4>
20
+ </div>
21
+
22
+ MCP server for Kubb. Exposes code generation as a tool over the [Model Context Protocol](https://modelcontextprotocol.io) so AI assistants like [Claude](https://claude.ai), [Cursor](https://cursor.sh), and other MCP-compatible clients can generate TypeScript types, API clients, and more from OpenAPI specs using natural language.
23
+
24
+ The server exposes a `generate` tool that runs a full Kubb build from a `kubb.config.ts`. It streams build events back to the client as real-time progress notifications. The server communicates over stdio and works with any MCP-compatible client.
17
25
 
18
26
  ## Installation
19
27
 
20
28
  Install as a dev dependency:
21
29
 
22
- ```bash [npm]
23
- npm install --save-dev @kubb/mcp
30
+ ```bash
31
+ bun add -D @kubb/mcp
32
+ # or
33
+ pnpm add -D @kubb/mcp
34
+ # or
35
+ npm install -D @kubb/mcp
24
36
  ```
25
37
 
26
38
  > [!IMPORTANT]
@@ -48,7 +60,7 @@ This starts an MCP server that communicates via stdio (standard input/output), m
48
60
 
49
61
  Add to your MCP client configuration (e.g., [Claude Desktop](https://claude.ai/download)'s `claude_desktop_config.json`):
50
62
 
51
- **Option 1: Using Kubb CLI (recommended):**
63
+ Using `kubb mcp`:
52
64
 
53
65
  ```json
54
66
  {
@@ -61,7 +73,7 @@ Add to your MCP client configuration (e.g., [Claude Desktop](https://claude.ai/d
61
73
  }
62
74
  ```
63
75
 
64
- **Option 2: Using standalone bin:**
76
+ Using the standalone package:
65
77
 
66
78
  ```json
67
79
  {
@@ -152,3 +164,28 @@ export default defineConfig({
152
164
  plugins: [pluginOas(), pluginTs(), pluginClient()],
153
165
  })
154
166
  ```
167
+
168
+ ## Supporting Kubb
169
+
170
+ Kubb is an open source project with its ongoing development made possible entirely by the support of Sponsors. If you would like to become a sponsor, please consider:
171
+
172
+ - [Become a Sponsor on GitHub](https://github.com/sponsors/stijnvanhulle)
173
+
174
+ <p align="center">
175
+ <a href="https://github.com/sponsors/stijnvanhulle">
176
+ <img src="https://raw.githubusercontent.com/stijnvanhulle/sponsors/main/sponsors.svg" alt="My sponsors" />
177
+ </a>
178
+ </p>
179
+
180
+ <!-- Badges -->
181
+
182
+ [npm-version-src]: https://img.shields.io/npm/v/@kubb/mcp?flat&colorA=18181B&colorB=f58517
183
+ [npm-version-href]: https://npmjs.com/package/@kubb/mcp
184
+ [npm-downloads-src]: https://img.shields.io/npm/dm/@kubb/mcp?flat&colorA=18181B&colorB=f58517
185
+ [npm-downloads-href]: https://npmjs.com/package/@kubb/mcp
186
+ [license-src]: https://img.shields.io/github/license/kubb-labs/kubb.svg?flat&colorA=18181B&colorB=f58517
187
+ [license-href]: https://github.com/kubb-labs/kubb/blob/main/LICENSE
188
+ [coverage-src]: https://img.shields.io/codecov/c/github/kubb-labs/kubb?style=flat&colorA=18181B&colorB=f58517
189
+ [coverage-href]: https://www.npmjs.com/package/@kubb/mcp
190
+ [sponsors-src]: https://img.shields.io/github/sponsors/stijnvanhulle?style=flat&colorA=18181B&colorB=f58517
191
+ [sponsors-href]: https://github.com/sponsors/stijnvanhulle/
package/dist/index.cjs CHANGED
@@ -21,34 +21,28 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- let node_process = require("node:process");
25
- node_process = __toESM(node_process, 1);
26
- let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
27
- let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
28
- let zod = require("zod");
24
+ let node_http = require("node:http");
25
+ node_http = __toESM(node_http, 1);
26
+ let _remix_run_node_fetch_server = require("@remix-run/node-fetch-server");
27
+ let _tmcp_adapter_valibot = require("@tmcp/adapter-valibot");
28
+ let _tmcp_transport_http = require("@tmcp/transport-http");
29
+ let _tmcp_transport_stdio = require("@tmcp/transport-stdio");
30
+ let tmcp = require("tmcp");
29
31
  let node_events = require("node:events");
30
32
  let node_fs = require("node:fs");
33
+ node_fs = __toESM(node_fs, 1);
31
34
  let node_path = require("node:path");
32
35
  node_path = __toESM(node_path, 1);
33
36
  let _kubb_core = require("@kubb/core");
34
- let unrun = require("unrun");
37
+ let tmcp_tool = require("tmcp/tool");
38
+ let tmcp_utils = require("tmcp/utils");
39
+ let valibot = require("valibot");
40
+ valibot = __toESM(valibot, 1);
41
+ let jiti = require("jiti");
42
+ let node_process = require("node:process");
43
+ node_process = __toESM(node_process, 1);
35
44
  //#region package.json
36
- var version = "5.0.0-beta.1";
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.11";
52
46
  //#endregion
53
47
  //#region ../../internals/utils/src/errors.ts
54
48
  /**
@@ -186,6 +180,21 @@ function isPromise(result) {
186
180
  return result !== null && result !== void 0 && typeof result["then"] === "function";
187
181
  }
188
182
  //#endregion
183
+ //#region src/schemas/generateSchema.ts
184
+ const generateSchema = valibot.object({
185
+ 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"))),
186
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
187
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
188
+ logLevel: valibot.optional(valibot.pipe(valibot.picklist([
189
+ "silent",
190
+ "error",
191
+ "warn",
192
+ "info",
193
+ "verbose",
194
+ "debug"
195
+ ]), valibot.description("Log level for build output")), "info")
196
+ });
197
+ //#endregion
189
198
  //#region src/types.ts
190
199
  const NotifyTypes = {
191
200
  INFO: "INFO",
@@ -222,18 +231,23 @@ const ALLOWED_CONFIG_EXTENSIONS = new Set([
222
231
  ]);
223
232
  //#endregion
224
233
  //#region src/utils/loadUserConfig.ts
234
+ const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, {
235
+ jsx: {
236
+ runtime: "automatic",
237
+ importSource: "@kubb/renderer-jsx"
238
+ },
239
+ moduleCache: false
240
+ });
225
241
  const loadedModules = /* @__PURE__ */ new Map();
226
242
  async function loadModule(filePath) {
227
243
  const ext = node_path.default.extname(filePath);
228
244
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
229
245
  if (loadedModules.has(filePath)) return loadedModules.get(filePath);
230
- const { module } = await (0, unrun.unrun)({ path: filePath });
231
- loadedModules.set(filePath, module);
232
- return module;
246
+ const mod = await jiti$1.import(filePath, { default: true });
247
+ loadedModules.set(filePath, mod);
248
+ return mod;
233
249
  }
234
250
  async function loadUserConfig(configPath, { notify }) {
235
- let userConfig;
236
- let cwd;
237
251
  if (configPath) {
238
252
  const ext = node_path.default.extname(configPath);
239
253
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -249,41 +263,44 @@ async function loadUserConfig(configPath, { notify }) {
249
263
  await notify(NotifyTypes.CONFIG_ERROR, msg);
250
264
  throw new Error(msg);
251
265
  }
252
- cwd = node_path.default.dirname(resolvedConfigPath);
266
+ const cwd = node_path.default.dirname(resolvedConfigPath);
253
267
  try {
254
- userConfig = await loadModule(resolvedConfigPath);
268
+ const userConfig = await loadModule(resolvedConfigPath);
255
269
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
270
+ return {
271
+ userConfig,
272
+ cwd
273
+ };
256
274
  } catch (error) {
257
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
258
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
259
- }
260
- } else {
261
- cwd = process.cwd();
262
- const configFileNames = [
263
- "kubb.config.ts",
264
- "kubb.config.mts",
265
- "kubb.config.cts",
266
- "kubb.config.js",
267
- "kubb.config.cjs"
268
- ];
269
- for (const configFileName of configFileNames) {
270
- const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
271
- if (!(0, node_fs.existsSync)(configFilePath)) continue;
272
- try {
273
- userConfig = await loadModule(configFilePath);
274
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
275
- break;
276
- } catch {}
275
+ const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
276
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
277
+ throw new Error(msg);
277
278
  }
278
- if (!userConfig) {
279
- await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
280
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
279
+ }
280
+ const cwd = process.cwd();
281
+ const configFileNames = [
282
+ "kubb.config.ts",
283
+ "kubb.config.mts",
284
+ "kubb.config.cts",
285
+ "kubb.config.js",
286
+ "kubb.config.cjs"
287
+ ];
288
+ for (const configFileName of configFileNames) {
289
+ const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
290
+ if (!(0, node_fs.existsSync)(configFilePath)) continue;
291
+ try {
292
+ const userConfig = await loadModule(configFilePath);
293
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
294
+ return {
295
+ userConfig,
296
+ cwd
297
+ };
298
+ } catch (err) {
299
+ await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
281
300
  }
282
301
  }
283
- return {
284
- userConfig,
285
- cwd
286
- };
302
+ await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
303
+ throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
287
304
  }
288
305
  //#endregion
289
306
  //#region src/utils/resolveCwd.ts
@@ -302,40 +319,27 @@ function resolveCwd(userConfig, cwd) {
302
319
  }
303
320
  //#endregion
304
321
  //#region src/utils/resolveUserConfig.ts
305
- /**
306
- * Resolve the config by handling function configs and returning the final configuration
307
- */
308
322
  async function resolveUserConfig(config, options) {
309
- let kubbUserConfig = Promise.resolve(config);
310
- if (typeof config === "function") {
311
- const possiblePromise = config({
312
- logLevel: options.logLevel,
313
- config: options.configPath
314
- });
315
- if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
316
- else kubbUserConfig = Promise.resolve(possiblePromise);
317
- }
318
- return await kubbUserConfig;
323
+ const result = typeof config === "function" ? config({
324
+ logLevel: options.logLevel,
325
+ config: options.configPath
326
+ }) : config;
327
+ const resolved = isPromise(result) ? await result : result;
328
+ return Array.isArray(resolved) ? resolved[0] : resolved;
319
329
  }
320
330
  //#endregion
321
331
  //#region src/tools/generate.ts
322
- /**
323
- * Build tool that generates code from OpenAPI specs using Kubb.
324
- * Sends real-time notifications of build progress and events.
325
- */
326
- async function generate(schema, handler) {
332
+ const generateTool = (0, tmcp_tool.defineTool)({
333
+ name: "generate",
334
+ description: "Generate OpenAPI spec helpers using Kubb configuration",
335
+ schema: generateSchema
336
+ }, async function generate(schema) {
327
337
  const { config: configPath, input, output, logLevel } = schema;
328
338
  try {
329
339
  const hooks = new AsyncEventEmitter();
330
340
  const messages = [];
331
- const notify = async (type, message, data) => {
341
+ const notify = async (type, message, _data) => {
332
342
  messages.push(`${type}: ${message}`);
333
- await handler.sendNotification("kubb/progress", {
334
- type,
335
- message,
336
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
337
- ...data
338
- });
339
343
  };
340
344
  hooks.on("kubb:info", async ({ message }) => {
341
345
  await notify(NotifyTypes.INFO, message);
@@ -344,7 +348,7 @@ async function generate(schema, handler) {
344
348
  await notify(NotifyTypes.SUCCESS, message);
345
349
  });
346
350
  hooks.on("kubb:error", async ({ error }) => {
347
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
351
+ await notify(NotifyTypes.ERROR, error.message);
348
352
  });
349
353
  hooks.on("kubb:warn", async ({ message }) => {
350
354
  await notify(NotifyTypes.WARN, message);
@@ -376,7 +380,7 @@ async function generate(schema, handler) {
376
380
  const configResult = await loadUserConfig(configPath, { notify });
377
381
  userConfig = configResult.userConfig;
378
382
  cwd = configResult.cwd;
379
- if (Array.isArray(userConfig) && userConfig.length) throw new Error("Array type in kubb.config.ts is not supported in this tool. Please provide a single configuration object.");
383
+ if (Array.isArray(userConfig)) throw new Error("Array type in kubb.config.ts is not supported in this tool. Please provide a single configuration object.");
380
384
  userConfig = await resolveUserConfig(userConfig, {
381
385
  configPath,
382
386
  logLevel
@@ -384,15 +388,9 @@ async function generate(schema, handler) {
384
388
  } catch (error) {
385
389
  const errorMessage = error instanceof Error ? error.message : String(error);
386
390
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
387
- return {
388
- content: [{
389
- type: "text",
390
- text: errorMessage
391
- }],
392
- isError: true
393
- };
391
+ return tmcp_utils.tool.error(errorMessage);
394
392
  }
395
- const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
393
+ const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
396
394
  const config = {
397
395
  ...userConfig,
398
396
  root: resolveCwd(userConfig, cwd),
@@ -405,7 +403,7 @@ async function generate(schema, handler) {
405
403
  path: output
406
404
  } : userConfig.output
407
405
  };
408
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
406
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
409
407
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
410
408
  const kubb = (0, _kubb_core.createKubb)(config, { hooks });
411
409
  await kubb.setup();
@@ -415,79 +413,238 @@ async function generate(schema, handler) {
415
413
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
416
414
  if (error || failedPlugins.size > 0) {
417
415
  const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
418
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
419
- errorCount: allErrors.length,
420
- errors: allErrors.map((err) => err.message)
421
- });
422
- return {
423
- content: [{
424
- type: "text",
425
- text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
426
- }],
427
- isError: true
428
- };
416
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`);
417
+ return tmcp_utils.tool.error(`Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`);
429
418
  }
430
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, { filesCount: files.length });
431
- return { content: [{
432
- type: "text",
433
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
434
- }] };
419
+ await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
420
+ return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
435
421
  } catch (caughtError) {
436
- const error = caughtError;
437
- await handler.sendNotification("kubb/progress", {
438
- type: NotifyTypes.FATAL_ERROR,
439
- message: error.message,
440
- stack: error.stack,
441
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
442
- });
443
- return {
444
- content: [{
445
- type: "text",
446
- text: `Build error: ${error.message}\n${error.stack || ""}`
447
- }],
448
- isError: true
449
- };
422
+ const error = toError(caughtError);
423
+ return tmcp_utils.tool.error(`Build error: ${error.message}\n${error.stack ?? ""}`);
424
+ }
425
+ });
426
+ //#endregion
427
+ //#region ../../internals/shared/src/constants.ts
428
+ const KUBB_CONFIG_FILENAME = "kubb.config.ts";
429
+ const availablePlugins = [
430
+ {
431
+ value: "plugin-ts",
432
+ label: "TypeScript",
433
+ hint: "Recommended",
434
+ packageName: "@kubb/plugin-ts",
435
+ importName: "pluginTs",
436
+ category: "types"
437
+ },
438
+ {
439
+ value: "plugin-client",
440
+ label: "Client (Fetch/Axios)",
441
+ packageName: "@kubb/plugin-client",
442
+ importName: "pluginClient",
443
+ category: "client"
444
+ },
445
+ {
446
+ value: "plugin-react-query",
447
+ label: "React Query / TanStack Query",
448
+ packageName: "@kubb/plugin-react-query",
449
+ importName: "pluginReactQuery",
450
+ category: "framework"
451
+ },
452
+ {
453
+ value: "plugin-vue-query",
454
+ label: "Vue Query",
455
+ packageName: "@kubb/plugin-vue-query",
456
+ importName: "pluginVueQuery",
457
+ category: "framework"
458
+ },
459
+ {
460
+ value: "plugin-zod",
461
+ label: "Zod Schemas",
462
+ packageName: "@kubb/plugin-zod",
463
+ importName: "pluginZod",
464
+ category: "validation"
465
+ },
466
+ {
467
+ value: "plugin-faker",
468
+ label: "Faker.js Mocks",
469
+ packageName: "@kubb/plugin-faker",
470
+ importName: "pluginFaker",
471
+ category: "mocks"
472
+ },
473
+ {
474
+ value: "plugin-msw",
475
+ label: "MSW Handlers",
476
+ packageName: "@kubb/plugin-msw",
477
+ importName: "pluginMsw",
478
+ category: "mocks"
479
+ },
480
+ {
481
+ value: "plugin-cypress",
482
+ label: "Cypress Tests",
483
+ packageName: "@kubb/plugin-cypress",
484
+ importName: "pluginCypress",
485
+ category: "testing"
486
+ },
487
+ {
488
+ value: "plugin-mcp",
489
+ label: "MCP Server (AI / Model Context Protocol)",
490
+ packageName: "@kubb/plugin-mcp",
491
+ importName: "pluginMcp",
492
+ category: "ai"
493
+ },
494
+ {
495
+ value: "plugin-redoc",
496
+ label: "ReDoc Documentation",
497
+ packageName: "@kubb/plugin-redoc",
498
+ importName: "pluginRedoc",
499
+ category: "documentation"
450
500
  }
501
+ ];
502
+ const pluginDefaultConfigs = {
503
+ "plugin-ts": `pluginTs({
504
+ output: { path: 'models' },
505
+ })`,
506
+ "plugin-client": `pluginClient({
507
+ output: { path: 'clients' },
508
+ })`,
509
+ "plugin-react-query": `pluginReactQuery({
510
+ output: { path: 'hooks' },
511
+ })`,
512
+ "plugin-vue-query": `pluginVueQuery({
513
+ output: { path: 'hooks' },
514
+ })`,
515
+ "plugin-zod": `pluginZod({
516
+ output: { path: 'zod' },
517
+ })`,
518
+ "plugin-faker": `pluginFaker({
519
+ output: { path: 'mocks' },
520
+ })`,
521
+ "plugin-msw": `pluginMsw({
522
+ output: { path: 'msw' },
523
+ })`,
524
+ "plugin-cypress": `pluginCypress({
525
+ output: { path: 'cypress' },
526
+ })`,
527
+ "plugin-mcp": `pluginMcp({
528
+ output: { path: 'mcp' },
529
+ })`,
530
+ "plugin-redoc": `pluginRedoc({
531
+ output: { path: 'redoc' },
532
+ })`
533
+ };
534
+ //#endregion
535
+ //#region ../../internals/shared/src/init.ts
536
+ function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
537
+ return `import { defineConfig } from 'kubb'
538
+ ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
539
+
540
+ export default defineConfig({
541
+ root: '.',
542
+ input: {
543
+ path: '${inputPath}',
544
+ },
545
+ output: {
546
+ path: '${outputPath}',
547
+ clean: true,
548
+ },
549
+ plugins: [
550
+ ${selectedPlugins.map((plugin) => {
551
+ return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
552
+ }).join("\n")}
553
+ ],
554
+ })
555
+ `;
451
556
  }
452
557
  //#endregion
453
- //#region src/server.ts
454
- /**
455
- * Kubb MCP Server
456
- *
457
- * Provides tools for building OpenAPI specifications using Kubb.
458
- */
459
- async function startServer() {
558
+ //#region src/schemas/initSchema.ts
559
+ const initSchema = valibot.object({
560
+ input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
561
+ output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory (default: ./src/gen)"))),
562
+ plugins: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
563
+ });
564
+ //#endregion
565
+ //#region src/tools/init.ts
566
+ function resolvePlugins(pluginsFlag) {
567
+ if (!pluginsFlag) return [];
568
+ const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
569
+ return availablePlugins.filter((p) => requested.includes(p.value));
570
+ }
571
+ const initTool = (0, tmcp_tool.defineTool)({
572
+ name: "init",
573
+ description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
574
+ schema: initSchema
575
+ }, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
576
+ const selected = resolvePlugins(plugins);
577
+ const content = generateConfigFile({
578
+ selectedPlugins: selected,
579
+ inputPath: input,
580
+ outputPath: output
581
+ });
582
+ const dest = node_path.default.join(node_process.default.cwd(), KUBB_CONFIG_FILENAME);
583
+ 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.`);
584
+ node_fs.default.writeFileSync(dest, content, "utf-8");
585
+ const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
586
+ return tmcp_utils.tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
587
+ });
588
+ //#endregion
589
+ //#region src/schemas/validateSchema.ts
590
+ const validateSchema = valibot.object({ input: valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path or URL to the OpenAPI/Swagger specification")) });
591
+ //#endregion
592
+ //#region src/tools/validate.ts
593
+ const validateTool = (0, tmcp_tool.defineTool)({
594
+ name: "validate",
595
+ description: "Validate an OpenAPI/Swagger specification file or URL",
596
+ schema: validateSchema
597
+ }, async ({ input }) => {
598
+ let mod;
460
599
  try {
461
- const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
462
- const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
463
- name: "Kubb",
464
- version
465
- });
466
- server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
467
- return generate(args, { sendNotification: async (method, params) => {
468
- try {
469
- await transport.send({
470
- jsonrpc: "2.0",
471
- method,
472
- params
473
- });
474
- } catch (error) {
475
- console.error("Failed to send notification:", error);
476
- }
477
- } });
478
- });
479
- await server.connect(transport);
480
- } catch (error) {
481
- console.error("Failed to start MCP server:", error);
482
- node_process.default.exit(1);
600
+ mod = await import("@kubb/adapter-oas");
601
+ } catch {
602
+ return tmcp_utils.tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
603
+ }
604
+ try {
605
+ await mod.adapterOas().validate(input, { throwOnError: true });
606
+ return tmcp_utils.tool.text(`Validation successful: ${input}`);
607
+ } catch (err) {
608
+ return tmcp_utils.tool.error(`Validation failed:\n${err instanceof Error ? err.message : String(err)}`);
609
+ }
610
+ });
611
+ //#endregion
612
+ //#region src/server.ts
613
+ function createMcpServer() {
614
+ const server = new tmcp.McpServer({
615
+ name: "Kubb",
616
+ version
617
+ }, {
618
+ adapter: new _tmcp_adapter_valibot.ValibotJsonSchemaAdapter(),
619
+ capabilities: { tools: {} }
620
+ });
621
+ server.tools([
622
+ generateTool,
623
+ validateTool,
624
+ initTool
625
+ ]);
626
+ return server;
627
+ }
628
+ async function startServer({ port, host = "localhost" } = {}) {
629
+ const server = createMcpServer();
630
+ if (port === void 0) {
631
+ new _tmcp_transport_stdio.StdioTransport(server).listen();
632
+ return;
483
633
  }
634
+ const transport = new _tmcp_transport_http.HttpTransport(server, { path: "/mcp" });
635
+ node_http.default.createServer((0, _remix_run_node_fetch_server.createRequestListener)(async (request) => {
636
+ return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
637
+ })).listen(port, host, () => {
638
+ console.log(`Kubb MCP server on http://${host}:${port}`);
639
+ });
484
640
  }
485
641
  //#endregion
486
642
  //#region src/index.ts
487
- async function run(_argv) {
488
- await startServer();
643
+ async function run(_argv, options) {
644
+ await startServer(options);
489
645
  }
490
646
  //#endregion
647
+ exports.createMcpServer = createMcpServer;
491
648
  exports.run = run;
492
649
 
493
650
  //# sourceMappingURL=index.cjs.map