@kubb/mcp 5.0.0-alpha.9 → 5.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -26
- package/dist/index.cjs +443 -199
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +437 -199
- package/dist/index.js.map +1 -1
- package/package.json +47 -45
- package/src/constants.ts +1 -0
- package/src/index.ts +5 -2
- package/src/schemas/generateSchema.ts +11 -11
- package/src/schemas/initSchema.ts +7 -0
- package/src/schemas/validateSchema.ts +5 -0
- package/src/server.ts +34 -41
- package/src/tools/generate.ts +118 -185
- package/src/tools/init.ts +37 -0
- package/src/tools/validate.ts +25 -0
- package/src/utils/loadUserConfig.ts +58 -35
- package/src/utils/resolveUserConfig.ts +5 -17
package/dist/index.js
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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";
|
|
9
|
+
import fs, { existsSync } from "node:fs";
|
|
7
10
|
import path from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
11
|
+
import { createKubb } from "@kubb/core";
|
|
12
|
+
import { defineTool } from "tmcp/tool";
|
|
13
|
+
import { tool } from "tmcp/utils";
|
|
14
|
+
import * as v from "valibot";
|
|
15
|
+
import { createJiti } from "jiti";
|
|
16
|
+
import process$1 from "node:process";
|
|
10
17
|
//#region package.json
|
|
11
|
-
var version = "5.0.0-
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region src/schemas/generateSchema.ts
|
|
14
|
-
const generateSchema = z.object({
|
|
15
|
-
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"),
|
|
16
|
-
input: z.string().optional().describe("Path to OpenAPI/Swagger spec file (overrides config)"),
|
|
17
|
-
output: z.string().optional().describe("Output directory path (overrides config)"),
|
|
18
|
-
logLevel: z.enum([
|
|
19
|
-
"silent",
|
|
20
|
-
"error",
|
|
21
|
-
"warn",
|
|
22
|
-
"info",
|
|
23
|
-
"verbose",
|
|
24
|
-
"debug"
|
|
25
|
-
]).optional().default("info").describe("Log level for build output")
|
|
26
|
-
});
|
|
18
|
+
var version = "5.0.0-beta.10";
|
|
27
19
|
//#endregion
|
|
28
20
|
//#region ../../internals/utils/src/errors.ts
|
|
29
21
|
/**
|
|
30
22
|
* Coerces an unknown thrown value to an `Error` instance.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
23
|
+
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* try { ... } catch(err) {
|
|
28
|
+
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
33
31
|
*/
|
|
34
32
|
function toError(value) {
|
|
35
33
|
return value instanceof Error ? value : new Error(String(value));
|
|
@@ -37,12 +35,19 @@ function toError(value) {
|
|
|
37
35
|
//#endregion
|
|
38
36
|
//#region ../../internals/utils/src/asyncEventEmitter.ts
|
|
39
37
|
/**
|
|
40
|
-
*
|
|
38
|
+
* Typed `EventEmitter` that awaits all async listeners before resolving.
|
|
41
39
|
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
|
|
44
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
45
|
+
* await emitter.emit('build', 'petstore') // all listeners awaited
|
|
46
|
+
* ```
|
|
42
47
|
*/
|
|
43
48
|
var AsyncEventEmitter = class {
|
|
44
49
|
/**
|
|
45
|
-
*
|
|
50
|
+
* Maximum number of listeners per event before Node emits a memory-leak warning.
|
|
46
51
|
* @default 10
|
|
47
52
|
*/
|
|
48
53
|
constructor(maxListener = 10) {
|
|
@@ -50,31 +55,48 @@ var AsyncEventEmitter = class {
|
|
|
50
55
|
}
|
|
51
56
|
#emitter = new EventEmitter();
|
|
52
57
|
/**
|
|
53
|
-
* Emits
|
|
58
|
+
* Emits `eventName` and awaits all registered listeners sequentially.
|
|
54
59
|
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* await emitter.emit('build', 'petstore')
|
|
64
|
+
* ```
|
|
55
65
|
*/
|
|
56
66
|
async emit(eventName, ...eventArgs) {
|
|
57
67
|
const listeners = this.#emitter.listeners(eventName);
|
|
58
68
|
if (listeners.length === 0) return;
|
|
59
|
-
|
|
69
|
+
for (const listener of listeners) try {
|
|
70
|
+
await listener(...eventArgs);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
let serializedArgs;
|
|
60
73
|
try {
|
|
61
|
-
|
|
62
|
-
} catch
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
serializedArgs = JSON.stringify(eventArgs);
|
|
66
|
-
} catch {
|
|
67
|
-
serializedArgs = String(eventArgs);
|
|
68
|
-
}
|
|
69
|
-
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
|
|
74
|
+
serializedArgs = JSON.stringify(eventArgs);
|
|
75
|
+
} catch {
|
|
76
|
+
serializedArgs = String(eventArgs);
|
|
70
77
|
}
|
|
71
|
-
|
|
78
|
+
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Registers a persistent listener for `eventName`.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
74
89
|
on(eventName, handler) {
|
|
75
90
|
this.#emitter.on(eventName, handler);
|
|
76
91
|
}
|
|
77
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Registers a one-shot listener that removes itself after the first invocation.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* emitter.onOnce('build', async (name) => { console.log(name) })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
78
100
|
onOnce(eventName, handler) {
|
|
79
101
|
const wrapper = (...args) => {
|
|
80
102
|
this.off(eventName, wrapper);
|
|
@@ -82,22 +104,70 @@ var AsyncEventEmitter = class {
|
|
|
82
104
|
};
|
|
83
105
|
this.on(eventName, wrapper);
|
|
84
106
|
}
|
|
85
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Removes a previously registered listener.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* emitter.off('build', handler)
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
86
115
|
off(eventName, handler) {
|
|
87
116
|
this.#emitter.off(eventName, handler);
|
|
88
117
|
}
|
|
89
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Returns the number of listeners registered for `eventName`.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* emitter.on('build', handler)
|
|
124
|
+
* emitter.listenerCount('build') // 1
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
listenerCount(eventName) {
|
|
128
|
+
return this.#emitter.listenerCount(eventName);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Removes all listeners from every event channel.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* emitter.removeAll()
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
90
138
|
removeAll() {
|
|
91
139
|
this.#emitter.removeAllListeners();
|
|
92
140
|
}
|
|
93
141
|
};
|
|
94
142
|
//#endregion
|
|
95
143
|
//#region ../../internals/utils/src/promise.ts
|
|
96
|
-
/** Returns `true` when `result` is a thenable `Promise`.
|
|
144
|
+
/** Returns `true` when `result` is a thenable `Promise`.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* isPromise(Promise.resolve(1)) // true
|
|
149
|
+
* isPromise(42) // false
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
97
152
|
function isPromise(result) {
|
|
98
153
|
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
99
154
|
}
|
|
100
155
|
//#endregion
|
|
156
|
+
//#region src/schemas/generateSchema.ts
|
|
157
|
+
const generateSchema = v.object({
|
|
158
|
+
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"))),
|
|
159
|
+
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
160
|
+
output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory path (overrides config)"))),
|
|
161
|
+
logLevel: v.optional(v.pipe(v.picklist([
|
|
162
|
+
"silent",
|
|
163
|
+
"error",
|
|
164
|
+
"warn",
|
|
165
|
+
"info",
|
|
166
|
+
"verbose",
|
|
167
|
+
"debug"
|
|
168
|
+
]), v.description("Log level for build output")), "info")
|
|
169
|
+
});
|
|
170
|
+
//#endregion
|
|
101
171
|
//#region src/types.ts
|
|
102
172
|
const NotifyTypes = {
|
|
103
173
|
INFO: "INFO",
|
|
@@ -123,45 +193,87 @@ const NotifyTypes = {
|
|
|
123
193
|
FATAL_ERROR: "FATAL_ERROR"
|
|
124
194
|
};
|
|
125
195
|
//#endregion
|
|
196
|
+
//#region src/constants.ts
|
|
197
|
+
const ALLOWED_CONFIG_EXTENSIONS = new Set([
|
|
198
|
+
".ts",
|
|
199
|
+
".mts",
|
|
200
|
+
".cts",
|
|
201
|
+
".js",
|
|
202
|
+
".mjs",
|
|
203
|
+
".cjs"
|
|
204
|
+
]);
|
|
205
|
+
//#endregion
|
|
126
206
|
//#region src/utils/loadUserConfig.ts
|
|
127
|
-
const jiti = createJiti(import.meta.url, {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
207
|
+
const jiti = createJiti(import.meta.url, {
|
|
208
|
+
jsx: {
|
|
209
|
+
runtime: "automatic",
|
|
210
|
+
importSource: "@kubb/renderer-jsx"
|
|
211
|
+
},
|
|
212
|
+
moduleCache: false
|
|
213
|
+
});
|
|
214
|
+
const loadedModules = /* @__PURE__ */ new Map();
|
|
215
|
+
async function loadModule(filePath) {
|
|
216
|
+
const ext = path.extname(filePath);
|
|
217
|
+
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
|
|
218
|
+
if (loadedModules.has(filePath)) return loadedModules.get(filePath);
|
|
219
|
+
const mod = await jiti.import(filePath, { default: true });
|
|
220
|
+
loadedModules.set(filePath, mod);
|
|
221
|
+
return mod;
|
|
222
|
+
}
|
|
131
223
|
async function loadUserConfig(configPath, { notify }) {
|
|
132
|
-
let userConfig;
|
|
133
|
-
let cwd;
|
|
134
224
|
if (configPath) {
|
|
135
|
-
|
|
225
|
+
const ext = path.extname(configPath);
|
|
226
|
+
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
|
|
227
|
+
const msg = `Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`;
|
|
228
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
229
|
+
throw new Error(msg);
|
|
230
|
+
}
|
|
231
|
+
const base = path.resolve(process.cwd());
|
|
232
|
+
const resolvedConfigPath = path.resolve(base, configPath);
|
|
233
|
+
const relative = path.relative(base, resolvedConfigPath);
|
|
234
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
235
|
+
const msg = "Invalid config file path: must be within the current working directory";
|
|
236
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
237
|
+
throw new Error(msg);
|
|
238
|
+
}
|
|
239
|
+
const cwd = path.dirname(resolvedConfigPath);
|
|
136
240
|
try {
|
|
137
|
-
userConfig = await
|
|
138
|
-
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${
|
|
241
|
+
const userConfig = await loadModule(resolvedConfigPath);
|
|
242
|
+
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
|
|
243
|
+
return {
|
|
244
|
+
userConfig,
|
|
245
|
+
cwd
|
|
246
|
+
};
|
|
139
247
|
} catch (error) {
|
|
140
|
-
|
|
141
|
-
|
|
248
|
+
const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
|
|
249
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
250
|
+
throw new Error(msg);
|
|
142
251
|
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
252
|
+
}
|
|
253
|
+
const cwd = process.cwd();
|
|
254
|
+
const configFileNames = [
|
|
255
|
+
"kubb.config.ts",
|
|
256
|
+
"kubb.config.mts",
|
|
257
|
+
"kubb.config.cts",
|
|
258
|
+
"kubb.config.js",
|
|
259
|
+
"kubb.config.cjs"
|
|
260
|
+
];
|
|
261
|
+
for (const configFileName of configFileNames) {
|
|
262
|
+
const configFilePath = path.resolve(process.cwd(), configFileName);
|
|
263
|
+
if (!existsSync(configFilePath)) continue;
|
|
264
|
+
try {
|
|
265
|
+
const userConfig = await loadModule(configFilePath);
|
|
153
266
|
await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
267
|
+
return {
|
|
268
|
+
userConfig,
|
|
269
|
+
cwd
|
|
270
|
+
};
|
|
271
|
+
} catch (err) {
|
|
272
|
+
await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
159
273
|
}
|
|
160
274
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
cwd
|
|
164
|
-
};
|
|
275
|
+
await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
|
|
276
|
+
throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
|
|
165
277
|
}
|
|
166
278
|
//#endregion
|
|
167
279
|
//#region src/utils/resolveCwd.ts
|
|
@@ -180,72 +292,59 @@ function resolveCwd(userConfig, cwd) {
|
|
|
180
292
|
}
|
|
181
293
|
//#endregion
|
|
182
294
|
//#region src/utils/resolveUserConfig.ts
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
logLevel: options.logLevel,
|
|
191
|
-
config: options.configPath
|
|
192
|
-
});
|
|
193
|
-
if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
|
|
194
|
-
else kubbUserConfig = Promise.resolve(possiblePromise);
|
|
195
|
-
}
|
|
196
|
-
return await kubbUserConfig;
|
|
295
|
+
async function resolveUserConfig(config, options) {
|
|
296
|
+
const result = typeof config === "function" ? config({
|
|
297
|
+
logLevel: options.logLevel,
|
|
298
|
+
config: options.configPath
|
|
299
|
+
}) : config;
|
|
300
|
+
const resolved = isPromise(result) ? await result : result;
|
|
301
|
+
return Array.isArray(resolved) ? resolved[0] : resolved;
|
|
197
302
|
}
|
|
198
303
|
//#endregion
|
|
199
304
|
//#region src/tools/generate.ts
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
async function generate(schema
|
|
305
|
+
const generateTool = defineTool({
|
|
306
|
+
name: "generate",
|
|
307
|
+
description: "Generate OpenAPI spec helpers using Kubb configuration",
|
|
308
|
+
schema: generateSchema
|
|
309
|
+
}, async function generate(schema) {
|
|
205
310
|
const { config: configPath, input, output, logLevel } = schema;
|
|
206
311
|
try {
|
|
207
|
-
const
|
|
312
|
+
const hooks = new AsyncEventEmitter();
|
|
208
313
|
const messages = [];
|
|
209
|
-
const notify = async (type, message,
|
|
314
|
+
const notify = async (type, message, _data) => {
|
|
210
315
|
messages.push(`${type}: ${message}`);
|
|
211
|
-
await handler.sendNotification("kubb/progress", {
|
|
212
|
-
type,
|
|
213
|
-
message,
|
|
214
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
215
|
-
...data
|
|
216
|
-
});
|
|
217
316
|
};
|
|
218
|
-
|
|
317
|
+
hooks.on("kubb:info", async ({ message }) => {
|
|
219
318
|
await notify(NotifyTypes.INFO, message);
|
|
220
319
|
});
|
|
221
|
-
|
|
320
|
+
hooks.on("kubb:success", async ({ message }) => {
|
|
222
321
|
await notify(NotifyTypes.SUCCESS, message);
|
|
223
322
|
});
|
|
224
|
-
|
|
225
|
-
await notify(NotifyTypes.ERROR, error.message
|
|
323
|
+
hooks.on("kubb:error", async ({ error }) => {
|
|
324
|
+
await notify(NotifyTypes.ERROR, error.message);
|
|
226
325
|
});
|
|
227
|
-
|
|
326
|
+
hooks.on("kubb:warn", async ({ message }) => {
|
|
228
327
|
await notify(NotifyTypes.WARN, message);
|
|
229
328
|
});
|
|
230
|
-
|
|
231
|
-
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${name}`);
|
|
329
|
+
hooks.on("kubb:plugin:start", async ({ plugin }) => {
|
|
330
|
+
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
|
|
232
331
|
});
|
|
233
|
-
|
|
234
|
-
await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${name}`, { duration });
|
|
332
|
+
hooks.on("kubb:plugin:end", async ({ plugin, duration }) => {
|
|
333
|
+
await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${plugin.name}`, { duration });
|
|
235
334
|
});
|
|
236
|
-
|
|
335
|
+
hooks.on("kubb:files:processing:start", async () => {
|
|
237
336
|
await notify(NotifyTypes.FILES_START, "Starting file processing");
|
|
238
337
|
});
|
|
239
|
-
|
|
338
|
+
hooks.on("kubb:file:processing:update", async ({ file }) => {
|
|
240
339
|
await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
|
|
241
340
|
});
|
|
242
|
-
|
|
341
|
+
hooks.on("kubb:files:processing:end", async () => {
|
|
243
342
|
await notify(NotifyTypes.FILES_END, "File processing complete");
|
|
244
343
|
});
|
|
245
|
-
|
|
344
|
+
hooks.on("kubb:generation:start", async () => {
|
|
246
345
|
await notify(NotifyTypes.GENERATION_START, "Generation started");
|
|
247
346
|
});
|
|
248
|
-
|
|
347
|
+
hooks.on("kubb:generation:end", async () => {
|
|
249
348
|
await notify(NotifyTypes.GENERATION_END, "Generation ended");
|
|
250
349
|
});
|
|
251
350
|
let userConfig;
|
|
@@ -254,7 +353,7 @@ async function generate(schema, handler) {
|
|
|
254
353
|
const configResult = await loadUserConfig(configPath, { notify });
|
|
255
354
|
userConfig = configResult.userConfig;
|
|
256
355
|
cwd = configResult.cwd;
|
|
257
|
-
if (Array.isArray(userConfig)
|
|
356
|
+
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.");
|
|
258
357
|
userConfig = await resolveUserConfig(userConfig, {
|
|
259
358
|
configPath,
|
|
260
359
|
logLevel
|
|
@@ -262,15 +361,9 @@ async function generate(schema, handler) {
|
|
|
262
361
|
} catch (error) {
|
|
263
362
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
264
363
|
await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
|
|
265
|
-
return
|
|
266
|
-
content: [{
|
|
267
|
-
type: "text",
|
|
268
|
-
text: errorMessage
|
|
269
|
-
}],
|
|
270
|
-
isError: true
|
|
271
|
-
};
|
|
364
|
+
return tool.error(errorMessage);
|
|
272
365
|
}
|
|
273
|
-
const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
|
|
366
|
+
const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
|
|
274
367
|
const config = {
|
|
275
368
|
...userConfig,
|
|
276
369
|
root: resolveCwd(userConfig, cwd),
|
|
@@ -283,99 +376,244 @@ async function generate(schema, handler) {
|
|
|
283
376
|
path: output
|
|
284
377
|
} : userConfig.output
|
|
285
378
|
};
|
|
286
|
-
await notify(NotifyTypes.CONFIG_READY, "Configuration ready"
|
|
379
|
+
await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
|
|
287
380
|
await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
events
|
|
291
|
-
});
|
|
381
|
+
const kubb = createKubb(config, { hooks });
|
|
382
|
+
await kubb.setup();
|
|
292
383
|
await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
|
|
293
384
|
await notify(NotifyTypes.BUILD_START, "Starting build");
|
|
294
|
-
const { files, failedPlugins, error } = await safeBuild(
|
|
295
|
-
config,
|
|
296
|
-
events
|
|
297
|
-
}, {
|
|
298
|
-
driver,
|
|
299
|
-
fabric,
|
|
300
|
-
events,
|
|
301
|
-
sources
|
|
302
|
-
});
|
|
385
|
+
const { files, failedPlugins, error } = await kubb.safeBuild();
|
|
303
386
|
await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
|
|
304
387
|
if (error || failedPlugins.size > 0) {
|
|
305
388
|
const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
|
|
306
|
-
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)
|
|
307
|
-
|
|
308
|
-
errors: allErrors.map((err) => err.message)
|
|
309
|
-
});
|
|
310
|
-
return {
|
|
311
|
-
content: [{
|
|
312
|
-
type: "text",
|
|
313
|
-
text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
|
|
314
|
-
}],
|
|
315
|
-
isError: true
|
|
316
|
-
};
|
|
389
|
+
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`);
|
|
390
|
+
return tool.error(`Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`);
|
|
317
391
|
}
|
|
318
|
-
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files
|
|
319
|
-
return
|
|
320
|
-
type: "text",
|
|
321
|
-
text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
|
|
322
|
-
}] };
|
|
392
|
+
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
|
|
393
|
+
return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
|
|
323
394
|
} catch (caughtError) {
|
|
324
|
-
const error = caughtError;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
395
|
+
const error = toError(caughtError);
|
|
396
|
+
return tool.error(`Build error: ${error.message}\n${error.stack ?? ""}`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
//#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"
|
|
338
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
|
+
`;
|
|
339
529
|
}
|
|
340
530
|
//#endregion
|
|
341
|
-
//#region src/
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
531
|
+
//#region src/schemas/initSchema.ts
|
|
532
|
+
const initSchema = v.object({
|
|
533
|
+
input: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
534
|
+
output: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Output directory (default: ./src/gen)"))),
|
|
535
|
+
plugins: v.optional(v.pipe(v.string(), v.minLength(1), v.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
|
|
536
|
+
});
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/tools/init.ts
|
|
539
|
+
function resolvePlugins(pluginsFlag) {
|
|
540
|
+
if (!pluginsFlag) return [];
|
|
541
|
+
const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
|
|
542
|
+
return availablePlugins.filter((p) => requested.includes(p.value));
|
|
543
|
+
}
|
|
544
|
+
const initTool = defineTool({
|
|
545
|
+
name: "init",
|
|
546
|
+
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
|
|
547
|
+
schema: initSchema
|
|
548
|
+
}, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
|
|
549
|
+
const selected = resolvePlugins(plugins);
|
|
550
|
+
const content = generateConfigFile({
|
|
551
|
+
selectedPlugins: selected,
|
|
552
|
+
inputPath: input,
|
|
553
|
+
outputPath: output
|
|
554
|
+
});
|
|
555
|
+
const dest = path.join(process$1.cwd(), KUBB_CONFIG_FILENAME);
|
|
556
|
+
if (fs.existsSync(dest)) return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
|
|
557
|
+
fs.writeFileSync(dest, content, "utf-8");
|
|
558
|
+
const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
|
|
559
|
+
return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
|
|
560
|
+
});
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/tools/validate.ts
|
|
563
|
+
const validateTool = defineTool({
|
|
564
|
+
name: "validate",
|
|
565
|
+
description: "Validate an OpenAPI/Swagger specification file or URL",
|
|
566
|
+
schema: v.object({ input: v.pipe(v.string(), v.minLength(1), v.description("Path or URL to the OpenAPI/Swagger specification")) })
|
|
567
|
+
}, async ({ input }) => {
|
|
568
|
+
let mod;
|
|
348
569
|
try {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
570
|
+
mod = await import("@kubb/adapter-oas");
|
|
571
|
+
} catch {
|
|
572
|
+
return tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
await mod.adapterOas().validate(input, { throwOnError: true });
|
|
576
|
+
return tool.text(`Validation successful: ${input}`);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
return tool.error(`Validation failed:\n${err instanceof Error ? err.message : String(err)}`);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/server.ts
|
|
583
|
+
function createMcpServer() {
|
|
584
|
+
const server = new McpServer({
|
|
585
|
+
name: "Kubb",
|
|
586
|
+
version
|
|
587
|
+
}, {
|
|
588
|
+
adapter: new ValibotJsonSchemaAdapter(),
|
|
589
|
+
capabilities: { tools: {} }
|
|
590
|
+
});
|
|
591
|
+
server.tools([
|
|
592
|
+
generateTool,
|
|
593
|
+
validateTool,
|
|
594
|
+
initTool
|
|
595
|
+
]);
|
|
596
|
+
return server;
|
|
597
|
+
}
|
|
598
|
+
async function startServer({ port, host = "localhost" } = {}) {
|
|
599
|
+
const server = createMcpServer();
|
|
600
|
+
if (port === void 0) {
|
|
601
|
+
new StdioTransport(server).listen();
|
|
602
|
+
return;
|
|
371
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
|
+
});
|
|
372
610
|
}
|
|
373
611
|
//#endregion
|
|
374
612
|
//#region src/index.ts
|
|
375
|
-
async function run(_argv) {
|
|
376
|
-
await startServer();
|
|
613
|
+
async function run(_argv, options) {
|
|
614
|
+
await startServer(options);
|
|
377
615
|
}
|
|
378
616
|
//#endregion
|
|
379
|
-
export { run };
|
|
617
|
+
export { createMcpServer, run };
|
|
380
618
|
|
|
381
619
|
//# sourceMappingURL=index.js.map
|