@kubb/mcp 5.0.0-beta.63 → 5.0.0-beta.65

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/src/tools/init.ts DELETED
@@ -1,41 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import process from 'node:process'
4
- import { availablePlugins, generateConfigFile, KUBB_CONFIG_FILENAME, type PluginOption } from '@internals/shared'
5
- import { defineTool } from 'tmcp/tool'
6
- import { tool } from 'tmcp/utils'
7
- import { initSchema } from '../schemas/initSchema.ts'
8
-
9
- /**
10
- * Resolves a comma-separated plugin flag into the matching known plugin options.
11
- * Unrecognized names are dropped, and a missing flag yields an empty list.
12
- */
13
- export function resolvePlugins(pluginsFlag: string | undefined): Array<PluginOption> {
14
- if (!pluginsFlag) {
15
- return []
16
- }
17
- const requested = pluginsFlag
18
- .split(',')
19
- .map((v) => v.trim())
20
- .filter(Boolean)
21
- return availablePlugins.filter((p) => requested.includes(p.value))
22
- }
23
-
24
- export const initTool = defineTool(
25
- {
26
- name: 'init',
27
- description: 'Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.',
28
- schema: initSchema,
29
- },
30
- async ({ input = './openapi.yaml', output = './src/gen', plugins }) => {
31
- const selected = resolvePlugins(plugins)
32
- const content = generateConfigFile({ selectedPlugins: selected, inputPath: input, outputPath: output })
33
- const dest = path.join(process.cwd(), KUBB_CONFIG_FILENAME)
34
- if (fs.existsSync(dest)) {
35
- return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`)
36
- }
37
- fs.writeFileSync(dest, content, 'utf-8')
38
- const packageList = ['kubb', ...selected.map((p) => p.packageName)].join(' ')
39
- return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`)
40
- },
41
- )
@@ -1,28 +0,0 @@
1
- import { Diagnostics } from '@kubb/core'
2
- import { defineTool } from 'tmcp/tool'
3
- import { tool } from 'tmcp/utils'
4
- import { validateSchema } from '../schemas/validateSchema.ts'
5
- import { formatDiagnostics } from '../utils.ts'
6
-
7
- export const validateTool = defineTool(
8
- {
9
- name: 'validate',
10
- description: 'Validate an OpenAPI/Swagger specification file or URL',
11
- schema: validateSchema,
12
- },
13
- async ({ input }) => {
14
- let mod: typeof import('@kubb/adapter-oas')
15
- try {
16
- mod = await import('@kubb/adapter-oas')
17
- } catch {
18
- return tool.error('The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas')
19
- }
20
- try {
21
- await mod.adapterOas().validate(input, { throwOnError: true })
22
- return tool.text(`Validation successful: ${input}`)
23
- } catch (err) {
24
- const serialized = Diagnostics.serialize(Diagnostics.from(err))
25
- return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``)
26
- }
27
- },
28
- )
package/src/utils.ts DELETED
@@ -1,152 +0,0 @@
1
- import { existsSync } from 'node:fs'
2
- import path from 'node:path'
3
- import { createModuleLoader } from '@internals/shared'
4
- import { isPromise } from '@internals/utils'
5
- import type { CLIOptions, Config, PossibleConfig, SerializedDiagnostic } from '@kubb/core'
6
- import { ALLOWED_CONFIG_EXTENSIONS, NotifyTypes } from './constants.ts'
7
-
8
- /**
9
- * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
10
- * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
11
- * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
12
- * unlike the CLI renderer.
13
- */
14
- export function formatDiagnostics(diagnostics: ReadonlyArray<SerializedDiagnostic>): string {
15
- return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join('\n\n')
16
- }
17
-
18
- function formatDiagnostic(diagnostic: SerializedDiagnostic): string {
19
- const { code, severity, message, location, help, plugin, docsUrl } = diagnostic
20
- const rule = plugin ? `${plugin}(${code})` : code
21
- const lines = [`${severity} ${rule}: ${message}`]
22
-
23
- if (location && 'pointer' in location) {
24
- lines.push(` at ${location.pointer}`)
25
- }
26
- if (help) {
27
- lines.push(` help: ${help}`)
28
- }
29
- if (docsUrl) {
30
- lines.push(` docs: ${docsUrl}`)
31
- }
32
-
33
- return lines.join('\n')
34
- }
35
-
36
- type NotifyFunction = (type: string, message: string, data?: Record<string, unknown>) => Promise<void>
37
-
38
- const loader = createModuleLoader()
39
-
40
- const loadedModules = new Map<string, unknown>()
41
-
42
- async function loadModule(filePath: string): Promise<unknown> {
43
- const ext = path.extname(filePath)
44
- if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
45
- throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(', ')}`)
46
- }
47
- if (loadedModules.has(filePath)) {
48
- return loadedModules.get(filePath)
49
- }
50
- const mod = await loader.load(filePath, { default: true })
51
- loadedModules.set(filePath, mod)
52
- return mod
53
- }
54
-
55
- /**
56
- * Loads the user's Kubb config and returns it with the directory it was found in.
57
- *
58
- * When `configPath` is given it must use an allowed extension and resolve inside
59
- * the current working directory, otherwise loading throws. When omitted, the
60
- * known `kubb.config.*` file names are tried in the current directory. Every
61
- * outcome is reported through `notify` before the function returns or throws.
62
- */
63
- export async function loadUserConfig(configPath: string | undefined, { notify }: { notify: NotifyFunction }): Promise<{ userConfig: Config; cwd: string }> {
64
- if (configPath) {
65
- const ext = path.extname(configPath)
66
- if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
67
- const msg = `Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(', ')}`
68
- await notify(NotifyTypes.CONFIG_ERROR, msg)
69
- throw new Error(msg)
70
- }
71
- const base = path.resolve(process.cwd())
72
- const resolvedConfigPath = path.resolve(base, configPath)
73
- const relative = path.relative(base, resolvedConfigPath)
74
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
75
- const msg = 'Invalid config file path: must be within the current working directory'
76
- await notify(NotifyTypes.CONFIG_ERROR, msg)
77
- throw new Error(msg)
78
- }
79
- const cwd = path.dirname(resolvedConfigPath)
80
- try {
81
- const userConfig = (await loadModule(resolvedConfigPath)) as Config
82
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`)
83
- return { userConfig, cwd }
84
- } catch (error) {
85
- const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`
86
- await notify(NotifyTypes.CONFIG_ERROR, msg)
87
- throw new Error(msg)
88
- }
89
- }
90
-
91
- const cwd = process.cwd()
92
- const configFileNames = ['kubb.config.ts', 'kubb.config.mts', 'kubb.config.cts', 'kubb.config.js', 'kubb.config.cjs']
93
-
94
- for (const configFileName of configFileNames) {
95
- const configFilePath = path.resolve(process.cwd(), configFileName)
96
- if (!existsSync(configFilePath)) continue
97
- try {
98
- const userConfig = (await loadModule(configFilePath)) as Config
99
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`)
100
- return { userConfig, cwd }
101
- } catch (err) {
102
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`)
103
- }
104
- }
105
-
106
- await notify(NotifyTypes.CONFIG_ERROR, 'No config file found')
107
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(', ')}`)
108
- }
109
-
110
- /**
111
- * Determine the root directory based on userConfig.root and resolvedConfigDir
112
- * 1. If userConfig.root exists and is absolute, use it as-is
113
- * 2. If userConfig.root exists and is relative, resolve it relative to config directory
114
- * 3. Otherwise, use the config directory as root
115
- */
116
- export function resolveCwd(userConfig: Config, cwd: string): string {
117
- if (userConfig.root) {
118
- if (path.isAbsolute(userConfig.root)) {
119
- return userConfig.root
120
- }
121
-
122
- return path.resolve(cwd, userConfig.root)
123
- }
124
-
125
- return cwd
126
- }
127
-
128
- /**
129
- * Inputs forwarded to a config when it is defined as a function.
130
- */
131
- export type ResolveUserConfigOptions = {
132
- /**
133
- * Path of the loaded config, passed through to the config function as `config`.
134
- */
135
- configPath?: string
136
- /**
137
- * Log level passed through to the config function.
138
- */
139
- logLevel?: string
140
- }
141
-
142
- /**
143
- * Normalizes a possible config into a single resolved `Config`.
144
- *
145
- * Calls the config when it is a function, awaits it when it is a promise, and
146
- * picks the first entry when it resolves to an array.
147
- */
148
- export async function resolveUserConfig(config: PossibleConfig<CLIOptions>, options: ResolveUserConfigOptions): Promise<Config> {
149
- const result = typeof config === 'function' ? config({ logLevel: options.logLevel as CLIOptions['logLevel'], config: options.configPath }) : config
150
- const resolved = isPromise(result) ? await result : result
151
- return (Array.isArray(resolved) ? resolved[0] : resolved) as Config
152
- }