@kubb/plugin-mcp 5.0.0-alpha.8 → 5.0.0-beta.10
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.
- package/LICENSE +17 -10
- package/README.md +24 -7
- package/dist/index.cjs +1007 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +215 -4
- package/dist/index.js +973 -90
- package/dist/index.js.map +1 -1
- package/extension.yaml +463 -0
- package/package.json +43 -66
- package/src/components/McpHandler.tsx +167 -0
- package/src/components/Server.tsx +89 -109
- package/src/generators/mcpGenerator.tsx +53 -84
- package/src/generators/serverGenerator.tsx +92 -58
- package/src/index.ts +11 -2
- package/src/plugin.ts +87 -135
- package/src/resolvers/resolverMcp.ts +31 -0
- package/src/types.ts +54 -28
- package/src/utils.ts +64 -0
- package/dist/Server-DV9zFrUP.cjs +0 -221
- package/dist/Server-DV9zFrUP.cjs.map +0 -1
- package/dist/Server-KWLMg0Lm.js +0 -173
- package/dist/Server-KWLMg0Lm.js.map +0 -1
- package/dist/components.cjs +0 -3
- package/dist/components.d.ts +0 -41
- package/dist/components.js +0 -2
- package/dist/generators-CWAFnA94.cjs +0 -285
- package/dist/generators-CWAFnA94.cjs.map +0 -1
- package/dist/generators-TtEOkDB1.js +0 -274
- package/dist/generators-TtEOkDB1.js.map +0 -1
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -479
- package/dist/generators.js +0 -2
- package/dist/types-DXZDZ3vf.d.ts +0 -64
- package/src/components/index.ts +0 -1
- package/src/generators/index.ts +0 -2
|
@@ -1,109 +1,78 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { useOas, useOperationManager } from '@kubb/plugin-oas/hooks'
|
|
5
|
-
import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
|
|
2
|
+
import { resolveOperationTypeNames } from '@internals/shared'
|
|
3
|
+
import { defineGenerator } from '@kubb/core'
|
|
6
4
|
import { pluginTsName } from '@kubb/plugin-ts'
|
|
7
|
-
import { File } from '@kubb/
|
|
8
|
-
import
|
|
5
|
+
import { File, jsxRenderer } from '@kubb/renderer-jsx'
|
|
6
|
+
import { McpHandler } from '../components/McpHandler.tsx'
|
|
7
|
+
import type { PluginMcp } from '../types.ts'
|
|
9
8
|
|
|
10
|
-
export const mcpGenerator =
|
|
9
|
+
export const mcpGenerator = defineGenerator<PluginMcp>({
|
|
11
10
|
name: 'mcp',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
11
|
+
renderer: jsxRenderer,
|
|
12
|
+
operation(node, ctx) {
|
|
13
|
+
const { resolver, driver, root } = ctx
|
|
14
|
+
const { output, client, paramsCasing, group } = ctx.options
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const pluginTs = driver.getPlugin(pluginTsName)
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
file: getFile(operation),
|
|
18
|
+
if (!pluginTs) {
|
|
19
|
+
return null
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
27
38
|
|
|
28
39
|
return (
|
|
29
|
-
<File
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{
|
|
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 ? (
|
|
37
48
|
<>
|
|
38
|
-
<File.Import name={'
|
|
39
|
-
<File.Import name={
|
|
40
|
-
{
|
|
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 />}
|
|
41
52
|
</>
|
|
42
53
|
) : (
|
|
43
54
|
<>
|
|
44
|
-
<File.Import name={['fetch']} root={mcp.file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
|
|
45
55
|
<File.Import
|
|
46
56
|
name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
|
|
47
|
-
root={
|
|
48
|
-
path={path.resolve(
|
|
57
|
+
root={meta.file.path}
|
|
58
|
+
path={path.resolve(root, '.kubb/fetch.ts')}
|
|
49
59
|
isTypeOnly
|
|
50
60
|
/>
|
|
51
|
-
{
|
|
52
|
-
|
|
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 />
|
|
53
64
|
)}
|
|
54
65
|
</>
|
|
55
66
|
)}
|
|
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
67
|
|
|
72
|
-
<
|
|
73
|
-
name={
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
baseURL={
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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>
|
|
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
|
+
/>
|
|
107
76
|
</File>
|
|
108
77
|
)
|
|
109
78
|
},
|
|
@@ -1,81 +1,115 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
|
|
5
|
-
import { pluginTsName } from '@kubb/plugin-ts'
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { findSuccessStatusCode, getOperationParameters } from '@internals/shared'
|
|
3
|
+
import { defineGenerator } from '@kubb/core'
|
|
6
4
|
import { pluginZodName } from '@kubb/plugin-zod'
|
|
7
|
-
import { File } from '@kubb/
|
|
8
|
-
import { Server } from '../components/Server'
|
|
9
|
-
import type { PluginMcp } from '../types'
|
|
5
|
+
import { File, jsxRenderer } from '@kubb/renderer-jsx'
|
|
6
|
+
import { Server } from '../components/Server.tsx'
|
|
7
|
+
import type { PluginMcp } from '../types.ts'
|
|
10
8
|
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
const {
|
|
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
|
|
18
|
-
|
|
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
|
|
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
|
|
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) }))
|
|
24
65
|
|
|
25
|
-
const operationsMapped = operations.map((operation) => {
|
|
26
66
|
return {
|
|
27
67
|
tool: {
|
|
28
|
-
name:
|
|
29
|
-
title:
|
|
30
|
-
description:
|
|
68
|
+
name: node.operationId,
|
|
69
|
+
title: node.summary || undefined,
|
|
70
|
+
description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`,
|
|
31
71
|
},
|
|
32
72
|
mcp: {
|
|
33
|
-
name:
|
|
34
|
-
|
|
35
|
-
suffix: 'handler',
|
|
36
|
-
}),
|
|
37
|
-
file: getFile(operation),
|
|
73
|
+
name: resolver.resolveHandlerName(node),
|
|
74
|
+
file: mcpFile,
|
|
38
75
|
},
|
|
39
76
|
zod: {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
file:
|
|
46
|
-
},
|
|
47
|
-
type: {
|
|
48
|
-
schemas: getSchemas(operation, { pluginName: pluginTsName, type: 'type' }),
|
|
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,
|
|
49
83
|
},
|
|
84
|
+
node: node,
|
|
50
85
|
}
|
|
51
86
|
})
|
|
52
87
|
|
|
53
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
|
+
|
|
54
99
|
return [
|
|
55
|
-
<File.Import key={mcp.name} name={[mcp.name]} root={
|
|
56
|
-
<File.Import
|
|
57
|
-
|
|
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
|
-
]
|
|
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)
|
|
69
103
|
})
|
|
70
104
|
|
|
71
105
|
return (
|
|
72
106
|
<>
|
|
73
107
|
<File
|
|
74
|
-
baseName={
|
|
75
|
-
path={
|
|
76
|
-
meta={
|
|
77
|
-
banner={
|
|
78
|
-
footer={
|
|
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 })}
|
|
79
113
|
>
|
|
80
114
|
<File.Import name={['McpServer']} path={'@modelcontextprotocol/sdk/server/mcp'} />
|
|
81
115
|
<File.Import name={['z']} path={'zod'} />
|
|
@@ -84,9 +118,9 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
|
|
|
84
118
|
{imports}
|
|
85
119
|
<Server
|
|
86
120
|
name={name}
|
|
87
|
-
serverName={
|
|
88
|
-
serverVersion={
|
|
89
|
-
paramsCasing={
|
|
121
|
+
serverName={adapter.inputNode?.meta?.title ?? 'server'}
|
|
122
|
+
serverVersion={adapter.inputNode?.meta?.version ?? '0.0.0'}
|
|
123
|
+
paramsCasing={paramsCasing}
|
|
90
124
|
operations={operationsMapped}
|
|
91
125
|
/>
|
|
92
126
|
</File>
|
|
@@ -96,10 +130,10 @@ export const serverGenerator = createReactGenerator<PluginMcp>({
|
|
|
96
130
|
{`
|
|
97
131
|
{
|
|
98
132
|
"mcpServers": {
|
|
99
|
-
"${
|
|
133
|
+
"${adapter.inputNode?.meta?.title || 'server'}": {
|
|
100
134
|
"type": "stdio",
|
|
101
135
|
"command": "npx",
|
|
102
|
-
"args": ["tsx", "${
|
|
136
|
+
"args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
|
|
103
137
|
}
|
|
104
138
|
}
|
|
105
139
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
121
|
-
exports: [],
|
|
122
|
-
})
|
|
123
|
-
}
|
|
46
|
+
} satisfies Group)
|
|
47
|
+
: undefined
|
|
124
48
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
}))
|