@kubb/mcp 5.0.0-beta.4 → 5.0.0-beta.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -17
- package/dist/index.cjs +368 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +364 -162
- package/dist/index.js.map +1 -1
- package/package.json +18 -13
- package/src/index.ts +5 -2
- package/src/schemas/generateSchema.ts +11 -10
- package/src/schemas/initSchema.ts +7 -0
- package/src/schemas/validateSchema.ts +5 -0
- package/src/server.ts +42 -41
- package/src/tools/generate.ts +115 -178
- package/src/tools/init.ts +37 -0
- package/src/tools/validate.ts +28 -0
- package/src/types.ts +2 -1
- package/src/utils/formatDiagnostics.ts +29 -0
- package/src/utils/loadUserConfig.ts +32 -31
- package/src/utils/resolveUserConfig.ts +5 -20
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
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
|
|
25
|
-
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
let
|
|
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
|
|
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.
|
|
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.41";
|
|
52
46
|
//#endregion
|
|
53
47
|
//#region ../../internals/utils/src/errors.ts
|
|
54
48
|
/**
|
|
@@ -96,9 +90,12 @@ var AsyncEventEmitter = class {
|
|
|
96
90
|
* await emitter.emit('build', 'petstore')
|
|
97
91
|
* ```
|
|
98
92
|
*/
|
|
99
|
-
|
|
93
|
+
emit(eventName, ...eventArgs) {
|
|
100
94
|
const listeners = this.#emitter.listeners(eventName);
|
|
101
95
|
if (listeners.length === 0) return;
|
|
96
|
+
return this.#emitAll(eventName, listeners, eventArgs);
|
|
97
|
+
}
|
|
98
|
+
async #emitAll(eventName, listeners, eventArgs) {
|
|
102
99
|
for (const listener of listeners) try {
|
|
103
100
|
await listener(...eventArgs);
|
|
104
101
|
} catch (err) {
|
|
@@ -161,6 +158,24 @@ var AsyncEventEmitter = class {
|
|
|
161
158
|
return this.#emitter.listenerCount(eventName);
|
|
162
159
|
}
|
|
163
160
|
/**
|
|
161
|
+
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
|
|
162
|
+
* Set this above the expected listener count when many listeners attach by design.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* emitter.setMaxListeners(40)
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
setMaxListeners(max) {
|
|
170
|
+
this.#emitter.setMaxListeners(max);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Returns the current per-event listener ceiling.
|
|
174
|
+
*/
|
|
175
|
+
getMaxListeners() {
|
|
176
|
+
return this.#emitter.getMaxListeners();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
164
179
|
* Removes all listeners from every event channel.
|
|
165
180
|
*
|
|
166
181
|
* @example
|
|
@@ -186,16 +201,48 @@ function isPromise(result) {
|
|
|
186
201
|
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
187
202
|
}
|
|
188
203
|
//#endregion
|
|
204
|
+
//#region src/schemas/generateSchema.ts
|
|
205
|
+
const generateSchema = valibot.object({
|
|
206
|
+
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"))),
|
|
207
|
+
input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
208
|
+
output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
|
|
209
|
+
logLevel: valibot.optional(valibot.pipe(valibot.picklist([
|
|
210
|
+
"silent",
|
|
211
|
+
"info",
|
|
212
|
+
"verbose"
|
|
213
|
+
]), valibot.description("Log level for build output")), "info")
|
|
214
|
+
});
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/utils/formatDiagnostics.ts
|
|
217
|
+
/**
|
|
218
|
+
* Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
|
|
219
|
+
* keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
|
|
220
|
+
* the agent can act on the problem rather than parsing a bare message. No ANSI styling,
|
|
221
|
+
* unlike the CLI renderer.
|
|
222
|
+
*/
|
|
223
|
+
function formatDiagnostics(diagnostics) {
|
|
224
|
+
return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
|
|
225
|
+
}
|
|
226
|
+
function formatDiagnostic(diagnostic) {
|
|
227
|
+
const { code, severity, message, location, help, plugin, docsUrl } = diagnostic;
|
|
228
|
+
const lines = [`${severity} ${plugin ? `${plugin}(${code})` : code}: ${message}`];
|
|
229
|
+
if (location && "pointer" in location) lines.push(` at ${location.pointer}`);
|
|
230
|
+
if (help) lines.push(` help: ${help}`);
|
|
231
|
+
if (docsUrl) lines.push(` docs: ${docsUrl}`);
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
189
235
|
//#region src/types.ts
|
|
190
236
|
const NotifyTypes = {
|
|
191
237
|
INFO: "INFO",
|
|
192
238
|
SUCCESS: "SUCCESS",
|
|
193
239
|
ERROR: "ERROR",
|
|
194
240
|
WARN: "WARN",
|
|
241
|
+
DIAGNOSTIC: "DIAGNOSTIC",
|
|
195
242
|
PLUGIN_START: "PLUGIN_START",
|
|
196
243
|
PLUGIN_END: "PLUGIN_END",
|
|
197
244
|
FILES_START: "FILES_START",
|
|
198
|
-
|
|
245
|
+
FILES_UPDATE: "FILES_UPDATE",
|
|
199
246
|
FILES_END: "FILES_END",
|
|
200
247
|
GENERATION_START: "GENERATION_START",
|
|
201
248
|
GENERATION_END: "GENERATION_END",
|
|
@@ -222,18 +269,23 @@ const ALLOWED_CONFIG_EXTENSIONS = new Set([
|
|
|
222
269
|
]);
|
|
223
270
|
//#endregion
|
|
224
271
|
//#region src/utils/loadUserConfig.ts
|
|
272
|
+
const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, {
|
|
273
|
+
jsx: {
|
|
274
|
+
runtime: "automatic",
|
|
275
|
+
importSource: "@kubb/renderer-jsx"
|
|
276
|
+
},
|
|
277
|
+
moduleCache: false
|
|
278
|
+
});
|
|
225
279
|
const loadedModules = /* @__PURE__ */ new Map();
|
|
226
280
|
async function loadModule(filePath) {
|
|
227
281
|
const ext = node_path.default.extname(filePath);
|
|
228
282
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
|
|
229
283
|
if (loadedModules.has(filePath)) return loadedModules.get(filePath);
|
|
230
|
-
const
|
|
231
|
-
loadedModules.set(filePath,
|
|
232
|
-
return
|
|
284
|
+
const mod = await jiti$1.import(filePath, { default: true });
|
|
285
|
+
loadedModules.set(filePath, mod);
|
|
286
|
+
return mod;
|
|
233
287
|
}
|
|
234
288
|
async function loadUserConfig(configPath, { notify }) {
|
|
235
|
-
let userConfig;
|
|
236
|
-
let cwd;
|
|
237
289
|
if (configPath) {
|
|
238
290
|
const ext = node_path.default.extname(configPath);
|
|
239
291
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
|
|
@@ -249,41 +301,44 @@ async function loadUserConfig(configPath, { notify }) {
|
|
|
249
301
|
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
250
302
|
throw new Error(msg);
|
|
251
303
|
}
|
|
252
|
-
cwd = node_path.default.dirname(resolvedConfigPath);
|
|
304
|
+
const cwd = node_path.default.dirname(resolvedConfigPath);
|
|
253
305
|
try {
|
|
254
|
-
userConfig = await loadModule(resolvedConfigPath);
|
|
306
|
+
const userConfig = await loadModule(resolvedConfigPath);
|
|
255
307
|
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
|
|
308
|
+
return {
|
|
309
|
+
userConfig,
|
|
310
|
+
cwd
|
|
311
|
+
};
|
|
256
312
|
} catch (error) {
|
|
257
|
-
|
|
258
|
-
|
|
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 {}
|
|
313
|
+
const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
|
|
314
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
315
|
+
throw new Error(msg);
|
|
277
316
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
317
|
+
}
|
|
318
|
+
const cwd = process.cwd();
|
|
319
|
+
const configFileNames = [
|
|
320
|
+
"kubb.config.ts",
|
|
321
|
+
"kubb.config.mts",
|
|
322
|
+
"kubb.config.cts",
|
|
323
|
+
"kubb.config.js",
|
|
324
|
+
"kubb.config.cjs"
|
|
325
|
+
];
|
|
326
|
+
for (const configFileName of configFileNames) {
|
|
327
|
+
const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
|
|
328
|
+
if (!(0, node_fs.existsSync)(configFilePath)) continue;
|
|
329
|
+
try {
|
|
330
|
+
const userConfig = await loadModule(configFilePath);
|
|
331
|
+
await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
|
|
332
|
+
return {
|
|
333
|
+
userConfig,
|
|
334
|
+
cwd
|
|
335
|
+
};
|
|
336
|
+
} catch (err) {
|
|
337
|
+
await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
281
338
|
}
|
|
282
339
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
cwd
|
|
286
|
-
};
|
|
340
|
+
await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
|
|
341
|
+
throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
|
|
287
342
|
}
|
|
288
343
|
//#endregion
|
|
289
344
|
//#region src/utils/resolveCwd.ts
|
|
@@ -302,40 +357,27 @@ function resolveCwd(userConfig, cwd) {
|
|
|
302
357
|
}
|
|
303
358
|
//#endregion
|
|
304
359
|
//#region src/utils/resolveUserConfig.ts
|
|
305
|
-
/**
|
|
306
|
-
* Resolve the config by handling function configs and returning the final configuration
|
|
307
|
-
*/
|
|
308
360
|
async function resolveUserConfig(config, options) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
|
|
316
|
-
else kubbUserConfig = Promise.resolve(possiblePromise);
|
|
317
|
-
}
|
|
318
|
-
return await kubbUserConfig;
|
|
361
|
+
const result = typeof config === "function" ? config({
|
|
362
|
+
logLevel: options.logLevel,
|
|
363
|
+
config: options.configPath
|
|
364
|
+
}) : config;
|
|
365
|
+
const resolved = isPromise(result) ? await result : result;
|
|
366
|
+
return Array.isArray(resolved) ? resolved[0] : resolved;
|
|
319
367
|
}
|
|
320
368
|
//#endregion
|
|
321
369
|
//#region src/tools/generate.ts
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
async function generate(schema
|
|
370
|
+
const generateTool = (0, tmcp_tool.defineTool)({
|
|
371
|
+
name: "generate",
|
|
372
|
+
description: "Generate OpenAPI spec helpers using Kubb configuration",
|
|
373
|
+
schema: generateSchema
|
|
374
|
+
}, async function generate(schema) {
|
|
327
375
|
const { config: configPath, input, output, logLevel } = schema;
|
|
328
376
|
try {
|
|
329
377
|
const hooks = new AsyncEventEmitter();
|
|
330
378
|
const messages = [];
|
|
331
379
|
const notify = async (type, message, data) => {
|
|
332
|
-
messages.push(`${type}: ${message}`);
|
|
333
|
-
await handler.sendNotification("kubb/progress", {
|
|
334
|
-
type,
|
|
335
|
-
message,
|
|
336
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
337
|
-
...data
|
|
338
|
-
});
|
|
380
|
+
messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
|
|
339
381
|
};
|
|
340
382
|
hooks.on("kubb:info", async ({ message }) => {
|
|
341
383
|
await notify(NotifyTypes.INFO, message);
|
|
@@ -344,11 +386,14 @@ async function generate(schema, handler) {
|
|
|
344
386
|
await notify(NotifyTypes.SUCCESS, message);
|
|
345
387
|
});
|
|
346
388
|
hooks.on("kubb:error", async ({ error }) => {
|
|
347
|
-
await notify(NotifyTypes.ERROR, error.message
|
|
389
|
+
await notify(NotifyTypes.ERROR, error.message);
|
|
348
390
|
});
|
|
349
391
|
hooks.on("kubb:warn", async ({ message }) => {
|
|
350
392
|
await notify(NotifyTypes.WARN, message);
|
|
351
393
|
});
|
|
394
|
+
hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
|
|
395
|
+
await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, _kubb_core.Diagnostics.serialize(diagnostic));
|
|
396
|
+
});
|
|
352
397
|
hooks.on("kubb:plugin:start", async ({ plugin }) => {
|
|
353
398
|
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
|
|
354
399
|
});
|
|
@@ -358,8 +403,8 @@ async function generate(schema, handler) {
|
|
|
358
403
|
hooks.on("kubb:files:processing:start", async () => {
|
|
359
404
|
await notify(NotifyTypes.FILES_START, "Starting file processing");
|
|
360
405
|
});
|
|
361
|
-
hooks.on("kubb:
|
|
362
|
-
await notify(NotifyTypes.
|
|
406
|
+
hooks.on("kubb:files:processing:update", async ({ files }) => {
|
|
407
|
+
await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
|
|
363
408
|
});
|
|
364
409
|
hooks.on("kubb:files:processing:end", async () => {
|
|
365
410
|
await notify(NotifyTypes.FILES_END, "File processing complete");
|
|
@@ -376,7 +421,7 @@ async function generate(schema, handler) {
|
|
|
376
421
|
const configResult = await loadUserConfig(configPath, { notify });
|
|
377
422
|
userConfig = configResult.userConfig;
|
|
378
423
|
cwd = configResult.cwd;
|
|
379
|
-
if (Array.isArray(userConfig)
|
|
424
|
+
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
425
|
userConfig = await resolveUserConfig(userConfig, {
|
|
381
426
|
configPath,
|
|
382
427
|
logLevel
|
|
@@ -384,13 +429,7 @@ async function generate(schema, handler) {
|
|
|
384
429
|
} catch (error) {
|
|
385
430
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
386
431
|
await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
|
|
387
|
-
return
|
|
388
|
-
content: [{
|
|
389
|
-
type: "text",
|
|
390
|
-
text: errorMessage
|
|
391
|
-
}],
|
|
392
|
-
isError: true
|
|
393
|
-
};
|
|
432
|
+
return tmcp_utils.tool.error(errorMessage);
|
|
394
433
|
}
|
|
395
434
|
const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
|
|
396
435
|
const config = {
|
|
@@ -405,89 +444,259 @@ async function generate(schema, handler) {
|
|
|
405
444
|
path: output
|
|
406
445
|
} : userConfig.output
|
|
407
446
|
};
|
|
408
|
-
await notify(NotifyTypes.CONFIG_READY, "Configuration ready"
|
|
447
|
+
await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
|
|
409
448
|
await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
|
|
410
449
|
const kubb = (0, _kubb_core.createKubb)(config, { hooks });
|
|
411
450
|
await kubb.setup();
|
|
412
451
|
await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
|
|
413
452
|
await notify(NotifyTypes.BUILD_START, "Starting build");
|
|
414
|
-
const { files,
|
|
453
|
+
const { files, diagnostics } = await kubb.safeBuild();
|
|
415
454
|
await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
};
|
|
455
|
+
const problems = diagnostics.filter(_kubb_core.Diagnostics.isProblem);
|
|
456
|
+
const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
|
|
457
|
+
if (errors.length > 0) {
|
|
458
|
+
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
|
|
459
|
+
const serialized = problems.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic));
|
|
460
|
+
return tmcp_utils.tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
429
461
|
}
|
|
430
|
-
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files
|
|
431
|
-
return
|
|
432
|
-
type: "text",
|
|
433
|
-
text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
|
|
434
|
-
}] };
|
|
462
|
+
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
|
|
463
|
+
return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
|
|
435
464
|
} catch (caughtError) {
|
|
436
|
-
const
|
|
437
|
-
|
|
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
|
-
};
|
|
465
|
+
const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(caughtError));
|
|
466
|
+
return tmcp_utils.tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
450
467
|
}
|
|
468
|
+
});
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region ../../internals/shared/src/constants.ts
|
|
471
|
+
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
472
|
+
const availablePlugins = [
|
|
473
|
+
{
|
|
474
|
+
value: "plugin-ts",
|
|
475
|
+
label: "TypeScript",
|
|
476
|
+
hint: "Recommended",
|
|
477
|
+
packageName: "@kubb/plugin-ts",
|
|
478
|
+
importName: "pluginTs",
|
|
479
|
+
category: "types"
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
value: "plugin-client",
|
|
483
|
+
label: "Client (Fetch/Axios)",
|
|
484
|
+
packageName: "@kubb/plugin-client",
|
|
485
|
+
importName: "pluginClient",
|
|
486
|
+
category: "client"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
value: "plugin-react-query",
|
|
490
|
+
label: "React Query / TanStack Query",
|
|
491
|
+
packageName: "@kubb/plugin-react-query",
|
|
492
|
+
importName: "pluginReactQuery",
|
|
493
|
+
category: "framework"
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
value: "plugin-vue-query",
|
|
497
|
+
label: "Vue Query",
|
|
498
|
+
packageName: "@kubb/plugin-vue-query",
|
|
499
|
+
importName: "pluginVueQuery",
|
|
500
|
+
category: "framework"
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
value: "plugin-zod",
|
|
504
|
+
label: "Zod Schemas",
|
|
505
|
+
packageName: "@kubb/plugin-zod",
|
|
506
|
+
importName: "pluginZod",
|
|
507
|
+
category: "validation"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
value: "plugin-faker",
|
|
511
|
+
label: "Faker.js Mocks",
|
|
512
|
+
packageName: "@kubb/plugin-faker",
|
|
513
|
+
importName: "pluginFaker",
|
|
514
|
+
category: "mocks"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
value: "plugin-msw",
|
|
518
|
+
label: "MSW Handlers",
|
|
519
|
+
packageName: "@kubb/plugin-msw",
|
|
520
|
+
importName: "pluginMsw",
|
|
521
|
+
category: "mocks"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
value: "plugin-cypress",
|
|
525
|
+
label: "Cypress Tests",
|
|
526
|
+
packageName: "@kubb/plugin-cypress",
|
|
527
|
+
importName: "pluginCypress",
|
|
528
|
+
category: "testing"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
value: "plugin-mcp",
|
|
532
|
+
label: "MCP Server (AI / Model Context Protocol)",
|
|
533
|
+
packageName: "@kubb/plugin-mcp",
|
|
534
|
+
importName: "pluginMcp",
|
|
535
|
+
category: "ai"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
value: "plugin-redoc",
|
|
539
|
+
label: "ReDoc Documentation",
|
|
540
|
+
packageName: "@kubb/plugin-redoc",
|
|
541
|
+
importName: "pluginRedoc",
|
|
542
|
+
category: "documentation"
|
|
543
|
+
}
|
|
544
|
+
];
|
|
545
|
+
const pluginDefaultConfigs = {
|
|
546
|
+
"plugin-ts": `pluginTs({
|
|
547
|
+
output: { path: 'models' },
|
|
548
|
+
})`,
|
|
549
|
+
"plugin-client": `pluginClient({
|
|
550
|
+
output: { path: 'clients' },
|
|
551
|
+
})`,
|
|
552
|
+
"plugin-react-query": `pluginReactQuery({
|
|
553
|
+
output: { path: 'hooks' },
|
|
554
|
+
})`,
|
|
555
|
+
"plugin-vue-query": `pluginVueQuery({
|
|
556
|
+
output: { path: 'hooks' },
|
|
557
|
+
})`,
|
|
558
|
+
"plugin-zod": `pluginZod({
|
|
559
|
+
output: { path: 'zod' },
|
|
560
|
+
})`,
|
|
561
|
+
"plugin-faker": `pluginFaker({
|
|
562
|
+
output: { path: 'mocks' },
|
|
563
|
+
})`,
|
|
564
|
+
"plugin-msw": `pluginMsw({
|
|
565
|
+
output: { path: 'msw' },
|
|
566
|
+
})`,
|
|
567
|
+
"plugin-cypress": `pluginCypress({
|
|
568
|
+
output: { path: 'cypress' },
|
|
569
|
+
})`,
|
|
570
|
+
"plugin-mcp": `pluginMcp({
|
|
571
|
+
output: { path: 'mcp' },
|
|
572
|
+
})`,
|
|
573
|
+
"plugin-redoc": `pluginRedoc({
|
|
574
|
+
output: { path: 'redoc' },
|
|
575
|
+
})`
|
|
576
|
+
};
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region ../../internals/shared/src/init.ts
|
|
579
|
+
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
580
|
+
return `import { defineConfig } from 'kubb'
|
|
581
|
+
${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
|
|
582
|
+
|
|
583
|
+
export default defineConfig({
|
|
584
|
+
root: '.',
|
|
585
|
+
input: {
|
|
586
|
+
path: '${inputPath}',
|
|
587
|
+
},
|
|
588
|
+
output: {
|
|
589
|
+
path: '${outputPath}',
|
|
590
|
+
clean: true,
|
|
591
|
+
},
|
|
592
|
+
plugins: [
|
|
593
|
+
${selectedPlugins.map((plugin) => {
|
|
594
|
+
return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
|
|
595
|
+
}).join("\n")}
|
|
596
|
+
],
|
|
597
|
+
})
|
|
598
|
+
`;
|
|
451
599
|
}
|
|
452
600
|
//#endregion
|
|
453
|
-
//#region src/
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
601
|
+
//#region src/schemas/initSchema.ts
|
|
602
|
+
const initSchema = valibot.object({
|
|
603
|
+
input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
604
|
+
output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory (default: ./src/gen)"))),
|
|
605
|
+
plugins: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
|
|
606
|
+
});
|
|
607
|
+
//#endregion
|
|
608
|
+
//#region src/tools/init.ts
|
|
609
|
+
function resolvePlugins(pluginsFlag) {
|
|
610
|
+
if (!pluginsFlag) return [];
|
|
611
|
+
const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
|
|
612
|
+
return availablePlugins.filter((p) => requested.includes(p.value));
|
|
613
|
+
}
|
|
614
|
+
const initTool = (0, tmcp_tool.defineTool)({
|
|
615
|
+
name: "init",
|
|
616
|
+
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
|
|
617
|
+
schema: initSchema
|
|
618
|
+
}, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
|
|
619
|
+
const selected = resolvePlugins(plugins);
|
|
620
|
+
const content = generateConfigFile({
|
|
621
|
+
selectedPlugins: selected,
|
|
622
|
+
inputPath: input,
|
|
623
|
+
outputPath: output
|
|
624
|
+
});
|
|
625
|
+
const dest = node_path.default.join(node_process.default.cwd(), KUBB_CONFIG_FILENAME);
|
|
626
|
+
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.`);
|
|
627
|
+
node_fs.default.writeFileSync(dest, content, "utf-8");
|
|
628
|
+
const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
|
|
629
|
+
return tmcp_utils.tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
|
|
630
|
+
});
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/schemas/validateSchema.ts
|
|
633
|
+
const validateSchema = valibot.object({ input: valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path or URL to the OpenAPI/Swagger specification")) });
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/tools/validate.ts
|
|
636
|
+
const validateTool = (0, tmcp_tool.defineTool)({
|
|
637
|
+
name: "validate",
|
|
638
|
+
description: "Validate an OpenAPI/Swagger specification file or URL",
|
|
639
|
+
schema: validateSchema
|
|
640
|
+
}, async ({ input }) => {
|
|
641
|
+
let mod;
|
|
460
642
|
try {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
643
|
+
mod = await import("@kubb/adapter-oas");
|
|
644
|
+
} catch {
|
|
645
|
+
return tmcp_utils.tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
await mod.adapterOas().validate(input, { throwOnError: true });
|
|
649
|
+
return tmcp_utils.tool.text(`Validation successful: ${input}`);
|
|
650
|
+
} catch (err) {
|
|
651
|
+
const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(err));
|
|
652
|
+
return tmcp_utils.tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region src/server.ts
|
|
657
|
+
function createMcpServer() {
|
|
658
|
+
const server = new tmcp.McpServer({
|
|
659
|
+
name: "Kubb",
|
|
660
|
+
version
|
|
661
|
+
}, {
|
|
662
|
+
adapter: new _tmcp_adapter_valibot.ValibotJsonSchemaAdapter(),
|
|
663
|
+
capabilities: { tools: {} }
|
|
664
|
+
});
|
|
665
|
+
server.tools([
|
|
666
|
+
generateTool,
|
|
667
|
+
validateTool,
|
|
668
|
+
initTool
|
|
669
|
+
]);
|
|
670
|
+
return server;
|
|
671
|
+
}
|
|
672
|
+
async function startServer({ port, host = "localhost" } = {}) {
|
|
673
|
+
const server = createMcpServer();
|
|
674
|
+
if (port === void 0) {
|
|
675
|
+
new _tmcp_transport_stdio.StdioTransport(server).listen();
|
|
676
|
+
return;
|
|
483
677
|
}
|
|
678
|
+
const transport = new _tmcp_transport_http.HttpTransport(server, { path: "/mcp" });
|
|
679
|
+
const httpServer = node_http.default.createServer((0, _remix_run_node_fetch_server.createRequestListener)(async (request) => {
|
|
680
|
+
return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
|
|
681
|
+
}));
|
|
682
|
+
httpServer.listen(port, host, () => {
|
|
683
|
+
console.log(`Kubb MCP server on http://${host}:${port}`);
|
|
684
|
+
});
|
|
685
|
+
const closeServer = () => httpServer.close();
|
|
686
|
+
process.once("SIGINT", closeServer);
|
|
687
|
+
process.once("SIGTERM", closeServer);
|
|
688
|
+
httpServer.once("close", () => {
|
|
689
|
+
process.off("SIGINT", closeServer);
|
|
690
|
+
process.off("SIGTERM", closeServer);
|
|
691
|
+
});
|
|
484
692
|
}
|
|
485
693
|
//#endregion
|
|
486
694
|
//#region src/index.ts
|
|
487
|
-
async function run(_argv) {
|
|
488
|
-
await startServer();
|
|
695
|
+
async function run(_argv, options) {
|
|
696
|
+
await startServer(options);
|
|
489
697
|
}
|
|
490
698
|
//#endregion
|
|
699
|
+
exports.createMcpServer = createMcpServer;
|
|
491
700
|
exports.run = run;
|
|
492
701
|
|
|
493
702
|
//# sourceMappingURL=index.cjs.map
|