@kubb/plugin-mcp 5.0.0-alpha.3 → 5.0.0-alpha.31

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,16 +1,34 @@
1
- import { camelCase, isValidVarName } from '@internals/utils'
2
- import type { KubbFile } from '@kubb/fabric-core/types'
3
- import type { SchemaObject } from '@kubb/oas'
4
- import type { OperationSchemas } from '@kubb/plugin-oas'
5
- import { isOptional } from '@kubb/plugin-oas/utils'
6
- import { Const, File, FunctionParams } from '@kubb/react-fabric'
1
+ import { caseParams, createFunctionParameter, createFunctionParameters, createParameterGroup } from '@kubb/ast'
2
+ import type { OperationNode } from '@kubb/ast/types'
3
+ import type { KubbFile } from '@kubb/core'
4
+ import { functionPrinter } from '@kubb/plugin-ts'
5
+ import { Const, File, Function } from '@kubb/react-fabric'
7
6
  import type { FabricReactNode } from '@kubb/react-fabric/types'
7
+ import type { PluginMcp } from '../types.ts'
8
+ import type { ZodParam } from '../utils.ts'
9
+ import { zodExprFromSchemaNode, zodGroupExpr } from '../utils.ts'
8
10
 
9
11
  type Props = {
12
+ /**
13
+ * Variable name for the MCP server instance (e.g. 'server').
14
+ */
10
15
  name: string
16
+ /**
17
+ * Human-readable server name passed to `new McpServer({ name })`.
18
+ */
11
19
  serverName: string
20
+ /**
21
+ * Semantic version string passed to `new McpServer({ version })`.
22
+ */
12
23
  serverVersion: string
13
- paramsCasing?: 'camelcase'
24
+ /**
25
+ * How to style your params.
26
+ */
27
+ paramsCasing?: PluginMcp['resolvedOptions']['paramsCasing']
28
+ /**
29
+ * Operations to register as MCP tools, each carrying its handler,
30
+ * zod schema, and AST node metadata.
31
+ */
14
32
  operations: Array<{
15
33
  tool: {
16
34
  name: string
@@ -22,94 +40,23 @@ type Props = {
22
40
  file: KubbFile.File
23
41
  }
24
42
  zod: {
25
- name: string
26
- file: KubbFile.File
27
- schemas: OperationSchemas
28
- }
29
- type: {
30
- schemas: OperationSchemas
43
+ pathParams: Array<ZodParam>
44
+ /**
45
+ * Query params — either a group schema name (kubbV4) or individual schemas to compose (v5).
46
+ */
47
+ queryParams?: string | Array<ZodParam>
48
+ /**
49
+ * Header params — either a group schema name (kubbV4) or individual schemas to compose (v5).
50
+ */
51
+ headerParams?: string | Array<ZodParam>
52
+ requestName?: string
53
+ responseName?: string
31
54
  }
55
+ node: OperationNode
32
56
  }>
33
57
  }
34
58
 
35
- type GetParamsProps = {
36
- schemas: OperationSchemas
37
- paramsCasing?: 'camelcase'
38
- }
39
-
40
- function zodExprFromOasSchema(schema: SchemaObject): string {
41
- const types = Array.isArray(schema.type) ? schema.type : [schema.type]
42
- const baseType = types.find((t) => t && t !== 'null')
43
- const isNullableType = types.includes('null')
44
-
45
- let expr: string
46
- switch (baseType) {
47
- case 'integer':
48
- expr = 'z.coerce.number()'
49
- break
50
- case 'number':
51
- expr = 'z.number()'
52
- break
53
- case 'boolean':
54
- expr = 'z.boolean()'
55
- break
56
- case 'array':
57
- expr = 'z.array(z.unknown())'
58
- break
59
- default:
60
- expr = 'z.string()'
61
- }
62
-
63
- if (isNullableType) {
64
- expr = `${expr}.nullable()`
65
- }
66
-
67
- return expr
68
- }
69
-
70
- function getParams({ schemas, paramsCasing }: GetParamsProps) {
71
- const pathParamProperties = schemas.pathParams?.schema?.properties ?? {}
72
- const requiredFields = Array.isArray(schemas.pathParams?.schema?.required) ? schemas.pathParams.schema.required : []
73
-
74
- const pathParamEntries = Object.entries(pathParamProperties).reduce<Record<string, { value: string; optional: boolean }>>(
75
- (acc, [originalKey, propSchema]) => {
76
- const key = paramsCasing === 'camelcase' || !isValidVarName(originalKey) ? camelCase(originalKey) : originalKey
77
- acc[key] = {
78
- value: zodExprFromOasSchema(propSchema as SchemaObject),
79
- optional: !requiredFields.includes(originalKey),
80
- }
81
- return acc
82
- },
83
- {},
84
- )
85
-
86
- return FunctionParams.factory({
87
- data: {
88
- mode: 'object',
89
- children: {
90
- ...pathParamEntries,
91
- data: schemas.request?.name
92
- ? {
93
- value: schemas.request?.name,
94
- optional: isOptional(schemas.request?.schema),
95
- }
96
- : undefined,
97
- params: schemas.queryParams?.name
98
- ? {
99
- value: schemas.queryParams?.name,
100
- optional: isOptional(schemas.queryParams?.schema),
101
- }
102
- : undefined,
103
- headers: schemas.headerParams?.name
104
- ? {
105
- value: schemas.headerParams?.name,
106
- optional: isOptional(schemas.headerParams?.schema),
107
- }
108
- : undefined,
109
- },
110
- },
111
- })
112
- }
59
+ const keysPrinter = functionPrinter({ mode: 'keys' })
113
60
 
114
61
  export function Server({ name, serverName, serverVersion, paramsCasing, operations }: Props): FabricReactNode {
115
62
  return (
@@ -124,9 +71,46 @@ export function Server({ name, serverName, serverVersion, paramsCasing, operatio
124
71
  </Const>
125
72
 
126
73
  {operations
127
- .map(({ tool, mcp, zod }) => {
128
- const paramsClient = getParams({ schemas: zod.schemas, paramsCasing })
129
- const outputSchema = zod.schemas.response?.name
74
+ .map(({ tool, mcp, zod, node }) => {
75
+ const casedParams = caseParams(node.parameters, paramsCasing)
76
+ const pathParams = casedParams.filter((p) => p.in === 'path')
77
+
78
+ const pathEntries: Array<{ key: string; value: string }> = []
79
+ const otherEntries: Array<{ key: string; value: string }> = []
80
+
81
+ for (const p of pathParams) {
82
+ const zodParam = zod.pathParams.find((zp) => zp.name === p.name)
83
+ pathEntries.push({ key: p.name, value: zodParam ? zodParam.schemaName : zodExprFromSchemaNode(p.schema) })
84
+ }
85
+
86
+ if (zod.requestName) {
87
+ otherEntries.push({ key: 'data', value: zod.requestName })
88
+ }
89
+
90
+ if (zod.queryParams) {
91
+ otherEntries.push({ key: 'params', value: zodGroupExpr(zod.queryParams) })
92
+ }
93
+
94
+ if (zod.headerParams) {
95
+ otherEntries.push({ key: 'headers', value: zodGroupExpr(zod.headerParams) })
96
+ }
97
+
98
+ otherEntries.sort((a, b) => a.key.localeCompare(b.key))
99
+ const entries = [...pathEntries, ...otherEntries]
100
+
101
+ const paramsNode = entries.length
102
+ ? createFunctionParameters({
103
+ params: [
104
+ createParameterGroup({
105
+ properties: entries.map((e) => createFunctionParameter({ name: e.key, optional: false })),
106
+ }),
107
+ ],
108
+ })
109
+ : undefined
110
+
111
+ const destructured = paramsNode ? (keysPrinter.print(paramsNode) ?? '') : ''
112
+ const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(', ')} }` : undefined
113
+ const outputSchema = zod.responseName
130
114
 
131
115
  const config = [
132
116
  tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
@@ -136,13 +120,13 @@ export function Server({ name, serverName, serverVersion, paramsCasing, operatio
136
120
  .filter(Boolean)
137
121
  .join(',\n ')
138
122
 
139
- if (zod.schemas.request?.name || zod.schemas.headerParams?.name || zod.schemas.queryParams?.name || zod.schemas.pathParams?.name) {
123
+ if (inputSchema) {
140
124
  return `
141
125
  server.registerTool(${JSON.stringify(tool.name)}, {
142
126
  ${config},
143
- inputSchema: ${paramsClient.toObjectValue()},
144
- }, async (${paramsClient.toObject()}) => {
145
- return ${mcp.name}(${paramsClient.toObject()})
127
+ inputSchema: ${inputSchema},
128
+ }, async (${destructured}) => {
129
+ return ${mcp.name}(${destructured})
146
130
  })
147
131
  `
148
132
  }
@@ -151,24 +135,22 @@ server.registerTool(${JSON.stringify(tool.name)}, {
151
135
  server.registerTool(${JSON.stringify(tool.name)}, {
152
136
  ${config},
153
137
  }, async () => {
154
- return ${mcp.name}(${paramsClient.toObject()})
138
+ return ${mcp.name}()
155
139
  })
156
140
  `
157
141
  })
158
142
  .filter(Boolean)}
159
143
 
160
- {`
161
- export async function startServer() {
162
- try {
144
+ <Function name="startServer" async export>
145
+ {`try {
163
146
  const transport = new StdioServerTransport()
164
147
  await server.connect(transport)
165
148
 
166
149
  } catch (error) {
167
150
  console.error('Failed to start server:', error)
168
151
  process.exit(1)
169
- }
170
- }
171
- `}
152
+ }`}
153
+ </Function>
172
154
  </File.Source>
173
155
  )
174
156
  }
@@ -1,109 +1,87 @@
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 { caseParams } from '@kubb/ast'
3
+ import { defineGenerator } from '@kubb/core'
6
4
  import { pluginTsName } from '@kubb/plugin-ts'
7
5
  import { File } from '@kubb/react-fabric'
8
- import type { PluginMcp } from '../types'
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
+ operation(node, options) {
12
+ const { resolver, driver, root } = this
13
+ const { output, client, paramsCasing, group } = 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?.resolver) {
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 casedParams = caseParams(node.parameters, paramsCasing)
22
+
23
+ const pathParams = casedParams.filter((p) => p.in === 'path')
24
+ const queryParams = casedParams.filter((p) => p.in === 'query')
25
+ const headerParams = casedParams.filter((p) => p.in === 'header')
26
+
27
+ const importedTypeNames = [
28
+ ...pathParams.map((p) => pluginTs.resolver.resolvePathParamsName(node, p)),
29
+ ...queryParams.map((p) => pluginTs.resolver.resolveQueryParamsName(node, p)),
30
+ ...headerParams.map((p) => pluginTs.resolver.resolveHeaderParamsName(node, p)),
31
+ node.requestBody?.schema ? pluginTs.resolver.resolveDataName(node) : undefined,
32
+ pluginTs.resolver.resolveResponseName(node),
33
+ ...node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => pluginTs.resolver.resolveResponseStatusName(node, r.statusCode)),
34
+ ].filter(Boolean)
35
+
36
+ const meta = {
37
+ name: resolver.resolveName(node.operationId),
38
+ file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
39
+ fileTs: pluginTs.resolver.resolveFile(
40
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
41
+ {
42
+ root,
43
+ output: pluginTs.options?.output ?? output,
44
+ group: pluginTs.options?.group,
45
+ },
46
+ ),
47
+ } as const
27
48
 
28
49
  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 ? (
50
+ <File baseName={meta.file.baseName} path={meta.file.path} meta={meta.file.meta}>
51
+ {meta.fileTs && importedTypeNames.length > 0 && (
52
+ <File.Import name={Array.from(new Set(importedTypeNames)).sort()} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
53
+ )}
54
+ <File.Import name={['CallToolResult']} path={'@modelcontextprotocol/sdk/types'} isTypeOnly />
55
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
56
+ {client.importPath ? (
37
57
  <>
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 />}
58
+ <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={client.importPath} isTypeOnly />
59
+ <File.Import name={'fetch'} path={client.importPath} />
60
+ {client.dataReturnType === 'full' && <File.Import name={['ResponseConfig']} path={client.importPath} isTypeOnly />}
41
61
  </>
42
62
  ) : (
43
63
  <>
44
- <File.Import name={['fetch']} root={mcp.file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
45
64
  <File.Import
46
65
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
47
- root={mcp.file.path}
48
- path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')}
66
+ root={meta.file.path}
67
+ path={path.resolve(root, '.kubb/fetch.ts')}
49
68
  isTypeOnly
50
69
  />
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 />
70
+ <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} />
71
+ {client.dataReturnType === 'full' && (
72
+ <File.Import name={['ResponseConfig']} root={meta.file.path} path={path.resolve(root, '.kubb/fetch.ts')} isTypeOnly />
53
73
  )}
54
74
  </>
55
75
  )}
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
76
 
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>
77
+ <McpHandler
78
+ name={meta.name}
79
+ node={node}
80
+ resolver={pluginTs.resolver}
81
+ baseURL={client.baseURL}
82
+ dataReturnType={client.dataReturnType || 'data'}
83
+ paramsCasing={paramsCasing}
84
+ />
107
85
  </File>
108
86
  )
109
87
  },
@@ -1,81 +1,116 @@
1
- import { usePluginManager } 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 { caseParams } from '@kubb/ast'
3
+ import { defineGenerator } from '@kubb/core'
6
4
  import { pluginZodName } from '@kubb/plugin-zod'
7
5
  import { File } from '@kubb/react-fabric'
8
- import { Server } from '../components/Server'
9
- import type { PluginMcp } from '../types'
6
+ import { Server } from '../components/Server.tsx'
7
+ import type { PluginMcp } from '../types.ts'
8
+ import { findSuccessStatusCode } from '../utils.ts'
10
9
 
11
- export const serverGenerator = createReactGenerator<PluginMcp>({
10
+ /**
11
+ * Default server generator for `compatibilityPreset: 'default'` (v5).
12
+ *
13
+ * Uses individual zod schemas for each param (e.g. `createPetsPathUuidSchema`, `createPetsQueryOffsetSchema`)
14
+ * and `resolveResponseStatusName` for per-status response schemas.
15
+ * Query and header params are composed into `z.object({ ... })` from individual schemas.
16
+ */
17
+ export const serverGenerator = defineGenerator<PluginMcp>({
12
18
  name: 'operations',
13
- Operations({ operations, generator, plugin }) {
14
- const pluginManager = usePluginManager()
15
- const { options } = plugin
19
+ operations(nodes, options) {
20
+ const { adapter, config, resolver, plugin, driver, root } = this
21
+ const { output, paramsCasing, group } = options
16
22
 
17
- const oas = useOas()
18
- const { getFile, getName, getSchemas } = useOperationManager(generator)
23
+ const pluginZod = driver.getPlugin(pluginZodName)
24
+
25
+ if (!pluginZod?.resolver) {
26
+ return
27
+ }
19
28
 
20
29
  const name = 'server'
21
- const file = pluginManager.getFile({ name, extname: '.ts', pluginName: plugin.name })
30
+ const serverFilePath = path.resolve(root, output.path, 'server.ts')
31
+ const serverFile = {
32
+ baseName: 'server.ts' as const,
33
+ path: serverFilePath,
34
+ meta: { pluginName: plugin.name },
35
+ }
36
+
37
+ const jsonFilePath = path.resolve(root, output.path, '.mcp.json')
38
+ const jsonFile = {
39
+ baseName: '.mcp.json' as const,
40
+ path: jsonFilePath,
41
+ meta: { pluginName: plugin.name },
42
+ }
43
+
44
+ const operationsMapped = nodes.map((node) => {
45
+ const casedParams = caseParams(node.parameters, paramsCasing)
46
+ const pathParams = casedParams.filter((p) => p.in === 'path')
47
+ const queryParams = casedParams.filter((p) => p.in === 'query')
48
+ const headerParams = casedParams.filter((p) => p.in === 'header')
49
+
50
+ const mcpFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
51
+
52
+ const zodFile = pluginZod.resolver.resolveFile(
53
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
54
+ {
55
+ root,
56
+ output: pluginZod.options?.output ?? output,
57
+ group: pluginZod.options?.group,
58
+ },
59
+ )
60
+
61
+ const requestName = node.requestBody?.schema ? pluginZod.resolver.resolveDataName(node) : undefined
62
+ const successStatus = findSuccessStatusCode(node.responses)
63
+ const responseName = successStatus ? pluginZod.resolver.resolveResponseStatusName(node, successStatus) : undefined
22
64
 
23
- const jsonFile = pluginManager.getFile({ name: '.mcp', extname: '.json', pluginName: plugin.name })
65
+ const resolveParams = (params: typeof pathParams) => params.map((p) => ({ name: p.name, schemaName: pluginZod.resolver.resolveParamName(node, p) }))
24
66
 
25
- const operationsMapped = operations.map((operation) => {
26
67
  return {
27
68
  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}`,
69
+ name: node.operationId,
70
+ title: node.summary || undefined,
71
+ description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`,
31
72
  },
32
73
  mcp: {
33
- name: getName(operation, {
34
- type: 'function',
35
- suffix: 'handler',
36
- }),
37
- file: getFile(operation),
74
+ name: resolver.resolveName(node.operationId),
75
+ file: mcpFile,
38
76
  },
39
77
  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' }),
78
+ pathParams: resolveParams(pathParams),
79
+ queryParams: queryParams.length ? resolveParams(queryParams) : undefined,
80
+ headerParams: headerParams.length ? resolveParams(headerParams) : undefined,
81
+ requestName,
82
+ responseName,
83
+ file: zodFile,
49
84
  },
85
+ node: node,
50
86
  }
51
87
  })
52
88
 
53
89
  const imports = operationsMapped.flatMap(({ mcp, zod }) => {
90
+ const zodNames = [
91
+ ...zod.pathParams.map((p) => p.schemaName),
92
+ ...(zod.queryParams ?? []).map((p) => p.schemaName),
93
+ ...(zod.headerParams ?? []).map((p) => p.schemaName),
94
+ zod.requestName,
95
+ zod.responseName,
96
+ ].filter(Boolean)
97
+
98
+ const uniqueNames = [...new Set(zodNames)].sort()
99
+
54
100
  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
- ]
101
+ <File.Import key={mcp.name} name={[mcp.name]} root={serverFile.path} path={mcp.file.path} />,
102
+ uniqueNames.length > 0 && <File.Import key={`zod-${mcp.name}`} name={uniqueNames} root={serverFile.path} path={zod.file.path} />,
103
+ ].filter(Boolean)
69
104
  })
70
105
 
71
106
  return (
72
107
  <>
73
108
  <File
74
- baseName={file.baseName}
75
- path={file.path}
76
- meta={file.meta}
77
- banner={getBanner({ oas, output: options.output, config: pluginManager.config })}
78
- footer={getFooter({ oas, output: options.output })}
109
+ baseName={serverFile.baseName}
110
+ path={serverFile.path}
111
+ meta={serverFile.meta}
112
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
113
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
79
114
  >
80
115
  <File.Import name={['McpServer']} path={'@modelcontextprotocol/sdk/server/mcp'} />
81
116
  <File.Import name={['z']} path={'zod'} />
@@ -84,9 +119,9 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
84
119
  {imports}
85
120
  <Server
86
121
  name={name}
87
- serverName={oas.api.info?.title}
88
- serverVersion={oas.getVersion()}
89
- paramsCasing={options.paramsCasing}
122
+ serverName={adapter.rootNode?.meta?.title ?? 'server'}
123
+ serverVersion={adapter.rootNode?.meta?.version ?? '0.0.0'}
124
+ paramsCasing={paramsCasing}
90
125
  operations={operationsMapped}
91
126
  />
92
127
  </File>
@@ -96,10 +131,10 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
96
131
  {`
97
132
  {
98
133
  "mcpServers": {
99
- "${oas.api.info?.title || 'server'}": {
134
+ "${adapter.rootNode?.meta?.title || 'server'}": {
100
135
  "type": "stdio",
101
136
  "command": "npx",
102
- "args": ["tsx", "${file.path}"]
137
+ "args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
103
138
  }
104
139
  }
105
140
  }