@kubb/plugin-mcp 5.0.0-alpha.9 → 5.0.0-beta.10

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.
@@ -1,109 +1,78 @@
1
1
  import path from 'node:path'
2
- import { Client } from '@kubb/plugin-client/components'
3
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
4
- import { useOas, useOperationManager } from '@kubb/plugin-oas/hooks'
5
- import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
2
+ import { resolveOperationTypeNames } from '@internals/shared'
3
+ import { defineGenerator } from '@kubb/core'
6
4
  import { pluginTsName } from '@kubb/plugin-ts'
7
- import { File } from '@kubb/react-fabric'
8
- import type { PluginMcp } from '../types'
5
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
6
+ import { McpHandler } from '../components/McpHandler.tsx'
7
+ import type { PluginMcp } from '../types.ts'
9
8
 
10
- export const mcpGenerator = createReactGenerator<PluginMcp>({
9
+ export const mcpGenerator = defineGenerator<PluginMcp>({
11
10
  name: 'mcp',
12
- Operation({ config, operation, generator, plugin }) {
13
- const { options } = plugin
14
- const oas = useOas()
11
+ renderer: jsxRenderer,
12
+ operation(node, ctx) {
13
+ const { resolver, driver, root } = ctx
14
+ const { output, client, paramsCasing, group } = ctx.options
15
15
 
16
- const { getSchemas, getName, getFile } = useOperationManager(generator)
16
+ const pluginTs = driver.getPlugin(pluginTsName)
17
17
 
18
- const mcp = {
19
- name: getName(operation, { type: 'function', suffix: 'handler' }),
20
- file: getFile(operation),
18
+ if (!pluginTs) {
19
+ return null
21
20
  }
22
21
 
23
- const type = {
24
- file: getFile(operation, { pluginName: pluginTsName }),
25
- schemas: getSchemas(operation, { pluginName: pluginTsName, type: 'type' }),
26
- }
22
+ const tsResolver = driver.getResolver(pluginTsName)
23
+
24
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing, responseStatusNames: 'error' })
25
+
26
+ const meta = {
27
+ name: resolver.resolveHandlerName(node),
28
+ file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
29
+ fileTs: tsResolver.resolveFile(
30
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
31
+ {
32
+ root,
33
+ output: pluginTs.options?.output ?? output,
34
+ group: pluginTs.options?.group,
35
+ },
36
+ ),
37
+ } as const
27
38
 
28
39
  return (
29
- <File
30
- baseName={mcp.file.baseName}
31
- path={mcp.file.path}
32
- meta={mcp.file.meta}
33
- banner={getBanner({ oas, output: options.output })}
34
- footer={getFooter({ oas, output: options.output })}
35
- >
36
- {options.client.importPath ? (
40
+ <File baseName={meta.file.baseName} path={meta.file.path} meta={meta.file.meta}>
41
+ {meta.fileTs && importedTypeNames.length > 0 && (
42
+ <File.Import name={Array.from(new Set(importedTypeNames)).sort()} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
43
+ )}
44
+ <File.Import name={['CallToolResult', 'ServerNotification', 'ServerRequest']} path={'@modelcontextprotocol/sdk/types'} isTypeOnly />
45
+ <File.Import name={['RequestHandlerExtra']} path={'@modelcontextprotocol/sdk/shared/protocol'} isTypeOnly />
46
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
47
+ {client.importPath ? (
37
48
  <>
38
- <File.Import name={'fetch'} path={options.client.importPath} />
39
- <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={options.client.importPath} isTypeOnly />
40
- {options.client.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={options.client.importPath} isTypeOnly />}
49
+ <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={client.importPath} isTypeOnly />
50
+ <File.Import name={'fetch'} path={client.importPath} />
51
+ {client.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={client.importPath} isTypeOnly />}
41
52
  </>
42
53
  ) : (
43
54
  <>
44
- <File.Import name={['fetch']} root={mcp.file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
45
55
  <File.Import
46
56
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
47
- root={mcp.file.path}
48
- path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')}
57
+ root={meta.file.path}
58
+ path={path.resolve(root, '.kubb/fetch.ts')}
49
59
  isTypeOnly
50
60
  />
51
- {options.client.dataReturnType === 'full' && (
52
- <File.Import name={['ResponseConfig']} root={mcp.file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} isTypeOnly />
61
+ <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />
62
+ {client.dataReturnType === 'full' && (
63
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
53
64
  )}
54
65
  </>
55
66
  )}
56
- <File.Import name={['buildFormData']} root={mcp.file.path} path={path.resolve(config.root, config.output.path, '.kubb/config.ts')} />
57
- <File.Import name={['CallToolResult']} path={'@modelcontextprotocol/sdk/types'} isTypeOnly />
58
- <File.Import
59
- name={[
60
- type.schemas.request?.name,
61
- type.schemas.response.name,
62
- type.schemas.pathParams?.name,
63
- type.schemas.queryParams?.name,
64
- type.schemas.headerParams?.name,
65
- ...(type.schemas.statusCodes?.map((item) => item.name) || []),
66
- ].filter(Boolean)}
67
- root={mcp.file.path}
68
- path={type.file.path}
69
- isTypeOnly
70
- />
71
67
 
72
- <Client
73
- name={mcp.name}
74
- isConfigurable={false}
75
- returnType={'Promise<CallToolResult>'}
76
- baseURL={options.client.baseURL}
77
- operation={operation}
78
- typeSchemas={type.schemas}
79
- zodSchemas={undefined}
80
- dataReturnType={options.client.dataReturnType || 'data'}
81
- paramsType={'object'}
82
- paramsCasing={options.client?.paramsCasing || options.paramsCasing}
83
- pathParamsType={'object'}
84
- parser={'client'}
85
- >
86
- {options.client.dataReturnType === 'data' &&
87
- `return {
88
- content: [
89
- {
90
- type: 'text',
91
- text: JSON.stringify(res.data)
92
- }
93
- ],
94
- structuredContent: { data: res.data }
95
- }`}
96
- {options.client.dataReturnType === 'full' &&
97
- `return {
98
- content: [
99
- {
100
- type: 'text',
101
- text: JSON.stringify(res)
102
- }
103
- ],
104
- structuredContent: { data: res.data }
105
- }`}
106
- </Client>
68
+ <McpHandler
69
+ name={meta.name}
70
+ node={node}
71
+ resolver={tsResolver}
72
+ baseURL={client.baseURL}
73
+ dataReturnType={client.dataReturnType || 'data'}
74
+ paramsCasing={paramsCasing}
75
+ />
107
76
  </File>
108
77
  )
109
78
  },
@@ -1,81 +1,115 @@
1
- import { usePluginDriver } from '@kubb/core/hooks'
2
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
3
- import { useOas, useOperationManager } from '@kubb/plugin-oas/hooks'
4
- import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
5
- import { pluginTsName } from '@kubb/plugin-ts'
1
+ import path from 'node:path'
2
+ import { findSuccessStatusCode, getOperationParameters } from '@internals/shared'
3
+ import { defineGenerator } from '@kubb/core'
6
4
  import { pluginZodName } from '@kubb/plugin-zod'
7
- import { File } from '@kubb/react-fabric'
8
- import { Server } from '../components/Server'
9
- import type { PluginMcp } from '../types'
5
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
6
+ import { Server } from '../components/Server.tsx'
7
+ import type { PluginMcp } from '../types.ts'
10
8
 
11
- export const serverGenerator = createReactGenerator<PluginMcp>({
9
+ /**
10
+ * Default v5 server generator for `@kubb/plugin-mcp`.
11
+ *
12
+ * Uses individual zod schemas for each param (e.g. `createPetsPathUuidSchema`, `createPetsQueryOffsetSchema`)
13
+ * and `resolveResponseStatusName` for per-status response schemas.
14
+ * Query and header params are composed into `z.object({ ... })` from individual schemas.
15
+ */
16
+ export const serverGenerator = defineGenerator<PluginMcp>({
12
17
  name: 'operations',
13
- Operations({ operations, generator, plugin }) {
14
- const driver = usePluginDriver()
15
- const { options } = plugin
18
+ renderer: jsxRenderer,
19
+ operations(nodes, ctx) {
20
+ const { adapter, config, resolver, plugin, driver, root } = ctx
21
+ const { output, paramsCasing, group } = ctx.options
16
22
 
17
- const oas = useOas()
18
- const { getFile, getName, getSchemas } = useOperationManager(generator)
23
+ const pluginZod = driver.getPlugin(pluginZodName)
24
+
25
+ if (!pluginZod) {
26
+ return
27
+ }
28
+
29
+ const zodResolver = driver.getResolver(pluginZodName)
19
30
 
20
31
  const name = 'server'
21
- const file = driver.getFile({ name, extname: '.ts', pluginName: plugin.name })
32
+ const serverFilePath = path.resolve(root, output.path, 'server.ts')
33
+ const serverFile = {
34
+ baseName: 'server.ts' as const,
35
+ path: serverFilePath,
36
+ meta: { pluginName: plugin.name },
37
+ }
38
+
39
+ const jsonFilePath = path.resolve(root, output.path, '.mcp.json')
40
+ const jsonFile = {
41
+ baseName: '.mcp.json' as const,
42
+ path: jsonFilePath,
43
+ meta: { pluginName: plugin.name },
44
+ }
22
45
 
23
- const jsonFile = driver.getFile({ name: '.mcp', extname: '.json', pluginName: plugin.name })
46
+ const operationsMapped = nodes.map((node) => {
47
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing })
48
+
49
+ const mcpFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
50
+
51
+ const zodFile = zodResolver.resolveFile(
52
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
53
+ {
54
+ root,
55
+ output: pluginZod.options?.output ?? output,
56
+ group: pluginZod.options?.group,
57
+ },
58
+ )
59
+
60
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : undefined
61
+ const successStatus = findSuccessStatusCode(node.responses)
62
+ const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : undefined
63
+
64
+ const resolveParams = (params: typeof pathParams) => params.map((p) => ({ name: p.name, schemaName: zodResolver.resolveParamName(node, p) }))
24
65
 
25
- const operationsMapped = operations.map((operation) => {
26
66
  return {
27
67
  tool: {
28
- name: operation.getOperationId() || operation.getSummary() || `${operation.method.toUpperCase()} ${operation.path}`,
29
- title: operation.getSummary() || undefined,
30
- description: operation.getDescription() || `Make a ${operation.method.toUpperCase()} request to ${operation.path}`,
68
+ name: node.operationId,
69
+ title: node.summary || undefined,
70
+ description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`,
31
71
  },
32
72
  mcp: {
33
- name: getName(operation, {
34
- type: 'function',
35
- suffix: 'handler',
36
- }),
37
- file: getFile(operation),
73
+ name: resolver.resolveHandlerName(node),
74
+ file: mcpFile,
38
75
  },
39
76
  zod: {
40
- name: getName(operation, {
41
- type: 'function',
42
- pluginName: pluginZodName,
43
- }),
44
- schemas: getSchemas(operation, { pluginName: pluginZodName, type: 'function' }),
45
- file: getFile(operation, { pluginName: pluginZodName }),
46
- },
47
- type: {
48
- schemas: getSchemas(operation, { pluginName: pluginTsName, type: 'type' }),
77
+ pathParams: resolveParams(pathParams),
78
+ queryParams: queryParams.length ? resolveParams(queryParams) : undefined,
79
+ headerParams: headerParams.length ? resolveParams(headerParams) : undefined,
80
+ requestName,
81
+ responseName,
82
+ file: zodFile,
49
83
  },
84
+ node: node,
50
85
  }
51
86
  })
52
87
 
53
88
  const imports = operationsMapped.flatMap(({ mcp, zod }) => {
89
+ const zodNames = [
90
+ ...zod.pathParams.map((p) => p.schemaName),
91
+ ...(zod.queryParams ?? []).map((p) => p.schemaName),
92
+ ...(zod.headerParams ?? []).map((p) => p.schemaName),
93
+ zod.requestName,
94
+ zod.responseName,
95
+ ].filter((name): name is string => Boolean(name))
96
+
97
+ const uniqueNames = [...new Set(zodNames)].sort()
98
+
54
99
  return [
55
- <File.Import key={mcp.name} name={[mcp.name]} root={file.path} path={mcp.file.path} />,
56
- <File.Import
57
- key={zod.name}
58
- name={[
59
- zod.schemas.request?.name,
60
- zod.schemas.pathParams?.name,
61
- zod.schemas.queryParams?.name,
62
- zod.schemas.headerParams?.name,
63
- zod.schemas.response?.name,
64
- ].filter(Boolean)}
65
- root={file.path}
66
- path={zod.file.path}
67
- />,
68
- ]
100
+ <File.Import key={mcp.name} name={[mcp.name]} root={serverFile.path} path={mcp.file.path} />,
101
+ uniqueNames.length > 0 && <File.Import key={`zod-${mcp.name}`} name={uniqueNames} root={serverFile.path} path={zod.file.path} />,
102
+ ].filter(Boolean)
69
103
  })
70
104
 
71
105
  return (
72
106
  <>
73
107
  <File
74
- baseName={file.baseName}
75
- path={file.path}
76
- meta={file.meta}
77
- banner={getBanner({ oas, output: options.output, config: driver.config })}
78
- footer={getFooter({ oas, output: options.output })}
108
+ baseName={serverFile.baseName}
109
+ path={serverFile.path}
110
+ meta={serverFile.meta}
111
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
112
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
79
113
  >
80
114
  <File.Import name={['McpServer']} path={'@modelcontextprotocol/sdk/server/mcp'} />
81
115
  <File.Import name={['z']} path={'zod'} />
@@ -84,9 +118,9 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
84
118
  {imports}
85
119
  <Server
86
120
  name={name}
87
- serverName={oas.api.info?.title}
88
- serverVersion={oas.getVersion()}
89
- paramsCasing={options.paramsCasing}
121
+ serverName={adapter.inputNode?.meta?.title ?? 'server'}
122
+ serverVersion={adapter.inputNode?.meta?.version ?? '0.0.0'}
123
+ paramsCasing={paramsCasing}
90
124
  operations={operationsMapped}
91
125
  />
92
126
  </File>
@@ -96,10 +130,10 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
96
130
  {`
97
131
  {
98
132
  "mcpServers": {
99
- "${oas.api.info?.title || 'server'}": {
133
+ "${adapter.inputNode?.meta?.title || 'server'}": {
100
134
  "type": "stdio",
101
135
  "command": "npx",
102
- "args": ["tsx", "${file.path}"]
136
+ "args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
103
137
  }
104
138
  }
105
139
  }
package/src/index.ts CHANGED
@@ -1,2 +1,11 @@
1
- export { pluginMcp, pluginMcpName } from './plugin.ts'
2
- export type { PluginMcp } from './types.ts'
1
+ export { McpHandler } from './components/McpHandler.tsx'
2
+ export { Server } from './components/Server.tsx'
3
+
4
+ export { mcpGenerator } from './generators/mcpGenerator.tsx'
5
+ export { serverGenerator } from './generators/serverGenerator.tsx'
6
+
7
+ export { default, pluginMcp, pluginMcpName } from './plugin.ts'
8
+
9
+ export { resolverMcp } from './resolvers/resolverMcp.ts'
10
+
11
+ export type { PluginMcp, ResolverMcp } from './types.ts'
package/src/plugin.ts CHANGED
@@ -1,170 +1,122 @@
1
1
  import path from 'node:path'
2
2
  import { camelCase } from '@internals/utils'
3
- import { createPlugin, type Group, getBarrelFiles, getMode } from '@kubb/core'
3
+
4
+ import { ast, definePlugin, type Group } from '@kubb/core'
4
5
  import { pluginClientName } from '@kubb/plugin-client'
5
6
  import { source as axiosClientSource } from '@kubb/plugin-client/templates/clients/axios.source'
6
7
  import { source as fetchClientSource } from '@kubb/plugin-client/templates/clients/fetch.source'
7
8
  import { source as configSource } from '@kubb/plugin-client/templates/config.source'
8
- import { OperationGenerator, pluginOasName } from '@kubb/plugin-oas'
9
9
  import { pluginTsName } from '@kubb/plugin-ts'
10
10
  import { pluginZodName } from '@kubb/plugin-zod'
11
- import { mcpGenerator, serverGenerator } from './generators'
11
+ import { mcpGenerator } from './generators/mcpGenerator.tsx'
12
+ import { serverGenerator } from './generators/serverGenerator.tsx'
13
+ import { resolverMcp } from './resolvers/resolverMcp.ts'
12
14
  import type { PluginMcp } from './types.ts'
13
15
 
14
16
  export const pluginMcpName = 'plugin-mcp' satisfies PluginMcp['name']
15
17
 
16
- export const pluginMcp = createPlugin<PluginMcp>((options) => {
18
+ export const pluginMcp = definePlugin<PluginMcp>((options) => {
17
19
  const {
18
20
  output = { path: 'mcp', barrelType: 'named' },
19
21
  group,
20
22
  exclude = [],
21
23
  include,
22
24
  override = [],
23
- transformers = {},
24
- generators = [mcpGenerator, serverGenerator].filter(Boolean),
25
- contentType,
26
25
  paramsCasing,
27
26
  client,
27
+ resolver: userResolver,
28
+ transformer: userTransformer,
29
+ generators: userGenerators = [],
28
30
  } = options
29
31
 
30
32
  const clientName = client?.client ?? 'axios'
31
33
  const clientImportPath = client?.importPath ?? (!client?.bundle ? `@kubb/plugin-client/clients/${clientName}` : undefined)
32
34
 
33
- return {
34
- name: pluginMcpName,
35
- options: {
36
- output,
37
- group,
38
- paramsCasing,
39
- client: {
40
- client: clientName,
41
- clientType: client?.clientType ?? 'function',
42
- importPath: clientImportPath,
43
- dataReturnType: client?.dataReturnType ?? 'data',
44
- bundle: client?.bundle,
45
- baseURL: client?.baseURL,
46
- paramsCasing: client?.paramsCasing,
47
- },
48
- },
49
- pre: [pluginOasName, pluginTsName, pluginZodName].filter(Boolean),
50
- resolvePath(baseName, pathMode, options) {
51
- const root = path.resolve(this.config.root, this.config.output.path)
52
- const mode = pathMode ?? getMode(path.resolve(root, output.path))
53
-
54
- if (mode === 'single') {
55
- /**
56
- * when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
57
- * Other plugins then need to call addOrAppend instead of just add from the fileManager class
58
- */
59
- return path.resolve(root, output.path)
60
- }
61
-
62
- if (group && (options?.group?.path || options?.group?.tag)) {
63
- const groupName: Group['name'] = group?.name
35
+ const groupConfig = group
36
+ ? ({
37
+ ...group,
38
+ name: group.name
64
39
  ? group.name
65
- : (ctx) => {
66
- if (group?.type === 'path') {
40
+ : (ctx: { group: string }) => {
41
+ if (group.type === 'path') {
67
42
  return `${ctx.group.split('/')[1]}`
68
43
  }
69
44
  return `${camelCase(ctx.group)}Requests`
70
- }
71
-
72
- return path.resolve(
73
- root,
74
- output.path,
75
- groupName({
76
- group: group.type === 'path' ? options.group.path! : options.group.tag!,
77
- }),
78
- baseName,
79
- )
80
- }
81
-
82
- return path.resolve(root, output.path, baseName)
83
- },
84
- resolveName(name, type) {
85
- const resolvedName = camelCase(name, {
86
- isFile: type === 'file',
87
- })
88
-
89
- if (type) {
90
- return transformers?.name?.(resolvedName, type) || resolvedName
91
- }
92
-
93
- return resolvedName
94
- },
95
- async install() {
96
- const root = path.resolve(this.config.root, this.config.output.path)
97
- const mode = getMode(path.resolve(root, output.path))
98
- const oas = await this.getOas()
99
- const baseURL = await this.getBaseURL()
100
-
101
- if (baseURL) {
102
- this.plugin.options.client.baseURL = baseURL
103
- }
104
-
105
- const hasClientPlugin = !!this.driver.getPluginByName(pluginClientName)
106
-
107
- if (this.plugin.options.client.bundle && !hasClientPlugin && !this.plugin.options.client.importPath) {
108
- // pre add bundled fetch
109
- await this.addFile({
110
- baseName: 'fetch.ts',
111
- path: path.resolve(root, '.kubb/fetch.ts'),
112
- sources: [
113
- {
114
- name: 'fetch',
115
- value: this.plugin.options.client.client === 'fetch' ? fetchClientSource : axiosClientSource,
116
- isExportable: true,
117
- isIndexable: true,
118
45
  },
119
- ],
120
- imports: [],
121
- exports: [],
122
- })
123
- }
46
+ } satisfies Group)
47
+ : undefined
124
48
 
125
- if (!hasClientPlugin) {
126
- await this.addFile({
127
- baseName: 'config.ts',
128
- path: path.resolve(root, '.kubb/config.ts'),
129
- sources: [
130
- {
131
- name: 'config',
132
- value: configSource,
133
- isExportable: false,
134
- isIndexable: false,
135
- },
136
- ],
137
- imports: [],
138
- exports: [],
49
+ return {
50
+ name: pluginMcpName,
51
+ options,
52
+ dependencies: [pluginTsName, pluginZodName],
53
+ hooks: {
54
+ 'kubb:plugin:setup'(ctx) {
55
+ const resolver = userResolver ? { ...resolverMcp, ...userResolver } : resolverMcp
56
+
57
+ ctx.setOptions({
58
+ output,
59
+ exclude,
60
+ include,
61
+ override,
62
+ group: groupConfig,
63
+ paramsCasing,
64
+ client: {
65
+ client: clientName,
66
+ clientType: client?.clientType ?? 'function',
67
+ importPath: clientImportPath,
68
+ dataReturnType: client?.dataReturnType ?? 'data',
69
+ bundle: client?.bundle,
70
+ baseURL: client?.baseURL,
71
+ paramsCasing: client?.paramsCasing,
72
+ },
73
+ resolver,
139
74
  })
140
- }
141
-
142
- const operationGenerator = new OperationGenerator(this.plugin.options, {
143
- fabric: this.fabric,
144
- oas,
145
- driver: this.driver,
146
- events: this.events,
147
- plugin: this.plugin,
148
- contentType,
149
- exclude,
150
- include,
151
- override,
152
- mode,
153
- })
154
-
155
- const files = await operationGenerator.build(...generators)
156
- await this.upsertFile(...files)
157
-
158
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
159
- type: output.barrelType ?? 'named',
160
- root,
161
- output,
162
- meta: {
163
- pluginName: this.plugin.name,
164
- },
165
- })
166
-
167
- await this.upsertFile(...barrelFiles)
75
+ ctx.setResolver(resolver)
76
+ if (userTransformer) {
77
+ ctx.setTransformer(userTransformer)
78
+ }
79
+ ctx.addGenerator(mcpGenerator)
80
+ ctx.addGenerator(serverGenerator)
81
+ for (const gen of userGenerators) {
82
+ ctx.addGenerator(gen)
83
+ }
84
+
85
+ const root = path.resolve(ctx.config.root, ctx.config.output.path)
86
+ const hasClientPlugin = ctx.config.plugins?.some((p) => p.name === pluginClientName)
87
+
88
+ if (client?.bundle && !hasClientPlugin && !clientImportPath) {
89
+ ctx.injectFile({
90
+ baseName: 'fetch.ts',
91
+ path: path.resolve(root, '.kubb/fetch.ts'),
92
+ sources: [
93
+ ast.createSource({
94
+ name: 'fetch',
95
+ nodes: [ast.createText(clientName === 'fetch' ? fetchClientSource : axiosClientSource)],
96
+ isExportable: true,
97
+ isIndexable: true,
98
+ }),
99
+ ],
100
+ })
101
+ }
102
+
103
+ if (!hasClientPlugin) {
104
+ ctx.injectFile({
105
+ baseName: 'config.ts',
106
+ path: path.resolve(root, '.kubb/config.ts'),
107
+ sources: [
108
+ ast.createSource({
109
+ name: 'config',
110
+ nodes: [ast.createText(configSource)],
111
+ isExportable: false,
112
+ isIndexable: false,
113
+ }),
114
+ ],
115
+ })
116
+ }
117
+ },
168
118
  },
169
119
  }
170
120
  })
121
+
122
+ export default pluginMcp
@@ -0,0 +1,31 @@
1
+ import { camelCase } from '@internals/utils'
2
+ import { defineResolver } from '@kubb/core'
3
+ import type { PluginMcp } from '../types.ts'
4
+
5
+ /**
6
+ * Naming convention resolver for MCP plugin.
7
+ *
8
+ * Provides default naming helpers using camelCase with a `handler` suffix for functions.
9
+ *
10
+ * @example
11
+ * `resolverMcp.default('addPet', 'function') // → 'addPetHandler'`
12
+ */
13
+ export const resolverMcp = defineResolver<PluginMcp>(() => ({
14
+ name: 'default',
15
+ pluginName: 'plugin-mcp',
16
+ default(name, type) {
17
+ if (type === 'file') {
18
+ return camelCase(name, { isFile: true })
19
+ }
20
+ return camelCase(name, { suffix: 'handler' })
21
+ },
22
+ resolveName(name) {
23
+ return this.default(name, 'function')
24
+ },
25
+ resolvePathName(name, type) {
26
+ return this.default(name, type)
27
+ },
28
+ resolveHandlerName(node) {
29
+ return this.resolveName(node.operationId)
30
+ },
31
+ }))