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

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,32 @@
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'
7
- import type { FabricReactNode } from '@kubb/react-fabric/types'
1
+ import { ast } from '@kubb/core'
2
+ import { functionPrinter } from '@kubb/plugin-ts'
3
+ import { Const, File, Function } from '@kubb/renderer-jsx'
4
+ import type { KubbReactNode } from '@kubb/renderer-jsx/types'
5
+ import type { PluginMcp } from '../types.ts'
6
+ import type { ZodParam } from '../utils.ts'
7
+ import { zodExprFromSchemaNode, zodGroupExpr } from '../utils.ts'
8
8
 
9
9
  type Props = {
10
+ /**
11
+ * Variable name for the MCP server instance (e.g. 'server').
12
+ */
10
13
  name: string
14
+ /**
15
+ * Human-readable server name passed to `new McpServer({ name })`.
16
+ */
11
17
  serverName: string
18
+ /**
19
+ * Semantic version string passed to `new McpServer({ version })`.
20
+ */
12
21
  serverVersion: string
13
- paramsCasing?: 'camelcase'
22
+ /**
23
+ * How to style your params.
24
+ */
25
+ paramsCasing?: PluginMcp['resolvedOptions']['paramsCasing']
26
+ /**
27
+ * Operations to register as MCP tools, each carrying its handler,
28
+ * zod schema, and AST node metadata.
29
+ */
14
30
  operations: Array<{
15
31
  tool: {
16
32
  name: string
@@ -19,99 +35,28 @@ type Props = {
19
35
  }
20
36
  mcp: {
21
37
  name: string
22
- file: KubbFile.File
38
+ file: ast.FileNode
23
39
  }
24
40
  zod: {
25
- name: string
26
- file: KubbFile.File
27
- schemas: OperationSchemas
28
- }
29
- type: {
30
- schemas: OperationSchemas
41
+ pathParams: Array<ZodParam>
42
+ /**
43
+ * Query params — individual schemas to compose into `z.object({ ... })`.
44
+ */
45
+ queryParams?: string | Array<ZodParam>
46
+ /**
47
+ * Header params — individual schemas to compose into `z.object({ ... })`.
48
+ */
49
+ headerParams?: string | Array<ZodParam>
50
+ requestName?: string
51
+ responseName?: string
31
52
  }
53
+ node: ast.OperationNode
32
54
  }>
33
55
  }
34
56
 
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
- }
57
+ const keysPrinter = functionPrinter({ mode: 'keys' })
69
58
 
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
- }
113
-
114
- export function Server({ name, serverName, serverVersion, paramsCasing, operations }: Props): FabricReactNode {
59
+ export function Server({ name, serverName, serverVersion, paramsCasing, operations }: Props): KubbReactNode {
115
60
  return (
116
61
  <File.Source name={name} isExportable isIndexable>
117
62
  <Const name={'server'} export>
@@ -124,9 +69,46 @@ export function Server({ name, serverName, serverVersion, paramsCasing, operatio
124
69
  </Const>
125
70
 
126
71
  {operations
127
- .map(({ tool, mcp, zod }) => {
128
- const paramsClient = getParams({ schemas: zod.schemas, paramsCasing })
129
- const outputSchema = zod.schemas.response?.name
72
+ .map(({ tool, mcp, zod, node }) => {
73
+ const casedParams = ast.caseParams(node.parameters, paramsCasing)
74
+ const pathParams = casedParams.filter((p) => p.in === 'path')
75
+
76
+ const pathEntries: Array<{ key: string; value: string }> = []
77
+ const otherEntries: Array<{ key: string; value: string }> = []
78
+
79
+ for (const p of pathParams) {
80
+ const zodParam = zod.pathParams.find((zp) => zp.name === p.name)
81
+ pathEntries.push({ key: p.name, value: zodParam ? zodParam.schemaName : zodExprFromSchemaNode(p.schema) })
82
+ }
83
+
84
+ if (zod.requestName) {
85
+ otherEntries.push({ key: 'data', value: zod.requestName })
86
+ }
87
+
88
+ if (zod.queryParams) {
89
+ otherEntries.push({ key: 'params', value: zodGroupExpr(zod.queryParams) })
90
+ }
91
+
92
+ if (zod.headerParams) {
93
+ otherEntries.push({ key: 'headers', value: zodGroupExpr(zod.headerParams) })
94
+ }
95
+
96
+ otherEntries.sort((a, b) => a.key.localeCompare(b.key))
97
+ const entries = [...pathEntries, ...otherEntries]
98
+
99
+ const paramsNode = entries.length
100
+ ? ast.createFunctionParameters({
101
+ params: [
102
+ ast.createParameterGroup({
103
+ properties: entries.map((e) => ast.createFunctionParameter({ name: e.key, optional: false })),
104
+ }),
105
+ ],
106
+ })
107
+ : undefined
108
+
109
+ const destructured = paramsNode ? (keysPrinter.print(paramsNode) ?? '') : ''
110
+ const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(', ')} }` : undefined
111
+ const outputSchema = zod.responseName
130
112
 
131
113
  const config = [
132
114
  tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
@@ -136,13 +118,13 @@ export function Server({ name, serverName, serverVersion, paramsCasing, operatio
136
118
  .filter(Boolean)
137
119
  .join(',\n ')
138
120
 
139
- if (zod.schemas.request?.name || zod.schemas.headerParams?.name || zod.schemas.queryParams?.name || zod.schemas.pathParams?.name) {
121
+ if (inputSchema) {
140
122
  return `
141
123
  server.registerTool(${JSON.stringify(tool.name)}, {
142
124
  ${config},
143
- inputSchema: ${paramsClient.toObjectValue()},
144
- }, async (${paramsClient.toObject()}) => {
145
- return ${mcp.name}(${paramsClient.toObject()})
125
+ inputSchema: ${inputSchema},
126
+ }, async (${destructured}, request) => {
127
+ return ${mcp.name}(${destructured}, request)
146
128
  })
147
129
  `
148
130
  }
@@ -150,25 +132,23 @@ server.registerTool(${JSON.stringify(tool.name)}, {
150
132
  return `
151
133
  server.registerTool(${JSON.stringify(tool.name)}, {
152
134
  ${config},
153
- }, async () => {
154
- return ${mcp.name}(${paramsClient.toObject()})
135
+ }, async (request) => {
136
+ return ${mcp.name}(request)
155
137
  })
156
138
  `
157
139
  })
158
140
  .filter(Boolean)}
159
141
 
160
- {`
161
- export async function startServer() {
162
- try {
142
+ <Function name="startServer" async export>
143
+ {`try {
163
144
  const transport = new StdioServerTransport()
164
145
  await server.connect(transport)
165
146
 
166
147
  } catch (error) {
167
148
  console.error('Failed to start server:', error)
168
149
  process.exit(1)
169
- }
170
- }
171
- `}
150
+ }`}
151
+ </Function>
172
152
  </File.Source>
173
153
  )
174
154
  }
@@ -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'