@kubb/mcp 5.0.0-beta.5 → 5.0.0-beta.51
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/LICENSE +17 -10
- package/README.md +64 -17
- package/dist/index.cjs +358 -156
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +353 -158
- package/dist/index.js.map +1 -1
- package/package.json +17 -19
- 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 +20 -27
- 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 {
|
|
9
|
+
import { Diagnostics, createKubb } from "@kubb/core";
|
|
10
|
+
import { defineTool } from "tmcp/tool";
|
|
11
|
+
import { tool } from "tmcp/utils";
|
|
12
|
+
import * as v from "valibot";
|
|
13
|
+
import fs, { existsSync } from "node:fs";
|
|
8
14
|
import path from "node:path";
|
|
9
|
-
import { createKubb } from "@kubb/core";
|
|
10
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.51";
|
|
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",
|
|
@@ -215,8 +259,6 @@ async function loadModule(filePath) {
|
|
|
215
259
|
return mod;
|
|
216
260
|
}
|
|
217
261
|
async function loadUserConfig(configPath, { notify }) {
|
|
218
|
-
let userConfig;
|
|
219
|
-
let cwd;
|
|
220
262
|
if (configPath) {
|
|
221
263
|
const ext = path.extname(configPath);
|
|
222
264
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
|
|
@@ -232,41 +274,44 @@ async function loadUserConfig(configPath, { notify }) {
|
|
|
232
274
|
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
233
275
|
throw new Error(msg);
|
|
234
276
|
}
|
|
235
|
-
cwd = path.dirname(resolvedConfigPath);
|
|
277
|
+
const cwd = path.dirname(resolvedConfigPath);
|
|
236
278
|
try {
|
|
237
|
-
userConfig = await loadModule(resolvedConfigPath);
|
|
279
|
+
const userConfig = await loadModule(resolvedConfigPath);
|
|
238
280
|
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
|
|
281
|
+
return {
|
|
282
|
+
userConfig,
|
|
283
|
+
cwd
|
|
284
|
+
};
|
|
239
285
|
} catch (error) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
} else {
|
|
244
|
-
cwd = process.cwd();
|
|
245
|
-
const configFileNames = [
|
|
246
|
-
"kubb.config.ts",
|
|
247
|
-
"kubb.config.mts",
|
|
248
|
-
"kubb.config.cts",
|
|
249
|
-
"kubb.config.js",
|
|
250
|
-
"kubb.config.cjs"
|
|
251
|
-
];
|
|
252
|
-
for (const configFileName of configFileNames) {
|
|
253
|
-
const configFilePath = path.resolve(process.cwd(), configFileName);
|
|
254
|
-
if (!existsSync(configFilePath)) continue;
|
|
255
|
-
try {
|
|
256
|
-
userConfig = await loadModule(configFilePath);
|
|
257
|
-
await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
|
|
258
|
-
break;
|
|
259
|
-
} 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);
|
|
260
289
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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)}`);
|
|
264
311
|
}
|
|
265
312
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
cwd
|
|
269
|
-
};
|
|
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(", ")}`);
|
|
270
315
|
}
|
|
271
316
|
//#endregion
|
|
272
317
|
//#region src/utils/resolveCwd.ts
|
|
@@ -285,40 +330,27 @@ function resolveCwd(userConfig, cwd) {
|
|
|
285
330
|
}
|
|
286
331
|
//#endregion
|
|
287
332
|
//#region src/utils/resolveUserConfig.ts
|
|
288
|
-
/**
|
|
289
|
-
* Resolve the config by handling function configs and returning the final configuration
|
|
290
|
-
*/
|
|
291
333
|
async function resolveUserConfig(config, options) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
|
|
299
|
-
else kubbUserConfig = Promise.resolve(possiblePromise);
|
|
300
|
-
}
|
|
301
|
-
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;
|
|
302
340
|
}
|
|
303
341
|
//#endregion
|
|
304
342
|
//#region src/tools/generate.ts
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
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) {
|
|
310
348
|
const { config: configPath, input, output, logLevel } = schema;
|
|
311
349
|
try {
|
|
312
350
|
const hooks = new AsyncEventEmitter();
|
|
313
351
|
const messages = [];
|
|
314
352
|
const notify = async (type, message, data) => {
|
|
315
|
-
messages.push(`${type}: ${message}`);
|
|
316
|
-
await handler.sendNotification("kubb/progress", {
|
|
317
|
-
type,
|
|
318
|
-
message,
|
|
319
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
320
|
-
...data
|
|
321
|
-
});
|
|
353
|
+
messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
|
|
322
354
|
};
|
|
323
355
|
hooks.on("kubb:info", async ({ message }) => {
|
|
324
356
|
await notify(NotifyTypes.INFO, message);
|
|
@@ -327,11 +359,14 @@ async function generate(schema, handler) {
|
|
|
327
359
|
await notify(NotifyTypes.SUCCESS, message);
|
|
328
360
|
});
|
|
329
361
|
hooks.on("kubb:error", async ({ error }) => {
|
|
330
|
-
await notify(NotifyTypes.ERROR, error.message
|
|
362
|
+
await notify(NotifyTypes.ERROR, error.message);
|
|
331
363
|
});
|
|
332
364
|
hooks.on("kubb:warn", async ({ message }) => {
|
|
333
365
|
await notify(NotifyTypes.WARN, message);
|
|
334
366
|
});
|
|
367
|
+
hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
|
|
368
|
+
await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
|
|
369
|
+
});
|
|
335
370
|
hooks.on("kubb:plugin:start", async ({ plugin }) => {
|
|
336
371
|
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
|
|
337
372
|
});
|
|
@@ -341,8 +376,8 @@ async function generate(schema, handler) {
|
|
|
341
376
|
hooks.on("kubb:files:processing:start", async () => {
|
|
342
377
|
await notify(NotifyTypes.FILES_START, "Starting file processing");
|
|
343
378
|
});
|
|
344
|
-
hooks.on("kubb:
|
|
345
|
-
await notify(NotifyTypes.
|
|
379
|
+
hooks.on("kubb:files:processing:update", async ({ files }) => {
|
|
380
|
+
await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
|
|
346
381
|
});
|
|
347
382
|
hooks.on("kubb:files:processing:end", async () => {
|
|
348
383
|
await notify(NotifyTypes.FILES_END, "File processing complete");
|
|
@@ -359,7 +394,7 @@ async function generate(schema, handler) {
|
|
|
359
394
|
const configResult = await loadUserConfig(configPath, { notify });
|
|
360
395
|
userConfig = configResult.userConfig;
|
|
361
396
|
cwd = configResult.cwd;
|
|
362
|
-
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.");
|
|
363
398
|
userConfig = await resolveUserConfig(userConfig, {
|
|
364
399
|
configPath,
|
|
365
400
|
logLevel
|
|
@@ -367,13 +402,7 @@ async function generate(schema, handler) {
|
|
|
367
402
|
} catch (error) {
|
|
368
403
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
369
404
|
await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
|
|
370
|
-
return
|
|
371
|
-
content: [{
|
|
372
|
-
type: "text",
|
|
373
|
-
text: errorMessage
|
|
374
|
-
}],
|
|
375
|
-
isError: true
|
|
376
|
-
};
|
|
405
|
+
return tool.error(errorMessage);
|
|
377
406
|
}
|
|
378
407
|
const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
|
|
379
408
|
const config = {
|
|
@@ -388,89 +417,255 @@ async function generate(schema, handler) {
|
|
|
388
417
|
path: output
|
|
389
418
|
} : userConfig.output
|
|
390
419
|
};
|
|
391
|
-
await notify(NotifyTypes.CONFIG_READY, "Configuration ready"
|
|
420
|
+
await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
|
|
392
421
|
await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
|
|
393
422
|
const kubb = createKubb(config, { hooks });
|
|
394
423
|
await kubb.setup();
|
|
395
424
|
await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
|
|
396
425
|
await notify(NotifyTypes.BUILD_START, "Starting build");
|
|
397
|
-
const { files,
|
|
426
|
+
const { files, diagnostics } = await kubb.safeBuild();
|
|
398
427
|
await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
});
|
|
405
|
-
return {
|
|
406
|
-
content: [{
|
|
407
|
-
type: "text",
|
|
408
|
-
text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
|
|
409
|
-
}],
|
|
410
|
-
isError: true
|
|
411
|
-
};
|
|
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\`\`\``);
|
|
412
434
|
}
|
|
413
|
-
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files
|
|
414
|
-
return
|
|
415
|
-
type: "text",
|
|
416
|
-
text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
|
|
417
|
-
}] };
|
|
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")}`);
|
|
418
437
|
} catch (caughtError) {
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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\`\`\``);
|
|
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"
|
|
433
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
|
+
`;
|
|
434
572
|
}
|
|
435
573
|
//#endregion
|
|
436
|
-
//#region src/
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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;
|
|
443
612
|
try {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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;
|
|
466
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
|
+
});
|
|
467
662
|
}
|
|
468
663
|
//#endregion
|
|
469
664
|
//#region src/index.ts
|
|
470
|
-
async function run(_argv) {
|
|
471
|
-
await startServer();
|
|
665
|
+
async function run(_argv, options) {
|
|
666
|
+
await startServer(options);
|
|
472
667
|
}
|
|
473
668
|
//#endregion
|
|
474
|
-
export { run };
|
|
669
|
+
export { createMcpServer, run };
|
|
475
670
|
|
|
476
671
|
//# sourceMappingURL=index.js.map
|