@kubb/mcp 5.0.0-beta.6 → 5.0.0-beta.60
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 +322 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +322 -187
- package/dist/index.js.map +1 -1
- package/package.json +16 -24
- package/src/constants.ts +24 -0
- package/src/index.ts +1 -1
- package/src/schemas/generateSchema.ts +1 -1
- package/src/server.ts +9 -1
- package/src/tools/generate.ts +22 -24
- package/src/tools/init.ts +1 -1
- package/src/tools/validate.ts +4 -1
- package/src/{utils/loadUserConfig.ts → utils.ts} +63 -12
- package/src/types.ts +0 -23
- package/src/utils/resolveCwd.ts +0 -20
- package/src/utils/resolveUserConfig.ts +0 -13
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./chunk
|
|
1
|
+
import "./chunk-C0LytTxp.js";
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import { createRequestListener } from "@remix-run/node-fetch-server";
|
|
4
4
|
import { ValibotJsonSchemaAdapter } from "@tmcp/adapter-valibot";
|
|
@@ -6,16 +6,17 @@ import { HttpTransport } from "@tmcp/transport-http";
|
|
|
6
6
|
import { StdioTransport } from "@tmcp/transport-stdio";
|
|
7
7
|
import { McpServer } from "tmcp";
|
|
8
8
|
import { EventEmitter } from "node:events";
|
|
9
|
-
import
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { createKubb } from "@kubb/core";
|
|
9
|
+
import { Diagnostics, createKubb } from "@kubb/core";
|
|
12
10
|
import { defineTool } from "tmcp/tool";
|
|
13
11
|
import { tool } from "tmcp/utils";
|
|
14
12
|
import * as v from "valibot";
|
|
13
|
+
import fs, { existsSync } from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
15
16
|
import { createJiti } from "jiti";
|
|
16
17
|
import process$1 from "node:process";
|
|
17
18
|
//#region package.json
|
|
18
|
-
var version = "5.0.0-beta.
|
|
19
|
+
var version = "5.0.0-beta.60";
|
|
19
20
|
//#endregion
|
|
20
21
|
//#region ../../internals/utils/src/errors.ts
|
|
21
22
|
/**
|
|
@@ -63,9 +64,12 @@ var AsyncEventEmitter = class {
|
|
|
63
64
|
* await emitter.emit('build', 'petstore')
|
|
64
65
|
* ```
|
|
65
66
|
*/
|
|
66
|
-
|
|
67
|
+
emit(eventName, ...eventArgs) {
|
|
67
68
|
const listeners = this.#emitter.listeners(eventName);
|
|
68
69
|
if (listeners.length === 0) return;
|
|
70
|
+
return this.#emitAll(eventName, listeners, eventArgs);
|
|
71
|
+
}
|
|
72
|
+
async #emitAll(eventName, listeners, eventArgs) {
|
|
69
73
|
for (const listener of listeners) try {
|
|
70
74
|
await listener(...eventArgs);
|
|
71
75
|
} catch (err) {
|
|
@@ -128,6 +132,24 @@ var AsyncEventEmitter = class {
|
|
|
128
132
|
return this.#emitter.listenerCount(eventName);
|
|
129
133
|
}
|
|
130
134
|
/**
|
|
135
|
+
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
|
|
136
|
+
* Set this above the expected listener count when many listeners attach by design.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* emitter.setMaxListeners(40)
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
setMaxListeners(max) {
|
|
144
|
+
this.#emitter.setMaxListeners(max);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Returns the current per-event listener ceiling.
|
|
148
|
+
*/
|
|
149
|
+
getMaxListeners() {
|
|
150
|
+
return this.#emitter.getMaxListeners();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
131
153
|
* Removes all listeners from every event channel.
|
|
132
154
|
*
|
|
133
155
|
* @example
|
|
@@ -153,31 +175,95 @@ function isPromise(result) {
|
|
|
153
175
|
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
154
176
|
}
|
|
155
177
|
//#endregion
|
|
156
|
-
//#region src/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
//#region ../../internals/utils/src/runtime.ts
|
|
179
|
+
/**
|
|
180
|
+
* Detects the JavaScript runtime executing the current process and exposes its name and version.
|
|
181
|
+
*
|
|
182
|
+
* Prefer the shared {@link runtime} instance over constructing your own.
|
|
183
|
+
*/
|
|
184
|
+
var Runtime = class {
|
|
185
|
+
/**
|
|
186
|
+
* `true` when the current process is running under Bun.
|
|
187
|
+
*
|
|
188
|
+
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
189
|
+
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
190
|
+
* otherwise look like Node.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* if (runtime.isBun) {
|
|
195
|
+
* await Bun.write(path, data)
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
get isBun() {
|
|
200
|
+
return typeof Bun !== "undefined";
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* `true` when the current process is running under Deno.
|
|
204
|
+
*/
|
|
205
|
+
get isDeno() {
|
|
206
|
+
return typeof globalThis.Deno !== "undefined";
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* `true` when the current process is running under Node.
|
|
210
|
+
*
|
|
211
|
+
* Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
|
|
212
|
+
*/
|
|
213
|
+
get isNode() {
|
|
214
|
+
return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Name of the runtime executing the current process.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
get name() {
|
|
225
|
+
if (this.isBun) return "bun";
|
|
226
|
+
if (this.isDeno) return "deno";
|
|
227
|
+
return "node";
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Version of the active runtime, or an empty string when it cannot be read.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* runtime.version // '1.3.11' under Bun, '22.22.2' under Node
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
get version() {
|
|
238
|
+
if (this.isBun) return process.versions.bun ?? "";
|
|
239
|
+
if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
|
|
240
|
+
return process.versions?.node ?? "";
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
|
|
245
|
+
*/
|
|
246
|
+
const runtime = new Runtime();
|
|
170
247
|
//#endregion
|
|
171
|
-
//#region src/
|
|
248
|
+
//#region src/constants.ts
|
|
249
|
+
const ALLOWED_CONFIG_EXTENSIONS = new Set([
|
|
250
|
+
".ts",
|
|
251
|
+
".mts",
|
|
252
|
+
".cts",
|
|
253
|
+
".js",
|
|
254
|
+
".mjs",
|
|
255
|
+
".cjs"
|
|
256
|
+
]);
|
|
172
257
|
const NotifyTypes = {
|
|
173
258
|
INFO: "INFO",
|
|
174
259
|
SUCCESS: "SUCCESS",
|
|
175
260
|
ERROR: "ERROR",
|
|
176
261
|
WARN: "WARN",
|
|
262
|
+
DIAGNOSTIC: "DIAGNOSTIC",
|
|
177
263
|
PLUGIN_START: "PLUGIN_START",
|
|
178
264
|
PLUGIN_END: "PLUGIN_END",
|
|
179
265
|
FILES_START: "FILES_START",
|
|
180
|
-
|
|
266
|
+
FILES_UPDATE: "FILES_UPDATE",
|
|
181
267
|
FILES_END: "FILES_END",
|
|
182
268
|
GENERATION_START: "GENERATION_START",
|
|
183
269
|
GENERATION_END: "GENERATION_END",
|
|
@@ -189,34 +275,201 @@ const NotifyTypes = {
|
|
|
189
275
|
BUILD_START: "BUILD_START",
|
|
190
276
|
BUILD_END: "BUILD_END",
|
|
191
277
|
BUILD_FAILED: "BUILD_FAILED",
|
|
192
|
-
BUILD_SUCCESS: "BUILD_SUCCESS"
|
|
193
|
-
FATAL_ERROR: "FATAL_ERROR"
|
|
278
|
+
BUILD_SUCCESS: "BUILD_SUCCESS"
|
|
194
279
|
};
|
|
195
280
|
//#endregion
|
|
196
|
-
//#region src/
|
|
197
|
-
const
|
|
198
|
-
".ts",
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
281
|
+
//#region src/schemas/generateSchema.ts
|
|
282
|
+
const generateSchema = v.object({
|
|
283
|
+
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"))),
|
|
284
|
+
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
285
|
+
output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory path (overrides config)"))),
|
|
286
|
+
logLevel: v.optional(v.pipe(v.picklist([
|
|
287
|
+
"silent",
|
|
288
|
+
"info",
|
|
289
|
+
"verbose"
|
|
290
|
+
]), v.description("Log level for build output")), "info")
|
|
291
|
+
});
|
|
205
292
|
//#endregion
|
|
206
|
-
//#region src/
|
|
207
|
-
const
|
|
293
|
+
//#region ../../internals/shared/src/constants.ts
|
|
294
|
+
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
295
|
+
const availablePlugins = [
|
|
296
|
+
{
|
|
297
|
+
value: "plugin-ts",
|
|
298
|
+
label: "TypeScript",
|
|
299
|
+
hint: "Recommended",
|
|
300
|
+
packageName: "@kubb/plugin-ts",
|
|
301
|
+
importName: "pluginTs",
|
|
302
|
+
category: "types"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
value: "plugin-client",
|
|
306
|
+
label: "Client (Fetch/Axios)",
|
|
307
|
+
packageName: "@kubb/plugin-client",
|
|
308
|
+
importName: "pluginClient",
|
|
309
|
+
category: "client"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
value: "plugin-react-query",
|
|
313
|
+
label: "React Query / TanStack Query",
|
|
314
|
+
packageName: "@kubb/plugin-react-query",
|
|
315
|
+
importName: "pluginReactQuery",
|
|
316
|
+
category: "framework"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
value: "plugin-vue-query",
|
|
320
|
+
label: "Vue Query",
|
|
321
|
+
packageName: "@kubb/plugin-vue-query",
|
|
322
|
+
importName: "pluginVueQuery",
|
|
323
|
+
category: "framework"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
value: "plugin-zod",
|
|
327
|
+
label: "Zod Schemas",
|
|
328
|
+
packageName: "@kubb/plugin-zod",
|
|
329
|
+
importName: "pluginZod",
|
|
330
|
+
category: "validation"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
value: "plugin-faker",
|
|
334
|
+
label: "Faker.js Mocks",
|
|
335
|
+
packageName: "@kubb/plugin-faker",
|
|
336
|
+
importName: "pluginFaker",
|
|
337
|
+
category: "mocks"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
value: "plugin-msw",
|
|
341
|
+
label: "MSW Handlers",
|
|
342
|
+
packageName: "@kubb/plugin-msw",
|
|
343
|
+
importName: "pluginMsw",
|
|
344
|
+
category: "mocks"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
value: "plugin-cypress",
|
|
348
|
+
label: "Cypress Tests",
|
|
349
|
+
packageName: "@kubb/plugin-cypress",
|
|
350
|
+
importName: "pluginCypress",
|
|
351
|
+
category: "testing"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
value: "plugin-mcp",
|
|
355
|
+
label: "MCP Server (AI / Model Context Protocol)",
|
|
356
|
+
packageName: "@kubb/plugin-mcp",
|
|
357
|
+
importName: "pluginMcp",
|
|
358
|
+
category: "ai"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
value: "plugin-redoc",
|
|
362
|
+
label: "ReDoc Documentation",
|
|
363
|
+
packageName: "@kubb/plugin-redoc",
|
|
364
|
+
importName: "pluginRedoc",
|
|
365
|
+
category: "documentation"
|
|
366
|
+
}
|
|
367
|
+
];
|
|
368
|
+
const pluginDefaultConfigs = {
|
|
369
|
+
"plugin-ts": `pluginTs()`,
|
|
370
|
+
"plugin-client": `pluginClient()`,
|
|
371
|
+
"plugin-react-query": `pluginReactQuery()`,
|
|
372
|
+
"plugin-vue-query": `pluginVueQuery()`,
|
|
373
|
+
"plugin-zod": `pluginZod()`,
|
|
374
|
+
"plugin-faker": `pluginFaker()`,
|
|
375
|
+
"plugin-msw": `pluginMsw()`,
|
|
376
|
+
"plugin-cypress": `pluginCypress()`,
|
|
377
|
+
"plugin-mcp": `pluginMcp()`,
|
|
378
|
+
"plugin-redoc": `pluginRedoc()`
|
|
379
|
+
};
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region ../../internals/shared/src/init.ts
|
|
382
|
+
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
383
|
+
return `import { defineConfig } from 'kubb'
|
|
384
|
+
${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
|
|
385
|
+
|
|
386
|
+
export default defineConfig({
|
|
387
|
+
root: '.',
|
|
388
|
+
input: {
|
|
389
|
+
path: '${inputPath}',
|
|
390
|
+
},
|
|
391
|
+
output: {
|
|
392
|
+
path: '${outputPath}',
|
|
393
|
+
clean: true,
|
|
394
|
+
},
|
|
395
|
+
plugins: [
|
|
396
|
+
${selectedPlugins.map((plugin) => {
|
|
397
|
+
return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
|
|
398
|
+
}).join("\n")}
|
|
399
|
+
],
|
|
400
|
+
})
|
|
401
|
+
`;
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region ../../internals/shared/src/loader.ts
|
|
405
|
+
/**
|
|
406
|
+
* jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
|
|
407
|
+
* `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
|
|
408
|
+
*/
|
|
409
|
+
const JITI_OPTIONS = {
|
|
208
410
|
jsx: {
|
|
209
411
|
runtime: "automatic",
|
|
210
412
|
importSource: "@kubb/renderer-jsx"
|
|
211
413
|
},
|
|
212
414
|
moduleCache: false
|
|
213
|
-
}
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Creates a runtime-aware loader for Kubb's TypeScript and JavaScript config modules.
|
|
418
|
+
*
|
|
419
|
+
* On Bun and Deno it imports the file natively, skipping jiti's transform step, and falls back to
|
|
420
|
+
* jiti only when the native import throws. On Node it always uses jiti, which transpiles TypeScript
|
|
421
|
+
* and the `@kubb/renderer-jsx` JSX runtime on the fly. The jiti instance is created lazily, so the
|
|
422
|
+
* Bun/Deno happy path never pays for it.
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```ts
|
|
426
|
+
* const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
function createModuleLoader() {
|
|
430
|
+
let jiti;
|
|
431
|
+
const getJiti = () => jiti ??= createJiti(import.meta.url, JITI_OPTIONS);
|
|
432
|
+
const viaJiti = (filePath, options) => options?.default ? getJiti().import(filePath, { default: true }) : getJiti().import(filePath);
|
|
433
|
+
const viaNative = async (filePath, options) => {
|
|
434
|
+
const href = pathToFileURL(filePath).href;
|
|
435
|
+
const mod = await (options?.bust != null ? import(`${href}?t=${options.bust}`) : import(href));
|
|
436
|
+
return options?.default ? mod.default ?? mod : mod;
|
|
437
|
+
};
|
|
438
|
+
return { async load(filePath, options) {
|
|
439
|
+
if (runtime.isBun || runtime.isDeno) try {
|
|
440
|
+
return await viaNative(filePath, options);
|
|
441
|
+
} catch {
|
|
442
|
+
return viaJiti(filePath, options);
|
|
443
|
+
}
|
|
444
|
+
return viaJiti(filePath, options);
|
|
445
|
+
} };
|
|
446
|
+
}
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/utils.ts
|
|
449
|
+
/**
|
|
450
|
+
* Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
|
|
451
|
+
* keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
|
|
452
|
+
* the agent can act on the problem rather than parsing a bare message. No ANSI styling,
|
|
453
|
+
* unlike the CLI renderer.
|
|
454
|
+
*/
|
|
455
|
+
function formatDiagnostics(diagnostics) {
|
|
456
|
+
return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
|
|
457
|
+
}
|
|
458
|
+
function formatDiagnostic(diagnostic) {
|
|
459
|
+
const { code, severity, message, location, help, plugin, docsUrl } = diagnostic;
|
|
460
|
+
const lines = [`${severity} ${plugin ? `${plugin}(${code})` : code}: ${message}`];
|
|
461
|
+
if (location && "pointer" in location) lines.push(` at ${location.pointer}`);
|
|
462
|
+
if (help) lines.push(` help: ${help}`);
|
|
463
|
+
if (docsUrl) lines.push(` docs: ${docsUrl}`);
|
|
464
|
+
return lines.join("\n");
|
|
465
|
+
}
|
|
466
|
+
const loader = createModuleLoader();
|
|
214
467
|
const loadedModules = /* @__PURE__ */ new Map();
|
|
215
468
|
async function loadModule(filePath) {
|
|
216
469
|
const ext = path.extname(filePath);
|
|
217
470
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
|
|
218
471
|
if (loadedModules.has(filePath)) return loadedModules.get(filePath);
|
|
219
|
-
const mod = await
|
|
472
|
+
const mod = await loader.load(filePath, { default: true });
|
|
220
473
|
loadedModules.set(filePath, mod);
|
|
221
474
|
return mod;
|
|
222
475
|
}
|
|
@@ -275,8 +528,6 @@ async function loadUserConfig(configPath, { notify }) {
|
|
|
275
528
|
await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
|
|
276
529
|
throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
|
|
277
530
|
}
|
|
278
|
-
//#endregion
|
|
279
|
-
//#region src/utils/resolveCwd.ts
|
|
280
531
|
/**
|
|
281
532
|
* Determine the root directory based on userConfig.root and resolvedConfigDir
|
|
282
533
|
* 1. If userConfig.root exists and is absolute, use it as-is
|
|
@@ -290,8 +541,6 @@ function resolveCwd(userConfig, cwd) {
|
|
|
290
541
|
}
|
|
291
542
|
return cwd;
|
|
292
543
|
}
|
|
293
|
-
//#endregion
|
|
294
|
-
//#region src/utils/resolveUserConfig.ts
|
|
295
544
|
async function resolveUserConfig(config, options) {
|
|
296
545
|
const result = typeof config === "function" ? config({
|
|
297
546
|
logLevel: options.logLevel,
|
|
@@ -311,8 +560,8 @@ const generateTool = defineTool({
|
|
|
311
560
|
try {
|
|
312
561
|
const hooks = new AsyncEventEmitter();
|
|
313
562
|
const messages = [];
|
|
314
|
-
const notify = async (type, message,
|
|
315
|
-
messages.push(`${type}: ${message}`);
|
|
563
|
+
const notify = async (type, message, data) => {
|
|
564
|
+
messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
|
|
316
565
|
};
|
|
317
566
|
hooks.on("kubb:info", async ({ message }) => {
|
|
318
567
|
await notify(NotifyTypes.INFO, message);
|
|
@@ -326,6 +575,9 @@ const generateTool = defineTool({
|
|
|
326
575
|
hooks.on("kubb:warn", async ({ message }) => {
|
|
327
576
|
await notify(NotifyTypes.WARN, message);
|
|
328
577
|
});
|
|
578
|
+
hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
|
|
579
|
+
await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
|
|
580
|
+
});
|
|
329
581
|
hooks.on("kubb:plugin:start", async ({ plugin }) => {
|
|
330
582
|
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
|
|
331
583
|
});
|
|
@@ -335,8 +587,8 @@ const generateTool = defineTool({
|
|
|
335
587
|
hooks.on("kubb:files:processing:start", async () => {
|
|
336
588
|
await notify(NotifyTypes.FILES_START, "Starting file processing");
|
|
337
589
|
});
|
|
338
|
-
hooks.on("kubb:
|
|
339
|
-
await notify(NotifyTypes.
|
|
590
|
+
hooks.on("kubb:files:processing:update", async ({ files }) => {
|
|
591
|
+
await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
|
|
340
592
|
});
|
|
341
593
|
hooks.on("kubb:files:processing:end", async () => {
|
|
342
594
|
await notify(NotifyTypes.FILES_END, "File processing complete");
|
|
@@ -382,152 +634,23 @@ const generateTool = defineTool({
|
|
|
382
634
|
await kubb.setup();
|
|
383
635
|
await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
|
|
384
636
|
await notify(NotifyTypes.BUILD_START, "Starting build");
|
|
385
|
-
const { files,
|
|
637
|
+
const { files, diagnostics } = await kubb.safeBuild();
|
|
386
638
|
await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
639
|
+
const problems = diagnostics.filter(Diagnostics.isProblem);
|
|
640
|
+
const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
|
|
641
|
+
if (errors.length > 0) {
|
|
642
|
+
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
|
|
643
|
+
const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic));
|
|
644
|
+
return tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
391
645
|
}
|
|
392
646
|
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
|
|
393
647
|
return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
|
|
394
648
|
} catch (caughtError) {
|
|
395
|
-
const
|
|
396
|
-
return tool.error(`Build error
|
|
649
|
+
const serialized = Diagnostics.serialize(Diagnostics.from(caughtError));
|
|
650
|
+
return tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
397
651
|
}
|
|
398
652
|
});
|
|
399
653
|
//#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
654
|
//#region src/schemas/initSchema.ts
|
|
532
655
|
const initSchema = v.object({
|
|
533
656
|
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
@@ -575,7 +698,8 @@ const validateTool = defineTool({
|
|
|
575
698
|
await mod.adapterOas().validate(input, { throwOnError: true });
|
|
576
699
|
return tool.text(`Validation successful: ${input}`);
|
|
577
700
|
} catch (err) {
|
|
578
|
-
|
|
701
|
+
const serialized = Diagnostics.serialize(Diagnostics.from(err));
|
|
702
|
+
return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
579
703
|
}
|
|
580
704
|
});
|
|
581
705
|
//#endregion
|
|
@@ -584,7 +708,10 @@ function createMcpServer() {
|
|
|
584
708
|
const server = new McpServer({
|
|
585
709
|
name: "Kubb",
|
|
586
710
|
version
|
|
587
|
-
}, {
|
|
711
|
+
}, {
|
|
712
|
+
adapter: new ValibotJsonSchemaAdapter(),
|
|
713
|
+
capabilities: { tools: {} }
|
|
714
|
+
});
|
|
588
715
|
server.tools([
|
|
589
716
|
generateTool,
|
|
590
717
|
validateTool,
|
|
@@ -599,11 +726,19 @@ async function startServer({ port, host = "localhost" } = {}) {
|
|
|
599
726
|
return;
|
|
600
727
|
}
|
|
601
728
|
const transport = new HttpTransport(server, { path: "/mcp" });
|
|
602
|
-
http.createServer(createRequestListener(async (request) => {
|
|
729
|
+
const httpServer = http.createServer(createRequestListener(async (request) => {
|
|
603
730
|
return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
|
|
604
|
-
}))
|
|
731
|
+
}));
|
|
732
|
+
httpServer.listen(port, host, () => {
|
|
605
733
|
console.log(`Kubb MCP server on http://${host}:${port}`);
|
|
606
734
|
});
|
|
735
|
+
const closeServer = () => httpServer.close();
|
|
736
|
+
process.once("SIGINT", closeServer);
|
|
737
|
+
process.once("SIGTERM", closeServer);
|
|
738
|
+
httpServer.once("close", () => {
|
|
739
|
+
process.off("SIGINT", closeServer);
|
|
740
|
+
process.off("SIGTERM", closeServer);
|
|
741
|
+
});
|
|
607
742
|
}
|
|
608
743
|
//#endregion
|
|
609
744
|
//#region src/index.ts
|