@kubb/mcp 5.10.1 → 5.10.3

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 CHANGED
@@ -38,9 +38,10 @@ let jiti = require("jiti");
38
38
  let _kubb_core = require("@kubb/core");
39
39
  let tmcp_tool = require("tmcp/tool");
40
40
  let tmcp_utils = require("tmcp/utils");
41
+ let node_util = require("node:util");
41
42
  let _kubb_adapter_oas = require("@kubb/adapter-oas");
42
43
  //#region package.json
43
- var version = "5.10.1";
44
+ var version = "5.10.3";
44
45
  //#endregion
45
46
  //#region ../../internals/shared/src/constants.ts
46
47
  const KUBB_CONFIG_FILENAME = "kubb.config.ts";
@@ -160,21 +161,13 @@ function isPathInside(path, parent) {
160
161
  //#endregion
161
162
  //#region src/utils.ts
162
163
  /**
163
- * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
164
- * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
165
- * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
166
- * unlike the CLI renderer.
164
+ * Renders diagnostics as a plain-text block for an AI assistant, through the same
165
+ * `Diagnostics.format` the CLI uses for agents, without ANSI styling. Each entry keeps the stable
166
+ * code, the source pointer, the cause, the fix, the underlying errors, the docs link, and the issue
167
+ * tracker, so the agent can act on the problem rather than parsing a bare message.
167
168
  */
168
169
  function formatDiagnostics(diagnostics) {
169
- return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
170
- }
171
- function formatDiagnostic(diagnostic) {
172
- const { code, message, location, help, plugin, docsUrl } = diagnostic;
173
- const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`];
174
- if (location && "pointer" in location) lines.push(` at: ${location.pointer}`);
175
- if (help) lines.push(` fix: ${help}`);
176
- if (docsUrl) lines.push(` see: ${docsUrl}`);
177
- return lines.join("\n");
170
+ return diagnostics.map((diagnostic) => (0, node_util.stripVTControlCharacters)(_kubb_core.Diagnostics.format(diagnostic, { audience: "agent" }).join("\n"))).join("\n\n");
178
171
  }
179
172
  //#endregion
180
173
  //#region src/tools/generate.ts
@@ -194,9 +187,18 @@ const generateTool = (0, tmcp_tool.defineTool)({
194
187
  schema: generateSchema
195
188
  }, async function generate(schema) {
196
189
  const { config: configPath, input, output, logLevel } = schema;
190
+ const messages = [];
191
+ /**
192
+ * The failure response: the diagnostics as text and JSON, then the messages collected so far,
193
+ * such as warnings, so the agent sees everything the run said.
194
+ */
195
+ const failure = (title, diagnostics) => {
196
+ const serialized = diagnostics.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic));
197
+ const log = messages.length ? `\n\nMessages:\n${messages.join("\n")}` : "";
198
+ return tmcp_utils.tool.error(`${title}:\n${formatDiagnostics(diagnostics)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\`${log}`);
199
+ };
197
200
  try {
198
201
  const hooks = new _kubb_core.Hookable();
199
- const messages = [];
200
202
  const notify = (type, message, data) => {
201
203
  messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
202
204
  };
@@ -220,10 +222,14 @@ const generateTool = (0, tmcp_tool.defineTool)({
220
222
  logLevel,
221
223
  config: configPath
222
224
  });
223
- if (!first) throw new Error("The config file exports an empty array of configurations");
225
+ if (!first) throw new _kubb_core.Diagnostics.Error({
226
+ code: _kubb_core.Diagnostics.code.unknown,
227
+ severity: "error",
228
+ message: "The config file exports an empty array of configurations"
229
+ });
224
230
  userConfig = first;
225
231
  } catch (error) {
226
- return tmcp_utils.tool.error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
232
+ return failure(`Failed to load config ${file}`, [_kubb_core.Diagnostics.from(error)]);
227
233
  }
228
234
  const cwd = node_path.default.dirname(file);
229
235
  const config = {
@@ -242,14 +248,12 @@ const generateTool = (0, tmcp_tool.defineTool)({
242
248
  const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
243
249
  if (errors.length > 0) {
244
250
  notify("BUILD_FAILED", `Build failed with ${errors.length} diagnostic(s)`);
245
- const serialized = problems.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic));
246
- return tmcp_utils.tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
251
+ return failure("Build failed", problems);
247
252
  }
248
253
  notify("BUILD_SUCCESS", `Build completed successfully - Generated ${result.files.length} files`);
249
254
  return tmcp_utils.tool.text(`Build completed successfully!\n\nGenerated ${result.files.length} files\n\n${messages.join("\n")}`);
250
255
  } catch (caughtError) {
251
- const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(caughtError));
252
- return tmcp_utils.tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
256
+ return failure("Build error", [_kubb_core.Diagnostics.from(caughtError)]);
253
257
  }
254
258
  });
255
259
  //#endregion
@@ -283,8 +287,9 @@ const validateTool = (0, tmcp_tool.defineTool)({
283
287
  await (0, _kubb_adapter_oas.adapterOas)().validate(input, { throwOnError: true });
284
288
  return tmcp_utils.tool.text(`Validation successful: ${input}`);
285
289
  } catch (err) {
286
- const serialized = _kubb_core.Diagnostics.serialize(_kubb_core.Diagnostics.from(err));
287
- return tmcp_utils.tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
290
+ const diagnostic = _kubb_core.Diagnostics.from(err);
291
+ const serialized = _kubb_core.Diagnostics.serialize(diagnostic);
292
+ return tmcp_utils.tool.error(`Validation failed:\n${formatDiagnostics([diagnostic])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
288
293
  }
289
294
  });
290
295
  //#endregion
@@ -1 +1 @@
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() {\n const server = createMcpServer()\n new StdioTransport(server).listen()\n return server\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","stripVTControlCharacters","Diagnostics","z","defineTool","Diagnostics","tool","Hookable","path","process","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 { stripVTControlCharacters } from 'node:util'\nimport { type Diagnostic, Diagnostics } from '@kubb/core'\n\n/**\n * Renders diagnostics as a plain-text block for an AI assistant, through the same\n * `Diagnostics.format` the CLI uses for agents, without ANSI styling. Each entry keeps the stable\n * code, the source pointer, the cause, the fix, the underlying errors, the docs link, and the issue\n * tracker, so the agent can act on the problem rather than parsing a bare message.\n */\nexport function formatDiagnostics(diagnostics: ReadonlyArray<Diagnostic>): string {\n return diagnostics.map((diagnostic) => stripVTControlCharacters(Diagnostics.format(diagnostic, { audience: 'agent' }).join('\\n'))).join('\\n\\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, type Diagnostic, 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 const messages: Array<string> = []\n\n /**\n * The failure response: the diagnostics as text and JSON, then the messages collected so far,\n * such as warnings, so the agent sees everything the run said.\n */\n const failure = (title: string, diagnostics: ReadonlyArray<Diagnostic>) => {\n const serialized = diagnostics.map((diagnostic) => Diagnostics.serialize(diagnostic))\n const log = messages.length ? `\\n\\nMessages:\\n${messages.join('\\n')}` : ''\n return tool.error(`${title}:\\n${formatDiagnostics(diagnostics)}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\`${log}`)\n }\n\n try {\n const hooks = new Hookable<KubbHooks>()\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)\n throw new Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'The config file exports an empty array of configurations',\n })\n userConfig = first\n } catch (error) {\n return failure(`Failed to load config ${file}`, [Diagnostics.from(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 return failure('Build failed', problems)\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 return failure('Build error', [Diagnostics.from(caughtError)])\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 diagnostic = Diagnostics.from(err)\n const serialized = Diagnostics.serialize(diagnostic)\n return tool.error(`Validation failed:\\n${formatDiagnostics([diagnostic])}\\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() {\n const server = createMcpServer()\n new StdioTransport(server).listen()\n return server\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACAA,MAAa,uBAAuB;;AAGpC,MAAa,oBAAoB;CAAC;CAAO;CAAQ;CAAQ;CAAO;CAAQ;AAAM;;;;;;;ACI9E,MAAM,eAAe;CACnB,KAAK;EAAE,SAAS;EAAa,cAAc;CAAqB;CAChE,aAAa;AACf;;;;;AAMA,MAAM,uBAAuB;AAY7B,MAAMA,UAAAA,GAAOC,KAAAA,WAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,MAA4B,YAAY;;;;;;;;;;;;AAarD,eAAsB,WAAwB,UAAkB,SAAyC;CACvG,IAAI,CAAC,qBAAqB,KAAK,QAAQ,GACrC,OAAQ,SAAS,UAAUD,OAAK,OAAO,UAAU,EAAE,SAAS,KAAK,CAAC,IAAIA,OAAK,OAAO,QAAQ;CAM5F,MAAM,MAAO,MAAMA,OAAK,WAAW,OAAA,GAAME,iBAAAA,SAAAA,CAAS,UAAU,MAAM,GAAG;EAAE,UAAU;EAAU,OAAO;EAAM,gBAAgB;CAAK,CAAC;CAG9H,OAAQ,SAAS,UAAW,KAAK,WAAW,MAAO;AACrD;;;AC/CA,MAAM,eAAe;CAAC;CAAI;CAAY;AAAU,CAAC,CAAC,SAAS,WAAW,CAAC,GAAG,OAAO,UAAU,GAAG,OAAO,YAAY,CAAC;;;;;AAMlH,SAAgB,eAAe,MAAcC,aAAAA,QAAQ,IAAI,GAAuB;CAC9E,KAAK,IAAI,OAAA,GAAMC,UAAAA,QAAAA,CAAQ,GAAG,IAAK,OAAA,GAAMC,UAAAA,QAAAA,CAAQ,GAAG,GAAG;EACjD,MAAM,QAAQ,aAAa,SAAS,SAAS,kBAAkB,KAAK,eAAA,GAAcC,UAAAA,KAAAA,CAAK,KAAK,GAAG,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,UAAA,GAASC,QAAAA,WAAAA,CAAW,IAAI,CAAC;EACpJ,IAAI,UAAA,GAASF,UAAAA,QAAAA,CAAQ,GAAG,MAAM,KAAK,OAAO;CAC5C;AACF;;;;;AAMA,eAAsB,eAAkB,MAAc,KAAiC;CACrF,MAAM,SAAS,MAAM,WAAoB,MAAM,EAAE,SAAS,KAAK,CAAC;CAChE,MAAM,WAAY,OAAO,OAAO,WAAW,aAAa,OAAO,GAAG,IAAI;CACtE,OAAO,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ;AACvD;;;;AC1BA,SAAgB,mBAAmB,EAAE,WAAW,cAAiE;CAC/G,OAAO;;;YAGG,UAAU;;aAET,WAAW;;;;;;AAMxB;;;;;;;;;;;;;;;;;AC0KA,SAAgB,aAAa,MAAc,QAAyB;CAClE,MAAM,gBAAA,GAAeG,UAAAA,QAAAA,CAAQ,IAAI;CACjC,MAAM,kBAAA,GAAiBA,UAAAA,QAAAA,CAAQ,MAAM;CACrC,IAAI,iBAAiB,gBACnB,OAAO;CAGT,MAAM,OAAA,GAAMC,UAAAA,SAAAA,CAAS,gBAAgB,YAAY;CAEjD,OAAO,QAAQ,MAAM,CAAC,IAAI,WAAW,IAAI,KAAK,EAAA,GAACC,UAAAA,WAAAA,CAAW,GAAG;AAC/D;;;;;;;;;ACxLA,SAAgB,kBAAkB,aAAgD;CAChF,OAAO,YAAY,KAAK,gBAAA,GAAeC,UAAAA,yBAAAA,CAAyBC,WAAAA,YAAY,OAAO,YAAY,EAAE,UAAU,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM;AAChJ;;;ACDA,MAAM,iBAAiBC,SAAE,OAAO;CAC9B,QAAQA,SAAE,SACRA,SACG,OAAO,CAAC,CACR,MACCA,SAAE,UAAU,CAAC,GACbA,SAAE,SACA,sCAAsC,kBAAkB,KAAK,IAAI,EAAE,qJACrE,CACF,CACJ;CACA,OAAOA,SAAE,SAASA,SAAE,OAAO,CAAC,CAAC,MAAMA,SAAE,UAAU,CAAC,GAAGA,SAAE,SAAS,sDAAsD,CAAC,CAAC;CACtH,QAAQA,SAAE,SAASA,SAAE,OAAO,CAAC,CAAC,MAAMA,SAAE,UAAU,CAAC,GAAGA,SAAE,SAAS,0CAA0C,CAAC,CAAC;CAC3G,UAAUA,SAAE,SAASA,SAAE,KAAK;EAAC;EAAU;EAAQ;CAAS,CAAC,CAAC,CAAC,MAAMA,SAAE,SAAS,4BAA4B,CAAC,GAAG,MAAM;AACpH,CAAC;AAED,MAAa,gBAAA,GAAeC,UAAAA,WAAAA,CAC1B;CACE,MAAM;CACN,aAAa;CACb,QAAQ;AACV,GACA,eAAe,SAAS,QAAwC;CAC9D,MAAM,EAAE,QAAQ,YAAY,OAAO,QAAQ,aAAa;CACxD,MAAM,WAA0B,CAAC;;;;;CAMjC,MAAM,WAAW,OAAe,gBAA2C;EACzE,MAAM,aAAa,YAAY,KAAK,eAAeC,WAAAA,YAAY,UAAU,UAAU,CAAC;EACpF,MAAM,MAAM,SAAS,SAAS,kBAAkB,SAAS,KAAK,IAAI,MAAM;EACxE,OAAOC,WAAAA,KAAK,MAAM,GAAG,MAAM,KAAK,kBAAkB,WAAW,EAAE,kBAAkB,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE,UAAU,KAAK;CACtI;CAEA,IAAI;EACF,MAAM,QAAQ,IAAIC,WAAAA,SAAoB;EAEtC,MAAM,UAAU,MAAc,SAAiB,SAAmC;GAChF,SAAS,KAAK,OAAO,GAAG,KAAK,IAAI,QAAQ,GAAG,KAAK,UAAU,IAAI,MAAM,GAAG,KAAK,IAAI,SAAS;EAC5F;EAEA,MAAM,KAAK,eAAe,EAAE,YAAY,OAAO,SAAS,MAAM,OAAO,CAAC;EACtE,MAAM,KAAK,cAAc,EAAE,cAAc,OAAO,QAAQ,OAAO,CAAC;EAChE,MAAM,KAAK,oBAAoB,EAAE,iBAAiB,OAAO,cAAc,WAAW,SAASF,WAAAA,YAAY,UAAU,UAAU,CAAC,CAAC;EAG7H,MAAM,OAAOG,UAAAA,QAAK,QAAQC,aAAAA,QAAQ,IAAI,CAAC;EACvC,IAAI;EACJ,IAAI,YAAY;GACd,MAAM,MAAMD,UAAAA,QAAK,QAAQ,UAAU;GACnC,IAAI,CAAC,kBAAkB,MAAM,cAAc,cAAc,GAAG,GAC1D,OAAOF,WAAAA,KAAK,MAAM,kCAAkC,IAAI,cAAc,kBAAkB,KAAK,IAAI,GAAG;GAEtG,OAAOE,UAAAA,QAAK,QAAQ,MAAM,UAAU;GACpC,IAAI,CAAC,aAAa,MAAM,IAAI,GAC1B,OAAOF,WAAAA,KAAK,MAAM,wEAAwE;EAE9F,OAAO;GACL,OAAO,eAAe,IAAI;GAC1B,IAAI,CAAC,MACH,OAAOA,WAAAA,KAAK,MACV,wEAAwE,kBAAkB,KAAK,cAAc,cAAc,WAAW,CAAC,CAAC,KAAK,IAAI,GACnJ;EAEJ;EAEA,IAAI;EACJ,IAAI;GACF,MAAM,CAAC,SAAS,MAAM,eAAuB,MAAM;IAAE;IAAU,QAAQ;GAAW,CAAC;GACnF,IAAI,CAAC,OACH,MAAM,IAAID,WAAAA,YAAY,MAAM;IAC1B,MAAMA,WAAAA,YAAY,KAAK;IACvB,UAAU;IACV,SAAS;GACX,CAAC;GACH,aAAa;EACf,SAAS,OAAO;GACd,OAAO,QAAQ,yBAAyB,QAAQ,CAACA,WAAAA,YAAY,KAAK,KAAK,CAAC,CAAC;EAC3E;EAEA,MAAM,MAAMG,UAAAA,QAAK,QAAQ,IAAI;EAC7B,MAAM,SAAiB;GACrB,GAAG;GACH,MAAMA,UAAAA,QAAK,QAAQ,KAAK,WAAW,QAAQ,GAAG;GAC9C,OAAO,SAAS,WAAW;GAC3B,QAAQ,SACJ;IACE,GAAG,WAAW;IACd,MAAM;GACR,IACA,WAAW;EACjB;EAEA,OAAO,gBAAgB,qBAAqB;EAG5C,MAAM,SAAS,OAAA,GADFE,WAAAA,WAAAA,CAAW,QAAQ,EAAE,MAAM,CAChB,CAAC,CAAC,SAAS;EACnC,OAAO,aAAa,8BAA8B,OAAO,MAAM,OAAO,OAAO;EAE7E,MAAM,WAAW,OAAO,YAAY,OAAOL,WAAAA,YAAY,SAAS;EAChE,MAAM,SAAS,SAAS,QAAQ,eAAe,WAAW,aAAa,OAAO;EAC9E,IAAI,OAAO,SAAS,GAAG;GACrB,OAAO,gBAAgB,qBAAqB,OAAO,OAAO,eAAe;GAEzE,OAAO,QAAQ,gBAAgB,QAAQ;EACzC;EAEA,OAAO,iBAAiB,4CAA4C,OAAO,MAAM,OAAO,OAAO;EAE/F,OAAOC,WAAAA,KAAK,KAAK,8CAA8C,OAAO,MAAM,OAAO,YAAY,SAAS,KAAK,IAAI,GAAG;CACtH,SAAS,aAAa;EACpB,OAAO,QAAQ,eAAe,CAACD,WAAAA,YAAY,KAAK,WAAW,CAAC,CAAC;CAC/D;AACF,CACF;;;ACtHA,MAAM,aAAaM,SAAE,OAAO;CAC1B,OAAOA,SAAE,SAASA,SAAE,OAAO,CAAC,CAAC,MAAMA,SAAE,UAAU,CAAC,GAAGA,SAAE,SAAS,gDAAgD,CAAC,CAAC;CAChH,QAAQA,SAAE,SAASA,SAAE,OAAO,CAAC,CAAC,MAAMA,SAAE,UAAU,CAAC,GAAGA,SAAE,SAAS,uCAAuC,CAAC,CAAC;AAC1G,CAAC;AAED,MAAa,YAAA,GAAWC,UAAAA,WAAAA,CACtB;CACE,MAAM;CACN,aACE;CACF,QAAQ;AACV,GACA,OAAO,EAAE,QAAQ,kBAAkB,SAAS,kBAAkB;CAC5D,MAAM,OAAOC,UAAAA,QAAK,KAAKC,aAAAA,QAAQ,IAAI,GAAG,oBAAoB;CAC1D,IAAIC,QAAAA,QAAG,WAAW,IAAI,GACpB,OAAOC,WAAAA,KAAK,MAAM,GAAG,qBAAqB,qBAAqB,KAAK,6CAA6C;CAEnH,QAAA,QAAG,cAAc,MAAM,mBAAmB;EAAE,WAAW;EAAO,YAAY;CAAO,CAAC,GAAG,OAAO;CAC5F,OAAOA,WAAAA,KAAK,KACV,WAAW,qBAAqB,8LAClC;AACF,CACF;;;ACvBA,MAAM,iBAAiBC,SAAE,OAAO,EAC9B,OAAOA,SAAE,OAAO,CAAC,CAAC,MAAMA,SAAE,UAAU,CAAC,GAAGA,SAAE,SAAS,kDAAkD,CAAC,EACxG,CAAC;AAED,MAAa,gBAAA,GAAeC,UAAAA,WAAAA,CAC1B;CACE,MAAM;CACN,aAAa;CACb,QAAQ;AACV,GACA,OAAO,EAAE,YAAY;CACnB,IAAI;EACF,OAAA,GAAMC,kBAAAA,WAAAA,CAAW,CAAC,CAAC,SAAS,OAAO,EAAE,cAAc,KAAK,CAAC;EACzD,OAAOC,WAAAA,KAAK,KAAK,0BAA0B,OAAO;CACpD,SAAS,KAAK;EACZ,MAAM,aAAaC,WAAAA,YAAY,KAAK,GAAG;EACvC,MAAM,aAAaA,WAAAA,YAAY,UAAU,UAAU;EACnD,OAAOD,WAAAA,KAAK,MAAM,uBAAuB,kBAAkB,CAAC,UAAU,CAAC,EAAE,kBAAkB,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE,SAAS;CAC1I;AACF,CACF;;;;;;;ACbA,IAAM,2BAAN,cAAuCE,aAAAA,kBAAiC;CACtE,MAAM,aAAa,QAAuB;EAExC,OAAOC,SAAE,aAAa,QAAQ;GAAE,QAAQ;GAAW,IAAI;EAAQ,CAAC;CAClE;AACF;;;;;;;AAQA,SAAgB,kBAAkB;CAEhC,SAAE,QAAA,GAAOC,YAAAA,GAAAA,CAAG,CAAC;CACb,MAAM,SAAS,IAAIC,KAAAA,UAAU;EAAE,MAAM;EAAQ;CAAQ,GAAG;EAAE,SAAS,IAAI,yBAAyB;EAAG,cAAc,EAAE,OAAO,CAAC,EAAE;CAAE,CAAC;CAChI,OAAO,MAAM;EAAC;EAAc;EAAc;CAAQ,CAAC;CACnD,OAAO;AACT;;;;;AAMA,SAAgB,cAAc;CAC5B,MAAM,SAAS,gBAAgB;CAC/B,IAAIC,sBAAAA,eAAe,MAAM,CAAC,CAAC,OAAO;CAClC,OAAO;AACT"}
@@ -0,0 +1,18 @@
1
+ import { McpServer } from "tmcp";
2
+ import * as z from "zod/mini";
3
+ //#region src/server.d.ts
4
+ /**
5
+ * Builds the Kubb MCP server with the generate, validate, and init tools registered.
6
+ *
7
+ * @example
8
+ * `const server = createMcpServer()`
9
+ */
10
+ export declare function createMcpServer(): McpServer<z.ZodMiniType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, undefined>;
11
+ /**
12
+ * Starts the Kubb MCP server over stdio, the transport every local MCP client
13
+ * (Claude, Copilot, editors) uses when it launches the server as a subprocess.
14
+ */
15
+ declare function startServer(): McpServer<z.ZodMiniType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, undefined>;
16
+ //#endregion
17
+ export { startServer as run };
18
+ //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
1
  import { McpServer } from "tmcp";
3
2
  import * as z from "zod/mini";
4
3
  //#region src/server.d.ts
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import "./rolldown-runtime-C0LytTxp.js";
2
1
  import { StdioTransport } from "@tmcp/transport-stdio";
3
2
  import { McpServer } from "tmcp";
4
3
  import { JsonSchemaAdapter } from "tmcp/adapter";
@@ -12,9 +11,11 @@ import { createJiti } from "jiti";
12
11
  import { Diagnostics, Hookable, createKubb } from "@kubb/core";
13
12
  import { defineTool } from "tmcp/tool";
14
13
  import { tool } from "tmcp/utils";
14
+ import { stripVTControlCharacters } from "node:util";
15
15
  import { adapterOas } from "@kubb/adapter-oas";
16
+ //#endregion
16
17
  //#region package.json
17
- var version = "5.10.1";
18
+ var version = "5.10.3";
18
19
  //#endregion
19
20
  //#region ../../internals/shared/src/constants.ts
20
21
  const KUBB_CONFIG_FILENAME = "kubb.config.ts";
@@ -134,21 +135,13 @@ function isPathInside(path, parent) {
134
135
  //#endregion
135
136
  //#region src/utils.ts
136
137
  /**
137
- * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
138
- * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
139
- * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
140
- * unlike the CLI renderer.
138
+ * Renders diagnostics as a plain-text block for an AI assistant, through the same
139
+ * `Diagnostics.format` the CLI uses for agents, without ANSI styling. Each entry keeps the stable
140
+ * code, the source pointer, the cause, the fix, the underlying errors, the docs link, and the issue
141
+ * tracker, so the agent can act on the problem rather than parsing a bare message.
141
142
  */
142
143
  function formatDiagnostics(diagnostics) {
143
- return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join("\n\n");
144
- }
145
- function formatDiagnostic(diagnostic) {
146
- const { code, message, location, help, plugin, docsUrl } = diagnostic;
147
- const lines = [plugin ? `[${code}] ${plugin}: ${message}` : `[${code}]: ${message}`];
148
- if (location && "pointer" in location) lines.push(` at: ${location.pointer}`);
149
- if (help) lines.push(` fix: ${help}`);
150
- if (docsUrl) lines.push(` see: ${docsUrl}`);
151
- return lines.join("\n");
144
+ return diagnostics.map((diagnostic) => stripVTControlCharacters(Diagnostics.format(diagnostic, { audience: "agent" }).join("\n"))).join("\n\n");
152
145
  }
153
146
  //#endregion
154
147
  //#region src/tools/generate.ts
@@ -168,9 +161,18 @@ const generateTool = defineTool({
168
161
  schema: generateSchema
169
162
  }, async function generate(schema) {
170
163
  const { config: configPath, input, output, logLevel } = schema;
164
+ const messages = [];
165
+ /**
166
+ * The failure response: the diagnostics as text and JSON, then the messages collected so far,
167
+ * such as warnings, so the agent sees everything the run said.
168
+ */
169
+ const failure = (title, diagnostics) => {
170
+ const serialized = diagnostics.map((diagnostic) => Diagnostics.serialize(diagnostic));
171
+ const log = messages.length ? `\n\nMessages:\n${messages.join("\n")}` : "";
172
+ return tool.error(`${title}:\n${formatDiagnostics(diagnostics)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\`${log}`);
173
+ };
171
174
  try {
172
175
  const hooks = new Hookable();
173
- const messages = [];
174
176
  const notify = (type, message, data) => {
175
177
  messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`);
176
178
  };
@@ -194,10 +196,14 @@ const generateTool = defineTool({
194
196
  logLevel,
195
197
  config: configPath
196
198
  });
197
- if (!first) throw new Error("The config file exports an empty array of configurations");
199
+ if (!first) throw new Diagnostics.Error({
200
+ code: Diagnostics.code.unknown,
201
+ severity: "error",
202
+ message: "The config file exports an empty array of configurations"
203
+ });
198
204
  userConfig = first;
199
205
  } catch (error) {
200
- return tool.error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
206
+ return failure(`Failed to load config ${file}`, [Diagnostics.from(error)]);
201
207
  }
202
208
  const cwd = path.dirname(file);
203
209
  const config = {
@@ -216,14 +222,12 @@ const generateTool = defineTool({
216
222
  const errors = problems.filter((diagnostic) => diagnostic.severity === "error");
217
223
  if (errors.length > 0) {
218
224
  notify("BUILD_FAILED", `Build failed with ${errors.length} diagnostic(s)`);
219
- const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic));
220
- return tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
225
+ return failure("Build failed", problems);
221
226
  }
222
227
  notify("BUILD_SUCCESS", `Build completed successfully - Generated ${result.files.length} files`);
223
228
  return tool.text(`Build completed successfully!\n\nGenerated ${result.files.length} files\n\n${messages.join("\n")}`);
224
229
  } catch (caughtError) {
225
- const serialized = Diagnostics.serialize(Diagnostics.from(caughtError));
226
- return tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
230
+ return failure("Build error", [Diagnostics.from(caughtError)]);
227
231
  }
228
232
  });
229
233
  //#endregion
@@ -257,8 +261,9 @@ const validateTool = defineTool({
257
261
  await adapterOas().validate(input, { throwOnError: true });
258
262
  return tool.text(`Validation successful: ${input}`);
259
263
  } catch (err) {
260
- const serialized = Diagnostics.serialize(Diagnostics.from(err));
261
- return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
264
+ const diagnostic = Diagnostics.from(err);
265
+ const serialized = Diagnostics.serialize(diagnostic);
266
+ return tool.error(`Validation failed:\n${formatDiagnostics([diagnostic])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``);
262
267
  }
263
268
  });
264
269
  //#endregion
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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() {\n const server = createMcpServer()\n new StdioTransport(server).listen()\n return server\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 { stripVTControlCharacters } from 'node:util'\nimport { type Diagnostic, Diagnostics } from '@kubb/core'\n\n/**\n * Renders diagnostics as a plain-text block for an AI assistant, through the same\n * `Diagnostics.format` the CLI uses for agents, without ANSI styling. Each entry keeps the stable\n * code, the source pointer, the cause, the fix, the underlying errors, the docs link, and the issue\n * tracker, so the agent can act on the problem rather than parsing a bare message.\n */\nexport function formatDiagnostics(diagnostics: ReadonlyArray<Diagnostic>): string {\n return diagnostics.map((diagnostic) => stripVTControlCharacters(Diagnostics.format(diagnostic, { audience: 'agent' }).join('\\n'))).join('\\n\\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, type Diagnostic, 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 const messages: Array<string> = []\n\n /**\n * The failure response: the diagnostics as text and JSON, then the messages collected so far,\n * such as warnings, so the agent sees everything the run said.\n */\n const failure = (title: string, diagnostics: ReadonlyArray<Diagnostic>) => {\n const serialized = diagnostics.map((diagnostic) => Diagnostics.serialize(diagnostic))\n const log = messages.length ? `\\n\\nMessages:\\n${messages.join('\\n')}` : ''\n return tool.error(`${title}:\\n${formatDiagnostics(diagnostics)}\\n\\n\\`\\`\\`json\\n${JSON.stringify(serialized, null, 2)}\\n\\`\\`\\`${log}`)\n }\n\n try {\n const hooks = new Hookable<KubbHooks>()\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)\n throw new Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'The config file exports an empty array of configurations',\n })\n userConfig = first\n } catch (error) {\n return failure(`Failed to load config ${file}`, [Diagnostics.from(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 return failure('Build failed', problems)\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 return failure('Build error', [Diagnostics.from(caughtError)])\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 diagnostic = Diagnostics.from(err)\n const serialized = Diagnostics.serialize(diagnostic)\n return tool.error(`Validation failed:\\n${formatDiagnostics([diagnostic])}\\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() {\n const server = createMcpServer()\n new StdioTransport(server).listen()\n return server\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.10.1",
3
+ "version": "5.10.3",
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",
@@ -51,9 +51,14 @@
51
51
  "types": "./dist/index.d.ts",
52
52
  "exports": {
53
53
  ".": {
54
- "types": "./dist/index.d.ts",
55
- "import": "./dist/index.js",
56
- "require": "./dist/index.cjs"
54
+ "import": {
55
+ "types": "./dist/index.d.ts",
56
+ "default": "./dist/index.js"
57
+ },
58
+ "require": {
59
+ "types": "./dist/index.d.cts",
60
+ "default": "./dist/index.cjs"
61
+ }
57
62
  },
58
63
  "./package.json": "./package.json"
59
64
  },
@@ -62,8 +67,8 @@
62
67
  "registry": "https://registry.npmjs.org/"
63
68
  },
64
69
  "dependencies": {
65
- "@kubb/adapter-oas": "5.10.1",
66
- "@kubb/core": "5.10.1",
70
+ "@kubb/adapter-oas": "5.10.3",
71
+ "@kubb/core": "5.10.3",
67
72
  "@tmcp/transport-stdio": "^0.5.0",
68
73
  "jiti": "^2.7.0",
69
74
  "tmcp": "^1.20.0",
@@ -72,10 +77,10 @@
72
77
  "devDependencies": {
73
78
  "@internals/shared": "0.0.1",
74
79
  "@internals/utils": "0.0.1",
75
- "@kubb/renderer-jsx": "5.10.1"
80
+ "@kubb/renderer-jsx": "5.10.3"
76
81
  },
77
82
  "peerDependencies": {
78
- "@kubb/renderer-jsx": "5.10.1"
83
+ "@kubb/renderer-jsx": "5.10.3"
79
84
  },
80
85
  "engines": {
81
86
  "node": ">=22"
@@ -1,8 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __name = (target, value) => __defProp(target, "name", {
4
- value,
5
- configurable: true
6
- });
7
- //#endregion
8
- export { __name as t };