@kubb/mcp 5.0.0-alpha.9 → 5.0.0-beta.10

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,35 +1,33 @@
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";
9
+ import fs, { existsSync } from "node:fs";
7
10
  import path from "node:path";
8
- import { safeBuild, setup } from "@kubb/core";
9
- import createJiti from "jiti";
11
+ import { 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";
10
17
  //#region package.json
11
- var version = "5.0.0-alpha.9";
12
- //#endregion
13
- //#region src/schemas/generateSchema.ts
14
- const generateSchema = z.object({
15
- 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"),
16
- input: z.string().optional().describe("Path to OpenAPI/Swagger spec file (overrides config)"),
17
- output: z.string().optional().describe("Output directory path (overrides config)"),
18
- logLevel: z.enum([
19
- "silent",
20
- "error",
21
- "warn",
22
- "info",
23
- "verbose",
24
- "debug"
25
- ]).optional().default("info").describe("Log level for build output")
26
- });
18
+ var version = "5.0.0-beta.10";
27
19
  //#endregion
28
20
  //#region ../../internals/utils/src/errors.ts
29
21
  /**
30
22
  * Coerces an unknown thrown value to an `Error` instance.
31
- * When the value is already an `Error` it is returned as-is;
32
- * otherwise a new `Error` is created whose message is `String(value)`.
23
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * try { ... } catch(err) {
28
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
29
+ * }
30
+ * ```
33
31
  */
34
32
  function toError(value) {
35
33
  return value instanceof Error ? value : new Error(String(value));
@@ -37,12 +35,19 @@ function toError(value) {
37
35
  //#endregion
38
36
  //#region ../../internals/utils/src/asyncEventEmitter.ts
39
37
  /**
40
- * A typed EventEmitter that awaits all async listeners before resolving.
38
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
41
39
  * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
44
+ * emitter.on('build', async (name) => { console.log(name) })
45
+ * await emitter.emit('build', 'petstore') // all listeners awaited
46
+ * ```
42
47
  */
43
48
  var AsyncEventEmitter = class {
44
49
  /**
45
- * `maxListener` controls the maximum number of listeners per event before Node emits a memory-leak warning.
50
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
46
51
  * @default 10
47
52
  */
48
53
  constructor(maxListener = 10) {
@@ -50,31 +55,48 @@ var AsyncEventEmitter = class {
50
55
  }
51
56
  #emitter = new EventEmitter();
52
57
  /**
53
- * Emits an event and awaits all registered listeners in parallel.
58
+ * Emits `eventName` and awaits all registered listeners sequentially.
54
59
  * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * await emitter.emit('build', 'petstore')
64
+ * ```
55
65
  */
56
66
  async emit(eventName, ...eventArgs) {
57
67
  const listeners = this.#emitter.listeners(eventName);
58
68
  if (listeners.length === 0) return;
59
- await Promise.all(listeners.map(async (listener) => {
69
+ for (const listener of listeners) try {
70
+ await listener(...eventArgs);
71
+ } catch (err) {
72
+ let serializedArgs;
60
73
  try {
61
- return await listener(...eventArgs);
62
- } catch (err) {
63
- let serializedArgs;
64
- try {
65
- serializedArgs = JSON.stringify(eventArgs);
66
- } catch {
67
- serializedArgs = String(eventArgs);
68
- }
69
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
74
+ serializedArgs = JSON.stringify(eventArgs);
75
+ } catch {
76
+ serializedArgs = String(eventArgs);
70
77
  }
71
- }));
78
+ throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
79
+ }
72
80
  }
73
- /** Registers a persistent listener for the given event. */
81
+ /**
82
+ * Registers a persistent listener for `eventName`.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * emitter.on('build', async (name) => { console.log(name) })
87
+ * ```
88
+ */
74
89
  on(eventName, handler) {
75
90
  this.#emitter.on(eventName, handler);
76
91
  }
77
- /** Registers a one-shot listener that removes itself after the first invocation. */
92
+ /**
93
+ * Registers a one-shot listener that removes itself after the first invocation.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * emitter.onOnce('build', async (name) => { console.log(name) })
98
+ * ```
99
+ */
78
100
  onOnce(eventName, handler) {
79
101
  const wrapper = (...args) => {
80
102
  this.off(eventName, wrapper);
@@ -82,22 +104,70 @@ var AsyncEventEmitter = class {
82
104
  };
83
105
  this.on(eventName, wrapper);
84
106
  }
85
- /** Removes a previously registered listener. */
107
+ /**
108
+ * Removes a previously registered listener.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * emitter.off('build', handler)
113
+ * ```
114
+ */
86
115
  off(eventName, handler) {
87
116
  this.#emitter.off(eventName, handler);
88
117
  }
89
- /** Removes all listeners from every event channel. */
118
+ /**
119
+ * Returns the number of listeners registered for `eventName`.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * emitter.on('build', handler)
124
+ * emitter.listenerCount('build') // 1
125
+ * ```
126
+ */
127
+ listenerCount(eventName) {
128
+ return this.#emitter.listenerCount(eventName);
129
+ }
130
+ /**
131
+ * Removes all listeners from every event channel.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * emitter.removeAll()
136
+ * ```
137
+ */
90
138
  removeAll() {
91
139
  this.#emitter.removeAllListeners();
92
140
  }
93
141
  };
94
142
  //#endregion
95
143
  //#region ../../internals/utils/src/promise.ts
96
- /** Returns `true` when `result` is a thenable `Promise`. */
144
+ /** Returns `true` when `result` is a thenable `Promise`.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * isPromise(Promise.resolve(1)) // true
149
+ * isPromise(42) // false
150
+ * ```
151
+ */
97
152
  function isPromise(result) {
98
153
  return result !== null && result !== void 0 && typeof result["then"] === "function";
99
154
  }
100
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
101
171
  //#region src/types.ts
102
172
  const NotifyTypes = {
103
173
  INFO: "INFO",
@@ -123,45 +193,87 @@ const NotifyTypes = {
123
193
  FATAL_ERROR: "FATAL_ERROR"
124
194
  };
125
195
  //#endregion
196
+ //#region src/constants.ts
197
+ const ALLOWED_CONFIG_EXTENSIONS = new Set([
198
+ ".ts",
199
+ ".mts",
200
+ ".cts",
201
+ ".js",
202
+ ".mjs",
203
+ ".cjs"
204
+ ]);
205
+ //#endregion
126
206
  //#region src/utils/loadUserConfig.ts
127
- const jiti = createJiti(import.meta.url, { sourceMaps: true });
128
- /**
129
- * Load the user configuration from the specified path or current directory
130
- */
207
+ const jiti = createJiti(import.meta.url, {
208
+ jsx: {
209
+ runtime: "automatic",
210
+ importSource: "@kubb/renderer-jsx"
211
+ },
212
+ moduleCache: false
213
+ });
214
+ const loadedModules = /* @__PURE__ */ new Map();
215
+ async function loadModule(filePath) {
216
+ const ext = path.extname(filePath);
217
+ if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
218
+ if (loadedModules.has(filePath)) return loadedModules.get(filePath);
219
+ const mod = await jiti.import(filePath, { default: true });
220
+ loadedModules.set(filePath, mod);
221
+ return mod;
222
+ }
131
223
  async function loadUserConfig(configPath, { notify }) {
132
- let userConfig;
133
- let cwd;
134
224
  if (configPath) {
135
- cwd = path.dirname(path.resolve(configPath));
225
+ const ext = path.extname(configPath);
226
+ if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
227
+ const msg = `Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`;
228
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
229
+ throw new Error(msg);
230
+ }
231
+ const base = path.resolve(process.cwd());
232
+ const resolvedConfigPath = path.resolve(base, configPath);
233
+ const relative = path.relative(base, resolvedConfigPath);
234
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
235
+ const msg = "Invalid config file path: must be within the current working directory";
236
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
237
+ throw new Error(msg);
238
+ }
239
+ const cwd = path.dirname(resolvedConfigPath);
136
240
  try {
137
- userConfig = await jiti.import(configPath, { default: true });
138
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${configPath}`);
241
+ const userConfig = await loadModule(resolvedConfigPath);
242
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
243
+ return {
244
+ userConfig,
245
+ cwd
246
+ };
139
247
  } catch (error) {
140
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
141
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
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);
142
251
  }
143
- } else {
144
- cwd = process.cwd();
145
- const configFileNames = [
146
- "kubb.config.ts",
147
- "kubb.config.js",
148
- "kubb.config.cjs"
149
- ];
150
- for (const configFileName of configFileNames) try {
151
- const configFilePath = path.resolve(process.cwd(), configFileName);
152
- userConfig = await jiti.import(configFilePath, { default: true });
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);
153
266
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
154
- break;
155
- } catch {}
156
- if (!userConfig) {
157
- await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
158
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
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)}`);
159
273
  }
160
274
  }
161
- return {
162
- userConfig,
163
- cwd
164
- };
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(", ")}`);
165
277
  }
166
278
  //#endregion
167
279
  //#region src/utils/resolveCwd.ts
@@ -180,72 +292,59 @@ function resolveCwd(userConfig, cwd) {
180
292
  }
181
293
  //#endregion
182
294
  //#region src/utils/resolveUserConfig.ts
183
- /**
184
- * Resolve the config by handling function configs and returning the final configuration
185
- */
186
- async function resolveUserConfig(userConfig, options) {
187
- let kubbUserConfig = Promise.resolve(userConfig);
188
- if (typeof userConfig === "function") {
189
- const possiblePromise = userConfig({
190
- logLevel: options.logLevel,
191
- config: options.configPath
192
- });
193
- if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
194
- else kubbUserConfig = Promise.resolve(possiblePromise);
195
- }
196
- return await kubbUserConfig;
295
+ async function resolveUserConfig(config, options) {
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;
197
302
  }
198
303
  //#endregion
199
304
  //#region src/tools/generate.ts
200
- /**
201
- * Build tool that generates code from OpenAPI specs using Kubb.
202
- * Sends real-time notifications of build progress and events.
203
- */
204
- 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) {
205
310
  const { config: configPath, input, output, logLevel } = schema;
206
311
  try {
207
- const events = new AsyncEventEmitter();
312
+ const hooks = new AsyncEventEmitter();
208
313
  const messages = [];
209
- const notify = async (type, message, data) => {
314
+ const notify = async (type, message, _data) => {
210
315
  messages.push(`${type}: ${message}`);
211
- await handler.sendNotification("kubb/progress", {
212
- type,
213
- message,
214
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
215
- ...data
216
- });
217
316
  };
218
- events.on("info", async (message) => {
317
+ hooks.on("kubb:info", async ({ message }) => {
219
318
  await notify(NotifyTypes.INFO, message);
220
319
  });
221
- events.on("success", async (message) => {
320
+ hooks.on("kubb:success", async ({ message }) => {
222
321
  await notify(NotifyTypes.SUCCESS, message);
223
322
  });
224
- events.on("error", async (error) => {
225
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
323
+ hooks.on("kubb:error", async ({ error }) => {
324
+ await notify(NotifyTypes.ERROR, error.message);
226
325
  });
227
- events.on("warn", async (message) => {
326
+ hooks.on("kubb:warn", async ({ message }) => {
228
327
  await notify(NotifyTypes.WARN, message);
229
328
  });
230
- events.on("plugin:start", async ({ name }) => {
231
- await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${name}`);
329
+ hooks.on("kubb:plugin:start", async ({ plugin }) => {
330
+ await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
232
331
  });
233
- events.on("plugin:end", async ({ name, duration }) => {
234
- await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${name}`, { duration });
332
+ hooks.on("kubb:plugin:end", async ({ plugin, duration }) => {
333
+ await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${plugin.name}`, { duration });
235
334
  });
236
- events.on("files:processing:start", async () => {
335
+ hooks.on("kubb:files:processing:start", async () => {
237
336
  await notify(NotifyTypes.FILES_START, "Starting file processing");
238
337
  });
239
- events.on("file:processing:update", async ({ file }) => {
338
+ hooks.on("kubb:file:processing:update", async ({ file }) => {
240
339
  await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
241
340
  });
242
- events.on("files:processing:end", async () => {
341
+ hooks.on("kubb:files:processing:end", async () => {
243
342
  await notify(NotifyTypes.FILES_END, "File processing complete");
244
343
  });
245
- events.on("generation:start", async () => {
344
+ hooks.on("kubb:generation:start", async () => {
246
345
  await notify(NotifyTypes.GENERATION_START, "Generation started");
247
346
  });
248
- events.on("generation:end", async () => {
347
+ hooks.on("kubb:generation:end", async () => {
249
348
  await notify(NotifyTypes.GENERATION_END, "Generation ended");
250
349
  });
251
350
  let userConfig;
@@ -254,7 +353,7 @@ async function generate(schema, handler) {
254
353
  const configResult = await loadUserConfig(configPath, { notify });
255
354
  userConfig = configResult.userConfig;
256
355
  cwd = configResult.cwd;
257
- 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.");
258
357
  userConfig = await resolveUserConfig(userConfig, {
259
358
  configPath,
260
359
  logLevel
@@ -262,15 +361,9 @@ async function generate(schema, handler) {
262
361
  } catch (error) {
263
362
  const errorMessage = error instanceof Error ? error.message : String(error);
264
363
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
265
- return {
266
- content: [{
267
- type: "text",
268
- text: errorMessage
269
- }],
270
- isError: true
271
- };
364
+ return tool.error(errorMessage);
272
365
  }
273
- const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
366
+ const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
274
367
  const config = {
275
368
  ...userConfig,
276
369
  root: resolveCwd(userConfig, cwd),
@@ -283,99 +376,244 @@ async function generate(schema, handler) {
283
376
  path: output
284
377
  } : userConfig.output
285
378
  };
286
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
379
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
287
380
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
288
- const { fabric, driver, sources } = await setup({
289
- config,
290
- events
291
- });
381
+ const kubb = createKubb(config, { hooks });
382
+ await kubb.setup();
292
383
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
293
384
  await notify(NotifyTypes.BUILD_START, "Starting build");
294
- const { files, failedPlugins, error } = await safeBuild({
295
- config,
296
- events
297
- }, {
298
- driver,
299
- fabric,
300
- events,
301
- sources
302
- });
385
+ const { files, failedPlugins, error } = await kubb.safeBuild();
303
386
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
304
387
  if (error || failedPlugins.size > 0) {
305
388
  const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
306
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
307
- errorCount: allErrors.length,
308
- errors: allErrors.map((err) => err.message)
309
- });
310
- return {
311
- content: [{
312
- type: "text",
313
- text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
314
- }],
315
- isError: true
316
- };
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")}`);
317
391
  }
318
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, { filesCount: files.length });
319
- return { content: [{
320
- type: "text",
321
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
322
- }] };
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")}`);
323
394
  } catch (caughtError) {
324
- const error = caughtError;
325
- await handler.sendNotification("kubb/progress", {
326
- type: NotifyTypes.FATAL_ERROR,
327
- message: error.message,
328
- stack: error.stack,
329
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
330
- });
331
- return {
332
- content: [{
333
- type: "text",
334
- text: `Build error: ${error.message}\n${error.stack || ""}`
335
- }],
336
- isError: true
337
- };
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"
338
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
+ `;
339
529
  }
340
530
  //#endregion
341
- //#region src/server.ts
342
- /**
343
- * Kubb MCP Server
344
- *
345
- * Provides tools for building OpenAPI specifications using Kubb.
346
- */
347
- 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;
348
569
  try {
349
- const transport = new StdioServerTransport();
350
- const server = new McpServer({
351
- name: "Kubb",
352
- version
353
- });
354
- server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
355
- return generate(args, { sendNotification: async (method, params) => {
356
- try {
357
- await transport.send({
358
- jsonrpc: "2.0",
359
- method,
360
- params
361
- });
362
- } catch (error) {
363
- console.error("Failed to send notification:", error);
364
- }
365
- } });
366
- });
367
- await server.connect(transport);
368
- } catch (error) {
369
- console.error("Failed to start MCP server:", error);
370
- 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
+ }, {
588
+ adapter: new ValibotJsonSchemaAdapter(),
589
+ capabilities: { tools: {} }
590
+ });
591
+ server.tools([
592
+ generateTool,
593
+ validateTool,
594
+ initTool
595
+ ]);
596
+ return server;
597
+ }
598
+ async function startServer({ port, host = "localhost" } = {}) {
599
+ const server = createMcpServer();
600
+ if (port === void 0) {
601
+ new StdioTransport(server).listen();
602
+ return;
371
603
  }
604
+ const transport = new HttpTransport(server, { path: "/mcp" });
605
+ http.createServer(createRequestListener(async (request) => {
606
+ return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
607
+ })).listen(port, host, () => {
608
+ console.log(`Kubb MCP server on http://${host}:${port}`);
609
+ });
372
610
  }
373
611
  //#endregion
374
612
  //#region src/index.ts
375
- async function run(_argv) {
376
- await startServer();
613
+ async function run(_argv, options) {
614
+ await startServer(options);
377
615
  }
378
616
  //#endregion
379
- export { run };
617
+ export { createMcpServer, run };
380
618
 
381
619
  //# sourceMappingURL=index.js.map