@kubb/mcp 5.9.2 → 5.9.4
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/dist/index.cjs +71 -205
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +70 -204
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -29,18 +29,18 @@ let zod_mini = require("zod/mini");
|
|
|
29
29
|
zod_mini = __toESM(zod_mini, 1);
|
|
30
30
|
let node_path = require("node:path");
|
|
31
31
|
node_path = __toESM(node_path, 1);
|
|
32
|
+
let node_process = require("node:process");
|
|
33
|
+
node_process = __toESM(node_process, 1);
|
|
34
|
+
let node_fs = require("node:fs");
|
|
35
|
+
node_fs = __toESM(node_fs, 1);
|
|
32
36
|
let node_fs_promises = require("node:fs/promises");
|
|
33
37
|
let jiti = require("jiti");
|
|
34
38
|
let _kubb_core = require("@kubb/core");
|
|
35
39
|
let tmcp_tool = require("tmcp/tool");
|
|
36
40
|
let tmcp_utils = require("tmcp/utils");
|
|
37
|
-
let node_fs = require("node:fs");
|
|
38
|
-
node_fs = __toESM(node_fs, 1);
|
|
39
|
-
let node_process = require("node:process");
|
|
40
|
-
node_process = __toESM(node_process, 1);
|
|
41
41
|
let _kubb_adapter_oas = require("@kubb/adapter-oas");
|
|
42
42
|
//#region package.json
|
|
43
|
-
var version = "5.9.
|
|
43
|
+
var version = "5.9.4";
|
|
44
44
|
//#endregion
|
|
45
45
|
//#region ../../internals/shared/src/constants.ts
|
|
46
46
|
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
@@ -53,113 +53,6 @@ const CONFIG_EXTENSIONS = [
|
|
|
53
53
|
".mjs",
|
|
54
54
|
".cjs"
|
|
55
55
|
];
|
|
56
|
-
const availablePlugins = [
|
|
57
|
-
{
|
|
58
|
-
value: "plugin-ts",
|
|
59
|
-
label: "TypeScript",
|
|
60
|
-
hint: "Recommended",
|
|
61
|
-
packageName: "@kubb/plugin-ts",
|
|
62
|
-
importName: "pluginTs",
|
|
63
|
-
category: "types"
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
value: "plugin-axios",
|
|
67
|
-
label: "Axios Client",
|
|
68
|
-
packageName: "@kubb/plugin-axios",
|
|
69
|
-
importName: "pluginAxios",
|
|
70
|
-
category: "client"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
value: "plugin-fetch",
|
|
74
|
-
label: "Fetch Client",
|
|
75
|
-
packageName: "@kubb/plugin-fetch",
|
|
76
|
-
importName: "pluginFetch",
|
|
77
|
-
category: "client"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
value: "plugin-react-query",
|
|
81
|
-
label: "React Query / TanStack Query",
|
|
82
|
-
packageName: "@kubb/plugin-react-query",
|
|
83
|
-
importName: "pluginReactQuery",
|
|
84
|
-
category: "framework"
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
value: "plugin-vue-query",
|
|
88
|
-
label: "Vue Query",
|
|
89
|
-
packageName: "@kubb/plugin-vue-query",
|
|
90
|
-
importName: "pluginVueQuery",
|
|
91
|
-
category: "framework"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
value: "plugin-zod",
|
|
95
|
-
label: "Zod Schemas",
|
|
96
|
-
packageName: "@kubb/plugin-zod",
|
|
97
|
-
importName: "pluginZod",
|
|
98
|
-
category: "validation"
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
value: "plugin-faker",
|
|
102
|
-
label: "Faker.js Mocks",
|
|
103
|
-
packageName: "@kubb/plugin-faker",
|
|
104
|
-
importName: "pluginFaker",
|
|
105
|
-
category: "mocks"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
value: "plugin-msw",
|
|
109
|
-
label: "MSW Handlers",
|
|
110
|
-
packageName: "@kubb/plugin-msw",
|
|
111
|
-
importName: "pluginMsw",
|
|
112
|
-
category: "mocks"
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
value: "plugin-cypress",
|
|
116
|
-
label: "Cypress Tests",
|
|
117
|
-
packageName: "@kubb/plugin-cypress",
|
|
118
|
-
importName: "pluginCypress",
|
|
119
|
-
category: "testing"
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
value: "plugin-mcp",
|
|
123
|
-
label: "MCP Server (AI / Model Context Protocol)",
|
|
124
|
-
packageName: "@kubb/plugin-mcp",
|
|
125
|
-
importName: "pluginMcp",
|
|
126
|
-
category: "ai"
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
value: "plugin-redoc",
|
|
130
|
-
label: "ReDoc Documentation",
|
|
131
|
-
packageName: "@kubb/plugin-redoc",
|
|
132
|
-
importName: "pluginRedoc",
|
|
133
|
-
category: "documentation"
|
|
134
|
-
}
|
|
135
|
-
];
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region ../../internals/shared/src/init.ts
|
|
138
|
-
/**
|
|
139
|
-
* Resolves a comma-separated plugin flag (e.g. `--plugins plugin-ts,plugin-zod`) into the
|
|
140
|
-
* matching known plugin options. Unrecognized names are dropped, and a missing flag yields
|
|
141
|
-
* an empty list.
|
|
142
|
-
*/
|
|
143
|
-
function resolvePlugins(pluginsFlag) {
|
|
144
|
-
if (!pluginsFlag) return [];
|
|
145
|
-
const requested = pluginsFlag.split(",").map((value) => value.trim()).filter(Boolean);
|
|
146
|
-
return availablePlugins.filter((plugin) => requested.includes(plugin.value));
|
|
147
|
-
}
|
|
148
|
-
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
149
|
-
return `${[`import { defineConfig } from 'kubb/config'`, ...selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`)].join("\n")}
|
|
150
|
-
|
|
151
|
-
export default defineConfig({
|
|
152
|
-
input: '${inputPath}',
|
|
153
|
-
output: {
|
|
154
|
-
path: '${outputPath}',
|
|
155
|
-
clean: true,
|
|
156
|
-
},
|
|
157
|
-
plugins: [
|
|
158
|
-
${selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join("\n")}
|
|
159
|
-
],
|
|
160
|
-
})
|
|
161
|
-
`;
|
|
162
|
-
}
|
|
163
56
|
//#endregion
|
|
164
57
|
//#region ../../internals/shared/src/loader.ts
|
|
165
58
|
/**
|
|
@@ -200,6 +93,48 @@ async function loadModule(filePath, options) {
|
|
|
200
93
|
return options?.default ? mod?.default ?? mod : mod;
|
|
201
94
|
}
|
|
202
95
|
//#endregion
|
|
96
|
+
//#region ../../internals/shared/src/config.ts
|
|
97
|
+
const SEARCH_FILES = [
|
|
98
|
+
"",
|
|
99
|
+
".config/",
|
|
100
|
+
"configs/"
|
|
101
|
+
].flatMap((prefix) => [`${prefix}.kubbrc`, `${prefix}kubb.config`]);
|
|
102
|
+
/**
|
|
103
|
+
* The first Kubb config file in `cwd` or the nearest parent folder that has one: `.kubbrc` before
|
|
104
|
+
* `kubb.config`, the folder itself before its `.config/` and `configs/`, and {@link CONFIG_EXTENSIONS} in order.
|
|
105
|
+
*/
|
|
106
|
+
function findConfigFile(cwd = node_process.default.cwd()) {
|
|
107
|
+
for (let dir = (0, node_path.resolve)(cwd);; dir = (0, node_path.dirname)(dir)) {
|
|
108
|
+
const found = SEARCH_FILES.flatMap((file) => CONFIG_EXTENSIONS.map((extension) => (0, node_path.join)(dir, `${file}${extension}`))).find((file) => (0, node_fs.existsSync)(file));
|
|
109
|
+
if (found || (0, node_path.dirname)(dir) === dir) return found;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Loads a Kubb config module, calls it with `cli` when it exports a function, and returns its
|
|
114
|
+
* configs as a list, so a single config and a `defineConfig([...])` array read the same.
|
|
115
|
+
*/
|
|
116
|
+
async function loadConfigFile(file, cli) {
|
|
117
|
+
const loaded = await loadModule(file, { default: true });
|
|
118
|
+
const resolved = await (typeof loaded === "function" ? loaded(cli) : loaded);
|
|
119
|
+
return Array.isArray(resolved) ? resolved : [resolved];
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region ../../internals/shared/src/init.ts
|
|
123
|
+
/** The `kubb.config.ts` `kubb init` writes before `kubb add` puts each plugin in. */
|
|
124
|
+
function generateConfigFile({ inputPath, outputPath }) {
|
|
125
|
+
return `import { defineConfig } from 'kubb/config'
|
|
126
|
+
|
|
127
|
+
export default defineConfig({
|
|
128
|
+
input: '${inputPath}',
|
|
129
|
+
output: {
|
|
130
|
+
path: '${outputPath}',
|
|
131
|
+
clean: true,
|
|
132
|
+
},
|
|
133
|
+
plugins: [],
|
|
134
|
+
})
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
203
138
|
//#region ../../internals/utils/src/fs.ts
|
|
204
139
|
/**
|
|
205
140
|
* Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved
|
|
@@ -224,7 +159,6 @@ function isPathInside(path, parent) {
|
|
|
224
159
|
}
|
|
225
160
|
//#endregion
|
|
226
161
|
//#region src/utils.ts
|
|
227
|
-
const CONFIG_FILE_NAMES = CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`);
|
|
228
162
|
/**
|
|
229
163
|
* Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
|
|
230
164
|
* keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
|
|
@@ -242,65 +176,10 @@ function formatDiagnostic(diagnostic) {
|
|
|
242
176
|
if (docsUrl) lines.push(` see: ${docsUrl}`);
|
|
243
177
|
return lines.join("\n");
|
|
244
178
|
}
|
|
245
|
-
/**
|
|
246
|
-
* Loads the user's Kubb config and returns it with the directory it was found in.
|
|
247
|
-
*
|
|
248
|
-
* When `configPath` is given it must use an allowed extension and resolve inside
|
|
249
|
-
* the current working directory, otherwise loading throws. When omitted, the
|
|
250
|
-
* known `kubb.config.*` file names are tried in the current directory. Every
|
|
251
|
-
* outcome is reported through `notify` before the function returns or throws.
|
|
252
|
-
*/
|
|
253
|
-
async function loadUserConfig(configPath, { notify }) {
|
|
254
|
-
if (configPath) {
|
|
255
|
-
const ext = node_path.default.extname(configPath);
|
|
256
|
-
if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) {
|
|
257
|
-
const msg = `Invalid config file extension "${ext}". Allowed: ${CONFIG_EXTENSIONS.join(", ")}`;
|
|
258
|
-
notify("CONFIG_ERROR", msg);
|
|
259
|
-
throw new Error(msg);
|
|
260
|
-
}
|
|
261
|
-
const base = node_path.default.resolve(process.cwd());
|
|
262
|
-
const resolvedConfigPath = node_path.default.resolve(base, configPath);
|
|
263
|
-
if (!isPathInside(resolvedConfigPath, base)) {
|
|
264
|
-
const msg = "Invalid config file path: must be within the current working directory";
|
|
265
|
-
notify("CONFIG_ERROR", msg);
|
|
266
|
-
throw new Error(msg);
|
|
267
|
-
}
|
|
268
|
-
const cwd = node_path.default.dirname(resolvedConfigPath);
|
|
269
|
-
try {
|
|
270
|
-
const userConfig = await loadModule(resolvedConfigPath, { default: true });
|
|
271
|
-
notify("CONFIG_LOADED", `Loaded config from ${resolvedConfigPath}`);
|
|
272
|
-
return {
|
|
273
|
-
userConfig,
|
|
274
|
-
cwd
|
|
275
|
-
};
|
|
276
|
-
} catch (error) {
|
|
277
|
-
const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
|
|
278
|
-
notify("CONFIG_ERROR", msg);
|
|
279
|
-
throw new Error(msg);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
const cwd = process.cwd();
|
|
283
|
-
for (const configFileName of CONFIG_FILE_NAMES) {
|
|
284
|
-
const configFilePath = node_path.default.resolve(process.cwd(), configFileName);
|
|
285
|
-
if (!(0, node_fs.existsSync)(configFilePath)) continue;
|
|
286
|
-
try {
|
|
287
|
-
const userConfig = await loadModule(configFilePath, { default: true });
|
|
288
|
-
notify("CONFIG_LOADED", `Loaded ${configFileName} from current directory`);
|
|
289
|
-
return {
|
|
290
|
-
userConfig,
|
|
291
|
-
cwd
|
|
292
|
-
};
|
|
293
|
-
} catch (err) {
|
|
294
|
-
notify("CONFIG_ERROR", `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
notify("CONFIG_ERROR", "No config file found");
|
|
298
|
-
throw new Error(`No config file found. Please provide a config path or create one of: ${CONFIG_FILE_NAMES.join(", ")}`);
|
|
299
|
-
}
|
|
300
179
|
//#endregion
|
|
301
180
|
//#region src/tools/generate.ts
|
|
302
181
|
const generateSchema = zod_mini.object({
|
|
303
|
-
config: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe(`Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(", ")}). If not provided, will look for kubb.config.* in current directory`))),
|
|
182
|
+
config: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe(`Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(", ")}). If not provided, will look for kubb.config.* or .kubbrc.* in the current directory, its .config/ and configs/ folders, then each parent directory`))),
|
|
304
183
|
input: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
305
184
|
output: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe("Output directory path (overrides config)"))),
|
|
306
185
|
logLevel: zod_mini._default(zod_mini.enum([
|
|
@@ -321,40 +200,32 @@ const generateTool = (0, tmcp_tool.defineTool)({
|
|
|
321
200
|
const notify = (type, message, data) => {
|
|
322
201
|
messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
|
|
323
202
|
};
|
|
324
|
-
hooks.hook("kubb:info", ({ message }) => notify("INFO", message));
|
|
325
|
-
hooks.hook("kubb:success", ({ message }) => notify("SUCCESS", message));
|
|
326
203
|
hooks.hook("kubb:error", ({ error }) => notify("ERROR", error.message));
|
|
327
204
|
hooks.hook("kubb:warn", ({ message }) => notify("WARN", message));
|
|
328
205
|
hooks.hook("kubb:diagnostic", ({ diagnostic }) => notify("DIAGNOSTIC", diagnostic.message, _kubb_core.Diagnostics.serialize(diagnostic)));
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
hooks.hook("kubb:generation:end", () => notify("GENERATION_END", "Generation ended"));
|
|
206
|
+
const base = node_path.default.resolve(node_process.default.cwd());
|
|
207
|
+
let file;
|
|
208
|
+
if (configPath) {
|
|
209
|
+
const ext = node_path.default.extname(configPath);
|
|
210
|
+
if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) return tmcp_utils.tool.error(`Invalid config file extension "${ext}". Allowed: ${CONFIG_EXTENSIONS.join(", ")}`);
|
|
211
|
+
file = node_path.default.resolve(base, configPath);
|
|
212
|
+
if (!isPathInside(file, base)) return tmcp_utils.tool.error("Invalid config file path: must be within the current working directory");
|
|
213
|
+
} else {
|
|
214
|
+
file = findConfigFile(base);
|
|
215
|
+
if (!file) return tmcp_utils.tool.error(`No config file found. Please provide a config path or create one of: ${CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`).join(", ")}`);
|
|
216
|
+
}
|
|
341
217
|
let userConfig;
|
|
342
|
-
let cwd;
|
|
343
218
|
try {
|
|
344
|
-
const
|
|
345
|
-
const loaded = configResult.userConfig;
|
|
346
|
-
cwd = configResult.cwd;
|
|
347
|
-
const resolved = await (typeof loaded === "function" ? loaded({
|
|
219
|
+
const [first] = await loadConfigFile(file, {
|
|
348
220
|
logLevel,
|
|
349
221
|
config: configPath
|
|
350
|
-
})
|
|
351
|
-
const first = Array.isArray(resolved) ? resolved[0] : resolved;
|
|
222
|
+
});
|
|
352
223
|
if (!first) throw new Error("The config file exports an empty array of configurations");
|
|
353
224
|
userConfig = first;
|
|
354
225
|
} catch (error) {
|
|
355
|
-
|
|
356
|
-
return tmcp_utils.tool.error(errorMessage);
|
|
226
|
+
return tmcp_utils.tool.error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
357
227
|
}
|
|
228
|
+
const cwd = node_path.default.dirname(file);
|
|
358
229
|
const config = {
|
|
359
230
|
...userConfig,
|
|
360
231
|
root: node_path.default.resolve(cwd, userConfig.root ?? "."),
|
|
@@ -385,25 +256,20 @@ const generateTool = (0, tmcp_tool.defineTool)({
|
|
|
385
256
|
//#region src/tools/init.ts
|
|
386
257
|
const initSchema = zod_mini.object({
|
|
387
258
|
input: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
388
|
-
output: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe("Output directory (default: ./src/gen)")))
|
|
389
|
-
plugins: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
|
|
259
|
+
output: zod_mini.optional(zod_mini.string().check(zod_mini.minLength(1), zod_mini.describe("Output directory (default: ./src/gen)")))
|
|
390
260
|
});
|
|
391
261
|
const initTool = (0, tmcp_tool.defineTool)({
|
|
392
262
|
name: "init",
|
|
393
|
-
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
|
|
263
|
+
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages or add plugins: run `kubb add <id> --yes` for each plugin afterwards.",
|
|
394
264
|
schema: initSchema
|
|
395
|
-
}, async ({ input = "./openapi.yaml", output = "./src/gen"
|
|
396
|
-
const selected = resolvePlugins(plugins);
|
|
397
|
-
const content = generateConfigFile({
|
|
398
|
-
selectedPlugins: selected,
|
|
399
|
-
inputPath: input,
|
|
400
|
-
outputPath: output
|
|
401
|
-
});
|
|
265
|
+
}, async ({ input = "./openapi.yaml", output = "./src/gen" }) => {
|
|
402
266
|
const dest = node_path.default.join(node_process.default.cwd(), KUBB_CONFIG_FILENAME);
|
|
403
267
|
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.`);
|
|
404
|
-
node_fs.default.writeFileSync(dest,
|
|
405
|
-
|
|
406
|
-
|
|
268
|
+
node_fs.default.writeFileSync(dest, generateConfigFile({
|
|
269
|
+
inputPath: input,
|
|
270
|
+
outputPath: output
|
|
271
|
+
}), "utf-8");
|
|
272
|
+
return tmcp_utils.tool.text(`Created ${KUBB_CONFIG_FILENAME}\n\nInstall Kubb:\n npm install kubb\n\nAdd each plugin with its registry id, which installs it and adds it to the config:\n npx kubb add plugin-ts --yes\n\nThen run:\n npx kubb generate`);
|
|
407
273
|
});
|
|
408
274
|
//#endregion
|
|
409
275
|
//#region src/tools/validate.ts
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["jiti","createJiti","readFile","resolve","relative","isAbsolute","path","existsSync","z","defineTool","Hookable","Diagnostics","tool","path","createKubb","z","defineTool","path","process","fs","tool","z","defineTool","adapterOas","tool","Diagnostics","JsonSchemaAdapter","z","en","McpServer","StdioTransport"],"sources":["../package.json","../../../internals/shared/src/constants.ts","../../../internals/shared/src/init.ts","../../../internals/shared/src/loader.ts","../../../internals/utils/src/fs.ts","../src/utils.ts","../src/tools/generate.ts","../src/tools/init.ts","../src/tools/validate.ts","../src/server.ts"],"sourcesContent":["","export type PluginOption = {\n value: string\n label: string\n hint?: string\n packageName: string\n importName: string\n category: 'types' | 'client' | 'framework' | 'validation' | 'testing' | 'mocks' | 'documentation' | 'ai'\n}\n\nexport const KUBB_CONFIG_FILENAME = 'kubb.config.ts' as const\n\n/** File extensions a Kubb config file may use. */\nexport const CONFIG_EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'] as const\n\nexport const KUBB_PACKAGE_NAME = 'kubb' as const\n\nexport const initDefaults = {\n inputPath: './openapi.yaml',\n outputPath: './src/gen',\n plugins: ['plugin-ts'],\n} as const\n\nexport const availablePlugins: Array<PluginOption> = [\n {\n value: 'plugin-ts',\n label: 'TypeScript',\n hint: 'Recommended',\n packageName: '@kubb/plugin-ts',\n importName: 'pluginTs',\n category: 'types',\n },\n {\n value: 'plugin-axios',\n label: 'Axios Client',\n packageName: '@kubb/plugin-axios',\n importName: 'pluginAxios',\n category: 'client',\n },\n {\n value: 'plugin-fetch',\n label: 'Fetch Client',\n packageName: '@kubb/plugin-fetch',\n importName: 'pluginFetch',\n category: 'client',\n },\n {\n value: 'plugin-react-query',\n label: 'React Query / TanStack Query',\n packageName: '@kubb/plugin-react-query',\n importName: 'pluginReactQuery',\n category: 'framework',\n },\n {\n value: 'plugin-vue-query',\n label: 'Vue Query',\n packageName: '@kubb/plugin-vue-query',\n importName: 'pluginVueQuery',\n category: 'framework',\n },\n {\n value: 'plugin-zod',\n label: 'Zod Schemas',\n packageName: '@kubb/plugin-zod',\n importName: 'pluginZod',\n category: 'validation',\n },\n {\n value: 'plugin-faker',\n label: 'Faker.js Mocks',\n packageName: '@kubb/plugin-faker',\n importName: 'pluginFaker',\n category: 'mocks',\n },\n {\n value: 'plugin-msw',\n label: 'MSW Handlers',\n packageName: '@kubb/plugin-msw',\n importName: 'pluginMsw',\n category: 'mocks',\n },\n {\n value: 'plugin-cypress',\n label: 'Cypress Tests',\n packageName: '@kubb/plugin-cypress',\n importName: 'pluginCypress',\n category: 'testing',\n },\n {\n value: 'plugin-mcp',\n label: 'MCP Server (AI / Model Context Protocol)',\n packageName: '@kubb/plugin-mcp',\n importName: 'pluginMcp',\n category: 'ai',\n },\n {\n value: 'plugin-redoc',\n label: 'ReDoc Documentation',\n packageName: '@kubb/plugin-redoc',\n importName: 'pluginRedoc',\n category: 'documentation',\n },\n]\n","import { availablePlugins, type PluginOption } from './constants.ts'\n\n/**\n * Resolves a comma-separated plugin flag (e.g. `--plugins plugin-ts,plugin-zod`) into the\n * matching known plugin options. Unrecognized names are dropped, and a missing flag yields\n * an empty list.\n */\nexport function resolvePlugins(pluginsFlag: string | undefined): Array<PluginOption> {\n if (!pluginsFlag) {\n return []\n }\n const requested = pluginsFlag\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean)\n return availablePlugins.filter((plugin) => requested.includes(plugin.value))\n}\n\nexport function generateConfigFile({\n selectedPlugins,\n inputPath,\n outputPath,\n}: {\n selectedPlugins: Array<PluginOption>\n inputPath: string\n outputPath: string\n}): string {\n const imports = [\n `import { defineConfig } from 'kubb/config'`,\n ...selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`),\n ].join('\\n')\n\n const pluginConfigs = selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join('\\n')\n\n return `${imports}\n\nexport default defineConfig({\n input: '${inputPath}',\n output: {\n path: '${outputPath}',\n clean: true,\n },\n plugins: [\n${pluginConfigs}\n ],\n})\n`\n}\n","import { readFile } from 'node:fs/promises'\nimport { createJiti } from 'jiti'\n\n/**\n * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at\n * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.\n */\nconst JITI_OPTIONS = {\n jsx: { runtime: 'automatic', importSource: '@kubb/renderer-jsx' },\n moduleCache: false,\n} satisfies Parameters<typeof createJiti>[1]\n\n/**\n * Plain JavaScript files. jiti hands these to Node's native `import()`, whose module cache never\n * lets go of a file, so `moduleCache: false` alone would keep returning the first version of it.\n */\nconst JAVASCRIPT_EXTENSION = /\\.[cm]?jsx?$/\n\n/**\n * Per-call options for {@link loadModule}.\n */\ntype LoadModuleOptions = {\n /**\n * Return the module's default export instead of the full namespace.\n */\n default?: boolean\n}\n\nconst jiti = createJiti(import.meta.url, JITI_OPTIONS)\n\n/**\n * Loads a Kubb TypeScript or JavaScript config module via jiti.\n *\n * jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly. Every load\n * re-evaluates the file, so a config edited on disk is picked up by the next load.\n *\n * @example\n * ```ts\n * const config = await loadModule('/abs/kubb.config.ts', { default: true })\n * ```\n */\nexport async function loadModule<T = unknown>(filePath: string, options?: LoadModuleOptions): Promise<T> {\n if (!JAVASCRIPT_EXTENSION.test(filePath)) {\n return (options?.default ? jiti.import(filePath, { default: true }) : jiti.import(filePath)) as Promise<T>\n }\n\n // Transpiled through jiti instead, so every load re-evaluates the file as it is on disk now.\n // A TypeScript file it imports reloads too. A plain JavaScript file it imports still goes\n // through Node's `import()` and stays cached until the process restarts.\n const mod = (await jiti.evalModule(await readFile(filePath, 'utf8'), { filename: filePath, async: true, forceTranspile: true })) as\n | { default?: unknown }\n | undefined\n return (options?.default ? (mod?.default ?? mod) : mod) as T\n}\n","import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import { existsSync } from 'node:fs'\nimport path from 'node:path'\nimport { CONFIG_EXTENSIONS, loadModule } from '@internals/shared'\nimport { isPathInside } from '@internals/utils'\nimport type { CLIOptions, PossibleConfig, SerializedDiagnostic } from '@kubb/core'\n\nconst CONFIG_FILE_NAMES = CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`)\n\n/**\n * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry\n * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so\n * the agent can act on the problem rather than parsing a bare message. No ANSI styling,\n * unlike the CLI renderer.\n */\nexport function formatDiagnostics(diagnostics: ReadonlyArray<SerializedDiagnostic>): string {\n return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join('\\n\\n')\n}\n\nfunction formatDiagnostic(diagnostic: SerializedDiagnostic): string {\n const { code, message, location, help, plugin, docsUrl } = diagnostic\n const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`]\n\n if (location && 'pointer' in location) {\n lines.push(` at: ${location.pointer}`)\n }\n if (help) {\n lines.push(` fix: ${help}`)\n }\n if (docsUrl) {\n lines.push(` see: ${docsUrl}`)\n }\n\n return lines.join('\\n')\n}\n\ntype NotifyFunction = (type: string, message: string, data?: Record<string, unknown>) => void\n\n/**\n * Loads the user's Kubb config and returns it with the directory it was found in.\n *\n * When `configPath` is given it must use an allowed extension and resolve inside\n * the current working directory, otherwise loading throws. When omitted, the\n * known `kubb.config.*` file names are tried in the current directory. Every\n * outcome is reported through `notify` before the function returns or throws.\n */\nexport async function loadUserConfig(\n configPath: string | undefined,\n { notify }: { notify: NotifyFunction },\n): Promise<{ userConfig: PossibleConfig<CLIOptions>; cwd: string }> {\n if (configPath) {\n const ext = path.extname(configPath)\n if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) {\n const msg = `Invalid config file extension \"${ext}\". Allowed: ${CONFIG_EXTENSIONS.join(', ')}`\n notify('CONFIG_ERROR', msg)\n throw new Error(msg)\n }\n const base = path.resolve(process.cwd())\n const resolvedConfigPath = path.resolve(base, configPath)\n if (!isPathInside(resolvedConfigPath, base)) {\n const msg = 'Invalid config file path: must be within the current working directory'\n notify('CONFIG_ERROR', msg)\n throw new Error(msg)\n }\n const cwd = path.dirname(resolvedConfigPath)\n try {\n const userConfig = await loadModule<PossibleConfig<CLIOptions>>(resolvedConfigPath, { default: true })\n notify('CONFIG_LOADED', `Loaded config from ${resolvedConfigPath}`)\n return { userConfig, cwd }\n } catch (error) {\n const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`\n notify('CONFIG_ERROR', msg)\n throw new Error(msg)\n }\n }\n\n const cwd = process.cwd()\n\n for (const configFileName of CONFIG_FILE_NAMES) {\n const configFilePath = path.resolve(process.cwd(), configFileName)\n if (!existsSync(configFilePath)) continue\n try {\n const userConfig = await loadModule<PossibleConfig<CLIOptions>>(configFilePath, { default: true })\n notify('CONFIG_LOADED', `Loaded ${configFileName} from current directory`)\n return { userConfig, cwd }\n } catch (err) {\n notify('CONFIG_ERROR', `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n notify('CONFIG_ERROR', 'No config file found')\n throw new Error(`No config file found. Please provide a config path or create one of: ${CONFIG_FILE_NAMES.join(', ')}`)\n}\n","import path from 'node:path'\nimport { CONFIG_EXTENSIONS } from '@internals/shared'\nimport { type Config, createKubb, Diagnostics, type KubbHooks, Hookable } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics, loadUserConfig } from '../utils.ts'\n\nconst generateSchema = z.object({\n config: z.optional(\n z\n .string()\n .check(\n z.minLength(1),\n z.describe(`Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(', ')}). If not provided, will look for kubb.config.* in current directory`),\n ),\n ),\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI/Swagger spec file (overrides config)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory path (overrides config)'))),\n logLevel: z._default(z.enum(['silent', 'info', 'verbose']).check(z.describe('Log level for build output')), 'info'),\n})\n\nexport const generateTool = defineTool(\n {\n name: 'generate',\n description: 'Generate OpenAPI spec helpers using Kubb configuration',\n schema: generateSchema,\n },\n async function generate(schema: z.input<typeof generateSchema>) {\n const { config: configPath, input, output, logLevel } = schema\n\n try {\n const hooks = new Hookable<KubbHooks>()\n const messages: Array<string> = []\n\n const notify = (type: string, message: string, data?: Record<string, unknown>) => {\n messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`)\n }\n\n hooks.hook('kubb:info', ({ message }) => notify('INFO', message))\n hooks.hook('kubb:success', ({ message }) => notify('SUCCESS', message))\n hooks.hook('kubb:error', ({ error }) => notify('ERROR', error.message))\n hooks.hook('kubb:warn', ({ message }) => notify('WARN', message))\n hooks.hook('kubb:diagnostic', ({ diagnostic }) => notify('DIAGNOSTIC', diagnostic.message, Diagnostics.serialize(diagnostic)))\n hooks.hook('kubb:plugin:start', ({ plugin }) => notify('PLUGIN_START', `Plugin starting: ${plugin.name}`))\n hooks.hook('kubb:plugin:end', ({ plugin, duration }) => notify('PLUGIN_END', `Plugin finished: ${plugin.name}`, { duration }))\n hooks.hook('kubb:files:processing:start', () => notify('FILES_START', 'Starting file processing'))\n hooks.hook('kubb:files:processing:update', ({ files }) => notify('FILES_UPDATE', `Processing ${files.length} files`))\n hooks.hook('kubb:files:processing:end', () => notify('FILES_END', 'File processing complete'))\n hooks.hook('kubb:generation:start', () => notify('GENERATION_START', 'Generation started'))\n hooks.hook('kubb:setup:start', () => notify('SETUP_START', 'Setting up Kubb'))\n hooks.hook('kubb:setup:end', () => {\n notify('SETUP_END', 'Kubb setup complete')\n notify('BUILD_START', 'Starting build')\n })\n hooks.hook('kubb:generation:end', () => notify('GENERATION_END', 'Generation ended'))\n\n let userConfig: Config\n let cwd: string\n\n try {\n const configResult = await loadUserConfig(configPath, { notify })\n const loaded = configResult.userConfig\n cwd = configResult.cwd\n\n const resolved = await (typeof loaded === 'function' ? loaded({ logLevel, config: configPath }) : loaded)\n const first = Array.isArray(resolved) ? resolved[0] : resolved\n if (!first) throw new Error('The config file exports an empty array of configurations')\n userConfig = first\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return tool.error(errorMessage)\n }\n\n const config: Config = {\n ...userConfig,\n root: path.resolve(cwd, userConfig.root ?? '.'),\n input: input ?? userConfig.input,\n output: output\n ? {\n ...userConfig.output,\n path: output,\n }\n : userConfig.output,\n }\n\n notify('CONFIG_READY', 'Configuration ready')\n\n const kubb = createKubb(config, { hooks })\n const result = await kubb.generate()\n notify('BUILD_END', `Build complete - Generated ${result.files.length} files`)\n\n const problems = result.diagnostics.filter(Diagnostics.isProblem)\n const errors = problems.filter((diagnostic) => diagnostic.severity === 'error')\n if (errors.length > 0) {\n notify('BUILD_FAILED', `Build failed with ${errors.length} diagnostic(s)`)\n\n const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic))\n return tool.error(`Build failed:\\n${formatDiagnostics(serialized)}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n\n notify('BUILD_SUCCESS', `Build completed successfully - Generated ${result.files.length} files`)\n\n return tool.text(`Build completed successfully!\\n\\nGenerated ${result.files.length} files\\n\\n${messages.join('\\n')}`)\n } catch (caughtError) {\n const serialized = Diagnostics.serialize(Diagnostics.from(caughtError))\n return tool.error(`Build error:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { generateConfigFile, KUBB_CONFIG_FILENAME, resolvePlugins } from '@internals/shared'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\n\nconst initSchema = z.object({\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI spec (default: ./openapi.yaml)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory (default: ./src/gen)'))),\n plugins: z.optional(z.string().check(z.minLength(1), z.describe('Comma-separated list of plugins: plugin-ts,plugin-zod,...'))),\n})\n\nexport const initTool = defineTool(\n {\n name: 'init',\n description: 'Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.',\n schema: initSchema,\n },\n async ({ input = './openapi.yaml', output = './src/gen', plugins }) => {\n const selected = resolvePlugins(plugins)\n const content = generateConfigFile({ selectedPlugins: selected, inputPath: input, outputPath: output })\n const dest = path.join(process.cwd(), KUBB_CONFIG_FILENAME)\n if (fs.existsSync(dest)) {\n return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`)\n }\n fs.writeFileSync(dest, content, 'utf-8')\n const packageList = ['kubb', ...selected.map((p) => p.packageName)].join(' ')\n return tool.text(`Created kubb.config.ts\\n\\nInstall packages:\\n npm install ${packageList}\\n\\nThen run:\\n npx kubb generate`)\n },\n)\n","import { adapterOas } from '@kubb/adapter-oas'\nimport { Diagnostics } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics } from '../utils.ts'\n\nconst validateSchema = z.object({\n input: z.string().check(z.minLength(1), z.describe('Path or URL to the OpenAPI/Swagger specification')),\n})\n\nexport const validateTool = defineTool(\n {\n name: 'validate',\n description: 'Validate an OpenAPI/Swagger specification file or URL',\n schema: validateSchema,\n },\n async ({ input }) => {\n try {\n await adapterOas().validate(input, { throwOnError: true })\n return tool.text(`Validation successful: ${input}`)\n } catch (err) {\n const serialized = Diagnostics.serialize(Diagnostics.from(err))\n return tool.error(`Validation failed:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import { StdioTransport } from '@tmcp/transport-stdio'\nimport { McpServer } from 'tmcp'\nimport { JsonSchemaAdapter } from 'tmcp/adapter'\nimport { en } from 'zod/locales'\nimport * as z from 'zod/mini'\nimport { version } from '../package.json'\nimport { generateTool } from './tools/generate.ts'\nimport { initTool } from './tools/init.ts'\nimport { validateTool } from './tools/validate.ts'\n\n/**\n * Describes each tool's input to MCP clients. `@tmcp/adapter-zod` imports the full `zod` build, so\n * this converts with Zod Mini instead. `io: 'input'` keeps fields with a default optional.\n */\nclass ZodMiniJsonSchemaAdapter extends JsonSchemaAdapter<z.ZodMiniType> {\n async toJsonSchema(schema: z.ZodMiniType) {\n // Zod's type also allows draft-4's boolean `exclusiveMaximum`, which the draft-7 target never emits.\n return z.toJSONSchema(schema, { target: 'draft-7', io: 'input' }) as Awaited<ReturnType<JsonSchemaAdapter<z.ZodMiniType>['toJsonSchema']>>\n }\n}\n\n/**\n * Builds the Kubb MCP server with the generate, validate, and init tools registered.\n *\n * @example\n * `const server = createMcpServer()`\n */\nexport function createMcpServer() {\n // Zod Mini ships without a locale, so validation errors would only say \"Invalid input\".\n z.config(en())\n const server = new McpServer({ name: 'Kubb', version }, { adapter: new ZodMiniJsonSchemaAdapter(), capabilities: { tools: {} } })\n server.tools([generateTool, validateTool, initTool])\n return server\n}\n\n/**\n * Starts the Kubb MCP server over stdio, the transport every local MCP client\n * (Claude, Copilot, editors) uses when it launches the server as a subprocess.\n */\nexport function startServer(): void {\n new StdioTransport(createMcpServer()).listen()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["jiti","createJiti","readFile","process","resolve","dirname","join","existsSync","resolve","relative","isAbsolute","z","defineTool","Hookable","Diagnostics","path","process","tool","createKubb","z","defineTool","path","process","fs","tool","z","defineTool","adapterOas","tool","Diagnostics","JsonSchemaAdapter","z","en","McpServer","StdioTransport"],"sources":["../package.json","../../../internals/shared/src/constants.ts","../../../internals/shared/src/loader.ts","../../../internals/shared/src/config.ts","../../../internals/shared/src/init.ts","../../../internals/utils/src/fs.ts","../src/utils.ts","../src/tools/generate.ts","../src/tools/init.ts","../src/tools/validate.ts","../src/server.ts"],"sourcesContent":["","export const KUBB_CONFIG_FILENAME = 'kubb.config.ts' as const\n\n/** File extensions a Kubb config file may use. */\nexport const CONFIG_EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'] as const\n\nexport const KUBB_PACKAGE_NAME = 'kubb' as const\n\nexport const initDefaults = {\n inputPath: './openapi.yaml',\n outputPath: './src/gen',\n plugins: ['plugin-ts'],\n} as const\n","import { readFile } from 'node:fs/promises'\nimport { createJiti } from 'jiti'\n\n/**\n * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at\n * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.\n */\nconst JITI_OPTIONS = {\n jsx: { runtime: 'automatic', importSource: '@kubb/renderer-jsx' },\n moduleCache: false,\n} satisfies Parameters<typeof createJiti>[1]\n\n/**\n * Plain JavaScript files. jiti hands these to Node's native `import()`, whose module cache never\n * lets go of a file, so `moduleCache: false` alone would keep returning the first version of it.\n */\nconst JAVASCRIPT_EXTENSION = /\\.[cm]?jsx?$/\n\n/**\n * Per-call options for {@link loadModule}.\n */\ntype LoadModuleOptions = {\n /**\n * Return the module's default export instead of the full namespace.\n */\n default?: boolean\n}\n\nconst jiti = createJiti(import.meta.url, JITI_OPTIONS)\n\n/**\n * Loads a Kubb TypeScript or JavaScript config module via jiti.\n *\n * jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly. Every load\n * re-evaluates the file, so a config edited on disk is picked up by the next load.\n *\n * @example\n * ```ts\n * const config = await loadModule('/abs/kubb.config.ts', { default: true })\n * ```\n */\nexport async function loadModule<T = unknown>(filePath: string, options?: LoadModuleOptions): Promise<T> {\n if (!JAVASCRIPT_EXTENSION.test(filePath)) {\n return (options?.default ? jiti.import(filePath, { default: true }) : jiti.import(filePath)) as Promise<T>\n }\n\n // Transpiled through jiti instead, so every load re-evaluates the file as it is on disk now.\n // A TypeScript file it imports reloads too. A plain JavaScript file it imports still goes\n // through Node's `import()` and stays cached until the process restarts.\n const mod = (await jiti.evalModule(await readFile(filePath, 'utf8'), { filename: filePath, async: true, forceTranspile: true })) as\n | { default?: unknown }\n | undefined\n return (options?.default ? (mod?.default ?? mod) : mod) as T\n}\n","import { existsSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\nimport { CONFIG_EXTENSIONS } from './constants.ts'\nimport { loadModule } from './loader.ts'\n\nconst SEARCH_FILES = ['', '.config/', 'configs/'].flatMap((prefix) => [`${prefix}.kubbrc`, `${prefix}kubb.config`])\n\n/**\n * The first Kubb config file in `cwd` or the nearest parent folder that has one: `.kubbrc` before\n * `kubb.config`, the folder itself before its `.config/` and `configs/`, and {@link CONFIG_EXTENSIONS} in order.\n */\nexport function findConfigFile(cwd: string = process.cwd()): string | undefined {\n for (let dir = resolve(cwd); ; dir = dirname(dir)) {\n const found = SEARCH_FILES.flatMap((file) => CONFIG_EXTENSIONS.map((extension) => join(dir, `${file}${extension}`))).find((file) => existsSync(file))\n if (found || dirname(dir) === dir) return found\n }\n}\n\n/**\n * Loads a Kubb config module, calls it with `cli` when it exports a function, and returns its\n * configs as a list, so a single config and a `defineConfig([...])` array read the same.\n */\nexport async function loadConfigFile<T>(file: string, cli: unknown): Promise<Array<T>> {\n const loaded = await loadModule<unknown>(file, { default: true })\n const resolved = (await (typeof loaded === 'function' ? loaded(cli) : loaded)) as T | Array<T>\n return Array.isArray(resolved) ? resolved : [resolved]\n}\n","/** The `kubb.config.ts` `kubb init` writes before `kubb add` puts each plugin in. */\nexport function generateConfigFile({ inputPath, outputPath }: { inputPath: string; outputPath: string }): string {\n return `import { defineConfig } from 'kubb/config'\n\nexport default defineConfig({\n input: '${inputPath}',\n output: {\n path: '${outputPath}',\n clean: true,\n },\n plugins: [],\n})\n`\n}\n","import { access, mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import type { SerializedDiagnostic } from '@kubb/core'\n\n/**\n * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry\n * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so\n * the agent can act on the problem rather than parsing a bare message. No ANSI styling,\n * unlike the CLI renderer.\n */\nexport function formatDiagnostics(diagnostics: ReadonlyArray<SerializedDiagnostic>): string {\n return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join('\\n\\n')\n}\n\nfunction formatDiagnostic(diagnostic: SerializedDiagnostic): string {\n const { code, message, location, help, plugin, docsUrl } = diagnostic\n const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`]\n\n if (location && 'pointer' in location) {\n lines.push(` at: ${location.pointer}`)\n }\n if (help) {\n lines.push(` fix: ${help}`)\n }\n if (docsUrl) {\n lines.push(` see: ${docsUrl}`)\n }\n\n return lines.join('\\n')\n}\n","import path from 'node:path'\nimport process from 'node:process'\nimport { CONFIG_EXTENSIONS, findConfigFile, loadConfigFile } from '@internals/shared'\nimport { isPathInside } from '@internals/utils'\nimport { type Config, createKubb, Diagnostics, type KubbHooks, Hookable } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics } from '../utils.ts'\n\nconst generateSchema = z.object({\n config: z.optional(\n z\n .string()\n .check(\n z.minLength(1),\n z.describe(\n `Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(', ')}). If not provided, will look for kubb.config.* or .kubbrc.* in the current directory, its .config/ and configs/ folders, then each parent directory`,\n ),\n ),\n ),\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI/Swagger spec file (overrides config)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory path (overrides config)'))),\n logLevel: z._default(z.enum(['silent', 'info', 'verbose']).check(z.describe('Log level for build output')), 'info'),\n})\n\nexport const generateTool = defineTool(\n {\n name: 'generate',\n description: 'Generate OpenAPI spec helpers using Kubb configuration',\n schema: generateSchema,\n },\n async function generate(schema: z.input<typeof generateSchema>) {\n const { config: configPath, input, output, logLevel } = schema\n\n try {\n const hooks = new Hookable<KubbHooks>()\n const messages: Array<string> = []\n\n const notify = (type: string, message: string, data?: Record<string, unknown>) => {\n messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`)\n }\n\n hooks.hook('kubb:error', ({ error }) => notify('ERROR', error.message))\n hooks.hook('kubb:warn', ({ message }) => notify('WARN', message))\n hooks.hook('kubb:diagnostic', ({ diagnostic }) => notify('DIAGNOSTIC', diagnostic.message, Diagnostics.serialize(diagnostic)))\n\n // The AI client supplies the path, so it must carry a config extension and stay inside the working directory.\n const base = path.resolve(process.cwd())\n let file: string | undefined\n if (configPath) {\n const ext = path.extname(configPath)\n if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) {\n return tool.error(`Invalid config file extension \"${ext}\". Allowed: ${CONFIG_EXTENSIONS.join(', ')}`)\n }\n file = path.resolve(base, configPath)\n if (!isPathInside(file, base)) {\n return tool.error('Invalid config file path: must be within the current working directory')\n }\n } else {\n file = findConfigFile(base)\n if (!file) {\n return tool.error(\n `No config file found. Please provide a config path or create one of: ${CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`).join(', ')}`,\n )\n }\n }\n\n let userConfig: Config\n try {\n const [first] = await loadConfigFile<Config>(file, { logLevel, config: configPath })\n if (!first) throw new Error('The config file exports an empty array of configurations')\n userConfig = first\n } catch (error) {\n return tool.error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`)\n }\n\n const cwd = path.dirname(file)\n const config: Config = {\n ...userConfig,\n root: path.resolve(cwd, userConfig.root ?? '.'),\n input: input ?? userConfig.input,\n output: output\n ? {\n ...userConfig.output,\n path: output,\n }\n : userConfig.output,\n }\n\n notify('CONFIG_READY', 'Configuration ready')\n\n const kubb = createKubb(config, { hooks })\n const result = await kubb.generate()\n notify('BUILD_END', `Build complete - Generated ${result.files.length} files`)\n\n const problems = result.diagnostics.filter(Diagnostics.isProblem)\n const errors = problems.filter((diagnostic) => diagnostic.severity === 'error')\n if (errors.length > 0) {\n notify('BUILD_FAILED', `Build failed with ${errors.length} diagnostic(s)`)\n\n const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic))\n return tool.error(`Build failed:\\n${formatDiagnostics(serialized)}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n\n notify('BUILD_SUCCESS', `Build completed successfully - Generated ${result.files.length} files`)\n\n return tool.text(`Build completed successfully!\\n\\nGenerated ${result.files.length} files\\n\\n${messages.join('\\n')}`)\n } catch (caughtError) {\n const serialized = Diagnostics.serialize(Diagnostics.from(caughtError))\n return tool.error(`Build error:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { generateConfigFile, KUBB_CONFIG_FILENAME } from '@internals/shared'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\n\nconst initSchema = z.object({\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI spec (default: ./openapi.yaml)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory (default: ./src/gen)'))),\n})\n\nexport const initTool = defineTool(\n {\n name: 'init',\n description:\n 'Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages or add plugins: run `kubb add <id> --yes` for each plugin afterwards.',\n schema: initSchema,\n },\n async ({ input = './openapi.yaml', output = './src/gen' }) => {\n const dest = path.join(process.cwd(), KUBB_CONFIG_FILENAME)\n if (fs.existsSync(dest)) {\n return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`)\n }\n fs.writeFileSync(dest, generateConfigFile({ inputPath: input, outputPath: output }), 'utf-8')\n return tool.text(\n `Created ${KUBB_CONFIG_FILENAME}\\n\\nInstall Kubb:\\n npm install kubb\\n\\nAdd each plugin with its registry id, which installs it and adds it to the config:\\n npx kubb add plugin-ts --yes\\n\\nThen run:\\n npx kubb generate`,\n )\n },\n)\n","import { adapterOas } from '@kubb/adapter-oas'\nimport { Diagnostics } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics } from '../utils.ts'\n\nconst validateSchema = z.object({\n input: z.string().check(z.minLength(1), z.describe('Path or URL to the OpenAPI/Swagger specification')),\n})\n\nexport const validateTool = defineTool(\n {\n name: 'validate',\n description: 'Validate an OpenAPI/Swagger specification file or URL',\n schema: validateSchema,\n },\n async ({ input }) => {\n try {\n await adapterOas().validate(input, { throwOnError: true })\n return tool.text(`Validation successful: ${input}`)\n } catch (err) {\n const serialized = Diagnostics.serialize(Diagnostics.from(err))\n return tool.error(`Validation failed:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import { StdioTransport } from '@tmcp/transport-stdio'\nimport { McpServer } from 'tmcp'\nimport { JsonSchemaAdapter } from 'tmcp/adapter'\nimport { en } from 'zod/locales'\nimport * as z from 'zod/mini'\nimport { version } from '../package.json'\nimport { generateTool } from './tools/generate.ts'\nimport { initTool } from './tools/init.ts'\nimport { validateTool } from './tools/validate.ts'\n\n/**\n * Describes each tool's input to MCP clients. `@tmcp/adapter-zod` imports the full `zod` build, so\n * this converts with Zod Mini instead. `io: 'input'` keeps fields with a default optional.\n */\nclass ZodMiniJsonSchemaAdapter extends JsonSchemaAdapter<z.ZodMiniType> {\n async toJsonSchema(schema: z.ZodMiniType) {\n // Zod's type also allows draft-4's boolean `exclusiveMaximum`, which the draft-7 target never emits.\n return z.toJSONSchema(schema, { target: 'draft-7', io: 'input' }) as Awaited<ReturnType<JsonSchemaAdapter<z.ZodMiniType>['toJsonSchema']>>\n }\n}\n\n/**\n * Builds the Kubb MCP server with the generate, validate, and init tools registered.\n *\n * @example\n * `const server = createMcpServer()`\n */\nexport function createMcpServer() {\n // Zod Mini ships without a locale, so validation errors would only say \"Invalid input\".\n z.config(en())\n const server = new McpServer({ name: 'Kubb', version }, { adapter: new ZodMiniJsonSchemaAdapter(), capabilities: { tools: {} } })\n server.tools([generateTool, validateTool, initTool])\n return server\n}\n\n/**\n * Starts the Kubb MCP server over stdio, the transport every local MCP client\n * (Claude, Copilot, editors) uses when it launches the server as a subprocess.\n */\nexport function startServer(): void {\n new StdioTransport(createMcpServer()).listen()\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -4,17 +4,17 @@ import { McpServer } from "tmcp";
|
|
|
4
4
|
import { JsonSchemaAdapter } from "tmcp/adapter";
|
|
5
5
|
import { en } from "zod/locales";
|
|
6
6
|
import * as z from "zod/mini";
|
|
7
|
-
import path, { isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
import path, { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
|
+
import process from "node:process";
|
|
9
|
+
import fs, { existsSync } from "node:fs";
|
|
8
10
|
import { readFile } from "node:fs/promises";
|
|
9
11
|
import { createJiti } from "jiti";
|
|
10
12
|
import { Diagnostics, Hookable, createKubb } from "@kubb/core";
|
|
11
13
|
import { defineTool } from "tmcp/tool";
|
|
12
14
|
import { tool } from "tmcp/utils";
|
|
13
|
-
import fs, { existsSync } from "node:fs";
|
|
14
|
-
import process$1 from "node:process";
|
|
15
15
|
import { adapterOas } from "@kubb/adapter-oas";
|
|
16
16
|
//#region package.json
|
|
17
|
-
var version = "5.9.
|
|
17
|
+
var version = "5.9.4";
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region ../../internals/shared/src/constants.ts
|
|
20
20
|
const KUBB_CONFIG_FILENAME = "kubb.config.ts";
|
|
@@ -27,113 +27,6 @@ const CONFIG_EXTENSIONS = [
|
|
|
27
27
|
".mjs",
|
|
28
28
|
".cjs"
|
|
29
29
|
];
|
|
30
|
-
const availablePlugins = [
|
|
31
|
-
{
|
|
32
|
-
value: "plugin-ts",
|
|
33
|
-
label: "TypeScript",
|
|
34
|
-
hint: "Recommended",
|
|
35
|
-
packageName: "@kubb/plugin-ts",
|
|
36
|
-
importName: "pluginTs",
|
|
37
|
-
category: "types"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
value: "plugin-axios",
|
|
41
|
-
label: "Axios Client",
|
|
42
|
-
packageName: "@kubb/plugin-axios",
|
|
43
|
-
importName: "pluginAxios",
|
|
44
|
-
category: "client"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
value: "plugin-fetch",
|
|
48
|
-
label: "Fetch Client",
|
|
49
|
-
packageName: "@kubb/plugin-fetch",
|
|
50
|
-
importName: "pluginFetch",
|
|
51
|
-
category: "client"
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
value: "plugin-react-query",
|
|
55
|
-
label: "React Query / TanStack Query",
|
|
56
|
-
packageName: "@kubb/plugin-react-query",
|
|
57
|
-
importName: "pluginReactQuery",
|
|
58
|
-
category: "framework"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
value: "plugin-vue-query",
|
|
62
|
-
label: "Vue Query",
|
|
63
|
-
packageName: "@kubb/plugin-vue-query",
|
|
64
|
-
importName: "pluginVueQuery",
|
|
65
|
-
category: "framework"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
value: "plugin-zod",
|
|
69
|
-
label: "Zod Schemas",
|
|
70
|
-
packageName: "@kubb/plugin-zod",
|
|
71
|
-
importName: "pluginZod",
|
|
72
|
-
category: "validation"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
value: "plugin-faker",
|
|
76
|
-
label: "Faker.js Mocks",
|
|
77
|
-
packageName: "@kubb/plugin-faker",
|
|
78
|
-
importName: "pluginFaker",
|
|
79
|
-
category: "mocks"
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
value: "plugin-msw",
|
|
83
|
-
label: "MSW Handlers",
|
|
84
|
-
packageName: "@kubb/plugin-msw",
|
|
85
|
-
importName: "pluginMsw",
|
|
86
|
-
category: "mocks"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
value: "plugin-cypress",
|
|
90
|
-
label: "Cypress Tests",
|
|
91
|
-
packageName: "@kubb/plugin-cypress",
|
|
92
|
-
importName: "pluginCypress",
|
|
93
|
-
category: "testing"
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
value: "plugin-mcp",
|
|
97
|
-
label: "MCP Server (AI / Model Context Protocol)",
|
|
98
|
-
packageName: "@kubb/plugin-mcp",
|
|
99
|
-
importName: "pluginMcp",
|
|
100
|
-
category: "ai"
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
value: "plugin-redoc",
|
|
104
|
-
label: "ReDoc Documentation",
|
|
105
|
-
packageName: "@kubb/plugin-redoc",
|
|
106
|
-
importName: "pluginRedoc",
|
|
107
|
-
category: "documentation"
|
|
108
|
-
}
|
|
109
|
-
];
|
|
110
|
-
//#endregion
|
|
111
|
-
//#region ../../internals/shared/src/init.ts
|
|
112
|
-
/**
|
|
113
|
-
* Resolves a comma-separated plugin flag (e.g. `--plugins plugin-ts,plugin-zod`) into the
|
|
114
|
-
* matching known plugin options. Unrecognized names are dropped, and a missing flag yields
|
|
115
|
-
* an empty list.
|
|
116
|
-
*/
|
|
117
|
-
function resolvePlugins(pluginsFlag) {
|
|
118
|
-
if (!pluginsFlag) return [];
|
|
119
|
-
const requested = pluginsFlag.split(",").map((value) => value.trim()).filter(Boolean);
|
|
120
|
-
return availablePlugins.filter((plugin) => requested.includes(plugin.value));
|
|
121
|
-
}
|
|
122
|
-
function generateConfigFile({ selectedPlugins, inputPath, outputPath }) {
|
|
123
|
-
return `${[`import { defineConfig } from 'kubb/config'`, ...selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`)].join("\n")}
|
|
124
|
-
|
|
125
|
-
export default defineConfig({
|
|
126
|
-
input: '${inputPath}',
|
|
127
|
-
output: {
|
|
128
|
-
path: '${outputPath}',
|
|
129
|
-
clean: true,
|
|
130
|
-
},
|
|
131
|
-
plugins: [
|
|
132
|
-
${selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join("\n")}
|
|
133
|
-
],
|
|
134
|
-
})
|
|
135
|
-
`;
|
|
136
|
-
}
|
|
137
30
|
//#endregion
|
|
138
31
|
//#region ../../internals/shared/src/loader.ts
|
|
139
32
|
/**
|
|
@@ -174,6 +67,48 @@ async function loadModule(filePath, options) {
|
|
|
174
67
|
return options?.default ? mod?.default ?? mod : mod;
|
|
175
68
|
}
|
|
176
69
|
//#endregion
|
|
70
|
+
//#region ../../internals/shared/src/config.ts
|
|
71
|
+
const SEARCH_FILES = [
|
|
72
|
+
"",
|
|
73
|
+
".config/",
|
|
74
|
+
"configs/"
|
|
75
|
+
].flatMap((prefix) => [`${prefix}.kubbrc`, `${prefix}kubb.config`]);
|
|
76
|
+
/**
|
|
77
|
+
* The first Kubb config file in `cwd` or the nearest parent folder that has one: `.kubbrc` before
|
|
78
|
+
* `kubb.config`, the folder itself before its `.config/` and `configs/`, and {@link CONFIG_EXTENSIONS} in order.
|
|
79
|
+
*/
|
|
80
|
+
function findConfigFile(cwd = process.cwd()) {
|
|
81
|
+
for (let dir = resolve(cwd);; dir = dirname(dir)) {
|
|
82
|
+
const found = SEARCH_FILES.flatMap((file) => CONFIG_EXTENSIONS.map((extension) => join(dir, `${file}${extension}`))).find((file) => existsSync(file));
|
|
83
|
+
if (found || dirname(dir) === dir) return found;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Loads a Kubb config module, calls it with `cli` when it exports a function, and returns its
|
|
88
|
+
* configs as a list, so a single config and a `defineConfig([...])` array read the same.
|
|
89
|
+
*/
|
|
90
|
+
async function loadConfigFile(file, cli) {
|
|
91
|
+
const loaded = await loadModule(file, { default: true });
|
|
92
|
+
const resolved = await (typeof loaded === "function" ? loaded(cli) : loaded);
|
|
93
|
+
return Array.isArray(resolved) ? resolved : [resolved];
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region ../../internals/shared/src/init.ts
|
|
97
|
+
/** The `kubb.config.ts` `kubb init` writes before `kubb add` puts each plugin in. */
|
|
98
|
+
function generateConfigFile({ inputPath, outputPath }) {
|
|
99
|
+
return `import { defineConfig } from 'kubb/config'
|
|
100
|
+
|
|
101
|
+
export default defineConfig({
|
|
102
|
+
input: '${inputPath}',
|
|
103
|
+
output: {
|
|
104
|
+
path: '${outputPath}',
|
|
105
|
+
clean: true,
|
|
106
|
+
},
|
|
107
|
+
plugins: [],
|
|
108
|
+
})
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
177
112
|
//#region ../../internals/utils/src/fs.ts
|
|
178
113
|
/**
|
|
179
114
|
* Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved
|
|
@@ -198,7 +133,6 @@ function isPathInside(path, parent) {
|
|
|
198
133
|
}
|
|
199
134
|
//#endregion
|
|
200
135
|
//#region src/utils.ts
|
|
201
|
-
const CONFIG_FILE_NAMES = CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`);
|
|
202
136
|
/**
|
|
203
137
|
* Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
|
|
204
138
|
* keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
|
|
@@ -216,65 +150,10 @@ function formatDiagnostic(diagnostic) {
|
|
|
216
150
|
if (docsUrl) lines.push(` see: ${docsUrl}`);
|
|
217
151
|
return lines.join("\n");
|
|
218
152
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Loads the user's Kubb config and returns it with the directory it was found in.
|
|
221
|
-
*
|
|
222
|
-
* When `configPath` is given it must use an allowed extension and resolve inside
|
|
223
|
-
* the current working directory, otherwise loading throws. When omitted, the
|
|
224
|
-
* known `kubb.config.*` file names are tried in the current directory. Every
|
|
225
|
-
* outcome is reported through `notify` before the function returns or throws.
|
|
226
|
-
*/
|
|
227
|
-
async function loadUserConfig(configPath, { notify }) {
|
|
228
|
-
if (configPath) {
|
|
229
|
-
const ext = path.extname(configPath);
|
|
230
|
-
if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) {
|
|
231
|
-
const msg = `Invalid config file extension "${ext}". Allowed: ${CONFIG_EXTENSIONS.join(", ")}`;
|
|
232
|
-
notify("CONFIG_ERROR", msg);
|
|
233
|
-
throw new Error(msg);
|
|
234
|
-
}
|
|
235
|
-
const base = path.resolve(process.cwd());
|
|
236
|
-
const resolvedConfigPath = path.resolve(base, configPath);
|
|
237
|
-
if (!isPathInside(resolvedConfigPath, base)) {
|
|
238
|
-
const msg = "Invalid config file path: must be within the current working directory";
|
|
239
|
-
notify("CONFIG_ERROR", msg);
|
|
240
|
-
throw new Error(msg);
|
|
241
|
-
}
|
|
242
|
-
const cwd = path.dirname(resolvedConfigPath);
|
|
243
|
-
try {
|
|
244
|
-
const userConfig = await loadModule(resolvedConfigPath, { default: true });
|
|
245
|
-
notify("CONFIG_LOADED", `Loaded config from ${resolvedConfigPath}`);
|
|
246
|
-
return {
|
|
247
|
-
userConfig,
|
|
248
|
-
cwd
|
|
249
|
-
};
|
|
250
|
-
} catch (error) {
|
|
251
|
-
const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`;
|
|
252
|
-
notify("CONFIG_ERROR", msg);
|
|
253
|
-
throw new Error(msg);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
const cwd = process.cwd();
|
|
257
|
-
for (const configFileName of CONFIG_FILE_NAMES) {
|
|
258
|
-
const configFilePath = path.resolve(process.cwd(), configFileName);
|
|
259
|
-
if (!existsSync(configFilePath)) continue;
|
|
260
|
-
try {
|
|
261
|
-
const userConfig = await loadModule(configFilePath, { default: true });
|
|
262
|
-
notify("CONFIG_LOADED", `Loaded ${configFileName} from current directory`);
|
|
263
|
-
return {
|
|
264
|
-
userConfig,
|
|
265
|
-
cwd
|
|
266
|
-
};
|
|
267
|
-
} catch (err) {
|
|
268
|
-
notify("CONFIG_ERROR", `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
notify("CONFIG_ERROR", "No config file found");
|
|
272
|
-
throw new Error(`No config file found. Please provide a config path or create one of: ${CONFIG_FILE_NAMES.join(", ")}`);
|
|
273
|
-
}
|
|
274
153
|
//#endregion
|
|
275
154
|
//#region src/tools/generate.ts
|
|
276
155
|
const generateSchema = z.object({
|
|
277
|
-
config: z.optional(z.string().check(z.minLength(1), z.describe(`Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(", ")}). If not provided, will look for kubb.config.* in current directory`))),
|
|
156
|
+
config: z.optional(z.string().check(z.minLength(1), z.describe(`Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(", ")}). If not provided, will look for kubb.config.* or .kubbrc.* in the current directory, its .config/ and configs/ folders, then each parent directory`))),
|
|
278
157
|
input: z.optional(z.string().check(z.minLength(1), z.describe("Path to OpenAPI/Swagger spec file (overrides config)"))),
|
|
279
158
|
output: z.optional(z.string().check(z.minLength(1), z.describe("Output directory path (overrides config)"))),
|
|
280
159
|
logLevel: z._default(z.enum([
|
|
@@ -295,40 +174,32 @@ const generateTool = defineTool({
|
|
|
295
174
|
const notify = (type, message, data) => {
|
|
296
175
|
messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
|
|
297
176
|
};
|
|
298
|
-
hooks.hook("kubb:info", ({ message }) => notify("INFO", message));
|
|
299
|
-
hooks.hook("kubb:success", ({ message }) => notify("SUCCESS", message));
|
|
300
177
|
hooks.hook("kubb:error", ({ error }) => notify("ERROR", error.message));
|
|
301
178
|
hooks.hook("kubb:warn", ({ message }) => notify("WARN", message));
|
|
302
179
|
hooks.hook("kubb:diagnostic", ({ diagnostic }) => notify("DIAGNOSTIC", diagnostic.message, Diagnostics.serialize(diagnostic)));
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
hooks.hook("kubb:generation:end", () => notify("GENERATION_END", "Generation ended"));
|
|
180
|
+
const base = path.resolve(process.cwd());
|
|
181
|
+
let file;
|
|
182
|
+
if (configPath) {
|
|
183
|
+
const ext = path.extname(configPath);
|
|
184
|
+
if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) return tool.error(`Invalid config file extension "${ext}". Allowed: ${CONFIG_EXTENSIONS.join(", ")}`);
|
|
185
|
+
file = path.resolve(base, configPath);
|
|
186
|
+
if (!isPathInside(file, base)) return tool.error("Invalid config file path: must be within the current working directory");
|
|
187
|
+
} else {
|
|
188
|
+
file = findConfigFile(base);
|
|
189
|
+
if (!file) return tool.error(`No config file found. Please provide a config path or create one of: ${CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`).join(", ")}`);
|
|
190
|
+
}
|
|
315
191
|
let userConfig;
|
|
316
|
-
let cwd;
|
|
317
192
|
try {
|
|
318
|
-
const
|
|
319
|
-
const loaded = configResult.userConfig;
|
|
320
|
-
cwd = configResult.cwd;
|
|
321
|
-
const resolved = await (typeof loaded === "function" ? loaded({
|
|
193
|
+
const [first] = await loadConfigFile(file, {
|
|
322
194
|
logLevel,
|
|
323
195
|
config: configPath
|
|
324
|
-
})
|
|
325
|
-
const first = Array.isArray(resolved) ? resolved[0] : resolved;
|
|
196
|
+
});
|
|
326
197
|
if (!first) throw new Error("The config file exports an empty array of configurations");
|
|
327
198
|
userConfig = first;
|
|
328
199
|
} catch (error) {
|
|
329
|
-
|
|
330
|
-
return tool.error(errorMessage);
|
|
200
|
+
return tool.error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
331
201
|
}
|
|
202
|
+
const cwd = path.dirname(file);
|
|
332
203
|
const config = {
|
|
333
204
|
...userConfig,
|
|
334
205
|
root: path.resolve(cwd, userConfig.root ?? "."),
|
|
@@ -359,25 +230,20 @@ const generateTool = defineTool({
|
|
|
359
230
|
//#region src/tools/init.ts
|
|
360
231
|
const initSchema = z.object({
|
|
361
232
|
input: z.optional(z.string().check(z.minLength(1), z.describe("Path to OpenAPI spec (default: ./openapi.yaml)"))),
|
|
362
|
-
output: z.optional(z.string().check(z.minLength(1), z.describe("Output directory (default: ./src/gen)")))
|
|
363
|
-
plugins: z.optional(z.string().check(z.minLength(1), z.describe("Comma-separated list of plugins: plugin-ts,plugin-zod,...")))
|
|
233
|
+
output: z.optional(z.string().check(z.minLength(1), z.describe("Output directory (default: ./src/gen)")))
|
|
364
234
|
});
|
|
365
235
|
const initTool = defineTool({
|
|
366
236
|
name: "init",
|
|
367
|
-
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.",
|
|
237
|
+
description: "Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages or add plugins: run `kubb add <id> --yes` for each plugin afterwards.",
|
|
368
238
|
schema: initSchema
|
|
369
|
-
}, async ({ input = "./openapi.yaml", output = "./src/gen"
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
239
|
+
}, async ({ input = "./openapi.yaml", output = "./src/gen" }) => {
|
|
240
|
+
const dest = path.join(process.cwd(), KUBB_CONFIG_FILENAME);
|
|
241
|
+
if (fs.existsSync(dest)) return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
|
|
242
|
+
fs.writeFileSync(dest, generateConfigFile({
|
|
373
243
|
inputPath: input,
|
|
374
244
|
outputPath: output
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
if (fs.existsSync(dest)) return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`);
|
|
378
|
-
fs.writeFileSync(dest, content, "utf-8");
|
|
379
|
-
const packageList = ["kubb", ...selected.map((p) => p.packageName)].join(" ");
|
|
380
|
-
return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`);
|
|
245
|
+
}), "utf-8");
|
|
246
|
+
return tool.text(`Created ${KUBB_CONFIG_FILENAME}\n\nInstall Kubb:\n npm install kubb\n\nAdd each plugin with its registry id, which installs it and adds it to the config:\n npx kubb add plugin-ts --yes\n\nThen run:\n npx kubb generate`);
|
|
381
247
|
});
|
|
382
248
|
//#endregion
|
|
383
249
|
//#region src/tools/validate.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["process"],"sources":["../package.json","../../../internals/shared/src/constants.ts","../../../internals/shared/src/init.ts","../../../internals/shared/src/loader.ts","../../../internals/utils/src/fs.ts","../src/utils.ts","../src/tools/generate.ts","../src/tools/init.ts","../src/tools/validate.ts","../src/server.ts"],"sourcesContent":["","export type PluginOption = {\n value: string\n label: string\n hint?: string\n packageName: string\n importName: string\n category: 'types' | 'client' | 'framework' | 'validation' | 'testing' | 'mocks' | 'documentation' | 'ai'\n}\n\nexport const KUBB_CONFIG_FILENAME = 'kubb.config.ts' as const\n\n/** File extensions a Kubb config file may use. */\nexport const CONFIG_EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'] as const\n\nexport const KUBB_PACKAGE_NAME = 'kubb' as const\n\nexport const initDefaults = {\n inputPath: './openapi.yaml',\n outputPath: './src/gen',\n plugins: ['plugin-ts'],\n} as const\n\nexport const availablePlugins: Array<PluginOption> = [\n {\n value: 'plugin-ts',\n label: 'TypeScript',\n hint: 'Recommended',\n packageName: '@kubb/plugin-ts',\n importName: 'pluginTs',\n category: 'types',\n },\n {\n value: 'plugin-axios',\n label: 'Axios Client',\n packageName: '@kubb/plugin-axios',\n importName: 'pluginAxios',\n category: 'client',\n },\n {\n value: 'plugin-fetch',\n label: 'Fetch Client',\n packageName: '@kubb/plugin-fetch',\n importName: 'pluginFetch',\n category: 'client',\n },\n {\n value: 'plugin-react-query',\n label: 'React Query / TanStack Query',\n packageName: '@kubb/plugin-react-query',\n importName: 'pluginReactQuery',\n category: 'framework',\n },\n {\n value: 'plugin-vue-query',\n label: 'Vue Query',\n packageName: '@kubb/plugin-vue-query',\n importName: 'pluginVueQuery',\n category: 'framework',\n },\n {\n value: 'plugin-zod',\n label: 'Zod Schemas',\n packageName: '@kubb/plugin-zod',\n importName: 'pluginZod',\n category: 'validation',\n },\n {\n value: 'plugin-faker',\n label: 'Faker.js Mocks',\n packageName: '@kubb/plugin-faker',\n importName: 'pluginFaker',\n category: 'mocks',\n },\n {\n value: 'plugin-msw',\n label: 'MSW Handlers',\n packageName: '@kubb/plugin-msw',\n importName: 'pluginMsw',\n category: 'mocks',\n },\n {\n value: 'plugin-cypress',\n label: 'Cypress Tests',\n packageName: '@kubb/plugin-cypress',\n importName: 'pluginCypress',\n category: 'testing',\n },\n {\n value: 'plugin-mcp',\n label: 'MCP Server (AI / Model Context Protocol)',\n packageName: '@kubb/plugin-mcp',\n importName: 'pluginMcp',\n category: 'ai',\n },\n {\n value: 'plugin-redoc',\n label: 'ReDoc Documentation',\n packageName: '@kubb/plugin-redoc',\n importName: 'pluginRedoc',\n category: 'documentation',\n },\n]\n","import { availablePlugins, type PluginOption } from './constants.ts'\n\n/**\n * Resolves a comma-separated plugin flag (e.g. `--plugins plugin-ts,plugin-zod`) into the\n * matching known plugin options. Unrecognized names are dropped, and a missing flag yields\n * an empty list.\n */\nexport function resolvePlugins(pluginsFlag: string | undefined): Array<PluginOption> {\n if (!pluginsFlag) {\n return []\n }\n const requested = pluginsFlag\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean)\n return availablePlugins.filter((plugin) => requested.includes(plugin.value))\n}\n\nexport function generateConfigFile({\n selectedPlugins,\n inputPath,\n outputPath,\n}: {\n selectedPlugins: Array<PluginOption>\n inputPath: string\n outputPath: string\n}): string {\n const imports = [\n `import { defineConfig } from 'kubb/config'`,\n ...selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`),\n ].join('\\n')\n\n const pluginConfigs = selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join('\\n')\n\n return `${imports}\n\nexport default defineConfig({\n input: '${inputPath}',\n output: {\n path: '${outputPath}',\n clean: true,\n },\n plugins: [\n${pluginConfigs}\n ],\n})\n`\n}\n","import { readFile } from 'node:fs/promises'\nimport { createJiti } from 'jiti'\n\n/**\n * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at\n * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.\n */\nconst JITI_OPTIONS = {\n jsx: { runtime: 'automatic', importSource: '@kubb/renderer-jsx' },\n moduleCache: false,\n} satisfies Parameters<typeof createJiti>[1]\n\n/**\n * Plain JavaScript files. jiti hands these to Node's native `import()`, whose module cache never\n * lets go of a file, so `moduleCache: false` alone would keep returning the first version of it.\n */\nconst JAVASCRIPT_EXTENSION = /\\.[cm]?jsx?$/\n\n/**\n * Per-call options for {@link loadModule}.\n */\ntype LoadModuleOptions = {\n /**\n * Return the module's default export instead of the full namespace.\n */\n default?: boolean\n}\n\nconst jiti = createJiti(import.meta.url, JITI_OPTIONS)\n\n/**\n * Loads a Kubb TypeScript or JavaScript config module via jiti.\n *\n * jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly. Every load\n * re-evaluates the file, so a config edited on disk is picked up by the next load.\n *\n * @example\n * ```ts\n * const config = await loadModule('/abs/kubb.config.ts', { default: true })\n * ```\n */\nexport async function loadModule<T = unknown>(filePath: string, options?: LoadModuleOptions): Promise<T> {\n if (!JAVASCRIPT_EXTENSION.test(filePath)) {\n return (options?.default ? jiti.import(filePath, { default: true }) : jiti.import(filePath)) as Promise<T>\n }\n\n // Transpiled through jiti instead, so every load re-evaluates the file as it is on disk now.\n // A TypeScript file it imports reloads too. A plain JavaScript file it imports still goes\n // through Node's `import()` and stays cached until the process restarts.\n const mod = (await jiti.evalModule(await readFile(filePath, 'utf8'), { filename: filePath, async: true, forceTranspile: true })) as\n | { default?: unknown }\n | undefined\n return (options?.default ? (mod?.default ?? mod) : mod) as T\n}\n","import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import { existsSync } from 'node:fs'\nimport path from 'node:path'\nimport { CONFIG_EXTENSIONS, loadModule } from '@internals/shared'\nimport { isPathInside } from '@internals/utils'\nimport type { CLIOptions, PossibleConfig, SerializedDiagnostic } from '@kubb/core'\n\nconst CONFIG_FILE_NAMES = CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`)\n\n/**\n * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry\n * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so\n * the agent can act on the problem rather than parsing a bare message. No ANSI styling,\n * unlike the CLI renderer.\n */\nexport function formatDiagnostics(diagnostics: ReadonlyArray<SerializedDiagnostic>): string {\n return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join('\\n\\n')\n}\n\nfunction formatDiagnostic(diagnostic: SerializedDiagnostic): string {\n const { code, message, location, help, plugin, docsUrl } = diagnostic\n const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`]\n\n if (location && 'pointer' in location) {\n lines.push(` at: ${location.pointer}`)\n }\n if (help) {\n lines.push(` fix: ${help}`)\n }\n if (docsUrl) {\n lines.push(` see: ${docsUrl}`)\n }\n\n return lines.join('\\n')\n}\n\ntype NotifyFunction = (type: string, message: string, data?: Record<string, unknown>) => void\n\n/**\n * Loads the user's Kubb config and returns it with the directory it was found in.\n *\n * When `configPath` is given it must use an allowed extension and resolve inside\n * the current working directory, otherwise loading throws. When omitted, the\n * known `kubb.config.*` file names are tried in the current directory. Every\n * outcome is reported through `notify` before the function returns or throws.\n */\nexport async function loadUserConfig(\n configPath: string | undefined,\n { notify }: { notify: NotifyFunction },\n): Promise<{ userConfig: PossibleConfig<CLIOptions>; cwd: string }> {\n if (configPath) {\n const ext = path.extname(configPath)\n if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) {\n const msg = `Invalid config file extension \"${ext}\". Allowed: ${CONFIG_EXTENSIONS.join(', ')}`\n notify('CONFIG_ERROR', msg)\n throw new Error(msg)\n }\n const base = path.resolve(process.cwd())\n const resolvedConfigPath = path.resolve(base, configPath)\n if (!isPathInside(resolvedConfigPath, base)) {\n const msg = 'Invalid config file path: must be within the current working directory'\n notify('CONFIG_ERROR', msg)\n throw new Error(msg)\n }\n const cwd = path.dirname(resolvedConfigPath)\n try {\n const userConfig = await loadModule<PossibleConfig<CLIOptions>>(resolvedConfigPath, { default: true })\n notify('CONFIG_LOADED', `Loaded config from ${resolvedConfigPath}`)\n return { userConfig, cwd }\n } catch (error) {\n const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`\n notify('CONFIG_ERROR', msg)\n throw new Error(msg)\n }\n }\n\n const cwd = process.cwd()\n\n for (const configFileName of CONFIG_FILE_NAMES) {\n const configFilePath = path.resolve(process.cwd(), configFileName)\n if (!existsSync(configFilePath)) continue\n try {\n const userConfig = await loadModule<PossibleConfig<CLIOptions>>(configFilePath, { default: true })\n notify('CONFIG_LOADED', `Loaded ${configFileName} from current directory`)\n return { userConfig, cwd }\n } catch (err) {\n notify('CONFIG_ERROR', `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n notify('CONFIG_ERROR', 'No config file found')\n throw new Error(`No config file found. Please provide a config path or create one of: ${CONFIG_FILE_NAMES.join(', ')}`)\n}\n","import path from 'node:path'\nimport { CONFIG_EXTENSIONS } from '@internals/shared'\nimport { type Config, createKubb, Diagnostics, type KubbHooks, Hookable } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics, loadUserConfig } from '../utils.ts'\n\nconst generateSchema = z.object({\n config: z.optional(\n z\n .string()\n .check(\n z.minLength(1),\n z.describe(`Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(', ')}). If not provided, will look for kubb.config.* in current directory`),\n ),\n ),\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI/Swagger spec file (overrides config)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory path (overrides config)'))),\n logLevel: z._default(z.enum(['silent', 'info', 'verbose']).check(z.describe('Log level for build output')), 'info'),\n})\n\nexport const generateTool = defineTool(\n {\n name: 'generate',\n description: 'Generate OpenAPI spec helpers using Kubb configuration',\n schema: generateSchema,\n },\n async function generate(schema: z.input<typeof generateSchema>) {\n const { config: configPath, input, output, logLevel } = schema\n\n try {\n const hooks = new Hookable<KubbHooks>()\n const messages: Array<string> = []\n\n const notify = (type: string, message: string, data?: Record<string, unknown>) => {\n messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`)\n }\n\n hooks.hook('kubb:info', ({ message }) => notify('INFO', message))\n hooks.hook('kubb:success', ({ message }) => notify('SUCCESS', message))\n hooks.hook('kubb:error', ({ error }) => notify('ERROR', error.message))\n hooks.hook('kubb:warn', ({ message }) => notify('WARN', message))\n hooks.hook('kubb:diagnostic', ({ diagnostic }) => notify('DIAGNOSTIC', diagnostic.message, Diagnostics.serialize(diagnostic)))\n hooks.hook('kubb:plugin:start', ({ plugin }) => notify('PLUGIN_START', `Plugin starting: ${plugin.name}`))\n hooks.hook('kubb:plugin:end', ({ plugin, duration }) => notify('PLUGIN_END', `Plugin finished: ${plugin.name}`, { duration }))\n hooks.hook('kubb:files:processing:start', () => notify('FILES_START', 'Starting file processing'))\n hooks.hook('kubb:files:processing:update', ({ files }) => notify('FILES_UPDATE', `Processing ${files.length} files`))\n hooks.hook('kubb:files:processing:end', () => notify('FILES_END', 'File processing complete'))\n hooks.hook('kubb:generation:start', () => notify('GENERATION_START', 'Generation started'))\n hooks.hook('kubb:setup:start', () => notify('SETUP_START', 'Setting up Kubb'))\n hooks.hook('kubb:setup:end', () => {\n notify('SETUP_END', 'Kubb setup complete')\n notify('BUILD_START', 'Starting build')\n })\n hooks.hook('kubb:generation:end', () => notify('GENERATION_END', 'Generation ended'))\n\n let userConfig: Config\n let cwd: string\n\n try {\n const configResult = await loadUserConfig(configPath, { notify })\n const loaded = configResult.userConfig\n cwd = configResult.cwd\n\n const resolved = await (typeof loaded === 'function' ? loaded({ logLevel, config: configPath }) : loaded)\n const first = Array.isArray(resolved) ? resolved[0] : resolved\n if (!first) throw new Error('The config file exports an empty array of configurations')\n userConfig = first\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return tool.error(errorMessage)\n }\n\n const config: Config = {\n ...userConfig,\n root: path.resolve(cwd, userConfig.root ?? '.'),\n input: input ?? userConfig.input,\n output: output\n ? {\n ...userConfig.output,\n path: output,\n }\n : userConfig.output,\n }\n\n notify('CONFIG_READY', 'Configuration ready')\n\n const kubb = createKubb(config, { hooks })\n const result = await kubb.generate()\n notify('BUILD_END', `Build complete - Generated ${result.files.length} files`)\n\n const problems = result.diagnostics.filter(Diagnostics.isProblem)\n const errors = problems.filter((diagnostic) => diagnostic.severity === 'error')\n if (errors.length > 0) {\n notify('BUILD_FAILED', `Build failed with ${errors.length} diagnostic(s)`)\n\n const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic))\n return tool.error(`Build failed:\\n${formatDiagnostics(serialized)}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n\n notify('BUILD_SUCCESS', `Build completed successfully - Generated ${result.files.length} files`)\n\n return tool.text(`Build completed successfully!\\n\\nGenerated ${result.files.length} files\\n\\n${messages.join('\\n')}`)\n } catch (caughtError) {\n const serialized = Diagnostics.serialize(Diagnostics.from(caughtError))\n return tool.error(`Build error:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { generateConfigFile, KUBB_CONFIG_FILENAME, resolvePlugins } from '@internals/shared'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\n\nconst initSchema = z.object({\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI spec (default: ./openapi.yaml)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory (default: ./src/gen)'))),\n plugins: z.optional(z.string().check(z.minLength(1), z.describe('Comma-separated list of plugins: plugin-ts,plugin-zod,...'))),\n})\n\nexport const initTool = defineTool(\n {\n name: 'init',\n description: 'Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.',\n schema: initSchema,\n },\n async ({ input = './openapi.yaml', output = './src/gen', plugins }) => {\n const selected = resolvePlugins(plugins)\n const content = generateConfigFile({ selectedPlugins: selected, inputPath: input, outputPath: output })\n const dest = path.join(process.cwd(), KUBB_CONFIG_FILENAME)\n if (fs.existsSync(dest)) {\n return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`)\n }\n fs.writeFileSync(dest, content, 'utf-8')\n const packageList = ['kubb', ...selected.map((p) => p.packageName)].join(' ')\n return tool.text(`Created kubb.config.ts\\n\\nInstall packages:\\n npm install ${packageList}\\n\\nThen run:\\n npx kubb generate`)\n },\n)\n","import { adapterOas } from '@kubb/adapter-oas'\nimport { Diagnostics } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics } from '../utils.ts'\n\nconst validateSchema = z.object({\n input: z.string().check(z.minLength(1), z.describe('Path or URL to the OpenAPI/Swagger specification')),\n})\n\nexport const validateTool = defineTool(\n {\n name: 'validate',\n description: 'Validate an OpenAPI/Swagger specification file or URL',\n schema: validateSchema,\n },\n async ({ input }) => {\n try {\n await adapterOas().validate(input, { throwOnError: true })\n return tool.text(`Validation successful: ${input}`)\n } catch (err) {\n const serialized = Diagnostics.serialize(Diagnostics.from(err))\n return tool.error(`Validation failed:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import { StdioTransport } from '@tmcp/transport-stdio'\nimport { McpServer } from 'tmcp'\nimport { JsonSchemaAdapter } from 'tmcp/adapter'\nimport { en } from 'zod/locales'\nimport * as z from 'zod/mini'\nimport { version } from '../package.json'\nimport { generateTool } from './tools/generate.ts'\nimport { initTool } from './tools/init.ts'\nimport { validateTool } from './tools/validate.ts'\n\n/**\n * Describes each tool's input to MCP clients. `@tmcp/adapter-zod` imports the full `zod` build, so\n * this converts with Zod Mini instead. `io: 'input'` keeps fields with a default optional.\n */\nclass ZodMiniJsonSchemaAdapter extends JsonSchemaAdapter<z.ZodMiniType> {\n async toJsonSchema(schema: z.ZodMiniType) {\n // Zod's type also allows draft-4's boolean `exclusiveMaximum`, which the draft-7 target never emits.\n return z.toJSONSchema(schema, { target: 'draft-7', io: 'input' }) as Awaited<ReturnType<JsonSchemaAdapter<z.ZodMiniType>['toJsonSchema']>>\n }\n}\n\n/**\n * Builds the Kubb MCP server with the generate, validate, and init tools registered.\n *\n * @example\n * `const server = createMcpServer()`\n */\nexport function createMcpServer() {\n // Zod Mini ships without a locale, so validation errors would only say \"Invalid input\".\n z.config(en())\n const server = new McpServer({ name: 'Kubb', version }, { adapter: new ZodMiniJsonSchemaAdapter(), capabilities: { tools: {} } })\n server.tools([generateTool, validateTool, initTool])\n return server\n}\n\n/**\n * Starts the Kubb MCP server over stdio, the transport every local MCP client\n * (Claude, Copilot, editors) uses when it launches the server as a subprocess.\n */\nexport function startServer(): void {\n new StdioTransport(createMcpServer()).listen()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../package.json","../../../internals/shared/src/constants.ts","../../../internals/shared/src/loader.ts","../../../internals/shared/src/config.ts","../../../internals/shared/src/init.ts","../../../internals/utils/src/fs.ts","../src/utils.ts","../src/tools/generate.ts","../src/tools/init.ts","../src/tools/validate.ts","../src/server.ts"],"sourcesContent":["","export const KUBB_CONFIG_FILENAME = 'kubb.config.ts' as const\n\n/** File extensions a Kubb config file may use. */\nexport const CONFIG_EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'] as const\n\nexport const KUBB_PACKAGE_NAME = 'kubb' as const\n\nexport const initDefaults = {\n inputPath: './openapi.yaml',\n outputPath: './src/gen',\n plugins: ['plugin-ts'],\n} as const\n","import { readFile } from 'node:fs/promises'\nimport { createJiti } from 'jiti'\n\n/**\n * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at\n * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.\n */\nconst JITI_OPTIONS = {\n jsx: { runtime: 'automatic', importSource: '@kubb/renderer-jsx' },\n moduleCache: false,\n} satisfies Parameters<typeof createJiti>[1]\n\n/**\n * Plain JavaScript files. jiti hands these to Node's native `import()`, whose module cache never\n * lets go of a file, so `moduleCache: false` alone would keep returning the first version of it.\n */\nconst JAVASCRIPT_EXTENSION = /\\.[cm]?jsx?$/\n\n/**\n * Per-call options for {@link loadModule}.\n */\ntype LoadModuleOptions = {\n /**\n * Return the module's default export instead of the full namespace.\n */\n default?: boolean\n}\n\nconst jiti = createJiti(import.meta.url, JITI_OPTIONS)\n\n/**\n * Loads a Kubb TypeScript or JavaScript config module via jiti.\n *\n * jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly. Every load\n * re-evaluates the file, so a config edited on disk is picked up by the next load.\n *\n * @example\n * ```ts\n * const config = await loadModule('/abs/kubb.config.ts', { default: true })\n * ```\n */\nexport async function loadModule<T = unknown>(filePath: string, options?: LoadModuleOptions): Promise<T> {\n if (!JAVASCRIPT_EXTENSION.test(filePath)) {\n return (options?.default ? jiti.import(filePath, { default: true }) : jiti.import(filePath)) as Promise<T>\n }\n\n // Transpiled through jiti instead, so every load re-evaluates the file as it is on disk now.\n // A TypeScript file it imports reloads too. A plain JavaScript file it imports still goes\n // through Node's `import()` and stays cached until the process restarts.\n const mod = (await jiti.evalModule(await readFile(filePath, 'utf8'), { filename: filePath, async: true, forceTranspile: true })) as\n | { default?: unknown }\n | undefined\n return (options?.default ? (mod?.default ?? mod) : mod) as T\n}\n","import { existsSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\nimport { CONFIG_EXTENSIONS } from './constants.ts'\nimport { loadModule } from './loader.ts'\n\nconst SEARCH_FILES = ['', '.config/', 'configs/'].flatMap((prefix) => [`${prefix}.kubbrc`, `${prefix}kubb.config`])\n\n/**\n * The first Kubb config file in `cwd` or the nearest parent folder that has one: `.kubbrc` before\n * `kubb.config`, the folder itself before its `.config/` and `configs/`, and {@link CONFIG_EXTENSIONS} in order.\n */\nexport function findConfigFile(cwd: string = process.cwd()): string | undefined {\n for (let dir = resolve(cwd); ; dir = dirname(dir)) {\n const found = SEARCH_FILES.flatMap((file) => CONFIG_EXTENSIONS.map((extension) => join(dir, `${file}${extension}`))).find((file) => existsSync(file))\n if (found || dirname(dir) === dir) return found\n }\n}\n\n/**\n * Loads a Kubb config module, calls it with `cli` when it exports a function, and returns its\n * configs as a list, so a single config and a `defineConfig([...])` array read the same.\n */\nexport async function loadConfigFile<T>(file: string, cli: unknown): Promise<Array<T>> {\n const loaded = await loadModule<unknown>(file, { default: true })\n const resolved = (await (typeof loaded === 'function' ? loaded(cli) : loaded)) as T | Array<T>\n return Array.isArray(resolved) ? resolved : [resolved]\n}\n","/** The `kubb.config.ts` `kubb init` writes before `kubb add` puts each plugin in. */\nexport function generateConfigFile({ inputPath, outputPath }: { inputPath: string; outputPath: string }): string {\n return `import { defineConfig } from 'kubb/config'\n\nexport default defineConfig({\n input: '${inputPath}',\n output: {\n path: '${outputPath}',\n clean: true,\n },\n plugins: [],\n})\n`\n}\n","import { access, mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import type { SerializedDiagnostic } from '@kubb/core'\n\n/**\n * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry\n * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so\n * the agent can act on the problem rather than parsing a bare message. No ANSI styling,\n * unlike the CLI renderer.\n */\nexport function formatDiagnostics(diagnostics: ReadonlyArray<SerializedDiagnostic>): string {\n return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join('\\n\\n')\n}\n\nfunction formatDiagnostic(diagnostic: SerializedDiagnostic): string {\n const { code, message, location, help, plugin, docsUrl } = diagnostic\n const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`]\n\n if (location && 'pointer' in location) {\n lines.push(` at: ${location.pointer}`)\n }\n if (help) {\n lines.push(` fix: ${help}`)\n }\n if (docsUrl) {\n lines.push(` see: ${docsUrl}`)\n }\n\n return lines.join('\\n')\n}\n","import path from 'node:path'\nimport process from 'node:process'\nimport { CONFIG_EXTENSIONS, findConfigFile, loadConfigFile } from '@internals/shared'\nimport { isPathInside } from '@internals/utils'\nimport { type Config, createKubb, Diagnostics, type KubbHooks, Hookable } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics } from '../utils.ts'\n\nconst generateSchema = z.object({\n config: z.optional(\n z\n .string()\n .check(\n z.minLength(1),\n z.describe(\n `Path to kubb.config file (supports ${CONFIG_EXTENSIONS.join(', ')}). If not provided, will look for kubb.config.* or .kubbrc.* in the current directory, its .config/ and configs/ folders, then each parent directory`,\n ),\n ),\n ),\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI/Swagger spec file (overrides config)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory path (overrides config)'))),\n logLevel: z._default(z.enum(['silent', 'info', 'verbose']).check(z.describe('Log level for build output')), 'info'),\n})\n\nexport const generateTool = defineTool(\n {\n name: 'generate',\n description: 'Generate OpenAPI spec helpers using Kubb configuration',\n schema: generateSchema,\n },\n async function generate(schema: z.input<typeof generateSchema>) {\n const { config: configPath, input, output, logLevel } = schema\n\n try {\n const hooks = new Hookable<KubbHooks>()\n const messages: Array<string> = []\n\n const notify = (type: string, message: string, data?: Record<string, unknown>) => {\n messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`)\n }\n\n hooks.hook('kubb:error', ({ error }) => notify('ERROR', error.message))\n hooks.hook('kubb:warn', ({ message }) => notify('WARN', message))\n hooks.hook('kubb:diagnostic', ({ diagnostic }) => notify('DIAGNOSTIC', diagnostic.message, Diagnostics.serialize(diagnostic)))\n\n // The AI client supplies the path, so it must carry a config extension and stay inside the working directory.\n const base = path.resolve(process.cwd())\n let file: string | undefined\n if (configPath) {\n const ext = path.extname(configPath)\n if (!CONFIG_EXTENSIONS.some((extension) => extension === ext)) {\n return tool.error(`Invalid config file extension \"${ext}\". Allowed: ${CONFIG_EXTENSIONS.join(', ')}`)\n }\n file = path.resolve(base, configPath)\n if (!isPathInside(file, base)) {\n return tool.error('Invalid config file path: must be within the current working directory')\n }\n } else {\n file = findConfigFile(base)\n if (!file) {\n return tool.error(\n `No config file found. Please provide a config path or create one of: ${CONFIG_EXTENSIONS.map((extension) => `kubb.config${extension}`).join(', ')}`,\n )\n }\n }\n\n let userConfig: Config\n try {\n const [first] = await loadConfigFile<Config>(file, { logLevel, config: configPath })\n if (!first) throw new Error('The config file exports an empty array of configurations')\n userConfig = first\n } catch (error) {\n return tool.error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`)\n }\n\n const cwd = path.dirname(file)\n const config: Config = {\n ...userConfig,\n root: path.resolve(cwd, userConfig.root ?? '.'),\n input: input ?? userConfig.input,\n output: output\n ? {\n ...userConfig.output,\n path: output,\n }\n : userConfig.output,\n }\n\n notify('CONFIG_READY', 'Configuration ready')\n\n const kubb = createKubb(config, { hooks })\n const result = await kubb.generate()\n notify('BUILD_END', `Build complete - Generated ${result.files.length} files`)\n\n const problems = result.diagnostics.filter(Diagnostics.isProblem)\n const errors = problems.filter((diagnostic) => diagnostic.severity === 'error')\n if (errors.length > 0) {\n notify('BUILD_FAILED', `Build failed with ${errors.length} diagnostic(s)`)\n\n const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic))\n return tool.error(`Build failed:\\n${formatDiagnostics(serialized)}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n\n notify('BUILD_SUCCESS', `Build completed successfully - Generated ${result.files.length} files`)\n\n return tool.text(`Build completed successfully!\\n\\nGenerated ${result.files.length} files\\n\\n${messages.join('\\n')}`)\n } catch (caughtError) {\n const serialized = Diagnostics.serialize(Diagnostics.from(caughtError))\n return tool.error(`Build error:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { generateConfigFile, KUBB_CONFIG_FILENAME } from '@internals/shared'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\n\nconst initSchema = z.object({\n input: z.optional(z.string().check(z.minLength(1), z.describe('Path to OpenAPI spec (default: ./openapi.yaml)'))),\n output: z.optional(z.string().check(z.minLength(1), z.describe('Output directory (default: ./src/gen)'))),\n})\n\nexport const initTool = defineTool(\n {\n name: 'init',\n description:\n 'Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages or add plugins: run `kubb add <id> --yes` for each plugin afterwards.',\n schema: initSchema,\n },\n async ({ input = './openapi.yaml', output = './src/gen' }) => {\n const dest = path.join(process.cwd(), KUBB_CONFIG_FILENAME)\n if (fs.existsSync(dest)) {\n return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`)\n }\n fs.writeFileSync(dest, generateConfigFile({ inputPath: input, outputPath: output }), 'utf-8')\n return tool.text(\n `Created ${KUBB_CONFIG_FILENAME}\\n\\nInstall Kubb:\\n npm install kubb\\n\\nAdd each plugin with its registry id, which installs it and adds it to the config:\\n npx kubb add plugin-ts --yes\\n\\nThen run:\\n npx kubb generate`,\n )\n },\n)\n","import { adapterOas } from '@kubb/adapter-oas'\nimport { Diagnostics } from '@kubb/core'\nimport { defineTool } from 'tmcp/tool'\nimport { tool } from 'tmcp/utils'\nimport * as z from 'zod/mini'\nimport { formatDiagnostics } from '../utils.ts'\n\nconst validateSchema = z.object({\n input: z.string().check(z.minLength(1), z.describe('Path or URL to the OpenAPI/Swagger specification')),\n})\n\nexport const validateTool = defineTool(\n {\n name: 'validate',\n description: 'Validate an OpenAPI/Swagger specification file or URL',\n schema: validateSchema,\n },\n async ({ input }) => {\n try {\n await adapterOas().validate(input, { throwOnError: true })\n return tool.text(`Validation successful: ${input}`)\n } catch (err) {\n const serialized = Diagnostics.serialize(Diagnostics.from(err))\n return tool.error(`Validation failed:\\n${formatDiagnostics([serialized])}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\``)\n }\n },\n)\n","import { StdioTransport } from '@tmcp/transport-stdio'\nimport { McpServer } from 'tmcp'\nimport { JsonSchemaAdapter } from 'tmcp/adapter'\nimport { en } from 'zod/locales'\nimport * as z from 'zod/mini'\nimport { version } from '../package.json'\nimport { generateTool } from './tools/generate.ts'\nimport { initTool } from './tools/init.ts'\nimport { validateTool } from './tools/validate.ts'\n\n/**\n * Describes each tool's input to MCP clients. `@tmcp/adapter-zod` imports the full `zod` build, so\n * this converts with Zod Mini instead. `io: 'input'` keeps fields with a default optional.\n */\nclass ZodMiniJsonSchemaAdapter extends JsonSchemaAdapter<z.ZodMiniType> {\n async toJsonSchema(schema: z.ZodMiniType) {\n // Zod's type also allows draft-4's boolean `exclusiveMaximum`, which the draft-7 target never emits.\n return z.toJSONSchema(schema, { target: 'draft-7', io: 'input' }) as Awaited<ReturnType<JsonSchemaAdapter<z.ZodMiniType>['toJsonSchema']>>\n }\n}\n\n/**\n * Builds the Kubb MCP server with the generate, validate, and init tools registered.\n *\n * @example\n * `const server = createMcpServer()`\n */\nexport function createMcpServer() {\n // Zod Mini ships without a locale, so validation errors would only say \"Invalid input\".\n z.config(en())\n const server = new McpServer({ name: 'Kubb', version }, { adapter: new ZodMiniJsonSchemaAdapter(), capabilities: { tools: {} } })\n server.tools([generateTool, validateTool, initTool])\n return server\n}\n\n/**\n * Starts the Kubb MCP server over stdio, the transport every local MCP client\n * (Claude, Copilot, editors) uses when it launches the server as a subprocess.\n */\nexport function startServer(): void {\n new StdioTransport(createMcpServer()).listen()\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/mcp",
|
|
3
|
-
"version": "5.9.
|
|
3
|
+
"version": "5.9.4",
|
|
4
4
|
"description": "MCP server for Kubb. Exposes code generation as a tool over the Model Context Protocol so AI assistants like Claude, Cursor, and other MCP-compatible clients can generate TypeScript types, clients, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -62,20 +62,20 @@
|
|
|
62
62
|
"registry": "https://registry.npmjs.org/"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@kubb/adapter-oas": "5.9.
|
|
66
|
-
"@kubb/core": "5.9.
|
|
65
|
+
"@kubb/adapter-oas": "5.9.4",
|
|
66
|
+
"@kubb/core": "5.9.4",
|
|
67
67
|
"@tmcp/transport-stdio": "^0.5.0",
|
|
68
68
|
"jiti": "^2.7.0",
|
|
69
69
|
"tmcp": "^1.20.0",
|
|
70
|
-
"zod": "^4.6.
|
|
70
|
+
"zod": "^4.6.6"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@internals/shared": "0.0.1",
|
|
74
74
|
"@internals/utils": "0.0.1",
|
|
75
|
-
"@kubb/renderer-jsx": "5.9.
|
|
75
|
+
"@kubb/renderer-jsx": "5.9.4"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@kubb/renderer-jsx": "5.9.
|
|
78
|
+
"@kubb/renderer-jsx": "5.9.4"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
81
|
"node": ">=22"
|