@kubb/mcp 5.0.0-beta.75 → 5.0.0-beta.8
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 +57 -20
- package/dist/index.cjs +310 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +304 -154
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/src/index.ts +5 -2
- package/src/schemas/generateSchema.ts +11 -10
- 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 +113 -177
- package/src/tools/init.ts +37 -0
- package/src/tools/validate.ts +25 -0
- package/src/utils/loadUserConfig.ts +32 -31
- package/src/utils/resolveUserConfig.ts +5 -20
package/README.md
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>@kubb/mcp</h1>
|
|
3
|
+
<a href="https://kubb.dev" target="_blank" rel="noopener noreferrer">
|
|
4
|
+
<img width="180" src="https://raw.githubusercontent.com/kubb-labs/kubb/main/assets/logo.png" alt="Kubb logo">
|
|
5
|
+
</a>
|
|
6
|
+
|
|
7
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
8
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
9
|
+
[![Coverage][coverage-src]][coverage-href]
|
|
10
|
+
[![License][license-src]][license-href]
|
|
11
|
+
[![Sponsors][sponsors-src]][sponsors-href]
|
|
12
|
+
|
|
13
|
+
<h4>
|
|
14
|
+
<a href="https://kubb.dev/" target="_blank">Documentation</a>
|
|
15
|
+
<span> · </span>
|
|
16
|
+
<a href="https://github.com/kubb-labs/kubb/issues/" target="_blank">Report Bug</a>
|
|
17
|
+
<span> · </span>
|
|
18
|
+
<a href="https://github.com/kubb-labs/kubb/issues/" target="_blank">Request Feature</a>
|
|
19
|
+
</h4>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
MCP server for Kubb. Exposes code generation as a tool over the [Model Context Protocol](https://modelcontextprotocol.io) so AI assistants like [Claude](https://claude.ai), [Cursor](https://cursor.sh), and other MCP-compatible clients can generate TypeScript types, API clients, and more from OpenAPI specs using natural language.
|
|
23
|
+
|
|
24
|
+
The server exposes a `generate` tool that runs a full Kubb build from a `kubb.config.ts`. It streams build events back to the client as real-time progress notifications. The server communicates over stdio and works with any MCP-compatible client.
|
|
17
25
|
|
|
18
26
|
## Installation
|
|
19
27
|
|
|
20
28
|
Install as a dev dependency:
|
|
21
29
|
|
|
22
|
-
```bash
|
|
23
|
-
|
|
30
|
+
```bash
|
|
31
|
+
bun add -D @kubb/mcp
|
|
32
|
+
# or
|
|
33
|
+
pnpm add -D @kubb/mcp
|
|
34
|
+
# or
|
|
35
|
+
npm install -D @kubb/mcp
|
|
24
36
|
```
|
|
25
37
|
|
|
26
38
|
> [!IMPORTANT]
|
|
@@ -48,7 +60,7 @@ This starts an MCP server that communicates via stdio (standard input/output), m
|
|
|
48
60
|
|
|
49
61
|
Add to your MCP client configuration (e.g., [Claude Desktop](https://claude.ai/download)'s `claude_desktop_config.json`):
|
|
50
62
|
|
|
51
|
-
|
|
63
|
+
Using `kubb mcp`:
|
|
52
64
|
|
|
53
65
|
```json
|
|
54
66
|
{
|
|
@@ -61,7 +73,7 @@ Add to your MCP client configuration (e.g., [Claude Desktop](https://claude.ai/d
|
|
|
61
73
|
}
|
|
62
74
|
```
|
|
63
75
|
|
|
64
|
-
|
|
76
|
+
Using the standalone package:
|
|
65
77
|
|
|
66
78
|
```json
|
|
67
79
|
{
|
|
@@ -152,3 +164,28 @@ export default defineConfig({
|
|
|
152
164
|
plugins: [pluginOas(), pluginTs(), pluginClient()],
|
|
153
165
|
})
|
|
154
166
|
```
|
|
167
|
+
|
|
168
|
+
## Supporting Kubb
|
|
169
|
+
|
|
170
|
+
Kubb is an open source project with its ongoing development made possible entirely by the support of Sponsors. If you would like to become a sponsor, please consider:
|
|
171
|
+
|
|
172
|
+
- [Become a Sponsor on GitHub](https://github.com/sponsors/stijnvanhulle)
|
|
173
|
+
|
|
174
|
+
<p align="center">
|
|
175
|
+
<a href="https://github.com/sponsors/stijnvanhulle">
|
|
176
|
+
<img src="https://raw.githubusercontent.com/stijnvanhulle/sponsors/main/sponsors.svg" alt="My sponsors" />
|
|
177
|
+
</a>
|
|
178
|
+
</p>
|
|
179
|
+
|
|
180
|
+
<!-- Badges -->
|
|
181
|
+
|
|
182
|
+
[npm-version-src]: https://img.shields.io/npm/v/@kubb/mcp?flat&colorA=18181B&colorB=f58517
|
|
183
|
+
[npm-version-href]: https://npmjs.com/package/@kubb/mcp
|
|
184
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@kubb/mcp?flat&colorA=18181B&colorB=f58517
|
|
185
|
+
[npm-downloads-href]: https://npmjs.com/package/@kubb/mcp
|
|
186
|
+
[license-src]: https://img.shields.io/github/license/kubb-labs/kubb.svg?flat&colorA=18181B&colorB=f58517
|
|
187
|
+
[license-href]: https://github.com/kubb-labs/kubb/blob/main/LICENSE
|
|
188
|
+
[coverage-src]: https://img.shields.io/codecov/c/github/kubb-labs/kubb?style=flat&colorA=18181B&colorB=f58517
|
|
189
|
+
[coverage-href]: https://www.npmjs.com/package/@kubb/mcp
|
|
190
|
+
[sponsors-src]: https://img.shields.io/github/sponsors/stijnvanhulle?style=flat&colorA=18181B&colorB=f58517
|
|
191
|
+
[sponsors-href]: https://github.com/sponsors/stijnvanhulle/
|
package/dist/index.cjs
CHANGED
|
@@ -21,34 +21,28 @@ 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");
|
|
30
32
|
let node_fs = require("node:fs");
|
|
33
|
+
node_fs = __toESM(node_fs, 1);
|
|
31
34
|
let node_path = require("node:path");
|
|
32
35
|
node_path = __toESM(node_path, 1);
|
|
33
36
|
let _kubb_core = require("@kubb/core");
|
|
34
|
-
let
|
|
37
|
+
let tmcp_tool = require("tmcp/tool");
|
|
38
|
+
let tmcp_utils = require("tmcp/utils");
|
|
39
|
+
let valibot = require("valibot");
|
|
40
|
+
valibot = __toESM(valibot, 1);
|
|
41
|
+
let jiti = require("jiti");
|
|
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-beta.
|
|
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.8";
|
|
52
46
|
//#endregion
|
|
53
47
|
//#region ../../internals/utils/src/errors.ts
|
|
54
48
|
/**
|
|
@@ -186,6 +180,21 @@ function isPromise(result) {
|
|
|
186
180
|
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
187
181
|
}
|
|
188
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
|
|
189
198
|
//#region src/types.ts
|
|
190
199
|
const NotifyTypes = {
|
|
191
200
|
INFO: "INFO",
|
|
@@ -222,18 +231,23 @@ const ALLOWED_CONFIG_EXTENSIONS = new Set([
|
|
|
222
231
|
]);
|
|
223
232
|
//#endregion
|
|
224
233
|
//#region src/utils/loadUserConfig.ts
|
|
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
|
+
});
|
|
225
241
|
const loadedModules = /* @__PURE__ */ new Map();
|
|
226
242
|
async function loadModule(filePath) {
|
|
227
243
|
const ext = node_path.default.extname(filePath);
|
|
228
244
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(", ")}`);
|
|
229
245
|
if (loadedModules.has(filePath)) return loadedModules.get(filePath);
|
|
230
|
-
const
|
|
231
|
-
loadedModules.set(filePath,
|
|
232
|
-
return
|
|
246
|
+
const mod = await jiti$1.import(filePath, { default: true });
|
|
247
|
+
loadedModules.set(filePath, mod);
|
|
248
|
+
return mod;
|
|
233
249
|
}
|
|
234
250
|
async function loadUserConfig(configPath, { notify }) {
|
|
235
|
-
let userConfig;
|
|
236
|
-
let cwd;
|
|
237
251
|
if (configPath) {
|
|
238
252
|
const ext = node_path.default.extname(configPath);
|
|
239
253
|
if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
|
|
@@ -249,41 +263,44 @@ async function loadUserConfig(configPath, { notify }) {
|
|
|
249
263
|
await notify(NotifyTypes.CONFIG_ERROR, msg);
|
|
250
264
|
throw new Error(msg);
|
|
251
265
|
}
|
|
252
|
-
cwd = node_path.default.dirname(resolvedConfigPath);
|
|
266
|
+
const cwd = node_path.default.dirname(resolvedConfigPath);
|
|
253
267
|
try {
|
|
254
|
-
userConfig = await loadModule(resolvedConfigPath);
|
|
268
|
+
const userConfig = await loadModule(resolvedConfigPath);
|
|
255
269
|
await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`);
|
|
270
|
+
return {
|
|
271
|
+
userConfig,
|
|
272
|
+
cwd
|
|
273
|
+
};
|
|
256
274
|
} catch (error) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
} else {
|
|
261
|
-
cwd = process.cwd();
|
|
262
|
-
const configFileNames = [
|
|
263
|
-
"kubb.config.ts",
|
|
264
|
-
"kubb.config.mts",
|
|
265
|
-
"kubb.config.cts",
|
|
266
|
-
"kubb.config.js",
|
|
267
|
-
"kubb.config.cjs"
|
|
268
|
-
];
|
|
269
|
-
for (const configFileName of configFileNames) {
|
|
270
|
-
const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
|
|
271
|
-
if (!(0, node_fs.existsSync)(configFilePath)) continue;
|
|
272
|
-
try {
|
|
273
|
-
userConfig = await loadModule(configFilePath);
|
|
274
|
-
await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
|
|
275
|
-
break;
|
|
276
|
-
} catch {}
|
|
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);
|
|
277
278
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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);
|
|
293
|
+
await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`);
|
|
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)}`);
|
|
281
300
|
}
|
|
282
301
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
cwd
|
|
286
|
-
};
|
|
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(", ")}`);
|
|
287
304
|
}
|
|
288
305
|
//#endregion
|
|
289
306
|
//#region src/utils/resolveCwd.ts
|
|
@@ -302,40 +319,27 @@ function resolveCwd(userConfig, cwd) {
|
|
|
302
319
|
}
|
|
303
320
|
//#endregion
|
|
304
321
|
//#region src/utils/resolveUserConfig.ts
|
|
305
|
-
/**
|
|
306
|
-
* Resolve the config by handling function configs and returning the final configuration
|
|
307
|
-
*/
|
|
308
322
|
async function resolveUserConfig(config, options) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (isPromise(possiblePromise)) kubbUserConfig = possiblePromise;
|
|
316
|
-
else kubbUserConfig = Promise.resolve(possiblePromise);
|
|
317
|
-
}
|
|
318
|
-
return await kubbUserConfig;
|
|
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;
|
|
319
329
|
}
|
|
320
330
|
//#endregion
|
|
321
331
|
//#region src/tools/generate.ts
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
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) {
|
|
327
337
|
const { config: configPath, input, output, logLevel } = schema;
|
|
328
338
|
try {
|
|
329
339
|
const hooks = new AsyncEventEmitter();
|
|
330
340
|
const messages = [];
|
|
331
|
-
const notify = async (type, message,
|
|
341
|
+
const notify = async (type, message, _data) => {
|
|
332
342
|
messages.push(`${type}: ${message}`);
|
|
333
|
-
await handler.sendNotification("kubb/progress", {
|
|
334
|
-
type,
|
|
335
|
-
message,
|
|
336
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
337
|
-
...data
|
|
338
|
-
});
|
|
339
343
|
};
|
|
340
344
|
hooks.on("kubb:info", async ({ message }) => {
|
|
341
345
|
await notify(NotifyTypes.INFO, message);
|
|
@@ -344,7 +348,7 @@ async function generate(schema, handler) {
|
|
|
344
348
|
await notify(NotifyTypes.SUCCESS, message);
|
|
345
349
|
});
|
|
346
350
|
hooks.on("kubb:error", async ({ error }) => {
|
|
347
|
-
await notify(NotifyTypes.ERROR, error.message
|
|
351
|
+
await notify(NotifyTypes.ERROR, error.message);
|
|
348
352
|
});
|
|
349
353
|
hooks.on("kubb:warn", async ({ message }) => {
|
|
350
354
|
await notify(NotifyTypes.WARN, message);
|
|
@@ -376,7 +380,7 @@ async function generate(schema, handler) {
|
|
|
376
380
|
const configResult = await loadUserConfig(configPath, { notify });
|
|
377
381
|
userConfig = configResult.userConfig;
|
|
378
382
|
cwd = configResult.cwd;
|
|
379
|
-
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.");
|
|
380
384
|
userConfig = await resolveUserConfig(userConfig, {
|
|
381
385
|
configPath,
|
|
382
386
|
logLevel
|
|
@@ -384,15 +388,9 @@ async function generate(schema, handler) {
|
|
|
384
388
|
} catch (error) {
|
|
385
389
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
386
390
|
await notify(NotifyTypes.CONFIG_ERROR, errorMessage);
|
|
387
|
-
return
|
|
388
|
-
content: [{
|
|
389
|
-
type: "text",
|
|
390
|
-
text: errorMessage
|
|
391
|
-
}],
|
|
392
|
-
isError: true
|
|
393
|
-
};
|
|
391
|
+
return tmcp_utils.tool.error(errorMessage);
|
|
394
392
|
}
|
|
395
|
-
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);
|
|
396
394
|
const config = {
|
|
397
395
|
...userConfig,
|
|
398
396
|
root: resolveCwd(userConfig, cwd),
|
|
@@ -405,7 +403,7 @@ async function generate(schema, handler) {
|
|
|
405
403
|
path: output
|
|
406
404
|
} : userConfig.output
|
|
407
405
|
};
|
|
408
|
-
await notify(NotifyTypes.CONFIG_READY, "Configuration ready"
|
|
406
|
+
await notify(NotifyTypes.CONFIG_READY, "Configuration ready");
|
|
409
407
|
await notify(NotifyTypes.SETUP_START, "Setting up Kubb");
|
|
410
408
|
const kubb = (0, _kubb_core.createKubb)(config, { hooks });
|
|
411
409
|
await kubb.setup();
|
|
@@ -415,79 +413,238 @@ async function generate(schema, handler) {
|
|
|
415
413
|
await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`);
|
|
416
414
|
if (error || failedPlugins.size > 0) {
|
|
417
415
|
const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
|
|
418
|
-
await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)
|
|
419
|
-
|
|
420
|
-
errors: allErrors.map((err) => err.message)
|
|
421
|
-
});
|
|
422
|
-
return {
|
|
423
|
-
content: [{
|
|
424
|
-
type: "text",
|
|
425
|
-
text: `Build failed:\n${allErrors.map((err) => err.message).join("\n")}\n\n${messages.join("\n")}`
|
|
426
|
-
}],
|
|
427
|
-
isError: true
|
|
428
|
-
};
|
|
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")}`);
|
|
429
418
|
}
|
|
430
|
-
await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files
|
|
431
|
-
return
|
|
432
|
-
type: "text",
|
|
433
|
-
text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join("\n")}`
|
|
434
|
-
}] };
|
|
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")}`);
|
|
435
421
|
} catch (caughtError) {
|
|
436
|
-
const error = caughtError;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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"
|
|
450
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
|
+
`;
|
|
451
556
|
}
|
|
452
557
|
//#endregion
|
|
453
|
-
//#region src/
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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;
|
|
460
599
|
try {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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;
|
|
483
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
|
+
});
|
|
484
640
|
}
|
|
485
641
|
//#endregion
|
|
486
642
|
//#region src/index.ts
|
|
487
|
-
async function run(_argv) {
|
|
488
|
-
await startServer();
|
|
643
|
+
async function run(_argv, options) {
|
|
644
|
+
await startServer(options);
|
|
489
645
|
}
|
|
490
646
|
//#endregion
|
|
647
|
+
exports.createMcpServer = createMcpServer;
|
|
491
648
|
exports.run = run;
|
|
492
649
|
|
|
493
650
|
//# sourceMappingURL=index.cjs.map
|