@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.cjs
CHANGED
|
@@ -21,40 +21,40 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
let
|
|
24
|
+
let node_http = require("node:http");
|
|
25
|
+
node_http = __toESM(node_http, 1);
|
|
26
|
+
let _remix_run_node_fetch_server = require("@remix-run/node-fetch-server");
|
|
27
|
+
let _tmcp_adapter_valibot = require("@tmcp/adapter-valibot");
|
|
28
|
+
let _tmcp_transport_http = require("@tmcp/transport-http");
|
|
29
|
+
let _tmcp_transport_stdio = require("@tmcp/transport-stdio");
|
|
30
|
+
let tmcp = require("tmcp");
|
|
29
31
|
let node_events = require("node:events");
|
|
32
|
+
let node_fs = require("node:fs");
|
|
33
|
+
node_fs = __toESM(node_fs, 1);
|
|
30
34
|
let node_path = require("node:path");
|
|
31
|
-
node_path = __toESM(node_path);
|
|
35
|
+
node_path = __toESM(node_path, 1);
|
|
32
36
|
let _kubb_core = require("@kubb/core");
|
|
37
|
+
let tmcp_tool = require("tmcp/tool");
|
|
38
|
+
let tmcp_utils = require("tmcp/utils");
|
|
39
|
+
let valibot = require("valibot");
|
|
40
|
+
valibot = __toESM(valibot, 1);
|
|
33
41
|
let jiti = require("jiti");
|
|
34
|
-
|
|
42
|
+
let node_process = require("node:process");
|
|
43
|
+
node_process = __toESM(node_process, 1);
|
|
35
44
|
//#region package.json
|
|
36
|
-
var version = "5.0.0-
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/schemas/generateSchema.ts
|
|
39
|
-
const generateSchema = zod.z.object({
|
|
40
|
-
config: zod.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"),
|
|
41
|
-
input: zod.z.string().optional().describe("Path to OpenAPI/Swagger spec file (overrides config)"),
|
|
42
|
-
output: zod.z.string().optional().describe("Output directory path (overrides config)"),
|
|
43
|
-
logLevel: zod.z.enum([
|
|
44
|
-
"silent",
|
|
45
|
-
"error",
|
|
46
|
-
"warn",
|
|
47
|
-
"info",
|
|
48
|
-
"verbose",
|
|
49
|
-
"debug"
|
|
50
|
-
]).optional().default("info").describe("Log level for build output")
|
|
51
|
-
});
|
|
45
|
+
var version = "5.0.0-beta.10";
|
|
52
46
|
//#endregion
|
|
53
47
|
//#region ../../internals/utils/src/errors.ts
|
|
54
48
|
/**
|
|
55
49
|
* Coerces an unknown thrown value to an `Error` instance.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
50
|
+
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* try { ... } catch(err) {
|
|
55
|
+
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
58
|
*/
|
|
59
59
|
function toError(value) {
|
|
60
60
|
return value instanceof Error ? value : new Error(String(value));
|
|
@@ -62,12 +62,19 @@ function toError(value) {
|
|
|
62
62
|
//#endregion
|
|
63
63
|
//#region ../../internals/utils/src/asyncEventEmitter.ts
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
65
|
+
* Typed `EventEmitter` that awaits all async listeners before resolving.
|
|
66
66
|
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
|
|
71
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
72
|
+
* await emitter.emit('build', 'petstore') // all listeners awaited
|
|
73
|
+
* ```
|
|
67
74
|
*/
|
|
68
75
|
var AsyncEventEmitter = class {
|
|
69
76
|
/**
|
|
70
|
-
*
|
|
77
|
+
* Maximum number of listeners per event before Node emits a memory-leak warning.
|
|
71
78
|
* @default 10
|
|
72
79
|
*/
|
|
73
80
|
constructor(maxListener = 10) {
|
|
@@ -75,31 +82,48 @@ var AsyncEventEmitter = class {
|
|
|
75
82
|
}
|
|
76
83
|
#emitter = new node_events.EventEmitter();
|
|
77
84
|
/**
|
|
78
|
-
* Emits
|
|
85
|
+
* Emits `eventName` and awaits all registered listeners sequentially.
|
|
79
86
|
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* await emitter.emit('build', 'petstore')
|
|
91
|
+
* ```
|
|
80
92
|
*/
|
|
81
93
|
async emit(eventName, ...eventArgs) {
|
|
82
94
|
const listeners = this.#emitter.listeners(eventName);
|
|
83
95
|
if (listeners.length === 0) return;
|
|
84
|
-
|
|
96
|
+
for (const listener of listeners) try {
|
|
97
|
+
await listener(...eventArgs);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
let serializedArgs;
|
|
85
100
|
try {
|
|
86
|
-
|
|
87
|
-
} catch
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
serializedArgs = JSON.stringify(eventArgs);
|
|
91
|
-
} catch {
|
|
92
|
-
serializedArgs = String(eventArgs);
|
|
93
|
-
}
|
|
94
|
-
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
|
|
101
|
+
serializedArgs = JSON.stringify(eventArgs);
|
|
102
|
+
} catch {
|
|
103
|
+
serializedArgs = String(eventArgs);
|
|
95
104
|
}
|
|
96
|
-
|
|
105
|
+
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
|
|
106
|
+
}
|
|
97
107
|
}
|
|
98
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Registers a persistent listener for `eventName`.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
99
116
|
on(eventName, handler) {
|
|
100
117
|
this.#emitter.on(eventName, handler);
|
|
101
118
|
}
|
|
102
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Registers a one-shot listener that removes itself after the first invocation.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* emitter.onOnce('build', async (name) => { console.log(name) })
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
103
127
|
onOnce(eventName, handler) {
|
|
104
128
|
const wrapper = (...args) => {
|
|
105
129
|
this.off(eventName, wrapper);
|
|
@@ -107,22 +131,70 @@ var AsyncEventEmitter = class {
|
|
|
107
131
|
};
|
|
108
132
|
this.on(eventName, wrapper);
|
|
109
133
|
}
|
|
110
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* Removes a previously registered listener.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* emitter.off('build', handler)
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
111
142
|
off(eventName, handler) {
|
|
112
143
|
this.#emitter.off(eventName, handler);
|
|
113
144
|
}
|
|
114
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Returns the number of listeners registered for `eventName`.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* emitter.on('build', handler)
|
|
151
|
+
* emitter.listenerCount('build') // 1
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
listenerCount(eventName) {
|
|
155
|
+
return this.#emitter.listenerCount(eventName);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Removes all listeners from every event channel.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* emitter.removeAll()
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
115
165
|
removeAll() {
|
|
116
166
|
this.#emitter.removeAllListeners();
|
|
117
167
|
}
|
|
118
168
|
};
|
|
119
169
|
//#endregion
|
|
120
170
|
//#region ../../internals/utils/src/promise.ts
|
|
121
|
-
/** Returns `true` when `result` is a thenable `Promise`.
|
|
171
|
+
/** Returns `true` when `result` is a thenable `Promise`.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* isPromise(Promise.resolve(1)) // true
|
|
176
|
+
* isPromise(42) // false
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
122
179
|
function isPromise(result) {
|
|
123
180
|
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
124
181
|
}
|
|
125
182
|
//#endregion
|
|
183
|
+
//#region src/schemas/generateSchema.ts
|
|
184
|
+
const generateSchema = valibot.object({
|
|
185
|
+
config: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to kubb.config file (supports .ts, .js, .cjs). If not provided, will look for kubb.config.{ts,js,cjs} in current directory"))),
|
|
186
|
+
input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
187
|
+
output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory path (overrides config)"))),
|
|
188
|
+
logLevel: valibot.optional(valibot.pipe(valibot.picklist([
|
|
189
|
+
"silent",
|
|
190
|
+
"error",
|
|
191
|
+
"warn",
|
|
192
|
+
"info",
|
|
193
|
+
"verbose",
|
|
194
|
+
"debug"
|
|
195
|
+
]), valibot.description("Log level for build output")), "info")
|
|
196
|
+
});
|
|
197
|
+
//#endregion
|
|
126
198
|
//#region src/types.ts
|
|
127
199
|
const NotifyTypes = {
|
|
128
200
|
INFO: "INFO",
|
|
@@ -148,45 +220,87 @@ const NotifyTypes = {
|
|
|
148
220
|
FATAL_ERROR: "FATAL_ERROR"
|
|
149
221
|
};
|
|
150
222
|
//#endregion
|
|
223
|
+
//#region src/constants.ts
|
|
224
|
+
const ALLOWED_CONFIG_EXTENSIONS = new Set([
|
|
225
|
+
".ts",
|
|
226
|
+
".mts",
|
|
227
|
+
".cts",
|
|
228
|
+
".js",
|
|
229
|
+
".mjs",
|
|
230
|
+
".cjs"
|
|
231
|
+
]);
|
|
232
|
+
//#endregion
|
|
151
233
|
//#region src/utils/loadUserConfig.ts
|
|
152
|
-
const jiti$1 = (0, jiti.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
234
|
+
const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, {
|
|
235
|
+
jsx: {
|
|
236
|
+
runtime: "automatic",
|
|
237
|
+
importSource: "@kubb/renderer-jsx"
|
|
238
|
+
},
|
|
239
|
+
moduleCache: false
|
|
240
|
+
});
|
|
241
|
+
const loadedModules = /* @__PURE__ */ new Map();
|
|
242
|
+
async function loadModule(filePath) {
|
|
243
|
+
const ext = node_path.default.extname(filePath);
|
|
244
|
+
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
|
|
245
|
+
if (loadedModules.has(filePath)) return loadedModules.get(filePath);
|
|
246
|
+
const mod = await jiti$1.import(filePath, { default: true });
|
|
247
|
+
loadedModules.set(filePath, mod);
|
|
248
|
+
return mod;
|
|
249
|
+
}
|
|
156
250
|
async function loadUserConfig(configPath, { notify }) {
|
|
157
|
-
let userConfig;
|
|
158
|
-
let cwd;
|
|
159
251
|
if (configPath) {
|
|
160
|
-
|
|
252
|
+
const ext = node_path.default.extname(configPath);
|
|
253
|
+
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
|
|
254
|
+
const msg = `Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`;
|
|
255
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
256
|
+
throw new Error(msg);
|
|
257
|
+
}
|
|
258
|
+
const base = node_path.default.resolve(process.cwd());
|
|
259
|
+
const resolvedConfigPath = node_path.default.resolve(base, configPath);
|
|
260
|
+
const relative = node_path.default.relative(base, resolvedConfigPath);
|
|
261
|
+
if (relative.startsWith("..") || node_path.default.isAbsolute(relative)) {
|
|
262
|
+
const msg = "Invalid config file path: must be within the current working directory";
|
|
263
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
264
|
+
throw new Error(msg);
|
|
265
|
+
}
|
|
266
|
+
const cwd = node_path.default.dirname(resolvedConfigPath);
|
|
161
267
|
try {
|
|
162
|
-
userConfig = await
|
|
163
|
-
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${
|
|
268
|
+
const userConfig = await loadModule(resolvedConfigPath);
|
|
269
|
+
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
|
|
270
|
+
return {
|
|
271
|
+
userConfig,
|
|
272
|
+
cwd
|
|
273
|
+
};
|
|
164
274
|
} catch (error) {
|
|
165
|
-
|
|
166
|
-
|
|
275
|
+
const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
|
|
276
|
+
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
277
|
+
throw new Error(msg);
|
|
167
278
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
279
|
+
}
|
|
280
|
+
const cwd = process.cwd();
|
|
281
|
+
const configFileNames = [
|
|
282
|
+
"kubb.config.ts",
|
|
283
|
+
"kubb.config.mts",
|
|
284
|
+
"kubb.config.cts",
|
|
285
|
+
"kubb.config.js",
|
|
286
|
+
"kubb.config.cjs"
|
|
287
|
+
];
|
|
288
|
+
for (const configFileName of configFileNames) {
|
|
289
|
+
const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
|
|
290
|
+
if (!(0, node_fs.existsSync)(configFilePath)) continue;
|
|
291
|
+
try {
|
|
292
|
+
const userConfig = await loadModule(configFilePath);
|
|
178
293
|
await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
294
|
+
return {
|
|
295
|
+
userConfig,
|
|
296
|
+
cwd
|
|
297
|
+
};
|
|
298
|
+
} catch (err) {
|
|
299
|
+
await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
184
300
|
}
|
|
185
301
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
cwd
|
|
189
|
-
};
|
|
302
|
+
await notify(NotifyTypes.CONFIG_ERROR, "No config file found");
|
|
303
|
+
throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(", ")}`);
|
|
190
304
|
}
|
|
191
305
|
//#endregion
|
|
192
306
|
//#region src/utils/resolveCwd.ts
|
|
@@ -205,72 +319,59 @@ function resolveCwd(userConfig, cwd) {
|
|
|
205
319
|
}
|
|
206
320
|
//#endregion
|
|
207
321
|
//#region src/utils/resolveUserConfig.ts
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
logLevel: options.logLevel,
|
|
216
|
-
config: options.configPath
|
|
217
|
-
});
|
|
218
|
-
if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
|
|
219
|
-
else kubbUserConfig = Promise.resolve(possiblePromise);
|
|
220
|
-
}
|
|
221
|
-
return await kubbUserConfig;
|
|
322
|
+
async function resolveUserConfig(config, options) {
|
|
323
|
+
const result = typeof config === "function" ? config({
|
|
324
|
+
logLevel: options.logLevel,
|
|
325
|
+
config: options.configPath
|
|
326
|
+
}) : config;
|
|
327
|
+
const resolved = isPromise(result) ? await result : result;
|
|
328
|
+
return Array.isArray(resolved) ? resolved[0] : resolved;
|
|
222
329
|
}
|
|
223
330
|
//#endregion
|
|
224
331
|
//#region src/tools/generate.ts
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
async function generate(schema
|
|
332
|
+
const generateTool = (0, tmcp_tool.defineTool)({
|
|
333
|
+
name: "generate",
|
|
334
|
+
description: "Generate OpenAPI spec helpers using Kubb configuration",
|
|
335
|
+
schema: generateSchema
|
|
336
|
+
}, async function generate(schema) {
|
|
230
337
|
const { config: configPath, input, output, logLevel } = schema;
|
|
231
338
|
try {
|
|
232
|
-
const
|
|
339
|
+
const hooks = new AsyncEventEmitter();
|
|
233
340
|
const messages = [];
|
|
234
|
-
const notify = async (type, message,
|
|
341
|
+
const notify = async (type, message, _data) => {
|
|
235
342
|
messages.push(`${type}: ${message}`);
|
|
236
|
-
await handler.sendNotification("kubb/progress", {
|
|
237
|
-
type,
|
|
238
|
-
message,
|
|
239
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
240
|
-
...data
|
|
241
|
-
});
|
|
242
343
|
};
|
|
243
|
-
|
|
344
|
+
hooks.on("kubb:info", async ({ message }) => {
|
|
244
345
|
await notify(NotifyTypes.INFO, message);
|
|
245
346
|
});
|
|
246
|
-
|
|
347
|
+
hooks.on("kubb:success", async ({ message }) => {
|
|
247
348
|
await notify(NotifyTypes.SUCCESS, message);
|
|
248
349
|
});
|
|
249
|
-
|
|
250
|
-
await notify(NotifyTypes.ERROR, error.message
|
|
350
|
+
hooks.on("kubb:error", async ({ error }) => {
|
|
351
|
+
await notify(NotifyTypes.ERROR, error.message);
|
|
251
352
|
});
|
|
252
|
-
|
|
353
|
+
hooks.on("kubb:warn", async ({ message }) => {
|
|
253
354
|
await notify(NotifyTypes.WARN, message);
|
|
254
355
|
});
|
|
255
|
-
|
|
256
|
-
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${name}`);
|
|
356
|
+
hooks.on("kubb:plugin:start", async ({ plugin }) => {
|
|
357
|
+
await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`);
|
|
257
358
|
});
|
|
258
|
-
|
|
259
|
-
await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${name}`, { duration });
|
|
359
|
+
hooks.on("kubb:plugin:end", async ({ plugin, duration }) => {
|
|
360
|
+
await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${plugin.name}`, { duration });
|
|
260
361
|
});
|
|
261
|
-
|
|
362
|
+
hooks.on("kubb:files:processing:start", async () => {
|
|
262
363
|
await notify(NotifyTypes.FILES_START, "Starting file processing");
|
|
263
364
|
});
|
|
264
|
-
|
|
365
|
+
hooks.on("kubb:file:processing:update", async ({ file }) => {
|
|
265
366
|
await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`);
|
|
266
367
|
});
|
|
267
|
-
|
|
368
|
+
hooks.on("kubb:files:processing:end", async () => {
|
|
268
369
|
await notify(NotifyTypes.FILES_END, "File processing complete");
|
|
269
370
|
});
|
|
270
|
-
|
|
371
|
+
hooks.on("kubb:generation:start", async () => {
|
|
271
372
|
await notify(NotifyTypes.GENERATION_START, "Generation started");
|
|
272
373
|
});
|
|
273
|
-
|
|
374
|
+
hooks.on("kubb:generation:end", async () => {
|
|
274
375
|
await notify(NotifyTypes.GENERATION_END, "Generation ended");
|
|
275
376
|
});
|
|
276
377
|
let userConfig;
|
|
@@ -279,7 +380,7 @@ async function generate(schema, handler) {
|
|
|
279
380
|
const configResult = await loadUserConfig(configPath, { notify });
|
|
280
381
|
userConfig = configResult.userConfig;
|
|
281
382
|
cwd = configResult.cwd;
|
|
282
|
-
if (Array.isArray(userConfig)
|
|
383
|
+
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.");
|
|
283
384
|
userConfig = await resolveUserConfig(userConfig, {
|
|
284
385
|
configPath,
|
|
285
386
|
logLevel
|
|
@@ -287,15 +388,9 @@ async function generate(schema, handler) {
|
|
|
287
388
|
} catch (error) {
|
|
288
389
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
289
390
|
await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
|
|
290
|
-
return
|
|
291
|
-
content: [{
|
|
292
|
-
type: "text",
|
|
293
|
-
text: errorMessage
|
|
294
|
-
}],
|
|
295
|
-
isError: true
|
|
296
|
-
};
|
|
391
|
+
return tmcp_utils.tool.error(errorMessage);
|
|
297
392
|
}
|
|
298
|
-
const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
|
|
393
|
+
const inputPath = input ?? (userConfig.input && "path" in userConfig.input ? userConfig.input.path : void 0);
|
|
299
394
|
const config = {
|
|
300
395
|
...userConfig,
|
|
301
396
|
root: resolveCwd(userConfig, cwd),
|
|
@@ -308,99 +403,248 @@ async function generate(schema, handler) {
|
|
|
308
403
|
path: output
|
|
309
404
|
} : userConfig.output
|
|
310
405
|
};
|
|
311
|
-
await notify(NotifyTypes.CONFIG_READY, "Configuration ready"
|
|
406
|
+
await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
|
|
312
407
|
await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
events
|
|
316
|
-
});
|
|
408
|
+
const kubb = (0, _kubb_core.createKubb)(config, { hooks });
|
|
409
|
+
await kubb.setup();
|
|
317
410
|
await notify(NotifyTypes.SETUP_END, "Kubb setup complete");
|
|
318
411
|
await notify(NotifyTypes.BUILD_START, "Starting build");
|
|
319
|
-
const { files, failedPlugins, error } = await
|
|
320
|
-
config,
|
|
321
|
-
events
|
|
322
|
-
}, {
|
|
323
|
-
driver,
|
|
324
|
-
fabric,
|
|
325
|
-
events,
|
|
326
|
-
sources
|
|
327
|
-
});
|
|
412
|
+
const { files, failedPlugins, error } = await kubb.safeBuild();
|
|
328
413
|
await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
|
|
329
414
|
if (error || failedPlugins.size > 0) {
|
|
330
415
|
const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
|
|
331
|
-
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)
|
|
332
|
-
|
|
333
|
-
errors: allErrors.map((err) => err.message)
|
|
334
|
-
});
|
|
335
|
-
return {
|
|
336
|
-
content: [{
|
|
337
|
-
type: "text",
|
|
338
|
-
text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
|
|
339
|
-
}],
|
|
340
|
-
isError: true
|
|
341
|
-
};
|
|
416
|
+
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`);
|
|
417
|
+
return tmcp_utils.tool.error(`Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`);
|
|
342
418
|
}
|
|
343
|
-
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files
|
|
344
|
-
return
|
|
345
|
-
type: "text",
|
|
346
|
-
text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
|
|
347
|
-
}] };
|
|
419
|
+
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`);
|
|
420
|
+
return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`);
|
|
348
421
|
} catch (caughtError) {
|
|
349
|
-
const error = caughtError;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
422
|
+
const error = toError(caughtError);
|
|
423
|
+
return tmcp_utils.tool.error(`Build error: ${error.message}\n${error.stack ?? ""}`);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region ../../internals/shared/src/constants.ts
|
|
428
|
+
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
429
|
+
const availablePlugins = [
|
|
430
|
+
{
|
|
431
|
+
value: "plugin-ts",
|
|
432
|
+
label: "TypeScript",
|
|
433
|
+
hint: "Recommended",
|
|
434
|
+
packageName: "@kubb/plugin-ts",
|
|
435
|
+
importName: "pluginTs",
|
|
436
|
+
category: "types"
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
value: "plugin-client",
|
|
440
|
+
label: "Client (Fetch/Axios)",
|
|
441
|
+
packageName: "@kubb/plugin-client",
|
|
442
|
+
importName: "pluginClient",
|
|
443
|
+
category: "client"
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
value: "plugin-react-query",
|
|
447
|
+
label: "React Query / TanStack Query",
|
|
448
|
+
packageName: "@kubb/plugin-react-query",
|
|
449
|
+
importName: "pluginReactQuery",
|
|
450
|
+
category: "framework"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
value: "plugin-vue-query",
|
|
454
|
+
label: "Vue Query",
|
|
455
|
+
packageName: "@kubb/plugin-vue-query",
|
|
456
|
+
importName: "pluginVueQuery",
|
|
457
|
+
category: "framework"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
value: "plugin-zod",
|
|
461
|
+
label: "Zod Schemas",
|
|
462
|
+
packageName: "@kubb/plugin-zod",
|
|
463
|
+
importName: "pluginZod",
|
|
464
|
+
category: "validation"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
value: "plugin-faker",
|
|
468
|
+
label: "Faker.js Mocks",
|
|
469
|
+
packageName: "@kubb/plugin-faker",
|
|
470
|
+
importName: "pluginFaker",
|
|
471
|
+
category: "mocks"
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
value: "plugin-msw",
|
|
475
|
+
label: "MSW Handlers",
|
|
476
|
+
packageName: "@kubb/plugin-msw",
|
|
477
|
+
importName: "pluginMsw",
|
|
478
|
+
category: "mocks"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
value: "plugin-cypress",
|
|
482
|
+
label: "Cypress Tests",
|
|
483
|
+
packageName: "@kubb/plugin-cypress",
|
|
484
|
+
importName: "pluginCypress",
|
|
485
|
+
category: "testing"
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
value: "plugin-mcp",
|
|
489
|
+
label: "MCP Server (AI / Model Context Protocol)",
|
|
490
|
+
packageName: "@kubb/plugin-mcp",
|
|
491
|
+
importName: "pluginMcp",
|
|
492
|
+
category: "ai"
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
value: "plugin-redoc",
|
|
496
|
+
label: "ReDoc Documentation",
|
|
497
|
+
packageName: "@kubb/plugin-redoc",
|
|
498
|
+
importName: "pluginRedoc",
|
|
499
|
+
category: "documentation"
|
|
363
500
|
}
|
|
501
|
+
];
|
|
502
|
+
const pluginDefaultConfigs = {
|
|
503
|
+
"plugin-ts": `pluginTs({
|
|
504
|
+
output: { path: 'models' },
|
|
505
|
+
})`,
|
|
506
|
+
"plugin-client": `pluginClient({
|
|
507
|
+
output: { path: 'clients' },
|
|
508
|
+
})`,
|
|
509
|
+
"plugin-react-query": `pluginReactQuery({
|
|
510
|
+
output: { path: 'hooks' },
|
|
511
|
+
})`,
|
|
512
|
+
"plugin-vue-query": `pluginVueQuery({
|
|
513
|
+
output: { path: 'hooks' },
|
|
514
|
+
})`,
|
|
515
|
+
"plugin-zod": `pluginZod({
|
|
516
|
+
output: { path: 'zod' },
|
|
517
|
+
})`,
|
|
518
|
+
"plugin-faker": `pluginFaker({
|
|
519
|
+
output: { path: 'mocks' },
|
|
520
|
+
})`,
|
|
521
|
+
"plugin-msw": `pluginMsw({
|
|
522
|
+
output: { path: 'msw' },
|
|
523
|
+
})`,
|
|
524
|
+
"plugin-cypress": `pluginCypress({
|
|
525
|
+
output: { path: 'cypress' },
|
|
526
|
+
})`,
|
|
527
|
+
"plugin-mcp": `pluginMcp({
|
|
528
|
+
output: { path: 'mcp' },
|
|
529
|
+
})`,
|
|
530
|
+
"plugin-redoc": `pluginRedoc({
|
|
531
|
+
output: { path: 'redoc' },
|
|
532
|
+
})`
|
|
533
|
+
};
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region ../../internals/shared/src/init.ts
|
|
536
|
+
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
537
|
+
return `import { defineConfig } from 'kubb'
|
|
538
|
+
${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")}
|
|
539
|
+
|
|
540
|
+
export default defineConfig({
|
|
541
|
+
root: '.',
|
|
542
|
+
input: {
|
|
543
|
+
path: '${inputPath}',
|
|
544
|
+
},
|
|
545
|
+
output: {
|
|
546
|
+
path: '${outputPath}',
|
|
547
|
+
clean: true,
|
|
548
|
+
},
|
|
549
|
+
plugins: [
|
|
550
|
+
${selectedPlugins.map((plugin) => {
|
|
551
|
+
return ` ${pluginDefaultConfigs[plugin.value] ?? `${plugin.importName}()`},`;
|
|
552
|
+
}).join("\n")}
|
|
553
|
+
],
|
|
554
|
+
})
|
|
555
|
+
`;
|
|
364
556
|
}
|
|
365
557
|
//#endregion
|
|
366
|
-
//#region src/
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
558
|
+
//#region src/schemas/initSchema.ts
|
|
559
|
+
const initSchema = valibot.object({
|
|
560
|
+
input: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
561
|
+
output: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Output directory (default: ./src/gen)"))),
|
|
562
|
+
plugins: valibot.optional(valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
|
|
563
|
+
});
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/tools/init.ts
|
|
566
|
+
function resolvePlugins(pluginsFlag) {
|
|
567
|
+
if (!pluginsFlag) return [];
|
|
568
|
+
const requested = pluginsFlag.split(",").map((v) => v.trim()).filter(Boolean);
|
|
569
|
+
return availablePlugins.filter((p) => requested.includes(p.value));
|
|
570
|
+
}
|
|
571
|
+
const initTool = (0, tmcp_tool.defineTool)({
|
|
572
|
+
name: "init",
|
|
573
|
+
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
|
|
574
|
+
schema: initSchema
|
|
575
|
+
}, async ({ input = "./openapi.yaml", output = "./src/gen", plugins }) => {
|
|
576
|
+
const selected = resolvePlugins(plugins);
|
|
577
|
+
const content = generateConfigFile({
|
|
578
|
+
selectedPlugins: selected,
|
|
579
|
+
inputPath: input,
|
|
580
|
+
outputPath: output
|
|
581
|
+
});
|
|
582
|
+
const dest = node_path.default.join(node_process.default.cwd(), KUBB_CONFIG_FILENAME);
|
|
583
|
+
if (node_fs.default.existsSync(dest)) return tmcp_utils.tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
|
|
584
|
+
node_fs.default.writeFileSync(dest, content, "utf-8");
|
|
585
|
+
const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
|
|
586
|
+
return tmcp_utils.tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
|
|
587
|
+
});
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/schemas/validateSchema.ts
|
|
590
|
+
const validateSchema = valibot.object({ input: valibot.pipe(valibot.string(), valibot.minLength(1), valibot.description("Path or URL to the OpenAPI/Swagger specification")) });
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/tools/validate.ts
|
|
593
|
+
const validateTool = (0, tmcp_tool.defineTool)({
|
|
594
|
+
name: "validate",
|
|
595
|
+
description: "Validate an OpenAPI/Swagger specification file or URL",
|
|
596
|
+
schema: validateSchema
|
|
597
|
+
}, async ({ input }) => {
|
|
598
|
+
let mod;
|
|
373
599
|
try {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
600
|
+
mod = await import("@kubb/adapter-oas");
|
|
601
|
+
} catch {
|
|
602
|
+
return tmcp_utils.tool.error("The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas");
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
await mod.adapterOas().validate(input, { throwOnError: true });
|
|
606
|
+
return tmcp_utils.tool.text(`Validation successful: ${input}`);
|
|
607
|
+
} catch (err) {
|
|
608
|
+
return tmcp_utils.tool.error(`Validation failed:\n${err instanceof Error ? err.message : String(err)}`);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/server.ts
|
|
613
|
+
function createMcpServer() {
|
|
614
|
+
const server = new tmcp.McpServer({
|
|
615
|
+
name: "Kubb",
|
|
616
|
+
version
|
|
617
|
+
}, {
|
|
618
|
+
adapter: new _tmcp_adapter_valibot.ValibotJsonSchemaAdapter(),
|
|
619
|
+
capabilities: { tools: {} }
|
|
620
|
+
});
|
|
621
|
+
server.tools([
|
|
622
|
+
generateTool,
|
|
623
|
+
validateTool,
|
|
624
|
+
initTool
|
|
625
|
+
]);
|
|
626
|
+
return server;
|
|
627
|
+
}
|
|
628
|
+
async function startServer({ port, host = "localhost" } = {}) {
|
|
629
|
+
const server = createMcpServer();
|
|
630
|
+
if (port === void 0) {
|
|
631
|
+
new _tmcp_transport_stdio.StdioTransport(server).listen();
|
|
632
|
+
return;
|
|
396
633
|
}
|
|
634
|
+
const transport = new _tmcp_transport_http.HttpTransport(server, { path: "/mcp" });
|
|
635
|
+
node_http.default.createServer((0, _remix_run_node_fetch_server.createRequestListener)(async (request) => {
|
|
636
|
+
return await transport.respond(request) ?? new Response("Not Found", { status: 404 });
|
|
637
|
+
})).listen(port, host, () => {
|
|
638
|
+
console.log(`Kubb MCP server on http://${host}:${port}`);
|
|
639
|
+
});
|
|
397
640
|
}
|
|
398
641
|
//#endregion
|
|
399
642
|
//#region src/index.ts
|
|
400
|
-
async function run(_argv) {
|
|
401
|
-
await startServer();
|
|
643
|
+
async function run(_argv, options) {
|
|
644
|
+
await startServer(options);
|
|
402
645
|
}
|
|
403
646
|
//#endregion
|
|
647
|
+
exports.createMcpServer = createMcpServer;
|
|
404
648
|
exports.run = run;
|
|
405
649
|
|
|
406
650
|
//# sourceMappingURL=index.cjs.map
|