@kubb/plugin-mcp 5.0.0-beta.3 → 5.0.0-beta.30

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/package.json CHANGED
@@ -1,19 +1,17 @@
1
1
  {
2
2
  "name": "@kubb/plugin-mcp",
3
- "version": "5.0.0-beta.3",
4
- "description": "Model Context Protocol (MCP) plugin for Kubb, generating MCP-compatible tools and schemas from OpenAPI specifications for AI assistants.",
3
+ "version": "5.0.0-beta.30",
4
+ "description": "Generate Model Context Protocol (MCP) tool definitions from your OpenAPI specification. Expose your REST APIs as AI-callable tools for LLMs, Claude, ChatGPT, and other AI assistants.",
5
5
  "keywords": [
6
6
  "ai",
7
7
  "ai-tools",
8
- "claude",
9
- "code-generator",
8
+ "code-generation",
10
9
  "codegen",
11
10
  "kubb",
12
11
  "llm",
13
12
  "mcp",
14
13
  "model-context-protocol",
15
14
  "openapi",
16
- "plugins",
17
15
  "swagger",
18
16
  "typescript"
19
17
  ],
@@ -27,7 +25,7 @@
27
25
  "files": [
28
26
  "src",
29
27
  "dist",
30
- "plugin.json",
28
+ "extension.yaml",
31
29
  "!/**/**.test.**",
32
30
  "!/**/__tests__/**",
33
31
  "!/**/__snapshots__/**"
@@ -49,17 +47,18 @@
49
47
  "registry": "https://registry.npmjs.org/"
50
48
  },
51
49
  "dependencies": {
52
- "@kubb/core": "5.0.0-beta.3",
53
- "@kubb/renderer-jsx": "5.0.0-beta.3",
54
- "@kubb/plugin-client": "5.0.0-beta.3",
55
- "@kubb/plugin-ts": "5.0.0-beta.3",
56
- "@kubb/plugin-zod": "5.0.0-beta.3"
50
+ "@kubb/core": "5.0.0-beta.29",
51
+ "@kubb/renderer-jsx": "5.0.0-beta.29",
52
+ "@kubb/plugin-client": "5.0.0-beta.30",
53
+ "@kubb/plugin-ts": "5.0.0-beta.30",
54
+ "@kubb/plugin-zod": "5.0.0-beta.30"
57
55
  },
58
56
  "devDependencies": {
57
+ "@internals/shared": "0.0.0",
59
58
  "@internals/utils": "0.0.0"
60
59
  },
61
60
  "peerDependencies": {
62
- "@kubb/renderer-jsx": "5.0.0-beta.3"
61
+ "@kubb/renderer-jsx": "5.0.0-beta.29"
63
62
  },
64
63
  "size-limit": [
65
64
  {
@@ -1,11 +1,11 @@
1
- import { isValidVarName, URLPath } from '@internals/utils'
1
+ import { buildOperationComments, buildTransformedParamsMapping, getOperationParameters } from '@internals/shared'
2
+ import { camelCase, isValidVarName, URLPath } from '@internals/utils'
2
3
  import { ast } from '@kubb/core'
3
4
  import type { ResolverTs } from '@kubb/plugin-ts'
4
5
  import { functionPrinter } from '@kubb/plugin-ts'
5
6
  import { File, Function } from '@kubb/renderer-jsx'
6
7
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
7
8
  import type { PluginMcp } from '../types.ts'
8
- import { getComments, getParamsMapping } from '../utils.ts'
9
9
 
10
10
  type Props = {
11
11
  /**
@@ -23,7 +23,7 @@ type Props = {
23
23
  /**
24
24
  * Base URL prepended to every generated request URL.
25
25
  */
26
- baseURL: string | undefined
26
+ baseURL: string | null | undefined
27
27
  /**
28
28
  * Return type when calling fetch.
29
29
  * - 'data' returns response data only.
@@ -54,16 +54,10 @@ export function McpHandler({ name, node, resolver, baseURL, dataReturnType, para
54
54
  const contentType = node.requestBody?.content?.[0]?.contentType
55
55
  const isFormData = contentType === 'multipart/form-data'
56
56
 
57
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
58
- const queryParams = casedParams.filter((p) => p.in === 'query')
59
- const headerParams = casedParams.filter((p) => p.in === 'header')
57
+ const { query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing })
58
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node)
60
59
 
61
- // Use original (uncased) parameters for mapping so original→camelCase difference is detected
62
- const originalPathParams = node.parameters.filter((p) => p.in === 'path')
63
- const originalQueryParams = node.parameters.filter((p) => p.in === 'query')
64
- const originalHeaderParams = node.parameters.filter((p) => p.in === 'header')
65
-
66
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : undefined
60
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null
67
61
  const responseName = resolver.resolveResponseName(node)
68
62
 
69
63
  const errorResponses = node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => resolver.resolveResponseStatusName(node, r.statusCode))
@@ -83,15 +77,15 @@ export function McpHandler({ name, node, resolver, baseURL, dataReturnType, para
83
77
  ? `${baseParamsSignature}, request: RequestHandlerExtra<ServerRequest, ServerNotification>`
84
78
  : 'request: RequestHandlerExtra<ServerRequest, ServerNotification>'
85
79
 
86
- const pathParamsMapping = paramsCasing ? getParamsMapping(originalPathParams) : undefined
87
- const queryParamsMapping = paramsCasing ? getParamsMapping(originalQueryParams) : undefined
88
- const headerParamsMapping = paramsCasing ? getParamsMapping(originalHeaderParams) : undefined
80
+ const pathParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalPathParams, camelCase) : null
81
+ const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) : null
82
+ const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) : null
89
83
 
90
84
  const contentTypeHeader =
91
- contentType && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : undefined
92
- const headers = [headerParams.length ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : undefined, contentTypeHeader].filter(Boolean)
85
+ contentType && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null
86
+ const headers = [headerParams.length ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : null, contentTypeHeader].filter(Boolean)
93
87
 
94
- const fetchConfig: string[] = []
88
+ const fetchConfig: Array<string> = []
95
89
  fetchConfig.push(`method: ${JSON.stringify(node.method.toUpperCase())}`)
96
90
  fetchConfig.push(`url: ${urlPath.template}`)
97
91
  if (baseURL) fetchConfig.push(`baseURL: \`${baseURL}\``)
@@ -128,7 +122,7 @@ export function McpHandler({ name, node, resolver, baseURL, dataReturnType, para
128
122
  export
129
123
  params={paramsSignature}
130
124
  JSDoc={{
131
- comments: getComments(node),
125
+ comments: buildOperationComments(node),
132
126
  }}
133
127
  returnType={'Promise<CallToolResult>'}
134
128
  >
@@ -164,7 +158,7 @@ export function McpHandler({ name, node, resolver, baseURL, dataReturnType, para
164
158
  <br />
165
159
  {isFormData && requestName && 'const formData = buildFormData(requestData)'}
166
160
  <br />
167
- {`const res = await fetch<${generics.join(', ')}>({ ${fetchConfig.join(', ')} }, request)`}
161
+ {`const res = await client<${generics.join(', ')}>({ ${fetchConfig.join(', ')} }, request)`}
168
162
  <br />
169
163
  {callToolResult}
170
164
  </Function>
@@ -1,3 +1,4 @@
1
+ import { getOperationParameters } from '@internals/shared'
1
2
  import { ast } from '@kubb/core'
2
3
  import { functionPrinter } from '@kubb/plugin-ts'
3
4
  import { Const, File, Function } from '@kubb/renderer-jsx'
@@ -42,13 +43,13 @@ type Props = {
42
43
  /**
43
44
  * Query params — individual schemas to compose into `z.object({ ... })`.
44
45
  */
45
- queryParams?: string | Array<ZodParam>
46
+ queryParams?: string | Array<ZodParam> | null
46
47
  /**
47
48
  * Header params — individual schemas to compose into `z.object({ ... })`.
48
49
  */
49
- headerParams?: string | Array<ZodParam>
50
- requestName?: string
51
- responseName?: string
50
+ headerParams?: string | Array<ZodParam> | null
51
+ requestName?: string | null
52
+ responseName?: string | null
52
53
  }
53
54
  node: ast.OperationNode
54
55
  }>
@@ -70,8 +71,7 @@ export function Server({ name, serverName, serverVersion, paramsCasing, operatio
70
71
 
71
72
  {operations
72
73
  .map(({ tool, mcp, zod, node }) => {
73
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
74
- const pathParams = casedParams.filter((p) => p.in === 'path')
74
+ const { path: pathParams } = getOperationParameters(node, { paramsCasing })
75
75
 
76
76
  const pathEntries: Array<{ key: string; value: string }> = []
77
77
  const otherEntries: Array<{ key: string; value: string }> = []
@@ -104,10 +104,10 @@ export function Server({ name, serverName, serverVersion, paramsCasing, operatio
104
104
  }),
105
105
  ],
106
106
  })
107
- : undefined
107
+ : null
108
108
 
109
109
  const destructured = paramsNode ? (keysPrinter.print(paramsNode) ?? '') : ''
110
- const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(', ')} }` : undefined
110
+ const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(', ')} }` : null
111
111
  const outputSchema = zod.responseName
112
112
 
113
113
  const config = [
@@ -1,13 +1,20 @@
1
1
  import path from 'node:path'
2
- import { ast, defineGenerator } from '@kubb/core'
2
+ import { resolveOperationTypeNames } from '@internals/shared'
3
+ import { defineGenerator } from '@kubb/core'
3
4
  import { pluginTsName } from '@kubb/plugin-ts'
4
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
5
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
5
6
  import { McpHandler } from '../components/McpHandler.tsx'
6
7
  import type { PluginMcp } from '../types.ts'
7
8
 
9
+ /**
10
+ * Built-in operation generator for `@kubb/plugin-mcp`. Emits one MCP tool
11
+ * handler per OpenAPI operation, wiring the input Zod schema, the HTTP call,
12
+ * and the response shape into a single function that an MCP server can
13
+ * register as a callable tool.
14
+ */
8
15
  export const mcpGenerator = defineGenerator<PluginMcp>({
9
16
  name: 'mcp',
10
- renderer: jsxRenderer,
17
+ renderer: jsxRendererSync,
11
18
  operation(node, ctx) {
12
19
  const { resolver, driver, root } = ctx
13
20
  const { output, client, paramsCasing, group } = ctx.options
@@ -20,30 +27,20 @@ export const mcpGenerator = defineGenerator<PluginMcp>({
20
27
 
21
28
  const tsResolver = driver.getResolver(pluginTsName)
22
29
 
23
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
24
-
25
- const pathParams = casedParams.filter((p) => p.in === 'path')
26
- const queryParams = casedParams.filter((p) => p.in === 'query')
27
- const headerParams = casedParams.filter((p) => p.in === 'header')
28
-
29
- const importedTypeNames = [
30
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
31
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
32
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
33
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
34
- tsResolver.resolveResponseName(node),
35
- ...node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode)),
36
- ].filter(Boolean)
30
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing, responseStatusNames: 'error' })
37
31
 
38
32
  const meta = {
39
- name: resolver.resolveName(node.operationId),
40
- file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
33
+ name: resolver.resolveHandlerName(node),
34
+ file: resolver.resolveFile(
35
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
36
+ { root, output, group: group ?? undefined },
37
+ ),
41
38
  fileTs: tsResolver.resolveFile(
42
39
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
43
40
  {
44
41
  root,
45
42
  output: pluginTs.options?.output ?? output,
46
- group: pluginTs.options?.group,
43
+ group: pluginTs.options?.group ?? undefined,
47
44
  },
48
45
  ),
49
46
  } as const
@@ -59,7 +56,7 @@ export const mcpGenerator = defineGenerator<PluginMcp>({
59
56
  {client.importPath ? (
60
57
  <>
61
58
  <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={client.importPath} isTypeOnly />
62
- <File.Import name={'fetch'} path={client.importPath} />
59
+ <File.Import name={'client'} path={client.importPath} />
63
60
  {client.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={client.importPath} isTypeOnly />}
64
61
  </>
65
62
  ) : (
@@ -67,12 +64,12 @@ export const mcpGenerator = defineGenerator<PluginMcp>({
67
64
  <File.Import
68
65
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
69
66
  root={meta.file.path}
70
- path={path.resolve(root, '.kubb/fetch.ts')}
67
+ path={path.resolve(root, '.kubb/client.ts')}
71
68
  isTypeOnly
72
69
  />
73
- <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />
70
+ <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />
74
71
  {client.dataReturnType === 'full' && (
75
- <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
72
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
76
73
  )}
77
74
  </>
78
75
  )}
@@ -1,10 +1,10 @@
1
1
  import path from 'node:path'
2
- import { ast, defineGenerator } from '@kubb/core'
2
+ import { findSuccessStatusCode, getOperationParameters } from '@internals/shared'
3
+ import { defineGenerator } from '@kubb/core'
3
4
  import { pluginZodName } from '@kubb/plugin-zod'
4
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
5
+ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
5
6
  import { Server } from '../components/Server.tsx'
6
7
  import type { PluginMcp } from '../types.ts'
7
- import { findSuccessStatusCode } from '../utils.ts'
8
8
 
9
9
  /**
10
10
  * Default v5 server generator for `@kubb/plugin-mcp`.
@@ -15,9 +15,9 @@ import { findSuccessStatusCode } from '../utils.ts'
15
15
  */
16
16
  export const serverGenerator = defineGenerator<PluginMcp>({
17
17
  name: 'operations',
18
- renderer: jsxRenderer,
18
+ renderer: jsxRendererSync,
19
19
  operations(nodes, ctx) {
20
- const { adapter, config, resolver, plugin, driver, root } = ctx
20
+ const { config, resolver, plugin, driver, root } = ctx
21
21
  const { output, paramsCasing, group } = ctx.options
22
22
 
23
23
  const pluginZod = driver.getPlugin(pluginZodName)
@@ -44,25 +44,25 @@ export const serverGenerator = defineGenerator<PluginMcp>({
44
44
  }
45
45
 
46
46
  const operationsMapped = nodes.map((node) => {
47
- const casedParams = ast.caseParams(node.parameters, paramsCasing)
48
- const pathParams = casedParams.filter((p) => p.in === 'path')
49
- const queryParams = casedParams.filter((p) => p.in === 'query')
50
- const headerParams = casedParams.filter((p) => p.in === 'header')
47
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing })
51
48
 
52
- const mcpFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
49
+ const mcpFile = resolver.resolveFile(
50
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
51
+ { root, output, group: group ?? undefined },
52
+ )
53
53
 
54
54
  const zodFile = zodResolver.resolveFile(
55
55
  { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
56
56
  {
57
57
  root,
58
58
  output: pluginZod.options?.output ?? output,
59
- group: pluginZod.options?.group,
59
+ group: pluginZod.options?.group ?? undefined,
60
60
  },
61
61
  )
62
62
 
63
- const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : undefined
63
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : null
64
64
  const successStatus = findSuccessStatusCode(node.responses)
65
- const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : undefined
65
+ const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : null
66
66
 
67
67
  const resolveParams = (params: typeof pathParams) => params.map((p) => ({ name: p.name, schemaName: zodResolver.resolveParamName(node, p) }))
68
68
 
@@ -73,13 +73,13 @@ export const serverGenerator = defineGenerator<PluginMcp>({
73
73
  description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`,
74
74
  },
75
75
  mcp: {
76
- name: resolver.resolveName(node.operationId),
76
+ name: resolver.resolveHandlerName(node),
77
77
  file: mcpFile,
78
78
  },
79
79
  zod: {
80
80
  pathParams: resolveParams(pathParams),
81
- queryParams: queryParams.length ? resolveParams(queryParams) : undefined,
82
- headerParams: headerParams.length ? resolveParams(headerParams) : undefined,
81
+ queryParams: queryParams.length ? resolveParams(queryParams) : null,
82
+ headerParams: headerParams.length ? resolveParams(headerParams) : null,
83
83
  requestName,
84
84
  responseName,
85
85
  file: zodFile,
@@ -95,7 +95,7 @@ export const serverGenerator = defineGenerator<PluginMcp>({
95
95
  ...(zod.headerParams ?? []).map((p) => p.schemaName),
96
96
  zod.requestName,
97
97
  zod.responseName,
98
- ].filter(Boolean)
98
+ ].filter((name): name is string => Boolean(name))
99
99
 
100
100
  const uniqueNames = [...new Set(zodNames)].sort()
101
101
 
@@ -111,8 +111,8 @@ export const serverGenerator = defineGenerator<PluginMcp>({
111
111
  baseName={serverFile.baseName}
112
112
  path={serverFile.path}
113
113
  meta={serverFile.meta}
114
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
115
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
114
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: serverFile.path, baseName: serverFile.baseName } })}
115
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: serverFile.path, baseName: serverFile.baseName } })}
116
116
  >
117
117
  <File.Import name={['McpServer']} path={'@modelcontextprotocol/sdk/server/mcp'} />
118
118
  <File.Import name={['z']} path={'zod'} />
@@ -121,8 +121,8 @@ export const serverGenerator = defineGenerator<PluginMcp>({
121
121
  {imports}
122
122
  <Server
123
123
  name={name}
124
- serverName={adapter.inputNode?.meta?.title ?? 'server'}
125
- serverVersion={adapter.inputNode?.meta?.version ?? '0.0.0'}
124
+ serverName={ctx.meta.title ?? 'server'}
125
+ serverVersion={ctx.meta.version ?? '0.0.0'}
126
126
  paramsCasing={paramsCasing}
127
127
  operations={operationsMapped}
128
128
  />
@@ -133,7 +133,7 @@ export const serverGenerator = defineGenerator<PluginMcp>({
133
133
  {`
134
134
  {
135
135
  "mcpServers": {
136
- "${adapter.inputNode?.meta?.title || 'server'}": {
136
+ "${ctx.meta.title || 'server'}": {
137
137
  "type": "stdio",
138
138
  "command": "npx",
139
139
  "args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
package/src/plugin.ts CHANGED
@@ -13,8 +13,40 @@ import { serverGenerator } from './generators/serverGenerator.tsx'
13
13
  import { resolverMcp } from './resolvers/resolverMcp.ts'
14
14
  import type { PluginMcp } from './types.ts'
15
15
 
16
+ /**
17
+ * Canonical plugin name for `@kubb/plugin-mcp`. Used for driver lookups and
18
+ * cross-plugin dependency references.
19
+ */
16
20
  export const pluginMcpName = 'plugin-mcp' satisfies PluginMcp['name']
17
21
 
22
+ /**
23
+ * Generates a Model Context Protocol (MCP) server from an OpenAPI spec. Every
24
+ * operation becomes a typed MCP tool that AI assistants (Claude Desktop, Claude
25
+ * Code, MCP-compatible clients) can call directly.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { defineConfig } from 'kubb'
30
+ * import { pluginTs } from '@kubb/plugin-ts'
31
+ * import { pluginClient } from '@kubb/plugin-client'
32
+ * import { pluginZod } from '@kubb/plugin-zod'
33
+ * import { pluginMcp } from '@kubb/plugin-mcp'
34
+ *
35
+ * export default defineConfig({
36
+ * input: { path: './petStore.yaml' },
37
+ * output: { path: './src/gen' },
38
+ * plugins: [
39
+ * pluginTs(),
40
+ * pluginClient(),
41
+ * pluginZod(),
42
+ * pluginMcp({
43
+ * output: { path: './mcp' },
44
+ * client: { baseURL: 'https://petstore.swagger.io/v2' },
45
+ * }),
46
+ * ],
47
+ * })
48
+ * ```
49
+ */
18
50
  export const pluginMcp = definePlugin<PluginMcp>((options) => {
19
51
  const {
20
52
  output = { path: 'mcp', barrelType: 'named' },
@@ -44,7 +76,7 @@ export const pluginMcp = definePlugin<PluginMcp>((options) => {
44
76
  return `${camelCase(ctx.group)}Requests`
45
77
  },
46
78
  } satisfies Group)
47
- : undefined
79
+ : null
48
80
 
49
81
  return {
50
82
  name: pluginMcpName,
@@ -87,11 +119,11 @@ export const pluginMcp = definePlugin<PluginMcp>((options) => {
87
119
 
88
120
  if (client?.bundle && !hasClientPlugin && !clientImportPath) {
89
121
  ctx.injectFile({
90
- baseName: 'fetch.ts',
91
- path: path.resolve(root, '.kubb/fetch.ts'),
122
+ baseName: 'client.ts',
123
+ path: path.resolve(root, '.kubb/client.ts'),
92
124
  sources: [
93
125
  ast.createSource({
94
- name: 'fetch',
126
+ name: 'client',
95
127
  nodes: [ast.createText(clientName === 'fetch' ? fetchClientSource : axiosClientSource)],
96
128
  isExportable: true,
97
129
  isIndexable: true,
@@ -3,14 +3,18 @@ import { defineResolver } from '@kubb/core'
3
3
  import type { PluginMcp } from '../types.ts'
4
4
 
5
5
  /**
6
- * Naming convention resolver for MCP plugin.
6
+ * Default resolver used by `@kubb/plugin-mcp`. Decides the names and file
7
+ * paths for every generated MCP tool handler. Function names get a `Handler`
8
+ * suffix so an operation `addPet` becomes `addPetHandler`.
7
9
  *
8
- * Provides default naming helpers using camelCase with a `handler` suffix for functions.
10
+ * @example Resolve a handler name
11
+ * ```ts
12
+ * import { resolverMcp } from '@kubb/plugin-mcp'
9
13
  *
10
- * @example
11
- * `resolverMcp.default('addPet', 'function') // → 'addPetHandler'`
14
+ * resolverMcp.default('addPet', 'function') // 'addPetHandler'
15
+ * ```
12
16
  */
13
- export const resolverMcp = defineResolver<PluginMcp>((ctx) => ({
17
+ export const resolverMcp = defineResolver<PluginMcp>(() => ({
14
18
  name: 'default',
15
19
  pluginName: 'plugin-mcp',
16
20
  default(name, type) {
@@ -20,6 +24,12 @@ export const resolverMcp = defineResolver<PluginMcp>((ctx) => ({
20
24
  return camelCase(name, { suffix: 'handler' })
21
25
  },
22
26
  resolveName(name) {
23
- return ctx.default(name, 'function')
27
+ return this.default(name, 'function')
28
+ },
29
+ resolvePathName(name, type) {
30
+ return this.default(name, type)
31
+ },
32
+ resolveHandlerName(node) {
33
+ return this.resolveName(node.operationId)
24
34
  },
25
35
  }))
package/src/types.ts CHANGED
@@ -6,54 +6,68 @@ import type { ClientImportPath, PluginClient } from '@kubb/plugin-client'
6
6
  */
7
7
  export type ResolverMcp = Resolver & {
8
8
  /**
9
- * Resolves the handler function name for an operation.
9
+ * Resolves the base handler function name for an operation.
10
10
  *
11
11
  * @example Resolving handler function names
12
12
  * `resolver.resolveName('show pet by id') // -> 'showPetByIdHandler'`
13
13
  */
14
14
  resolveName(this: ResolverMcp, name: string): string
15
+ /**
16
+ * Resolves the output file name for an MCP module.
17
+ */
18
+ resolvePathName(this: ResolverMcp, name: string, type?: 'file' | 'function' | 'type' | 'const'): string
19
+ /**
20
+ * Resolves the handler function name for an operation.
21
+ */
22
+ resolveHandlerName(this: ResolverMcp, node: ast.OperationNode): string
15
23
  }
16
24
 
17
25
  export type Options = {
18
26
  /**
19
- * Specify the export location for the files and define the behavior of the output.
20
- * @default { path: 'mcp', barrelType: 'named' }
27
+ * Where the generated MCP tool handlers are written and how they are exported.
28
+ *
29
+ * @default { path: 'mcp', barrel: { type: 'named' } }
21
30
  */
22
31
  output?: Output
23
32
  /**
24
- * Client configuration for HTTP request generation.
33
+ * HTTP client used by each MCP handler to call the underlying API. Mirrors a
34
+ * subset of `pluginClient` options.
25
35
  */
26
36
  client?: ClientImportPath & Pick<PluginClient['options'], 'clientType' | 'dataReturnType' | 'baseURL' | 'bundle' | 'paramsCasing'>
27
37
  /**
28
- * Apply casing to parameter names to match your configuration.
38
+ * Rename parameter properties in the generated handlers. The HTTP layer still
39
+ * uses the original spec names; Kubb writes the mapping for you.
40
+ *
41
+ * @note Must match the value of `paramsCasing` on `@kubb/plugin-ts`.
29
42
  */
30
43
  paramsCasing?: 'camelcase'
31
44
  /**
32
- * Group the MCP requests based on the provided name.
45
+ * Split generated files into subfolders based on the operation's tag.
33
46
  */
34
47
  group?: Group
35
48
  /**
36
- * Tags, operations, or paths to exclude from generation.
49
+ * Skip operations matching at least one entry in the list.
37
50
  */
38
51
  exclude?: Array<Exclude>
39
52
  /**
40
- * Tags, operations, or paths to include in generation.
53
+ * Restrict generation to operations matching at least one entry in the list.
41
54
  */
42
55
  include?: Array<Include>
43
56
  /**
44
- * Override options for specific tags, operations, or paths.
57
+ * Apply a different options object to operations matching a pattern.
45
58
  */
46
59
  override?: Array<Override<ResolvedOptions>>
47
60
  /**
48
- * Override naming conventions for function names and types.
61
+ * Override how handler names and file paths are built. Methods you omit fall
62
+ * back to the default `resolverMcp`.
49
63
  */
50
64
  resolver?: Partial<ResolverMcp> & ThisType<ResolverMcp>
51
65
  /**
52
- * AST visitor to transform generated nodes.
66
+ * AST visitor applied to each operation node before printing.
53
67
  */
54
68
  transformer?: ast.Visitor
55
69
  /**
56
- * Additional generators alongside the default generators.
70
+ * Custom generators that run alongside the built-in MCP generators.
57
71
  */
58
72
  generators?: Array<Generator<PluginMcp>>
59
73
  }
@@ -63,7 +77,7 @@ type ResolvedOptions = {
63
77
  exclude: Array<Exclude>
64
78
  include: Array<Include> | undefined
65
79
  override: Array<Override<ResolvedOptions>>
66
- group: Group | undefined
80
+ group: Group | null
67
81
  client: Pick<PluginClient['options'], 'client' | 'clientType' | 'dataReturnType' | 'importPath' | 'baseURL' | 'bundle' | 'paramsCasing'>
68
82
  paramsCasing: Options['paramsCasing']
69
83
  resolver: ResolverMcp