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

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,174 +0,0 @@
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'
8
-
9
- type Props = {
10
- name: string
11
- serverName: string
12
- serverVersion: string
13
- paramsCasing?: 'camelcase'
14
- operations: Array<{
15
- tool: {
16
- name: string
17
- title?: string
18
- description: string
19
- }
20
- mcp: {
21
- name: string
22
- file: KubbFile.File
23
- }
24
- zod: {
25
- name: string
26
- file: KubbFile.File
27
- schemas: OperationSchemas
28
- }
29
- type: {
30
- schemas: OperationSchemas
31
- }
32
- }>
33
- }
34
-
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
- }
113
-
114
- export function Server({ name, serverName, serverVersion, paramsCasing, operations }: Props): FabricReactNode {
115
- return (
116
- <File.Source name={name} isExportable isIndexable>
117
- <Const name={'server'} export>
118
- {`
119
- new McpServer({
120
- name: '${serverName}',
121
- version: '${serverVersion}',
122
- })
123
- `}
124
- </Const>
125
-
126
- {operations
127
- .map(({ tool, mcp, zod }) => {
128
- const paramsClient = getParams({ schemas: zod.schemas, paramsCasing })
129
- const outputSchema = zod.schemas.response?.name
130
-
131
- const config = [
132
- tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
133
- `description: ${JSON.stringify(tool.description)}`,
134
- outputSchema ? `outputSchema: { data: ${outputSchema} }` : null,
135
- ]
136
- .filter(Boolean)
137
- .join(',\n ')
138
-
139
- if (zod.schemas.request?.name || zod.schemas.headerParams?.name || zod.schemas.queryParams?.name || zod.schemas.pathParams?.name) {
140
- return `
141
- server.registerTool(${JSON.stringify(tool.name)}, {
142
- ${config},
143
- inputSchema: ${paramsClient.toObjectValue()},
144
- }, async (${paramsClient.toObject()}) => {
145
- return ${mcp.name}(${paramsClient.toObject()})
146
- })
147
- `
148
- }
149
-
150
- return `
151
- server.registerTool(${JSON.stringify(tool.name)}, {
152
- ${config},
153
- }, async () => {
154
- return ${mcp.name}(${paramsClient.toObject()})
155
- })
156
- `
157
- })
158
- .filter(Boolean)}
159
-
160
- {`
161
- export async function startServer() {
162
- try {
163
- const transport = new StdioServerTransport()
164
- await server.connect(transport)
165
-
166
- } catch (error) {
167
- console.error('Failed to start server:', error)
168
- process.exit(1)
169
- }
170
- }
171
- `}
172
- </File.Source>
173
- )
174
- }
@@ -1 +0,0 @@
1
- export { Server } from './Server.tsx'
@@ -1,2 +0,0 @@
1
- export { mcpGenerator } from './mcpGenerator.tsx'
2
- export { serverGenerator } from './serverGenerator.tsx'
@@ -1,110 +0,0 @@
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'
6
- import { pluginTsName } from '@kubb/plugin-ts'
7
- import { File } from '@kubb/react-fabric'
8
- import type { PluginMcp } from '../types'
9
-
10
- export const mcpGenerator = createReactGenerator<PluginMcp>({
11
- name: 'mcp',
12
- Operation({ config, operation, generator, plugin }) {
13
- const { options } = plugin
14
- const oas = useOas()
15
-
16
- const { getSchemas, getName, getFile } = useOperationManager(generator)
17
-
18
- const mcp = {
19
- name: getName(operation, { type: 'function', suffix: 'handler' }),
20
- file: getFile(operation),
21
- }
22
-
23
- const type = {
24
- file: getFile(operation, { pluginName: pluginTsName }),
25
- schemas: getSchemas(operation, { pluginName: pluginTsName, type: 'type' }),
26
- }
27
-
28
- 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 ? (
37
- <>
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 />}
41
- </>
42
- ) : (
43
- <>
44
- <File.Import name={['fetch']} root={mcp.file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
45
- <File.Import
46
- name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
47
- root={mcp.file.path}
48
- path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')}
49
- isTypeOnly
50
- />
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 />
53
- )}
54
- </>
55
- )}
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
-
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>
107
- </File>
108
- )
109
- },
110
- })
@@ -1,112 +0,0 @@
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'
6
- 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'
10
-
11
- export const serverGenerator = createReactGenerator<PluginMcp>({
12
- name: 'operations',
13
- Operations({ operations, generator, plugin }) {
14
- const driver = usePluginDriver()
15
- const { options } = plugin
16
-
17
- const oas = useOas()
18
- const { getFile, getName, getSchemas } = useOperationManager(generator)
19
-
20
- const name = 'server'
21
- const file = driver.getFile({ name, extname: '.ts', pluginName: plugin.name })
22
-
23
- const jsonFile = driver.getFile({ name: '.mcp', extname: '.json', pluginName: plugin.name })
24
-
25
- const operationsMapped = operations.map((operation) => {
26
- return {
27
- 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}`,
31
- },
32
- mcp: {
33
- name: getName(operation, {
34
- type: 'function',
35
- suffix: 'handler',
36
- }),
37
- file: getFile(operation),
38
- },
39
- 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' }),
49
- },
50
- }
51
- })
52
-
53
- const imports = operationsMapped.flatMap(({ mcp, zod }) => {
54
- 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
- ]
69
- })
70
-
71
- return (
72
- <>
73
- <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 })}
79
- >
80
- <File.Import name={['McpServer']} path={'@modelcontextprotocol/sdk/server/mcp'} />
81
- <File.Import name={['z']} path={'zod'} />
82
- <File.Import name={['StdioServerTransport']} path={'@modelcontextprotocol/sdk/server/stdio'} />
83
-
84
- {imports}
85
- <Server
86
- name={name}
87
- serverName={oas.api.info?.title}
88
- serverVersion={oas.getVersion()}
89
- paramsCasing={options.paramsCasing}
90
- operations={operationsMapped}
91
- />
92
- </File>
93
-
94
- <File baseName={jsonFile.baseName} path={jsonFile.path} meta={jsonFile.meta}>
95
- <File.Source name={name}>
96
- {`
97
- {
98
- "mcpServers": {
99
- "${oas.api.info?.title || 'server'}": {
100
- "type": "stdio",
101
- "command": "npx",
102
- "args": ["tsx", "${file.path}"]
103
- }
104
- }
105
- }
106
- `}
107
- </File.Source>
108
- </File>
109
- </>
110
- )
111
- },
112
- })
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { pluginMcp, pluginMcpName } from './plugin.ts'
2
- export type { PluginMcp } from './types.ts'
package/src/plugin.ts DELETED
@@ -1,170 +0,0 @@
1
- import path from 'node:path'
2
- import { camelCase } from '@internals/utils'
3
- import { createPlugin, type Group, getBarrelFiles, getMode } from '@kubb/core'
4
- import { pluginClientName } from '@kubb/plugin-client'
5
- import { source as axiosClientSource } from '@kubb/plugin-client/templates/clients/axios.source'
6
- import { source as fetchClientSource } from '@kubb/plugin-client/templates/clients/fetch.source'
7
- import { source as configSource } from '@kubb/plugin-client/templates/config.source'
8
- import { OperationGenerator, pluginOasName } from '@kubb/plugin-oas'
9
- import { pluginTsName } from '@kubb/plugin-ts'
10
- import { pluginZodName } from '@kubb/plugin-zod'
11
- import { mcpGenerator, serverGenerator } from './generators'
12
- import type { PluginMcp } from './types.ts'
13
-
14
- export const pluginMcpName = 'plugin-mcp' satisfies PluginMcp['name']
15
-
16
- export const pluginMcp = createPlugin<PluginMcp>((options) => {
17
- const {
18
- output = { path: 'mcp', barrelType: 'named' },
19
- group,
20
- exclude = [],
21
- include,
22
- override = [],
23
- transformers = {},
24
- generators = [mcpGenerator, serverGenerator].filter(Boolean),
25
- contentType,
26
- paramsCasing,
27
- client,
28
- } = options
29
-
30
- const clientName = client?.client ?? 'axios'
31
- const clientImportPath = client?.importPath ?? (!client?.bundle ? `@kubb/plugin-client/clients/${clientName}` : undefined)
32
-
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
64
- ? group.name
65
- : (ctx) => {
66
- if (group?.type === 'path') {
67
- return `${ctx.group.split('/')[1]}`
68
- }
69
- 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
- },
119
- ],
120
- imports: [],
121
- exports: [],
122
- })
123
- }
124
-
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: [],
139
- })
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)
168
- },
169
- }
170
- })
package/src/types.ts DELETED
@@ -1,62 +0,0 @@
1
- import type { Group, Output, PluginFactoryOptions, ResolveNameParams } from '@kubb/core'
2
-
3
- import type { contentType, Oas } from '@kubb/oas'
4
- import type { ClientImportPath, PluginClient } from '@kubb/plugin-client'
5
- import type { Exclude, Include, Override, ResolvePathOptions } from '@kubb/plugin-oas'
6
- import type { Generator } from '@kubb/plugin-oas/generators'
7
-
8
- export type Options = {
9
- /**
10
- * Specify the export location for the files and define the behavior of the output
11
- * @default { path: 'mcp', barrelType: 'named' }
12
- */
13
- output?: Output<Oas>
14
- /**
15
- * Define which contentType should be used.
16
- * By default, the first JSON valid mediaType is used
17
- */
18
- contentType?: contentType
19
- client?: ClientImportPath & Pick<PluginClient['options'], 'clientType' | 'dataReturnType' | 'baseURL' | 'bundle' | 'paramsCasing'>
20
- /**
21
- * Transform parameter names to a specific casing format.
22
- * When set to 'camelcase', parameter names in path, query, and header params will be transformed to camelCase.
23
- * This should match the paramsCasing setting used in @kubb/plugin-ts.
24
- * @default undefined
25
- */
26
- paramsCasing?: 'camelcase'
27
- /**
28
- * Group the mcp requests based on the provided name.
29
- */
30
- group?: Group
31
- /**
32
- * Array containing exclude parameters to exclude/skip tags/operations/methods/paths.
33
- */
34
- exclude?: Array<Exclude>
35
- /**
36
- * Array containing include parameters to include tags/operations/methods/paths.
37
- */
38
- include?: Array<Include>
39
- /**
40
- * Array containing override parameters to override `options` based on tags/operations/methods/paths.
41
- */
42
- override?: Array<Override<ResolvedOptions>>
43
- transformers?: {
44
- /**
45
- * Customize the names based on the type that is provided by the plugin.
46
- */
47
- name?: (name: ResolveNameParams['name'], type?: ResolveNameParams['type']) => string
48
- }
49
- /**
50
- * Define some generators next to the Mcp generators.
51
- */
52
- generators?: Array<Generator<PluginMcp>>
53
- }
54
-
55
- type ResolvedOptions = {
56
- output: Output<Oas>
57
- group: Options['group']
58
- client: Pick<PluginClient['options'], 'client' | 'clientType' | 'dataReturnType' | 'importPath' | 'baseURL' | 'bundle' | 'paramsCasing'>
59
- paramsCasing: Options['paramsCasing']
60
- }
61
-
62
- export type PluginMcp = PluginFactoryOptions<'plugin-mcp', Options, ResolvedOptions, never, ResolvePathOptions>