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