@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.cjs CHANGED
@@ -21,40 +21,40 @@ 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);
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");
32
+ let node_fs = require("node:fs");
33
+ node_fs = __toESM(node_fs, 1);
30
34
  let node_path = require("node:path");
31
- node_path = __toESM(node_path);
35
+ node_path = __toESM(node_path, 1);
32
36
  let _kubb_core = require("@kubb/core");
37
+ let tmcp_tool = require("tmcp/tool");
38
+ let tmcp_utils = require("tmcp/utils");
39
+ let valibot = require("valibot");
40
+ valibot = __toESM(valibot, 1);
33
41
  let jiti = require("jiti");
34
- jiti = __toESM(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-alpha.9";
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.10";
52
46
  //#endregion
53
47
  //#region ../../internals/utils/src/errors.ts
54
48
  /**
55
49
  * Coerces an unknown thrown value to an `Error` instance.
56
- * When the value is already an `Error` it is returned as-is;
57
- * otherwise a new `Error` is created whose message is `String(value)`.
50
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * try { ... } catch(err) {
55
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
56
+ * }
57
+ * ```
58
58
  */
59
59
  function toError(value) {
60
60
  return value instanceof Error ? value : new Error(String(value));
@@ -62,12 +62,19 @@ function toError(value) {
62
62
  //#endregion
63
63
  //#region ../../internals/utils/src/asyncEventEmitter.ts
64
64
  /**
65
- * A typed EventEmitter that awaits all async listeners before resolving.
65
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
66
66
  * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
71
+ * emitter.on('build', async (name) => { console.log(name) })
72
+ * await emitter.emit('build', 'petstore') // all listeners awaited
73
+ * ```
67
74
  */
68
75
  var AsyncEventEmitter = class {
69
76
  /**
70
- * `maxListener` controls the maximum number of listeners per event before Node emits a memory-leak warning.
77
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
71
78
  * @default 10
72
79
  */
73
80
  constructor(maxListener = 10) {
@@ -75,31 +82,48 @@ var AsyncEventEmitter = class {
75
82
  }
76
83
  #emitter = new node_events.EventEmitter();
77
84
  /**
78
- * Emits an event and awaits all registered listeners in parallel.
85
+ * Emits `eventName` and awaits all registered listeners sequentially.
79
86
  * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * await emitter.emit('build', 'petstore')
91
+ * ```
80
92
  */
81
93
  async emit(eventName, ...eventArgs) {
82
94
  const listeners = this.#emitter.listeners(eventName);
83
95
  if (listeners.length === 0) return;
84
- await Promise.all(listeners.map(async (listener) => {
96
+ for (const listener of listeners) try {
97
+ await listener(...eventArgs);
98
+ } catch (err) {
99
+ let serializedArgs;
85
100
  try {
86
- return await listener(...eventArgs);
87
- } catch (err) {
88
- let serializedArgs;
89
- try {
90
- serializedArgs = JSON.stringify(eventArgs);
91
- } catch {
92
- serializedArgs = String(eventArgs);
93
- }
94
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
101
+ serializedArgs = JSON.stringify(eventArgs);
102
+ } catch {
103
+ serializedArgs = String(eventArgs);
95
104
  }
96
- }));
105
+ throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
106
+ }
97
107
  }
98
- /** Registers a persistent listener for the given event. */
108
+ /**
109
+ * Registers a persistent listener for `eventName`.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * emitter.on('build', async (name) => { console.log(name) })
114
+ * ```
115
+ */
99
116
  on(eventName, handler) {
100
117
  this.#emitter.on(eventName, handler);
101
118
  }
102
- /** Registers a one-shot listener that removes itself after the first invocation. */
119
+ /**
120
+ * Registers a one-shot listener that removes itself after the first invocation.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * emitter.onOnce('build', async (name) => { console.log(name) })
125
+ * ```
126
+ */
103
127
  onOnce(eventName, handler) {
104
128
  const wrapper = (...args) => {
105
129
  this.off(eventName, wrapper);
@@ -107,22 +131,70 @@ var AsyncEventEmitter = class {
107
131
  };
108
132
  this.on(eventName, wrapper);
109
133
  }
110
- /** Removes a previously registered listener. */
134
+ /**
135
+ * Removes a previously registered listener.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * emitter.off('build', handler)
140
+ * ```
141
+ */
111
142
  off(eventName, handler) {
112
143
  this.#emitter.off(eventName, handler);
113
144
  }
114
- /** Removes all listeners from every event channel. */
145
+ /**
146
+ * Returns the number of listeners registered for `eventName`.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * emitter.on('build', handler)
151
+ * emitter.listenerCount('build') // 1
152
+ * ```
153
+ */
154
+ listenerCount(eventName) {
155
+ return this.#emitter.listenerCount(eventName);
156
+ }
157
+ /**
158
+ * Removes all listeners from every event channel.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * emitter.removeAll()
163
+ * ```
164
+ */
115
165
  removeAll() {
116
166
  this.#emitter.removeAllListeners();
117
167
  }
118
168
  };
119
169
  //#endregion
120
170
  //#region ../../internals/utils/src/promise.ts
121
- /** Returns `true` when `result` is a thenable `Promise`. */
171
+ /** Returns `true` when `result` is a thenable `Promise`.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * isPromise(Promise.resolve(1)) // true
176
+ * isPromise(42) // false
177
+ * ```
178
+ */
122
179
  function isPromise(result) {
123
180
  return result !== null && result !== void 0 && typeof result["then"] === "function";
124
181
  }
125
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
126
198
  //#region src/types.ts
127
199
  const NotifyTypes = {
128
200
  INFO: "INFO",
@@ -148,45 +220,87 @@ const NotifyTypes = {
148
220
  FATAL_ERROR: "FATAL_ERROR"
149
221
  };
150
222
  //#endregion
223
+ //#region src/constants.ts
224
+ const ALLOWED_CONFIG_EXTENSIONS = new Set([
225
+ ".ts",
226
+ ".mts",
227
+ ".cts",
228
+ ".js",
229
+ ".mjs",
230
+ ".cjs"
231
+ ]);
232
+ //#endregion
151
233
  //#region src/utils/loadUserConfig.ts
152
- const jiti$1 = (0, jiti.default)(require("url").pathToFileURL(__filename).href, { sourceMaps: true });
153
- /**
154
- * Load the user configuration from the specified path or current directory
155
- */
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
+ });
241
+ const loadedModules = /* @__PURE__ */ new Map();
242
+ async function loadModule(filePath) {
243
+ const ext = node_path.default.extname(filePath);
244
+ if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
245
+ if (loadedModules.has(filePath)) return loadedModules.get(filePath);
246
+ const mod = await jiti$1.import(filePath, { default: true });
247
+ loadedModules.set(filePath, mod);
248
+ return mod;
249
+ }
156
250
  async function loadUserConfig(configPath, { notify }) {
157
- let userConfig;
158
- let cwd;
159
251
  if (configPath) {
160
- cwd = node_path.default.dirname(node_path.default.resolve(configPath));
252
+ const ext = node_path.default.extname(configPath);
253
+ if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
254
+ const msg = `Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`;
255
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
256
+ throw new Error(msg);
257
+ }
258
+ const base = node_path.default.resolve(process.cwd());
259
+ const resolvedConfigPath = node_path.default.resolve(base, configPath);
260
+ const relative = node_path.default.relative(base, resolvedConfigPath);
261
+ if (relative.startsWith("..") || node_path.default.isAbsolute(relative)) {
262
+ const msg = "Invalid config file path: must be within the current working directory";
263
+ await notify(NotifyTypes.CONFIG_ERROR, msg);
264
+ throw new Error(msg);
265
+ }
266
+ const cwd = node_path.default.dirname(resolvedConfigPath);
161
267
  try {
162
- userConfig = await jiti$1.import(configPath, { default: true });
163
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${configPath}`);
268
+ const userConfig = await loadModule(resolvedConfigPath);
269
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
270
+ return {
271
+ userConfig,
272
+ cwd
273
+ };
164
274
  } catch (error) {
165
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
166
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
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);
167
278
  }
168
- } else {
169
- cwd = process.cwd();
170
- const configFileNames = [
171
- "kubb.config.ts",
172
- "kubb.config.js",
173
- "kubb.config.cjs"
174
- ];
175
- for (const configFileName of configFileNames) try {
176
- const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
177
- userConfig = await jiti$1.import(configFilePath, { default: true });
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);
178
293
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
179
- break;
180
- } catch {}
181
- if (!userConfig) {
182
- await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
183
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
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)}`);
184
300
  }
185
301
  }
186
- return {
187
- userConfig,
188
- cwd
189
- };
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(", ")}`);
190
304
  }
191
305
  //#endregion
192
306
  //#region src/utils/resolveCwd.ts
@@ -205,72 +319,59 @@ function resolveCwd(userConfig, cwd) {
205
319
  }
206
320
  //#endregion
207
321
  //#region src/utils/resolveUserConfig.ts
208
- /**
209
- * Resolve the config by handling function configs and returning the final configuration
210
- */
211
- async function resolveUserConfig(userConfig, options) {
212
- let kubbUserConfig = Promise.resolve(userConfig);
213
- if (typeof userConfig === "function") {
214
- const possiblePromise = userConfig({
215
- logLevel: options.logLevel,
216
- config: options.configPath
217
- });
218
- if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
219
- else kubbUserConfig = Promise.resolve(possiblePromise);
220
- }
221
- return await kubbUserConfig;
322
+ async function resolveUserConfig(config, options) {
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;
222
329
  }
223
330
  //#endregion
224
331
  //#region src/tools/generate.ts
225
- /**
226
- * Build tool that generates code from OpenAPI specs using Kubb.
227
- * Sends real-time notifications of build progress and events.
228
- */
229
- 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) {
230
337
  const { config: configPath, input, output, logLevel } = schema;
231
338
  try {
232
- const events = new AsyncEventEmitter();
339
+ const hooks = new AsyncEventEmitter();
233
340
  const messages = [];
234
- const notify = async (type, message, data) => {
341
+ const notify = async (type, message, _data) => {
235
342
  messages.push(`${type}: ${message}`);
236
- await handler.sendNotification("kubb/progress", {
237
- type,
238
- message,
239
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
240
- ...data
241
- });
242
343
  };
243
- events.on("info", async (message) => {
344
+ hooks.on("kubb:info", async ({ message }) => {
244
345
  await notify(NotifyTypes.INFO, message);
245
346
  });
246
- events.on("success", async (message) => {
347
+ hooks.on("kubb:success", async ({ message }) => {
247
348
  await notify(NotifyTypes.SUCCESS, message);
248
349
  });
249
- events.on("error", async (error) => {
250
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack });
350
+ hooks.on("kubb:error", async ({ error }) => {
351
+ await notify(NotifyTypes.ERROR, error.message);
251
352
  });
252
- events.on("warn", async (message) => {
353
+ hooks.on("kubb:warn", async ({ message }) => {
253
354
  await notify(NotifyTypes.WARN, message);
254
355
  });
255
- events.on("plugin:start", async ({ name }) => {
256
- await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${name}`);
356
+ hooks.on("kubb:plugin:start", async ({ plugin }) => {
357
+ await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
257
358
  });
258
- events.on("plugin:end", async ({ name, duration }) => {
259
- await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${name}`, { duration });
359
+ hooks.on("kubb:plugin:end", async ({ plugin, duration }) => {
360
+ await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${plugin.name}`, { duration });
260
361
  });
261
- events.on("files:processing:start", async () => {
362
+ hooks.on("kubb:files:processing:start", async () => {
262
363
  await notify(NotifyTypes.FILES_START, "Starting file processing");
263
364
  });
264
- events.on("file:processing:update", async ({ file }) => {
365
+ hooks.on("kubb:file:processing:update", async ({ file }) => {
265
366
  await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
266
367
  });
267
- events.on("files:processing:end", async () => {
368
+ hooks.on("kubb:files:processing:end", async () => {
268
369
  await notify(NotifyTypes.FILES_END, "File processing complete");
269
370
  });
270
- events.on("generation:start", async () => {
371
+ hooks.on("kubb:generation:start", async () => {
271
372
  await notify(NotifyTypes.GENERATION_START, "Generation started");
272
373
  });
273
- events.on("generation:end", async () => {
374
+ hooks.on("kubb:generation:end", async () => {
274
375
  await notify(NotifyTypes.GENERATION_END, "Generation ended");
275
376
  });
276
377
  let userConfig;
@@ -279,7 +380,7 @@ async function generate(schema, handler) {
279
380
  const configResult = await loadUserConfig(configPath, { notify });
280
381
  userConfig = configResult.userConfig;
281
382
  cwd = configResult.cwd;
282
- 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.");
283
384
  userConfig = await resolveUserConfig(userConfig, {
284
385
  configPath,
285
386
  logLevel
@@ -287,15 +388,9 @@ async function generate(schema, handler) {
287
388
  } catch (error) {
288
389
  const errorMessage = error instanceof Error ? error.message : String(error);
289
390
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
290
- return {
291
- content: [{
292
- type: "text",
293
- text: errorMessage
294
- }],
295
- isError: true
296
- };
391
+ return tmcp_utils.tool.error(errorMessage);
297
392
  }
298
- 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);
299
394
  const config = {
300
395
  ...userConfig,
301
396
  root: resolveCwd(userConfig, cwd),
@@ -308,99 +403,248 @@ async function generate(schema, handler) {
308
403
  path: output
309
404
  } : userConfig.output
310
405
  };
311
- await notify(NotifyTypes.CONFIG_READY, "Configuration ready", { root: config.root });
406
+ await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
312
407
  await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
313
- const { fabric, driver, sources } = await (0, _kubb_core.setup)({
314
- config,
315
- events
316
- });
408
+ const kubb = (0, _kubb_core.createKubb)(config, { hooks });
409
+ await kubb.setup();
317
410
  await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
318
411
  await notify(NotifyTypes.BUILD_START, "Starting build");
319
- const { files, failedPlugins, error } = await (0, _kubb_core.safeBuild)({
320
- config,
321
- events
322
- }, {
323
- driver,
324
- fabric,
325
- events,
326
- sources
327
- });
412
+ const { files, failedPlugins, error } = await kubb.safeBuild();
328
413
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
329
414
  if (error || failedPlugins.size > 0) {
330
415
  const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
331
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
332
- errorCount: allErrors.length,
333
- errors: allErrors.map((err) => err.message)
334
- });
335
- return {
336
- content: [{
337
- type: "text",
338
- text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
339
- }],
340
- isError: true
341
- };
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")}`);
342
418
  }
343
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, { filesCount: files.length });
344
- return { content: [{
345
- type: "text",
346
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
347
- }] };
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")}`);
348
421
  } catch (caughtError) {
349
- const error = caughtError;
350
- await handler.sendNotification("kubb/progress", {
351
- type: NotifyTypes.FATAL_ERROR,
352
- message: error.message,
353
- stack: error.stack,
354
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
355
- });
356
- return {
357
- content: [{
358
- type: "text",
359
- text: `Build error: ${error.message}\n${error.stack || ""}`
360
- }],
361
- isError: true
362
- };
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"
363
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
+ `;
364
556
  }
365
557
  //#endregion
366
- //#region src/server.ts
367
- /**
368
- * Kubb MCP Server
369
- *
370
- * Provides tools for building OpenAPI specifications using Kubb.
371
- */
372
- 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;
373
599
  try {
374
- const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
375
- const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
376
- name: "Kubb",
377
- version
378
- });
379
- server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
380
- return generate(args, { sendNotification: async (method, params) => {
381
- try {
382
- await transport.send({
383
- jsonrpc: "2.0",
384
- method,
385
- params
386
- });
387
- } catch (error) {
388
- console.error("Failed to send notification:", error);
389
- }
390
- } });
391
- });
392
- await server.connect(transport);
393
- } catch (error) {
394
- console.error("Failed to start MCP server:", error);
395
- 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;
396
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
+ });
397
640
  }
398
641
  //#endregion
399
642
  //#region src/index.ts
400
- async function run(_argv) {
401
- await startServer();
643
+ async function run(_argv, options) {
644
+ await startServer(options);
402
645
  }
403
646
  //#endregion
647
+ exports.createMcpServer = createMcpServer;
404
648
  exports.run = run;
405
649
 
406
650
  //# sourceMappingURL=index.cjs.map