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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,109 +1,90 @@
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 { ast, defineGenerator } from '@kubb/core'
6
3
  import { pluginTsName } from '@kubb/plugin-ts'
7
- import { File } from '@kubb/react-fabric'
8
- import type { PluginMcp } from '../types'
4
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
5
+ import { McpHandler } from '../components/McpHandler.tsx'
6
+ import type { PluginMcp } from '../types.ts'
9
7
 
10
- export const mcpGenerator = createReactGenerator<PluginMcp>({
8
+ export const mcpGenerator = defineGenerator<PluginMcp>({
11
9
  name: 'mcp',
12
- Operation({ config, operation, generator, plugin }) {
13
- const { options } = plugin
14
- const oas = useOas()
10
+ renderer: jsxRenderer,
11
+ operation(node, ctx) {
12
+ const { resolver, driver, root } = ctx
13
+ const { output, client, paramsCasing, group } = ctx.options
15
14
 
16
- const { getSchemas, getName, getFile } = useOperationManager(generator)
15
+ const pluginTs = driver.getPlugin(pluginTsName)
17
16
 
18
- const mcp = {
19
- name: getName(operation, { type: 'function', suffix: 'handler' }),
20
- file: getFile(operation),
17
+ if (!pluginTs) {
18
+ return null
21
19
  }
22
20
 
23
- const type = {
24
- file: getFile(operation, { pluginName: pluginTsName }),
25
- schemas: getSchemas(operation, { pluginName: pluginTsName, type: 'type' }),
26
- }
21
+ const tsResolver = driver.getResolver(pluginTsName)
22
+
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)
37
+
38
+ 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 }),
41
+ fileTs: tsResolver.resolveFile(
42
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
43
+ {
44
+ root,
45
+ output: pluginTs.options?.output ?? output,
46
+ group: pluginTs.options?.group,
47
+ },
48
+ ),
49
+ } as const
27
50
 
28
51
  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 ? (
52
+ <File baseName={meta.file.baseName} path={meta.file.path} meta={meta.file.meta}>
53
+ {meta.fileTs && importedTypeNames.length > 0 && (
54
+ <File.Import name={Array.from(new Set(importedTypeNames)).sort()} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
55
+ )}
56
+ <File.Import name={['CallToolResult', 'ServerNotification', 'ServerRequest']} path={'@modelcontextprotocol/sdk/types'} isTypeOnly />
57
+ <File.Import name={['RequestHandlerExtra']} path={'@modelcontextprotocol/sdk/shared/protocol'} isTypeOnly />
58
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
59
+ {client.importPath ? (
37
60
  <>
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 />}
61
+ <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={client.importPath} isTypeOnly />
62
+ <File.Import name={'fetch'} path={client.importPath} />
63
+ {client.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={client.importPath} isTypeOnly />}
41
64
  </>
42
65
  ) : (
43
66
  <>
44
- <File.Import name={['fetch']} root={mcp.file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
45
67
  <File.Import
46
68
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
47
- root={mcp.file.path}
48
- path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')}
69
+ root={meta.file.path}
70
+ path={path.resolve(root, '.kubb/fetch.ts')}
49
71
  isTypeOnly
50
72
  />
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 />
73
+ <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />
74
+ {client.dataReturnType === 'full' && (
75
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
53
76
  )}
54
77
  </>
55
78
  )}
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
79
 
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>
80
+ <McpHandler
81
+ name={meta.name}
82
+ node={node}
83
+ resolver={tsResolver}
84
+ baseURL={client.baseURL}
85
+ dataReturnType={client.dataReturnType || 'data'}
86
+ paramsCasing={paramsCasing}
87
+ />
107
88
  </File>
108
89
  )
109
90
  },
@@ -1,81 +1,118 @@
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 { ast, defineGenerator } from '@kubb/core'
6
3
  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'
4
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
5
+ import { Server } from '../components/Server.tsx'
6
+ import type { PluginMcp } from '../types.ts'
7
+ import { findSuccessStatusCode } from '../utils.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 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')
51
+
52
+ const mcpFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
53
+
54
+ const zodFile = zodResolver.resolveFile(
55
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
56
+ {
57
+ root,
58
+ output: pluginZod.options?.output ?? output,
59
+ group: pluginZod.options?.group,
60
+ },
61
+ )
62
+
63
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : undefined
64
+ const successStatus = findSuccessStatusCode(node.responses)
65
+ const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : undefined
66
+
67
+ const resolveParams = (params: typeof pathParams) => params.map((p) => ({ name: p.name, schemaName: zodResolver.resolveParamName(node, p) }))
24
68
 
25
- const operationsMapped = operations.map((operation) => {
26
69
  return {
27
70
  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}`,
71
+ name: node.operationId,
72
+ title: node.summary || undefined,
73
+ description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`,
31
74
  },
32
75
  mcp: {
33
- name: getName(operation, {
34
- type: 'function',
35
- suffix: 'handler',
36
- }),
37
- file: getFile(operation),
76
+ name: resolver.resolveName(node.operationId),
77
+ file: mcpFile,
38
78
  },
39
79
  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' }),
80
+ pathParams: resolveParams(pathParams),
81
+ queryParams: queryParams.length ? resolveParams(queryParams) : undefined,
82
+ headerParams: headerParams.length ? resolveParams(headerParams) : undefined,
83
+ requestName,
84
+ responseName,
85
+ file: zodFile,
49
86
  },
87
+ node: node,
50
88
  }
51
89
  })
52
90
 
53
91
  const imports = operationsMapped.flatMap(({ mcp, zod }) => {
92
+ const zodNames = [
93
+ ...zod.pathParams.map((p) => p.schemaName),
94
+ ...(zod.queryParams ?? []).map((p) => p.schemaName),
95
+ ...(zod.headerParams ?? []).map((p) => p.schemaName),
96
+ zod.requestName,
97
+ zod.responseName,
98
+ ].filter(Boolean)
99
+
100
+ const uniqueNames = [...new Set(zodNames)].sort()
101
+
54
102
  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
- ]
103
+ <File.Import key={mcp.name} name={[mcp.name]} root={serverFile.path} path={mcp.file.path} />,
104
+ uniqueNames.length > 0 && <File.Import key={`zod-${mcp.name}`} name={uniqueNames} root={serverFile.path} path={zod.file.path} />,
105
+ ].filter(Boolean)
69
106
  })
70
107
 
71
108
  return (
72
109
  <>
73
110
  <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 })}
111
+ baseName={serverFile.baseName}
112
+ path={serverFile.path}
113
+ meta={serverFile.meta}
114
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
115
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
79
116
  >
80
117
  <File.Import name={['McpServer']} path={'@modelcontextprotocol/sdk/server/mcp'} />
81
118
  <File.Import name={['z']} path={'zod'} />
@@ -84,9 +121,9 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
84
121
  {imports}
85
122
  <Server
86
123
  name={name}
87
- serverName={oas.api.info?.title}
88
- serverVersion={oas.getVersion()}
89
- paramsCasing={options.paramsCasing}
124
+ serverName={adapter.inputNode?.meta?.title ?? 'server'}
125
+ serverVersion={adapter.inputNode?.meta?.version ?? '0.0.0'}
126
+ paramsCasing={paramsCasing}
90
127
  operations={operationsMapped}
91
128
  />
92
129
  </File>
@@ -96,10 +133,10 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
96
133
  {`
97
134
  {
98
135
  "mcpServers": {
99
- "${oas.api.info?.title || 'server'}": {
136
+ "${adapter.inputNode?.meta?.title || 'server'}": {
100
137
  "type": "stdio",
101
138
  "command": "npx",
102
- "args": ["tsx", "${file.path}"]
139
+ "args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
103
140
  }
104
141
  }
105
142
  }
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