@kubb/mcp 5.0.0-beta.7 → 5.0.0-beta.71
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 +27 -17
- package/dist/index.cjs +338 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +14 -9
- package/dist/index.js +338 -201
- package/dist/index.js.map +1 -1
- package/package.json +15 -24
- package/src/constants.ts +0 -1
- package/src/index.ts +0 -8
- package/src/schemas/generateSchema.ts +0 -13
- package/src/schemas/initSchema.ts +0 -7
- package/src/schemas/validateSchema.ts +0 -5
- package/src/server.ts +0 -41
- package/src/tools/generate.ts +0 -146
- package/src/tools/init.ts +0 -37
- package/src/tools/validate.ts +0 -25
- package/src/types.ts +0 -23
- package/src/utils/loadUserConfig.ts +0 -78
- package/src/utils/resolveCwd.ts +0 -20
- package/src/utils/resolveUserConfig.ts +0 -13
- /package/dist/{chunk--u3MIqq1.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import http from "node:http";
|
|
3
|
-
import { createRequestListener } from "@remix-run/node-fetch-server";
|
|
1
|
+
import "./rolldown-runtime-C0LytTxp.js";
|
|
4
2
|
import { ValibotJsonSchemaAdapter } from "@tmcp/adapter-valibot";
|
|
5
|
-
import { HttpTransport } from "@tmcp/transport-http";
|
|
6
3
|
import { StdioTransport } from "@tmcp/transport-stdio";
|
|
7
4
|
import { McpServer } from "tmcp";
|
|
8
5
|
import { EventEmitter } from "node:events";
|
|
9
|
-
import
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { createKubb } from "@kubb/core";
|
|
6
|
+
import { Diagnostics, createKubb } from "@kubb/core";
|
|
12
7
|
import { defineTool } from "tmcp/tool";
|
|
13
8
|
import { tool } from "tmcp/utils";
|
|
14
9
|
import * as v from "valibot";
|
|
10
|
+
import fs, { existsSync } from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { pathToFileURL } from "node:url";
|
|
15
13
|
import { createJiti } from "jiti";
|
|
16
14
|
import process$1 from "node:process";
|
|
17
15
|
//#region package.json
|
|
18
|
-
var version = "5.0.0-beta.
|
|
16
|
+
var version = "5.0.0-beta.71";
|
|
19
17
|
//#endregion
|
|
20
18
|
//#region ../../internals/utils/src/errors.ts
|
|
21
19
|
/**
|
|
@@ -63,9 +61,12 @@ var AsyncEventEmitter = class {
|
|
|
63
61
|
* await emitter.emit('build', 'petstore')
|
|
64
62
|
* ```
|
|
65
63
|
*/
|
|
66
|
-
|
|
64
|
+
emit(eventName, ...eventArgs) {
|
|
67
65
|
const listeners = this.#emitter.listeners(eventName);
|
|
68
66
|
if (listeners.length === 0) return;
|
|
67
|
+
return this.#emitAll(eventName, listeners, eventArgs);
|
|
68
|
+
}
|
|
69
|
+
async #emitAll(eventName, listeners, eventArgs) {
|
|
69
70
|
for (const listener of listeners) try {
|
|
70
71
|
await listener(...eventArgs);
|
|
71
72
|
} catch (err) {
|
|
@@ -128,6 +129,24 @@ var AsyncEventEmitter = class {
|
|
|
128
129
|
return this.#emitter.listenerCount(eventName);
|
|
129
130
|
}
|
|
130
131
|
/**
|
|
132
|
+
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
|
|
133
|
+
* Set this above the expected listener count when many listeners attach by design.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* emitter.setMaxListeners(40)
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
setMaxListeners(max) {
|
|
141
|
+
this.#emitter.setMaxListeners(max);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Returns the current per-event listener ceiling.
|
|
145
|
+
*/
|
|
146
|
+
getMaxListeners() {
|
|
147
|
+
return this.#emitter.getMaxListeners();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
131
150
|
* Removes all listeners from every event channel.
|
|
132
151
|
*
|
|
133
152
|
* @example
|
|
@@ -153,31 +172,103 @@ function isPromise(result) {
|
|
|
153
172
|
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
154
173
|
}
|
|
155
174
|
//#endregion
|
|
156
|
-
//#region src/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
//#region ../../internals/utils/src/runtime.ts
|
|
176
|
+
/**
|
|
177
|
+
* Detects the JavaScript runtime executing the current process and exposes its name and version.
|
|
178
|
+
*
|
|
179
|
+
* Prefer the shared {@link runtime} instance over constructing your own.
|
|
180
|
+
*/
|
|
181
|
+
var Runtime = class {
|
|
182
|
+
/**
|
|
183
|
+
* `true` when the current process is running under Bun.
|
|
184
|
+
*
|
|
185
|
+
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
186
|
+
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
187
|
+
* otherwise look like Node.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* if (runtime.isBun) {
|
|
192
|
+
* await Bun.write(path, data)
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
get isBun() {
|
|
197
|
+
return typeof Bun !== "undefined";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* `true` when the current process is running under Deno.
|
|
201
|
+
*/
|
|
202
|
+
get isDeno() {
|
|
203
|
+
return typeof globalThis.Deno !== "undefined";
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* `true` when the current process is running under Node.
|
|
207
|
+
*
|
|
208
|
+
* Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
|
|
209
|
+
*/
|
|
210
|
+
get isNode() {
|
|
211
|
+
return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Name of the runtime executing the current process.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
get name() {
|
|
222
|
+
if (this.isBun) return "bun";
|
|
223
|
+
if (this.isDeno) return "deno";
|
|
224
|
+
return "node";
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Version of the active runtime, or an empty string when it cannot be read.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* runtime.version // '1.3.11' under Bun, '22.22.2' under Node
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
get version() {
|
|
235
|
+
if (this.isBun) return process.versions.bun ?? "";
|
|
236
|
+
if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
|
|
237
|
+
return process.versions?.node ?? "";
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
|
|
242
|
+
*/
|
|
243
|
+
const runtime = new Runtime();
|
|
170
244
|
//#endregion
|
|
171
|
-
//#region src/
|
|
245
|
+
//#region src/constants.ts
|
|
246
|
+
/**
|
|
247
|
+
* File extensions a Kubb config is allowed to use. A config path with any other
|
|
248
|
+
* extension is rejected before it is loaded.
|
|
249
|
+
*/
|
|
250
|
+
const ALLOWED_CONFIG_EXTENSIONS = new Set([
|
|
251
|
+
".ts",
|
|
252
|
+
".mts",
|
|
253
|
+
".cts",
|
|
254
|
+
".js",
|
|
255
|
+
".mjs",
|
|
256
|
+
".cjs"
|
|
257
|
+
]);
|
|
258
|
+
/**
|
|
259
|
+
* Notification kinds reported back to the MCP client while loading config and
|
|
260
|
+
* running generation, covering progress, results, and errors.
|
|
261
|
+
*/
|
|
172
262
|
const NotifyTypes = {
|
|
173
263
|
INFO: "INFO",
|
|
174
264
|
SUCCESS: "SUCCESS",
|
|
175
265
|
ERROR: "ERROR",
|
|
176
266
|
WARN: "WARN",
|
|
267
|
+
DIAGNOSTIC: "DIAGNOSTIC",
|
|
177
268
|
PLUGIN_START: "PLUGIN_START",
|
|
178
269
|
PLUGIN_END: "PLUGIN_END",
|
|
179
270
|
FILES_START: "FILES_START",
|
|
180
|
-
|
|
271
|
+
FILES_UPDATE: "FILES_UPDATE",
|
|
181
272
|
FILES_END: "FILES_END",
|
|
182
273
|
GENERATION_START: "GENERATION_START",
|
|
183
274
|
GENERATION_END: "GENERATION_END",
|
|
@@ -189,37 +280,198 @@ const NotifyTypes = {
|
|
|
189
280
|
BUILD_START: "BUILD_START",
|
|
190
281
|
BUILD_END: "BUILD_END",
|
|
191
282
|
BUILD_FAILED: "BUILD_FAILED",
|
|
192
|
-
BUILD_SUCCESS: "BUILD_SUCCESS"
|
|
193
|
-
FATAL_ERROR: "FATAL_ERROR"
|
|
283
|
+
BUILD_SUCCESS: "BUILD_SUCCESS"
|
|
194
284
|
};
|
|
195
285
|
//#endregion
|
|
196
|
-
//#region src/
|
|
197
|
-
const
|
|
198
|
-
".ts",
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
286
|
+
//#region src/schemas/generateSchema.ts
|
|
287
|
+
const generateSchema = v.object({
|
|
288
|
+
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"))),
|
|
289
|
+
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
290
|
+
output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory path (overrides config)"))),
|
|
291
|
+
logLevel: v.optional(v.pipe(v.picklist([
|
|
292
|
+
"silent",
|
|
293
|
+
"info",
|
|
294
|
+
"verbose"
|
|
295
|
+
]), v.description("Log level for build output")), "info")
|
|
296
|
+
});
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region ../../internals/shared/src/constants.ts
|
|
299
|
+
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
300
|
+
const availablePlugins = [
|
|
301
|
+
{
|
|
302
|
+
value: "plugin-ts",
|
|
303
|
+
label: "TypeScript",
|
|
304
|
+
hint: "Recommended",
|
|
305
|
+
packageName: "@kubb/plugin-ts",
|
|
306
|
+
importName: "pluginTs",
|
|
307
|
+
category: "types"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
value: "plugin-client",
|
|
311
|
+
label: "Client (Fetch/Axios)",
|
|
312
|
+
packageName: "@kubb/plugin-client",
|
|
313
|
+
importName: "pluginClient",
|
|
314
|
+
category: "client"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
value: "plugin-react-query",
|
|
318
|
+
label: "React Query / TanStack Query",
|
|
319
|
+
packageName: "@kubb/plugin-react-query",
|
|
320
|
+
importName: "pluginReactQuery",
|
|
321
|
+
category: "framework"
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
value: "plugin-vue-query",
|
|
325
|
+
label: "Vue Query",
|
|
326
|
+
packageName: "@kubb/plugin-vue-query",
|
|
327
|
+
importName: "pluginVueQuery",
|
|
328
|
+
category: "framework"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
value: "plugin-zod",
|
|
332
|
+
label: "Zod Schemas",
|
|
333
|
+
packageName: "@kubb/plugin-zod",
|
|
334
|
+
importName: "pluginZod",
|
|
335
|
+
category: "validation"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
value: "plugin-faker",
|
|
339
|
+
label: "Faker.js Mocks",
|
|
340
|
+
packageName: "@kubb/plugin-faker",
|
|
341
|
+
importName: "pluginFaker",
|
|
342
|
+
category: "mocks"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
value: "plugin-msw",
|
|
346
|
+
label: "MSW Handlers",
|
|
347
|
+
packageName: "@kubb/plugin-msw",
|
|
348
|
+
importName: "pluginMsw",
|
|
349
|
+
category: "mocks"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
value: "plugin-cypress",
|
|
353
|
+
label: "Cypress Tests",
|
|
354
|
+
packageName: "@kubb/plugin-cypress",
|
|
355
|
+
importName: "pluginCypress",
|
|
356
|
+
category: "testing"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
value: "plugin-mcp",
|
|
360
|
+
label: "MCP Server (AI / Model Context Protocol)",
|
|
361
|
+
packageName: "@kubb/plugin-mcp",
|
|
362
|
+
importName: "pluginMcp",
|
|
363
|
+
category: "ai"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
value: "plugin-redoc",
|
|
367
|
+
label: "ReDoc Documentation",
|
|
368
|
+
packageName: "@kubb/plugin-redoc",
|
|
369
|
+
importName: "pluginRedoc",
|
|
370
|
+
category: "documentation"
|
|
371
|
+
}
|
|
372
|
+
];
|
|
205
373
|
//#endregion
|
|
206
|
-
//#region src/
|
|
207
|
-
|
|
374
|
+
//#region ../../internals/shared/src/init.ts
|
|
375
|
+
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
376
|
+
return `import { defineConfig } from 'kubb'
|
|
377
|
+
${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
|
|
378
|
+
|
|
379
|
+
export default defineConfig({
|
|
380
|
+
root: '.',
|
|
381
|
+
input: {
|
|
382
|
+
path: '${inputPath}',
|
|
383
|
+
},
|
|
384
|
+
output: {
|
|
385
|
+
path: '${outputPath}',
|
|
386
|
+
clean: true,
|
|
387
|
+
},
|
|
388
|
+
plugins: [
|
|
389
|
+
${selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join("\n")}
|
|
390
|
+
],
|
|
391
|
+
})
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region ../../internals/shared/src/loader.ts
|
|
396
|
+
/**
|
|
397
|
+
* jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
|
|
398
|
+
* `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
|
|
399
|
+
*/
|
|
400
|
+
const JITI_OPTIONS = {
|
|
208
401
|
jsx: {
|
|
209
402
|
runtime: "automatic",
|
|
210
403
|
importSource: "@kubb/renderer-jsx"
|
|
211
404
|
},
|
|
212
405
|
moduleCache: false
|
|
213
|
-
}
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
408
|
+
* Creates a runtime-aware loader for Kubb's TypeScript and JavaScript config modules.
|
|
409
|
+
*
|
|
410
|
+
* On Bun and Deno it imports the file natively, skipping jiti's transform step, and falls back to
|
|
411
|
+
* jiti only when the native import throws. On Node it always uses jiti, which transpiles TypeScript
|
|
412
|
+
* and the `@kubb/renderer-jsx` JSX runtime on the fly. The jiti instance is created lazily, so the
|
|
413
|
+
* Bun/Deno happy path never pays for it.
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```ts
|
|
417
|
+
* const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
function createModuleLoader() {
|
|
421
|
+
let jiti;
|
|
422
|
+
const getJiti = () => jiti ??= createJiti(import.meta.url, JITI_OPTIONS);
|
|
423
|
+
const viaJiti = (filePath, options) => options?.default ? getJiti().import(filePath, { default: true }) : getJiti().import(filePath);
|
|
424
|
+
const viaNative = async (filePath, options) => {
|
|
425
|
+
const href = pathToFileURL(filePath).href;
|
|
426
|
+
const mod = await (options?.bust != null ? import(`${href}?t=${options.bust}`) : import(href));
|
|
427
|
+
return options?.default ? mod.default ?? mod : mod;
|
|
428
|
+
};
|
|
429
|
+
return { async load(filePath, options) {
|
|
430
|
+
if (runtime.isBun || runtime.isDeno) try {
|
|
431
|
+
return await viaNative(filePath, options);
|
|
432
|
+
} catch {
|
|
433
|
+
return viaJiti(filePath, options);
|
|
434
|
+
}
|
|
435
|
+
return viaJiti(filePath, options);
|
|
436
|
+
} };
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/utils.ts
|
|
440
|
+
/**
|
|
441
|
+
* Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
|
|
442
|
+
* keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
|
|
443
|
+
* the agent can act on the problem rather than parsing a bare message. No ANSI styling,
|
|
444
|
+
* unlike the CLI renderer.
|
|
445
|
+
*/
|
|
446
|
+
function formatDiagnostics(diagnostics) {
|
|
447
|
+
return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
|
|
448
|
+
}
|
|
449
|
+
function formatDiagnostic(diagnostic) {
|
|
450
|
+
const { code, message, location, help, plugin, docsUrl } = diagnostic;
|
|
451
|
+
const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`];
|
|
452
|
+
if (location && "pointer" in location) lines.push(` at: ${location.pointer}`);
|
|
453
|
+
if (help) lines.push(` fix: ${help}`);
|
|
454
|
+
if (docsUrl) lines.push(` see: ${docsUrl}`);
|
|
455
|
+
return lines.join("\n");
|
|
456
|
+
}
|
|
457
|
+
const loader = createModuleLoader();
|
|
214
458
|
const loadedModules = /* @__PURE__ */ new Map();
|
|
215
459
|
async function loadModule(filePath) {
|
|
216
460
|
const ext = path.extname(filePath);
|
|
217
461
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
|
|
218
462
|
if (loadedModules.has(filePath)) return loadedModules.get(filePath);
|
|
219
|
-
const mod = await
|
|
463
|
+
const mod = await loader.load(filePath, { default: true });
|
|
220
464
|
loadedModules.set(filePath, mod);
|
|
221
465
|
return mod;
|
|
222
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Loads the user's Kubb config and returns it with the directory it was found in.
|
|
469
|
+
*
|
|
470
|
+
* When `configPath` is given it must use an allowed extension and resolve inside
|
|
471
|
+
* the current working directory, otherwise loading throws. When omitted, the
|
|
472
|
+
* known `kubb.config.*` file names are tried in the current directory. Every
|
|
473
|
+
* outcome is reported through `notify` before the function returns or throws.
|
|
474
|
+
*/
|
|
223
475
|
async function loadUserConfig(configPath, { notify }) {
|
|
224
476
|
if (configPath) {
|
|
225
477
|
const ext = path.extname(configPath);
|
|
@@ -275,8 +527,6 @@ async function loadUserConfig(configPath, { notify }) {
|
|
|
275
527
|
await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
|
|
276
528
|
throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
|
|
277
529
|
}
|
|
278
|
-
//#endregion
|
|
279
|
-
//#region src/utils/resolveCwd.ts
|
|
280
530
|
/**
|
|
281
531
|
* Determine the root directory based on userConfig.root and resolvedConfigDir
|
|
282
532
|
* 1. If userConfig.root exists and is absolute, use it as-is
|
|
@@ -290,8 +540,12 @@ function resolveCwd(userConfig, cwd) {
|
|
|
290
540
|
}
|
|
291
541
|
return cwd;
|
|
292
542
|
}
|
|
293
|
-
|
|
294
|
-
|
|
543
|
+
/**
|
|
544
|
+
* Normalizes a possible config into a single resolved `Config`.
|
|
545
|
+
*
|
|
546
|
+
* Calls the config when it is a function, awaits it when it is a promise, and
|
|
547
|
+
* picks the first entry when it resolves to an array.
|
|
548
|
+
*/
|
|
295
549
|
async function resolveUserConfig(config, options) {
|
|
296
550
|
const result = typeof config === "function" ? config({
|
|
297
551
|
logLevel: options.logLevel,
|
|
@@ -311,8 +565,8 @@ const generateTool = defineTool({
|
|
|
311
565
|
try {
|
|
312
566
|
const hooks = new AsyncEventEmitter();
|
|
313
567
|
const messages = [];
|
|
314
|
-
const notify = async (type, message,
|
|
315
|
-
messages.push(`${type}: ${message}`);
|
|
568
|
+
const notify = async (type, message, data) => {
|
|
569
|
+
messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
|
|
316
570
|
};
|
|
317
571
|
hooks.on("kubb:info", async ({ message }) => {
|
|
318
572
|
await notify(NotifyTypes.INFO, message);
|
|
@@ -326,6 +580,9 @@ const generateTool = defineTool({
|
|
|
326
580
|
hooks.on("kubb:warn", async ({ message }) => {
|
|
327
581
|
await notify(NotifyTypes.WARN, message);
|
|
328
582
|
});
|
|
583
|
+
hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
|
|
584
|
+
await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
|
|
585
|
+
});
|
|
329
586
|
hooks.on("kubb:plugin:start", async ({ plugin }) => {
|
|
330
587
|
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
|
|
331
588
|
});
|
|
@@ -335,8 +592,8 @@ const generateTool = defineTool({
|
|
|
335
592
|
hooks.on("kubb:files:processing:start", async () => {
|
|
336
593
|
await notify(NotifyTypes.FILES_START, "Starting file processing");
|
|
337
594
|
});
|
|
338
|
-
hooks.on("kubb:
|
|
339
|
-
await notify(NotifyTypes.
|
|
595
|
+
hooks.on("kubb:files:processing:update", async ({ files }) => {
|
|
596
|
+
await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
|
|
340
597
|
});
|
|
341
598
|
hooks.on("kubb:files:processing:end", async () => {
|
|
342
599
|
await notify(NotifyTypes.FILES_END, "File processing complete");
|
|
@@ -382,152 +639,23 @@ const generateTool = defineTool({
|
|
|
382
639
|
await kubb.setup();
|
|
383
640
|
await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
|
|
384
641
|
await notify(NotifyTypes.BUILD_START, "Starting build");
|
|
385
|
-
const { files,
|
|
642
|
+
const { files, diagnostics } = await kubb.safeBuild();
|
|
386
643
|
await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
644
|
+
const problems = diagnostics.filter(Diagnostics.isProblem);
|
|
645
|
+
const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
|
|
646
|
+
if (errors.length > 0) {
|
|
647
|
+
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
|
|
648
|
+
const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic));
|
|
649
|
+
return tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
391
650
|
}
|
|
392
651
|
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
|
|
393
652
|
return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
|
|
394
653
|
} catch (caughtError) {
|
|
395
|
-
const
|
|
396
|
-
return tool.error(`Build error
|
|
654
|
+
const serialized = Diagnostics.serialize(Diagnostics.from(caughtError));
|
|
655
|
+
return tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
397
656
|
}
|
|
398
657
|
});
|
|
399
658
|
//#endregion
|
|
400
|
-
//#region ../../internals/shared/src/constants.ts
|
|
401
|
-
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
402
|
-
const availablePlugins = [
|
|
403
|
-
{
|
|
404
|
-
value: "plugin-ts",
|
|
405
|
-
label: "TypeScript",
|
|
406
|
-
hint: "Recommended",
|
|
407
|
-
packageName: "@kubb/plugin-ts",
|
|
408
|
-
importName: "pluginTs",
|
|
409
|
-
category: "types"
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
value: "plugin-client",
|
|
413
|
-
label: "Client (Fetch/Axios)",
|
|
414
|
-
packageName: "@kubb/plugin-client",
|
|
415
|
-
importName: "pluginClient",
|
|
416
|
-
category: "client"
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
value: "plugin-react-query",
|
|
420
|
-
label: "React Query / TanStack Query",
|
|
421
|
-
packageName: "@kubb/plugin-react-query",
|
|
422
|
-
importName: "pluginReactQuery",
|
|
423
|
-
category: "framework"
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
value: "plugin-vue-query",
|
|
427
|
-
label: "Vue Query",
|
|
428
|
-
packageName: "@kubb/plugin-vue-query",
|
|
429
|
-
importName: "pluginVueQuery",
|
|
430
|
-
category: "framework"
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
value: "plugin-zod",
|
|
434
|
-
label: "Zod Schemas",
|
|
435
|
-
packageName: "@kubb/plugin-zod",
|
|
436
|
-
importName: "pluginZod",
|
|
437
|
-
category: "validation"
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
value: "plugin-faker",
|
|
441
|
-
label: "Faker.js Mocks",
|
|
442
|
-
packageName: "@kubb/plugin-faker",
|
|
443
|
-
importName: "pluginFaker",
|
|
444
|
-
category: "mocks"
|
|
445
|
-
},
|
|
446
|
-
{
|
|
447
|
-
value: "plugin-msw",
|
|
448
|
-
label: "MSW Handlers",
|
|
449
|
-
packageName: "@kubb/plugin-msw",
|
|
450
|
-
importName: "pluginMsw",
|
|
451
|
-
category: "mocks"
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
value: "plugin-cypress",
|
|
455
|
-
label: "Cypress Tests",
|
|
456
|
-
packageName: "@kubb/plugin-cypress",
|
|
457
|
-
importName: "pluginCypress",
|
|
458
|
-
category: "testing"
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
value: "plugin-mcp",
|
|
462
|
-
label: "MCP Server (AI / Model Context Protocol)",
|
|
463
|
-
packageName: "@kubb/plugin-mcp",
|
|
464
|
-
importName: "pluginMcp",
|
|
465
|
-
category: "ai"
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
value: "plugin-redoc",
|
|
469
|
-
label: "ReDoc Documentation",
|
|
470
|
-
packageName: "@kubb/plugin-redoc",
|
|
471
|
-
importName: "pluginRedoc",
|
|
472
|
-
category: "documentation"
|
|
473
|
-
}
|
|
474
|
-
];
|
|
475
|
-
const pluginDefaultConfigs = {
|
|
476
|
-
"plugin-ts": `pluginTs({
|
|
477
|
-
output: { path: 'models' },
|
|
478
|
-
})`,
|
|
479
|
-
"plugin-client": `pluginClient({
|
|
480
|
-
output: { path: 'clients' },
|
|
481
|
-
})`,
|
|
482
|
-
"plugin-react-query": `pluginReactQuery({
|
|
483
|
-
output: { path: 'hooks' },
|
|
484
|
-
})`,
|
|
485
|
-
"plugin-vue-query": `pluginVueQuery({
|
|
486
|
-
output: { path: 'hooks' },
|
|
487
|
-
})`,
|
|
488
|
-
"plugin-zod": `pluginZod({
|
|
489
|
-
output: { path: 'zod' },
|
|
490
|
-
})`,
|
|
491
|
-
"plugin-faker": `pluginFaker({
|
|
492
|
-
output: { path: 'mocks' },
|
|
493
|
-
})`,
|
|
494
|
-
"plugin-msw": `pluginMsw({
|
|
495
|
-
output: { path: 'msw' },
|
|
496
|
-
})`,
|
|
497
|
-
"plugin-cypress": `pluginCypress({
|
|
498
|
-
output: { path: 'cypress' },
|
|
499
|
-
})`,
|
|
500
|
-
"plugin-mcp": `pluginMcp({
|
|
501
|
-
output: { path: 'mcp' },
|
|
502
|
-
})`,
|
|
503
|
-
"plugin-redoc": `pluginRedoc({
|
|
504
|
-
output: { path: 'redoc' },
|
|
505
|
-
})`
|
|
506
|
-
};
|
|
507
|
-
//#endregion
|
|
508
|
-
//#region ../../internals/shared/src/init.ts
|
|
509
|
-
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
510
|
-
return `import { defineConfig } from 'kubb'
|
|
511
|
-
${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
|
|
512
|
-
|
|
513
|
-
export default defineConfig({
|
|
514
|
-
root: '.',
|
|
515
|
-
input: {
|
|
516
|
-
path: '${inputPath}',
|
|
517
|
-
},
|
|
518
|
-
output: {
|
|
519
|
-
path: '${outputPath}',
|
|
520
|
-
clean: true,
|
|
521
|
-
},
|
|
522
|
-
plugins: [
|
|
523
|
-
${selectedPlugins.map((plugin) => {
|
|
524
|
-
return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
|
|
525
|
-
}).join("\n")}
|
|
526
|
-
],
|
|
527
|
-
})
|
|
528
|
-
`;
|
|
529
|
-
}
|
|
530
|
-
//#endregion
|
|
531
659
|
//#region src/schemas/initSchema.ts
|
|
532
660
|
const initSchema = v.object({
|
|
533
661
|
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
@@ -536,6 +664,10 @@ const initSchema = v.object({
|
|
|
536
664
|
});
|
|
537
665
|
//#endregion
|
|
538
666
|
//#region src/tools/init.ts
|
|
667
|
+
/**
|
|
668
|
+
* Resolves a comma-separated plugin flag into the matching known plugin options.
|
|
669
|
+
* Unrecognized names are dropped, and a missing flag yields an empty list.
|
|
670
|
+
*/
|
|
539
671
|
function resolvePlugins(pluginsFlag) {
|
|
540
672
|
if (!pluginsFlag) return [];
|
|
541
673
|
const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
|
|
@@ -575,11 +707,18 @@ const validateTool = defineTool({
|
|
|
575
707
|
await mod.adapterOas().validate(input, { throwOnError: true });
|
|
576
708
|
return tool.text(`Validation successful: ${input}`);
|
|
577
709
|
} catch (err) {
|
|
578
|
-
|
|
710
|
+
const serialized = Diagnostics.serialize(Diagnostics.from(err));
|
|
711
|
+
return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
579
712
|
}
|
|
580
713
|
});
|
|
581
714
|
//#endregion
|
|
582
715
|
//#region src/server.ts
|
|
716
|
+
/**
|
|
717
|
+
* Builds the Kubb MCP server with the generate, validate, and init tools registered.
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* `const server = createMcpServer()`
|
|
721
|
+
*/
|
|
583
722
|
function createMcpServer() {
|
|
584
723
|
const server = new McpServer({
|
|
585
724
|
name: "Kubb",
|
|
@@ -595,23 +734,21 @@ function createMcpServer() {
|
|
|
595
734
|
]);
|
|
596
735
|
return server;
|
|
597
736
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const transport = new HttpTransport(server, { path: "/mcp" });
|
|
605
|
-
http.createServer(createRequestListener(async (request) => {
|
|
606
|
-
return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
|
|
607
|
-
})).listen(port, host, () => {
|
|
608
|
-
console.log(`Kubb MCP server on http://${host}:${port}`);
|
|
609
|
-
});
|
|
737
|
+
/**
|
|
738
|
+
* Starts the Kubb MCP server over stdio, the transport every local MCP client
|
|
739
|
+
* (Claude, Copilot, editors) uses when it launches the server as a subprocess.
|
|
740
|
+
*/
|
|
741
|
+
async function startServer() {
|
|
742
|
+
new StdioTransport(createMcpServer()).listen();
|
|
610
743
|
}
|
|
611
744
|
//#endregion
|
|
612
745
|
//#region src/index.ts
|
|
613
|
-
|
|
614
|
-
|
|
746
|
+
/**
|
|
747
|
+
* Entry point that starts the MCP server over stdio. The argument is accepted
|
|
748
|
+
* for CLI parity but ignored.
|
|
749
|
+
*/
|
|
750
|
+
async function run(_argv) {
|
|
751
|
+
await startServer();
|
|
615
752
|
}
|
|
616
753
|
//#endregion
|
|
617
754
|
export { createMcpServer, run };
|