@kubb/plugin-mcp 5.0.0-beta.10 → 5.0.0-beta.103

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,154 +0,0 @@
1
- import { getOperationParameters } from '@internals/shared'
2
- import { ast } from '@kubb/core'
3
- import { functionPrinter } from '@kubb/plugin-ts'
4
- import { Const, File, Function } from '@kubb/renderer-jsx'
5
- import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
- import type { PluginMcp } from '../types.ts'
7
- import type { ZodParam } from '../utils.ts'
8
- import { zodExprFromSchemaNode, zodGroupExpr } from '../utils.ts'
9
-
10
- type Props = {
11
- /**
12
- * Variable name for the MCP server instance (e.g. 'server').
13
- */
14
- name: string
15
- /**
16
- * Human-readable server name passed to `new McpServer({ name })`.
17
- */
18
- serverName: string
19
- /**
20
- * Semantic version string passed to `new McpServer({ version })`.
21
- */
22
- serverVersion: string
23
- /**
24
- * How to style your params.
25
- */
26
- paramsCasing?: PluginMcp['resolvedOptions']['paramsCasing']
27
- /**
28
- * Operations to register as MCP tools, each carrying its handler,
29
- * zod schema, and AST node metadata.
30
- */
31
- operations: Array<{
32
- tool: {
33
- name: string
34
- title?: string
35
- description: string
36
- }
37
- mcp: {
38
- name: string
39
- file: ast.FileNode
40
- }
41
- zod: {
42
- pathParams: Array<ZodParam>
43
- /**
44
- * Query params — individual schemas to compose into `z.object({ ... })`.
45
- */
46
- queryParams?: string | Array<ZodParam>
47
- /**
48
- * Header params — individual schemas to compose into `z.object({ ... })`.
49
- */
50
- headerParams?: string | Array<ZodParam>
51
- requestName?: string
52
- responseName?: string
53
- }
54
- node: ast.OperationNode
55
- }>
56
- }
57
-
58
- const keysPrinter = functionPrinter({ mode: 'keys' })
59
-
60
- export function Server({ name, serverName, serverVersion, paramsCasing, operations }: Props): KubbReactNode {
61
- return (
62
- <File.Source name={name} isExportable isIndexable>
63
- <Const name={'server'} export>
64
- {`
65
- new McpServer({
66
- name: '${serverName}',
67
- version: '${serverVersion}',
68
- })
69
- `}
70
- </Const>
71
-
72
- {operations
73
- .map(({ tool, mcp, zod, node }) => {
74
- const { path: pathParams } = getOperationParameters(node, { paramsCasing })
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
112
-
113
- const config = [
114
- tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
115
- `description: ${JSON.stringify(tool.description)}`,
116
- outputSchema ? `outputSchema: { data: ${outputSchema} }` : null,
117
- ]
118
- .filter(Boolean)
119
- .join(',\n ')
120
-
121
- if (inputSchema) {
122
- return `
123
- server.registerTool(${JSON.stringify(tool.name)}, {
124
- ${config},
125
- inputSchema: ${inputSchema},
126
- }, async (${destructured}, request) => {
127
- return ${mcp.name}(${destructured}, request)
128
- })
129
- `
130
- }
131
-
132
- return `
133
- server.registerTool(${JSON.stringify(tool.name)}, {
134
- ${config},
135
- }, async (request) => {
136
- return ${mcp.name}(request)
137
- })
138
- `
139
- })
140
- .filter(Boolean)}
141
-
142
- <Function name="startServer" async export>
143
- {`try {
144
- const transport = new StdioServerTransport()
145
- await server.connect(transport)
146
-
147
- } catch (error) {
148
- console.error('Failed to start server:', error)
149
- process.exit(1)
150
- }`}
151
- </Function>
152
- </File.Source>
153
- )
154
- }
@@ -1,79 +0,0 @@
1
- import path from 'node:path'
2
- import { resolveOperationTypeNames } from '@internals/shared'
3
- import { defineGenerator } from '@kubb/core'
4
- import { pluginTsName } from '@kubb/plugin-ts'
5
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
6
- import { McpHandler } from '../components/McpHandler.tsx'
7
- import type { PluginMcp } from '../types.ts'
8
-
9
- export const mcpGenerator = defineGenerator<PluginMcp>({
10
- name: 'mcp',
11
- renderer: jsxRenderer,
12
- operation(node, ctx) {
13
- const { resolver, driver, root } = ctx
14
- const { output, client, paramsCasing, group } = ctx.options
15
-
16
- const pluginTs = driver.getPlugin(pluginTsName)
17
-
18
- if (!pluginTs) {
19
- return null
20
- }
21
-
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
38
-
39
- return (
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 ? (
48
- <>
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 />}
52
- </>
53
- ) : (
54
- <>
55
- <File.Import
56
- name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
57
- root={meta.file.path}
58
- path={path.resolve(root, '.kubb/fetch.ts')}
59
- isTypeOnly
60
- />
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 />
64
- )}
65
- </>
66
- )}
67
-
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
- />
76
- </File>
77
- )
78
- },
79
- })
@@ -1,146 +0,0 @@
1
- import path from 'node:path'
2
- import { findSuccessStatusCode, getOperationParameters } from '@internals/shared'
3
- import { defineGenerator } from '@kubb/core'
4
- import { pluginZodName } from '@kubb/plugin-zod'
5
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
6
- import { Server } from '../components/Server.tsx'
7
- import type { PluginMcp } from '../types.ts'
8
-
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>({
17
- name: 'operations',
18
- renderer: jsxRenderer,
19
- operations(nodes, ctx) {
20
- const { adapter, config, resolver, plugin, driver, root } = ctx
21
- const { output, paramsCasing, group } = ctx.options
22
-
23
- const pluginZod = driver.getPlugin(pluginZodName)
24
-
25
- if (!pluginZod) {
26
- return
27
- }
28
-
29
- const zodResolver = driver.getResolver(pluginZodName)
30
-
31
- const name = 'server'
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
- }
45
-
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) }))
65
-
66
- return {
67
- tool: {
68
- name: node.operationId,
69
- title: node.summary || undefined,
70
- description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`,
71
- },
72
- mcp: {
73
- name: resolver.resolveHandlerName(node),
74
- file: mcpFile,
75
- },
76
- zod: {
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,
83
- },
84
- node: node,
85
- }
86
- })
87
-
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
-
99
- return [
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)
103
- })
104
-
105
- return (
106
- <>
107
- <File
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 })}
113
- >
114
- <File.Import name={['McpServer']} path={'@modelcontextprotocol/sdk/server/mcp'} />
115
- <File.Import name={['z']} path={'zod'} />
116
- <File.Import name={['StdioServerTransport']} path={'@modelcontextprotocol/sdk/server/stdio'} />
117
-
118
- {imports}
119
- <Server
120
- name={name}
121
- serverName={adapter.inputNode?.meta?.title ?? 'server'}
122
- serverVersion={adapter.inputNode?.meta?.version ?? '0.0.0'}
123
- paramsCasing={paramsCasing}
124
- operations={operationsMapped}
125
- />
126
- </File>
127
-
128
- <File baseName={jsonFile.baseName} path={jsonFile.path} meta={jsonFile.meta}>
129
- <File.Source name={name}>
130
- {`
131
- {
132
- "mcpServers": {
133
- "${adapter.inputNode?.meta?.title || 'server'}": {
134
- "type": "stdio",
135
- "command": "npx",
136
- "args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
137
- }
138
- }
139
- }
140
- `}
141
- </File.Source>
142
- </File>
143
- </>
144
- )
145
- },
146
- })
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
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 DELETED
@@ -1,122 +0,0 @@
1
- import path from 'node:path'
2
- import { camelCase } from '@internals/utils'
3
-
4
- import { ast, definePlugin, type Group } from '@kubb/core'
5
- import { pluginClientName } from '@kubb/plugin-client'
6
- import { source as axiosClientSource } from '@kubb/plugin-client/templates/clients/axios.source'
7
- import { source as fetchClientSource } from '@kubb/plugin-client/templates/clients/fetch.source'
8
- import { source as configSource } from '@kubb/plugin-client/templates/config.source'
9
- import { pluginTsName } from '@kubb/plugin-ts'
10
- import { pluginZodName } from '@kubb/plugin-zod'
11
- import { mcpGenerator } from './generators/mcpGenerator.tsx'
12
- import { serverGenerator } from './generators/serverGenerator.tsx'
13
- import { resolverMcp } from './resolvers/resolverMcp.ts'
14
- import type { PluginMcp } from './types.ts'
15
-
16
- export const pluginMcpName = 'plugin-mcp' satisfies PluginMcp['name']
17
-
18
- export const pluginMcp = definePlugin<PluginMcp>((options) => {
19
- const {
20
- output = { path: 'mcp', barrelType: 'named' },
21
- group,
22
- exclude = [],
23
- include,
24
- override = [],
25
- paramsCasing,
26
- client,
27
- resolver: userResolver,
28
- transformer: userTransformer,
29
- generators: userGenerators = [],
30
- } = options
31
-
32
- const clientName = client?.client ?? 'axios'
33
- const clientImportPath = client?.importPath ?? (!client?.bundle ? `@kubb/plugin-client/clients/${clientName}` : undefined)
34
-
35
- const groupConfig = group
36
- ? ({
37
- ...group,
38
- name: group.name
39
- ? group.name
40
- : (ctx: { group: string }) => {
41
- if (group.type === 'path') {
42
- return `${ctx.group.split('/')[1]}`
43
- }
44
- return `${camelCase(ctx.group)}Requests`
45
- },
46
- } satisfies Group)
47
- : undefined
48
-
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,
74
- })
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
- },
118
- },
119
- }
120
- })
121
-
122
- export default pluginMcp
@@ -1,31 +0,0 @@
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
- }))
package/src/types.ts DELETED
@@ -1,88 +0,0 @@
1
- import type { ast, Exclude, Generator, Group, Include, Output, Override, PluginFactoryOptions, Resolver } from '@kubb/core'
2
- import type { ClientImportPath, PluginClient } from '@kubb/plugin-client'
3
-
4
- /**
5
- * Resolver for MCP that provides naming methods for handler functions.
6
- */
7
- export type ResolverMcp = Resolver & {
8
- /**
9
- * Resolves the base handler function name for an operation.
10
- *
11
- * @example Resolving handler function names
12
- * `resolver.resolveName('show pet by id') // -> 'showPetByIdHandler'`
13
- */
14
- resolveName(this: ResolverMcp, name: string): string
15
- /**
16
- * Resolves the output file name for an MCP module.
17
- */
18
- resolvePathName(this: ResolverMcp, name: string, type?: 'file' | 'function' | 'type' | 'const'): string
19
- /**
20
- * Resolves the handler function name for an operation.
21
- */
22
- resolveHandlerName(this: ResolverMcp, node: ast.OperationNode): string
23
- }
24
-
25
- export type Options = {
26
- /**
27
- * Specify the export location for the files and define the behavior of the output.
28
- * @default { path: 'mcp', barrelType: 'named' }
29
- */
30
- output?: Output
31
- /**
32
- * Client configuration for HTTP request generation.
33
- */
34
- client?: ClientImportPath & Pick<PluginClient['options'], 'clientType' | 'dataReturnType' | 'baseURL' | 'bundle' | 'paramsCasing'>
35
- /**
36
- * Apply casing to parameter names to match your configuration.
37
- */
38
- paramsCasing?: 'camelcase'
39
- /**
40
- * Group the MCP requests based on the provided name.
41
- */
42
- group?: Group
43
- /**
44
- * Tags, operations, or paths to exclude from generation.
45
- */
46
- exclude?: Array<Exclude>
47
- /**
48
- * Tags, operations, or paths to include in generation.
49
- */
50
- include?: Array<Include>
51
- /**
52
- * Override options for specific tags, operations, or paths.
53
- */
54
- override?: Array<Override<ResolvedOptions>>
55
- /**
56
- * Override naming conventions for function names and types.
57
- */
58
- resolver?: Partial<ResolverMcp> & ThisType<ResolverMcp>
59
- /**
60
- * AST visitor to transform generated nodes.
61
- */
62
- transformer?: ast.Visitor
63
- /**
64
- * Additional generators alongside the default generators.
65
- */
66
- generators?: Array<Generator<PluginMcp>>
67
- }
68
-
69
- type ResolvedOptions = {
70
- output: Output
71
- exclude: Array<Exclude>
72
- include: Array<Include> | undefined
73
- override: Array<Override<ResolvedOptions>>
74
- group: Group | undefined
75
- client: Pick<PluginClient['options'], 'client' | 'clientType' | 'dataReturnType' | 'importPath' | 'baseURL' | 'bundle' | 'paramsCasing'>
76
- paramsCasing: Options['paramsCasing']
77
- resolver: ResolverMcp
78
- }
79
-
80
- export type PluginMcp = PluginFactoryOptions<'plugin-mcp', Options, ResolvedOptions, ResolverMcp>
81
-
82
- declare global {
83
- namespace Kubb {
84
- interface PluginRegistry {
85
- 'plugin-mcp': PluginMcp
86
- }
87
- }
88
- }