@kubb/mcp 5.0.0-beta.75 → 5.0.0-beta.77
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 +66 -19
- package/dist/index.cjs +379 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.js +375 -188
- package/dist/index.js.map +1 -1
- package/package.json +17 -22
- package/src/constants.ts +0 -1
- package/src/index.ts +0 -5
- package/src/schemas/generateSchema.ts +0 -12
- package/src/server.ts +0 -48
- package/src/tools/generate.ts +0 -210
- package/src/types.ts +0 -23
- package/src/utils/loadUserConfig.ts +0 -77
- package/src/utils/resolveCwd.ts +0 -20
- package/src/utils/resolveUserConfig.ts +0 -28
- /package/dist/{chunk--u3MIqq1.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { z } from "zod";
|
|
1
|
+
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
+
import { ValibotJsonSchemaAdapter } from "@tmcp/adapter-valibot";
|
|
3
|
+
import { StdioTransport } from "@tmcp/transport-stdio";
|
|
4
|
+
import { McpServer } from "tmcp";
|
|
6
5
|
import { EventEmitter } from "node:events";
|
|
7
|
-
import {
|
|
6
|
+
import { Diagnostics, createKubb } from "@kubb/core";
|
|
7
|
+
import { defineTool } from "tmcp/tool";
|
|
8
|
+
import { tool } from "tmcp/utils";
|
|
9
|
+
import * as v from "valibot";
|
|
10
|
+
import fs, { existsSync } from "node:fs";
|
|
8
11
|
import path from "node:path";
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
12
|
+
import { createJiti } from "jiti";
|
|
13
|
+
import process$1 from "node:process";
|
|
11
14
|
//#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
|
-
});
|
|
15
|
+
var version = "5.0.0-beta.77";
|
|
28
16
|
//#endregion
|
|
29
17
|
//#region ../../internals/utils/src/errors.ts
|
|
30
18
|
/**
|
|
@@ -72,9 +60,12 @@ var AsyncEventEmitter = class {
|
|
|
72
60
|
* await emitter.emit('build', 'petstore')
|
|
73
61
|
* ```
|
|
74
62
|
*/
|
|
75
|
-
|
|
63
|
+
emit(eventName, ...eventArgs) {
|
|
76
64
|
const listeners = this.#emitter.listeners(eventName);
|
|
77
65
|
if (listeners.length === 0) return;
|
|
66
|
+
return this.#emitAll(eventName, listeners, eventArgs);
|
|
67
|
+
}
|
|
68
|
+
async #emitAll(eventName, listeners, eventArgs) {
|
|
78
69
|
for (const listener of listeners) try {
|
|
79
70
|
await listener(...eventArgs);
|
|
80
71
|
} catch (err) {
|
|
@@ -99,21 +90,6 @@ var AsyncEventEmitter = class {
|
|
|
99
90
|
this.#emitter.on(eventName, handler);
|
|
100
91
|
}
|
|
101
92
|
/**
|
|
102
|
-
* Registers a one-shot listener that removes itself after the first invocation.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* emitter.onOnce('build', async (name) => { console.log(name) })
|
|
107
|
-
* ```
|
|
108
|
-
*/
|
|
109
|
-
onOnce(eventName, handler) {
|
|
110
|
-
const wrapper = (...args) => {
|
|
111
|
-
this.off(eventName, wrapper);
|
|
112
|
-
return handler(...args);
|
|
113
|
-
};
|
|
114
|
-
this.on(eventName, wrapper);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
93
|
* Removes a previously registered listener.
|
|
118
94
|
*
|
|
119
95
|
* @example
|
|
@@ -137,6 +113,18 @@ var AsyncEventEmitter = class {
|
|
|
137
113
|
return this.#emitter.listenerCount(eventName);
|
|
138
114
|
}
|
|
139
115
|
/**
|
|
116
|
+
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
|
|
117
|
+
* Set this above the expected listener count when many listeners attach by design.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* emitter.setMaxListeners(40)
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
setMaxListeners(max) {
|
|
125
|
+
this.#emitter.setMaxListeners(max);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
140
128
|
* Removes all listeners from every event channel.
|
|
141
129
|
*
|
|
142
130
|
* @example
|
|
@@ -162,16 +150,33 @@ function isPromise(result) {
|
|
|
162
150
|
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
163
151
|
}
|
|
164
152
|
//#endregion
|
|
165
|
-
//#region src/
|
|
153
|
+
//#region src/constants.ts
|
|
154
|
+
/**
|
|
155
|
+
* File extensions a Kubb config is allowed to use. A config path with any other
|
|
156
|
+
* extension is rejected before it is loaded.
|
|
157
|
+
*/
|
|
158
|
+
const ALLOWED_CONFIG_EXTENSIONS = new Set([
|
|
159
|
+
".ts",
|
|
160
|
+
".mts",
|
|
161
|
+
".cts",
|
|
162
|
+
".js",
|
|
163
|
+
".mjs",
|
|
164
|
+
".cjs"
|
|
165
|
+
]);
|
|
166
|
+
/**
|
|
167
|
+
* Notification kinds reported back to the MCP client while loading config and
|
|
168
|
+
* running generation, covering progress, results, and errors.
|
|
169
|
+
*/
|
|
166
170
|
const NotifyTypes = {
|
|
167
171
|
INFO: "INFO",
|
|
168
172
|
SUCCESS: "SUCCESS",
|
|
169
173
|
ERROR: "ERROR",
|
|
170
174
|
WARN: "WARN",
|
|
175
|
+
DIAGNOSTIC: "DIAGNOSTIC",
|
|
171
176
|
PLUGIN_START: "PLUGIN_START",
|
|
172
177
|
PLUGIN_END: "PLUGIN_END",
|
|
173
178
|
FILES_START: "FILES_START",
|
|
174
|
-
|
|
179
|
+
FILES_UPDATE: "FILES_UPDATE",
|
|
175
180
|
FILES_END: "FILES_END",
|
|
176
181
|
GENERATION_START: "GENERATION_START",
|
|
177
182
|
GENERATION_END: "GENERATION_END",
|
|
@@ -183,33 +188,191 @@ const NotifyTypes = {
|
|
|
183
188
|
BUILD_START: "BUILD_START",
|
|
184
189
|
BUILD_END: "BUILD_END",
|
|
185
190
|
BUILD_FAILED: "BUILD_FAILED",
|
|
186
|
-
BUILD_SUCCESS: "BUILD_SUCCESS"
|
|
187
|
-
FATAL_ERROR: "FATAL_ERROR"
|
|
191
|
+
BUILD_SUCCESS: "BUILD_SUCCESS"
|
|
188
192
|
};
|
|
189
193
|
//#endregion
|
|
190
|
-
//#region src/
|
|
191
|
-
const
|
|
192
|
-
".ts",
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
//#region src/schemas/generateSchema.ts
|
|
195
|
+
const generateSchema = v.object({
|
|
196
|
+
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"))),
|
|
197
|
+
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
198
|
+
output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory path (overrides config)"))),
|
|
199
|
+
logLevel: v.optional(v.pipe(v.picklist([
|
|
200
|
+
"silent",
|
|
201
|
+
"info",
|
|
202
|
+
"verbose"
|
|
203
|
+
]), v.description("Log level for build output")), "info")
|
|
204
|
+
});
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region ../../internals/shared/src/constants.ts
|
|
207
|
+
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
208
|
+
const availablePlugins = [
|
|
209
|
+
{
|
|
210
|
+
value: "plugin-ts",
|
|
211
|
+
label: "TypeScript",
|
|
212
|
+
hint: "Recommended",
|
|
213
|
+
packageName: "@kubb/plugin-ts",
|
|
214
|
+
importName: "pluginTs",
|
|
215
|
+
category: "types"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
value: "plugin-axios",
|
|
219
|
+
label: "Axios Client",
|
|
220
|
+
packageName: "@kubb/plugin-axios",
|
|
221
|
+
importName: "pluginAxios",
|
|
222
|
+
category: "client"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
value: "plugin-fetch",
|
|
226
|
+
label: "Fetch Client",
|
|
227
|
+
packageName: "@kubb/plugin-fetch",
|
|
228
|
+
importName: "pluginFetch",
|
|
229
|
+
category: "client"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
value: "plugin-react-query",
|
|
233
|
+
label: "React Query / TanStack Query",
|
|
234
|
+
packageName: "@kubb/plugin-react-query",
|
|
235
|
+
importName: "pluginReactQuery",
|
|
236
|
+
category: "framework"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
value: "plugin-vue-query",
|
|
240
|
+
label: "Vue Query",
|
|
241
|
+
packageName: "@kubb/plugin-vue-query",
|
|
242
|
+
importName: "pluginVueQuery",
|
|
243
|
+
category: "framework"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
value: "plugin-zod",
|
|
247
|
+
label: "Zod Schemas",
|
|
248
|
+
packageName: "@kubb/plugin-zod",
|
|
249
|
+
importName: "pluginZod",
|
|
250
|
+
category: "validation"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
value: "plugin-faker",
|
|
254
|
+
label: "Faker.js Mocks",
|
|
255
|
+
packageName: "@kubb/plugin-faker",
|
|
256
|
+
importName: "pluginFaker",
|
|
257
|
+
category: "mocks"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
value: "plugin-msw",
|
|
261
|
+
label: "MSW Handlers",
|
|
262
|
+
packageName: "@kubb/plugin-msw",
|
|
263
|
+
importName: "pluginMsw",
|
|
264
|
+
category: "mocks"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
value: "plugin-cypress",
|
|
268
|
+
label: "Cypress Tests",
|
|
269
|
+
packageName: "@kubb/plugin-cypress",
|
|
270
|
+
importName: "pluginCypress",
|
|
271
|
+
category: "testing"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
value: "plugin-mcp",
|
|
275
|
+
label: "MCP Server (AI / Model Context Protocol)",
|
|
276
|
+
packageName: "@kubb/plugin-mcp",
|
|
277
|
+
importName: "pluginMcp",
|
|
278
|
+
category: "ai"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
value: "plugin-redoc",
|
|
282
|
+
label: "ReDoc Documentation",
|
|
283
|
+
packageName: "@kubb/plugin-redoc",
|
|
284
|
+
importName: "pluginRedoc",
|
|
285
|
+
category: "documentation"
|
|
286
|
+
}
|
|
287
|
+
];
|
|
199
288
|
//#endregion
|
|
200
|
-
//#region src/
|
|
289
|
+
//#region ../../internals/shared/src/init.ts
|
|
290
|
+
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
291
|
+
return `import { defineConfig } from 'kubb'
|
|
292
|
+
${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
|
|
293
|
+
|
|
294
|
+
export default defineConfig({
|
|
295
|
+
root: '.',
|
|
296
|
+
input: {
|
|
297
|
+
path: '${inputPath}',
|
|
298
|
+
},
|
|
299
|
+
output: {
|
|
300
|
+
path: '${outputPath}',
|
|
301
|
+
clean: true,
|
|
302
|
+
},
|
|
303
|
+
plugins: [
|
|
304
|
+
${selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join("\n")}
|
|
305
|
+
],
|
|
306
|
+
})
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region ../../internals/shared/src/loader.ts
|
|
311
|
+
/**
|
|
312
|
+
* jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
|
|
313
|
+
* `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
|
|
314
|
+
*/
|
|
315
|
+
const JITI_OPTIONS = {
|
|
316
|
+
jsx: {
|
|
317
|
+
runtime: "automatic",
|
|
318
|
+
importSource: "@kubb/renderer-jsx"
|
|
319
|
+
},
|
|
320
|
+
moduleCache: false
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* Creates a jiti-based loader for Kubb's TypeScript and JavaScript config modules.
|
|
324
|
+
*
|
|
325
|
+
* jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
function createModuleLoader() {
|
|
333
|
+
const jiti = createJiti(import.meta.url, JITI_OPTIONS);
|
|
334
|
+
return { load(filePath, options) {
|
|
335
|
+
return options?.default ? jiti.import(filePath, { default: true }) : jiti.import(filePath);
|
|
336
|
+
} };
|
|
337
|
+
}
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/utils.ts
|
|
340
|
+
/**
|
|
341
|
+
* Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
|
|
342
|
+
* keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
|
|
343
|
+
* the agent can act on the problem rather than parsing a bare message. No ANSI styling,
|
|
344
|
+
* unlike the CLI renderer.
|
|
345
|
+
*/
|
|
346
|
+
function formatDiagnostics(diagnostics) {
|
|
347
|
+
return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
|
|
348
|
+
}
|
|
349
|
+
function formatDiagnostic(diagnostic) {
|
|
350
|
+
const { code, message, location, help, plugin, docsUrl } = diagnostic;
|
|
351
|
+
const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`];
|
|
352
|
+
if (location && "pointer" in location) lines.push(` at: ${location.pointer}`);
|
|
353
|
+
if (help) lines.push(` fix: ${help}`);
|
|
354
|
+
if (docsUrl) lines.push(` see: ${docsUrl}`);
|
|
355
|
+
return lines.join("\n");
|
|
356
|
+
}
|
|
357
|
+
const loader = createModuleLoader();
|
|
201
358
|
const loadedModules = /* @__PURE__ */ new Map();
|
|
202
359
|
async function loadModule(filePath) {
|
|
203
360
|
const ext = path.extname(filePath);
|
|
204
361
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
|
|
205
362
|
if (loadedModules.has(filePath)) return loadedModules.get(filePath);
|
|
206
|
-
const
|
|
207
|
-
loadedModules.set(filePath,
|
|
208
|
-
return
|
|
363
|
+
const mod = await loader.load(filePath, { default: true });
|
|
364
|
+
loadedModules.set(filePath, mod);
|
|
365
|
+
return mod;
|
|
209
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Loads the user's Kubb config and returns it with the directory it was found in.
|
|
369
|
+
*
|
|
370
|
+
* When `configPath` is given it must use an allowed extension and resolve inside
|
|
371
|
+
* the current working directory, otherwise loading throws. When omitted, the
|
|
372
|
+
* known `kubb.config.*` file names are tried in the current directory. Every
|
|
373
|
+
* outcome is reported through `notify` before the function returns or throws.
|
|
374
|
+
*/
|
|
210
375
|
async function loadUserConfig(configPath, { notify }) {
|
|
211
|
-
let userConfig;
|
|
212
|
-
let cwd;
|
|
213
376
|
if (configPath) {
|
|
214
377
|
const ext = path.extname(configPath);
|
|
215
378
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
|
|
@@ -225,44 +388,45 @@ async function loadUserConfig(configPath, { notify }) {
|
|
|
225
388
|
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
226
389
|
throw new Error(msg);
|
|
227
390
|
}
|
|
228
|
-
cwd = path.dirname(resolvedConfigPath);
|
|
391
|
+
const cwd = path.dirname(resolvedConfigPath);
|
|
229
392
|
try {
|
|
230
|
-
userConfig = await loadModule(resolvedConfigPath);
|
|
393
|
+
const userConfig = await loadModule(resolvedConfigPath);
|
|
231
394
|
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
|
|
395
|
+
return {
|
|
396
|
+
userConfig,
|
|
397
|
+
cwd
|
|
398
|
+
};
|
|
232
399
|
} 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 {}
|
|
400
|
+
const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
|
|
401
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
402
|
+
throw new Error(msg);
|
|
253
403
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
404
|
+
}
|
|
405
|
+
const cwd = process.cwd();
|
|
406
|
+
const configFileNames = [
|
|
407
|
+
"kubb.config.ts",
|
|
408
|
+
"kubb.config.mts",
|
|
409
|
+
"kubb.config.cts",
|
|
410
|
+
"kubb.config.js",
|
|
411
|
+
"kubb.config.cjs"
|
|
412
|
+
];
|
|
413
|
+
for (const configFileName of configFileNames) {
|
|
414
|
+
const configFilePath = path.resolve(process.cwd(), configFileName);
|
|
415
|
+
if (!existsSync(configFilePath)) continue;
|
|
416
|
+
try {
|
|
417
|
+
const userConfig = await loadModule(configFilePath);
|
|
418
|
+
await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
|
|
419
|
+
return {
|
|
420
|
+
userConfig,
|
|
421
|
+
cwd
|
|
422
|
+
};
|
|
423
|
+
} catch (err) {
|
|
424
|
+
await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
257
425
|
}
|
|
258
426
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
cwd
|
|
262
|
-
};
|
|
427
|
+
await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
|
|
428
|
+
throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
|
|
263
429
|
}
|
|
264
|
-
//#endregion
|
|
265
|
-
//#region src/utils/resolveCwd.ts
|
|
266
430
|
/**
|
|
267
431
|
* Determine the root directory based on userConfig.root and resolvedConfigDir
|
|
268
432
|
* 1. If userConfig.root exists and is absolute, use it as-is
|
|
@@ -276,42 +440,33 @@ function resolveCwd(userConfig, cwd) {
|
|
|
276
440
|
}
|
|
277
441
|
return cwd;
|
|
278
442
|
}
|
|
279
|
-
//#endregion
|
|
280
|
-
//#region src/utils/resolveUserConfig.ts
|
|
281
443
|
/**
|
|
282
|
-
*
|
|
444
|
+
* Normalizes a possible config into a single resolved `Config`.
|
|
445
|
+
*
|
|
446
|
+
* Calls the config when it is a function, awaits it when it is a promise, and
|
|
447
|
+
* picks the first entry when it resolves to an array.
|
|
283
448
|
*/
|
|
284
449
|
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;
|
|
450
|
+
const result = typeof config === "function" ? config({
|
|
451
|
+
logLevel: options.logLevel,
|
|
452
|
+
config: options.configPath
|
|
453
|
+
}) : config;
|
|
454
|
+
const resolved = isPromise(result) ? await result : result;
|
|
455
|
+
return Array.isArray(resolved) ? resolved[0] : resolved;
|
|
295
456
|
}
|
|
296
457
|
//#endregion
|
|
297
458
|
//#region src/tools/generate.ts
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
async function generate(schema
|
|
459
|
+
const generateTool = defineTool({
|
|
460
|
+
name: "generate",
|
|
461
|
+
description: "Generate OpenAPI spec helpers using Kubb configuration",
|
|
462
|
+
schema: generateSchema
|
|
463
|
+
}, async function generate(schema) {
|
|
303
464
|
const { config: configPath, input, output, logLevel } = schema;
|
|
304
465
|
try {
|
|
305
466
|
const hooks = new AsyncEventEmitter();
|
|
306
467
|
const messages = [];
|
|
307
468
|
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
|
-
});
|
|
469
|
+
messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
|
|
315
470
|
};
|
|
316
471
|
hooks.on("kubb:info", async ({ message }) => {
|
|
317
472
|
await notify(NotifyTypes.INFO, message);
|
|
@@ -320,11 +475,14 @@ async function generate(schema, handler) {
|
|
|
320
475
|
await notify(NotifyTypes.SUCCESS, message);
|
|
321
476
|
});
|
|
322
477
|
hooks.on("kubb:error", async ({ error }) => {
|
|
323
|
-
await notify(NotifyTypes.ERROR, error.message
|
|
478
|
+
await notify(NotifyTypes.ERROR, error.message);
|
|
324
479
|
});
|
|
325
480
|
hooks.on("kubb:warn", async ({ message }) => {
|
|
326
481
|
await notify(NotifyTypes.WARN, message);
|
|
327
482
|
});
|
|
483
|
+
hooks.on("kubb:diagnostic", async ({ diagnostic }) => {
|
|
484
|
+
await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic));
|
|
485
|
+
});
|
|
328
486
|
hooks.on("kubb:plugin:start", async ({ plugin }) => {
|
|
329
487
|
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
|
|
330
488
|
});
|
|
@@ -334,8 +492,8 @@ async function generate(schema, handler) {
|
|
|
334
492
|
hooks.on("kubb:files:processing:start", async () => {
|
|
335
493
|
await notify(NotifyTypes.FILES_START, "Starting file processing");
|
|
336
494
|
});
|
|
337
|
-
hooks.on("kubb:
|
|
338
|
-
await notify(NotifyTypes.
|
|
495
|
+
hooks.on("kubb:files:processing:update", async ({ files }) => {
|
|
496
|
+
await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`);
|
|
339
497
|
});
|
|
340
498
|
hooks.on("kubb:files:processing:end", async () => {
|
|
341
499
|
await notify(NotifyTypes.FILES_END, "File processing complete");
|
|
@@ -352,7 +510,7 @@ async function generate(schema, handler) {
|
|
|
352
510
|
const configResult = await loadUserConfig(configPath, { notify });
|
|
353
511
|
userConfig = configResult.userConfig;
|
|
354
512
|
cwd = configResult.cwd;
|
|
355
|
-
if (Array.isArray(userConfig)
|
|
513
|
+
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
514
|
userConfig = await resolveUserConfig(userConfig, {
|
|
357
515
|
configPath,
|
|
358
516
|
logLevel
|
|
@@ -360,15 +518,9 @@ async function generate(schema, handler) {
|
|
|
360
518
|
} catch (error) {
|
|
361
519
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
362
520
|
await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
|
|
363
|
-
return
|
|
364
|
-
content: [{
|
|
365
|
-
type: "text",
|
|
366
|
-
text: errorMessage
|
|
367
|
-
}],
|
|
368
|
-
isError: true
|
|
369
|
-
};
|
|
521
|
+
return tool.error(errorMessage);
|
|
370
522
|
}
|
|
371
|
-
const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
|
|
523
|
+
const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
|
|
372
524
|
const config = {
|
|
373
525
|
...userConfig,
|
|
374
526
|
root: resolveCwd(userConfig, cwd),
|
|
@@ -381,89 +533,124 @@ async function generate(schema, handler) {
|
|
|
381
533
|
path: output
|
|
382
534
|
} : userConfig.output
|
|
383
535
|
};
|
|
384
|
-
await notify(NotifyTypes.CONFIG_READY, "Configuration ready"
|
|
536
|
+
await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
|
|
385
537
|
await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
|
|
386
538
|
const kubb = createKubb(config, { hooks });
|
|
387
539
|
await kubb.setup();
|
|
388
540
|
await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
|
|
389
541
|
await notify(NotifyTypes.BUILD_START, "Starting build");
|
|
390
|
-
const { files,
|
|
542
|
+
const { files, diagnostics } = await kubb.safeBuild();
|
|
391
543
|
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
|
-
};
|
|
544
|
+
const problems = diagnostics.filter(Diagnostics.isProblem);
|
|
545
|
+
const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
|
|
546
|
+
if (errors.length > 0) {
|
|
547
|
+
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`);
|
|
548
|
+
const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic));
|
|
549
|
+
return tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
405
550
|
}
|
|
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
|
-
}] };
|
|
551
|
+
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
|
|
552
|
+
return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
|
|
411
553
|
} 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
|
-
};
|
|
554
|
+
const serialized = Diagnostics.serialize(Diagnostics.from(caughtError));
|
|
555
|
+
return tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
426
556
|
}
|
|
557
|
+
});
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/schemas/initSchema.ts
|
|
560
|
+
const initSchema = v.object({
|
|
561
|
+
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
562
|
+
output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory (default: ./src/gen)"))),
|
|
563
|
+
plugins: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
|
|
564
|
+
});
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/tools/init.ts
|
|
567
|
+
/**
|
|
568
|
+
* Resolves a comma-separated plugin flag into the matching known plugin options.
|
|
569
|
+
* Unrecognized names are dropped, and a missing flag yields an empty list.
|
|
570
|
+
*/
|
|
571
|
+
function resolvePlugins(pluginsFlag) {
|
|
572
|
+
if (!pluginsFlag) return [];
|
|
573
|
+
const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
|
|
574
|
+
return availablePlugins.filter((p) => requested.includes(p.value));
|
|
427
575
|
}
|
|
576
|
+
const initTool = defineTool({
|
|
577
|
+
name: "init",
|
|
578
|
+
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
|
|
579
|
+
schema: initSchema
|
|
580
|
+
}, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
|
|
581
|
+
const selected = resolvePlugins(plugins);
|
|
582
|
+
const content = generateConfigFile({
|
|
583
|
+
selectedPlugins: selected,
|
|
584
|
+
inputPath: input,
|
|
585
|
+
outputPath: output
|
|
586
|
+
});
|
|
587
|
+
const dest = path.join(process$1.cwd(), KUBB_CONFIG_FILENAME);
|
|
588
|
+
if (fs.existsSync(dest)) return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
|
|
589
|
+
fs.writeFileSync(dest, content, "utf-8");
|
|
590
|
+
const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
|
|
591
|
+
return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
|
|
592
|
+
});
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/tools/validate.ts
|
|
595
|
+
const validateTool = defineTool({
|
|
596
|
+
name: "validate",
|
|
597
|
+
description: "Validate an OpenAPI/Swagger specification file or URL",
|
|
598
|
+
schema: v.object({ input: v.pipe(v.string(), v.minLength(1), v.description("Path or URL to the OpenAPI/Swagger specification")) })
|
|
599
|
+
}, async ({ input }) => {
|
|
600
|
+
let mod;
|
|
601
|
+
try {
|
|
602
|
+
mod = await import("@kubb/adapter-oas");
|
|
603
|
+
} catch {
|
|
604
|
+
return tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
|
|
605
|
+
}
|
|
606
|
+
try {
|
|
607
|
+
await mod.adapterOas().validate(input, { throwOnError: true });
|
|
608
|
+
return tool.text(`Validation successful: ${input}`);
|
|
609
|
+
} catch (err) {
|
|
610
|
+
const serialized = Diagnostics.serialize(Diagnostics.from(err));
|
|
611
|
+
return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
428
614
|
//#endregion
|
|
429
615
|
//#region src/server.ts
|
|
430
616
|
/**
|
|
431
|
-
* Kubb MCP
|
|
617
|
+
* Builds the Kubb MCP server with the generate, validate, and init tools registered.
|
|
432
618
|
*
|
|
433
|
-
*
|
|
619
|
+
* @example
|
|
620
|
+
* `const server = createMcpServer()`
|
|
621
|
+
*/
|
|
622
|
+
function createMcpServer() {
|
|
623
|
+
const server = new McpServer({
|
|
624
|
+
name: "Kubb",
|
|
625
|
+
version
|
|
626
|
+
}, {
|
|
627
|
+
adapter: new ValibotJsonSchemaAdapter(),
|
|
628
|
+
capabilities: { tools: {} }
|
|
629
|
+
});
|
|
630
|
+
server.tools([
|
|
631
|
+
generateTool,
|
|
632
|
+
validateTool,
|
|
633
|
+
initTool
|
|
634
|
+
]);
|
|
635
|
+
return server;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Starts the Kubb MCP server over stdio, the transport every local MCP client
|
|
639
|
+
* (Claude, Copilot, editors) uses when it launches the server as a subprocess.
|
|
434
640
|
*/
|
|
435
641
|
async function startServer() {
|
|
436
|
-
|
|
437
|
-
const transport = new StdioServerTransport();
|
|
438
|
-
const server = new McpServer({
|
|
439
|
-
name: "Kubb",
|
|
440
|
-
version
|
|
441
|
-
});
|
|
442
|
-
server.tool("generate", "Generate OpenAPI spec helpers using Kubb configuration", generateSchema.shape, async (args) => {
|
|
443
|
-
return generate(args, { sendNotification: async (method, params) => {
|
|
444
|
-
try {
|
|
445
|
-
await transport.send({
|
|
446
|
-
jsonrpc: "2.0",
|
|
447
|
-
method,
|
|
448
|
-
params
|
|
449
|
-
});
|
|
450
|
-
} catch (error) {
|
|
451
|
-
console.error("Failed to send notification:", error);
|
|
452
|
-
}
|
|
453
|
-
} });
|
|
454
|
-
});
|
|
455
|
-
await server.connect(transport);
|
|
456
|
-
} catch (error) {
|
|
457
|
-
console.error("Failed to start MCP server:", error);
|
|
458
|
-
process$1.exit(1);
|
|
459
|
-
}
|
|
642
|
+
new StdioTransport(createMcpServer()).listen();
|
|
460
643
|
}
|
|
461
644
|
//#endregion
|
|
462
645
|
//#region src/index.ts
|
|
646
|
+
/**
|
|
647
|
+
* Entry point that starts the MCP server over stdio. The argument is accepted
|
|
648
|
+
* for CLI parity but ignored.
|
|
649
|
+
*/
|
|
463
650
|
async function run(_argv) {
|
|
464
651
|
await startServer();
|
|
465
652
|
}
|
|
466
653
|
//#endregion
|
|
467
|
-
export { run };
|
|
654
|
+
export { createMcpServer, run };
|
|
468
655
|
|
|
469
656
|
//# sourceMappingURL=index.js.map
|