@kubb/plugin-mcp 5.0.0-alpha.33 → 5.0.0-alpha.35

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/src/plugin.ts CHANGED
@@ -1,20 +1,22 @@
1
1
  import path from 'node:path'
2
2
  import { camelCase } from '@internals/utils'
3
- import { createFile, createSource, createText } from '@kubb/ast'
4
- import { createPlugin, type Group, getPreset, mergeGenerators } from '@kubb/core'
3
+
4
+ import { ast, definePlugin, type Group } from '@kubb/core'
5
5
  import { pluginClientName } from '@kubb/plugin-client'
6
6
  import { source as axiosClientSource } from '@kubb/plugin-client/templates/clients/axios.source'
7
7
  import { source as fetchClientSource } from '@kubb/plugin-client/templates/clients/fetch.source'
8
8
  import { source as configSource } from '@kubb/plugin-client/templates/config.source'
9
9
  import { pluginTsName } from '@kubb/plugin-ts'
10
10
  import { pluginZodName } from '@kubb/plugin-zod'
11
- import { version } from '../package.json'
12
- import { presets } from './presets.ts'
11
+ import { mcpGenerator } from './generators/mcpGenerator.tsx'
12
+ import { serverGenerator } from './generators/serverGenerator.tsx'
13
+ import { serverGeneratorLegacy } from './generators/serverGeneratorLegacy.tsx'
14
+ import { resolverMcp } from './resolvers/resolverMcp.ts'
13
15
  import type { PluginMcp } from './types.ts'
14
16
 
15
17
  export const pluginMcpName = 'plugin-mcp' satisfies PluginMcp['name']
16
18
 
17
- export const pluginMcp = createPlugin<PluginMcp>((options) => {
19
+ export const pluginMcp = definePlugin<PluginMcp>((options) => {
18
20
  const {
19
21
  output = { path: 'mcp', barrelType: 'named' },
20
22
  group,
@@ -23,127 +25,100 @@ export const pluginMcp = createPlugin<PluginMcp>((options) => {
23
25
  override = [],
24
26
  paramsCasing,
25
27
  client,
26
- compatibilityPreset = 'default',
27
28
  resolver: userResolver,
28
29
  transformer: userTransformer,
29
30
  generators: userGenerators = [],
31
+ compatibilityPreset = 'default',
30
32
  } = options
31
33
 
34
+ const defaultServerGenerator = compatibilityPreset === 'kubbV4' ? serverGeneratorLegacy : serverGenerator
35
+
32
36
  const clientName = client?.client ?? 'axios'
33
37
  const clientImportPath = client?.importPath ?? (!client?.bundle ? `@kubb/plugin-client/clients/${clientName}` : undefined)
34
38
 
35
- const preset = getPreset({
36
- preset: compatibilityPreset,
37
- presets,
38
- resolver: userResolver,
39
- transformer: userTransformer,
40
- generators: userGenerators,
41
- })
42
-
43
- const generators = preset.generators ?? []
44
- const mergedGenerator = mergeGenerators(generators)
39
+ const groupConfig = group
40
+ ? ({
41
+ ...group,
42
+ name: group.name
43
+ ? group.name
44
+ : (ctx: { group: string }) => {
45
+ if (group.type === 'path') {
46
+ return `${ctx.group.split('/')[1]}`
47
+ }
48
+ return `${camelCase(ctx.group)}Requests`
49
+ },
50
+ } satisfies Group)
51
+ : undefined
45
52
 
46
53
  return {
47
54
  name: pluginMcpName,
48
- version,
49
- get resolver() {
50
- return preset.resolver
51
- },
52
- get transformer() {
53
- return preset.transformer
54
- },
55
- get options() {
56
- return {
57
- output,
58
- exclude,
59
- include,
60
- override,
61
- group: group
62
- ? ({
63
- ...group,
64
- name: group.name
65
- ? group.name
66
- : (ctx: { group: string }) => {
67
- if (group.type === 'path') {
68
- return `${ctx.group.split('/')[1]}`
69
- }
70
- return `${camelCase(ctx.group)}Requests`
71
- },
72
- } satisfies Group)
73
- : undefined,
74
- paramsCasing,
75
- client: {
76
- client: clientName,
77
- clientType: client?.clientType ?? 'function',
78
- importPath: clientImportPath,
79
- dataReturnType: client?.dataReturnType ?? 'data',
80
- bundle: client?.bundle,
81
- baseURL: client?.baseURL,
82
- paramsCasing: client?.paramsCasing,
83
- },
84
- resolver: preset.resolver,
85
- }
86
- },
87
- pre: [pluginTsName, pluginZodName].filter(Boolean),
88
- async schema(node, options) {
89
- return mergedGenerator.schema?.call(this, node, options)
90
- },
91
- async operation(node, options) {
92
- return mergedGenerator.operation?.call(this, node, options)
93
- },
94
- async operations(nodes, options) {
95
- return mergedGenerator.operations?.call(this, nodes, options)
96
- },
97
- async buildStart() {
98
- const { adapter, driver } = this
99
- const root = this.root
55
+ options,
56
+ dependencies: [pluginTsName, pluginZodName],
57
+ hooks: {
58
+ 'kubb:plugin:setup'(ctx) {
59
+ const resolver = userResolver ? { ...resolverMcp, ...userResolver } : resolverMcp
100
60
 
101
- const baseURL = adapter?.inputNode?.meta?.baseURL
102
- if (baseURL) {
103
- this.plugin.options.client.baseURL = this.plugin.options.client.baseURL || baseURL
104
- }
61
+ ctx.setOptions({
62
+ output,
63
+ exclude,
64
+ include,
65
+ override,
66
+ group: groupConfig,
67
+ paramsCasing,
68
+ client: {
69
+ client: clientName,
70
+ clientType: client?.clientType ?? 'function',
71
+ importPath: clientImportPath,
72
+ dataReturnType: client?.dataReturnType ?? 'data',
73
+ bundle: client?.bundle,
74
+ baseURL: client?.baseURL,
75
+ paramsCasing: client?.paramsCasing,
76
+ },
77
+ resolver,
78
+ })
79
+ ctx.setResolver(resolver)
80
+ if (userTransformer) {
81
+ ctx.setTransformer(userTransformer)
82
+ }
83
+ ctx.addGenerator(mcpGenerator)
84
+ ctx.addGenerator(defaultServerGenerator)
85
+ for (const gen of userGenerators) {
86
+ ctx.addGenerator(gen)
87
+ }
105
88
 
106
- const hasClientPlugin = !!driver.getPlugin(pluginClientName)
89
+ const root = path.resolve(ctx.config.root, ctx.config.output.path)
90
+ const hasClientPlugin = ctx.config.plugins?.some((p) => p.name === pluginClientName)
107
91
 
108
- if (this.plugin.options.client.bundle && !hasClientPlugin && !this.plugin.options.client.importPath) {
109
- await this.addFile(
110
- createFile({
92
+ if (client?.bundle && !hasClientPlugin && !clientImportPath) {
93
+ ctx.injectFile({
111
94
  baseName: 'fetch.ts',
112
95
  path: path.resolve(root, '.kubb/fetch.ts'),
113
96
  sources: [
114
- createSource({
97
+ ast.createSource({
115
98
  name: 'fetch',
116
- nodes: [createText(this.plugin.options.client.client === 'fetch' ? fetchClientSource : axiosClientSource)],
99
+ nodes: [ast.createText(clientName === 'fetch' ? fetchClientSource : axiosClientSource)],
117
100
  isExportable: true,
118
101
  isIndexable: true,
119
102
  }),
120
103
  ],
121
- imports: [],
122
- exports: [],
123
- }),
124
- )
125
- }
104
+ })
105
+ }
126
106
 
127
- if (!hasClientPlugin) {
128
- await this.addFile(
129
- createFile({
107
+ if (!hasClientPlugin) {
108
+ ctx.injectFile({
130
109
  baseName: 'config.ts',
131
110
  path: path.resolve(root, '.kubb/config.ts'),
132
111
  sources: [
133
- createSource({
112
+ ast.createSource({
134
113
  name: 'config',
135
- nodes: [createText(configSource)],
114
+ nodes: [ast.createText(configSource)],
136
115
  isExportable: false,
137
116
  isIndexable: false,
138
117
  }),
139
118
  ],
140
- imports: [],
141
- exports: [],
142
- }),
143
- )
144
- }
145
-
146
- await this.openInStudio({ ast: true })
119
+ })
120
+ }
121
+ },
147
122
  },
148
123
  }
149
124
  })
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Visitor } from '@kubb/ast/types'
2
1
  import type {
2
+ ast,
3
3
  CompatibilityPreset,
4
4
  Exclude,
5
5
  Generator,
@@ -74,7 +74,7 @@ export type Options = {
74
74
  * A single AST visitor applied before printing.
75
75
  * When a visitor method returns `null` or `undefined`, the preset transformer's result is used instead.
76
76
  */
77
- transformer?: Visitor
77
+ transformer?: ast.Visitor
78
78
  /**
79
79
  * Define some generators next to the default MCP generators.
80
80
  */
package/src/utils.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { camelCase } from '@internals/utils'
2
- import type { OperationNode, SchemaNode, StatusCode } from '@kubb/ast/types'
2
+ import type { ast } from '@kubb/core'
3
3
 
4
4
  /**
5
5
  * Find the first 2xx response status code from an operation's responses.
6
6
  */
7
- export function findSuccessStatusCode(responses: Array<{ statusCode: number | string }>): StatusCode | undefined {
7
+ export function findSuccessStatusCode(responses: Array<{ statusCode: number | string }>): ast.StatusCode | undefined {
8
8
  for (const res of responses) {
9
9
  const code = Number(res.statusCode)
10
10
  if (code >= 200 && code < 300) {
11
- return res.statusCode as StatusCode
11
+ return res.statusCode as ast.StatusCode
12
12
  }
13
13
  }
14
14
  return undefined
@@ -34,7 +34,7 @@ export function zodGroupExpr(entry: string | Array<ZodParam>): string {
34
34
  /**
35
35
  * Build JSDoc comment lines from an OperationNode.
36
36
  */
37
- export function getComments(node: OperationNode): Array<string> {
37
+ export function getComments(node: ast.OperationNode): Array<string> {
38
38
  return [
39
39
  node.description && `@description ${node.description}`,
40
40
  node.summary && `@summary ${node.summary}`,
@@ -70,9 +70,25 @@ export function getParamsMapping(params: Array<{ name: string }>): Record<string
70
70
  * Convert a SchemaNode type to an inline Zod expression string.
71
71
  * Used as fallback when no named zod schema is available for a path parameter.
72
72
  */
73
- export function zodExprFromSchemaNode(schema: SchemaNode): string {
73
+ export function zodExprFromSchemaNode(schema: ast.SchemaNode): string {
74
74
  let expr: string
75
75
  switch (schema.type) {
76
+ case 'enum': {
77
+ // namedEnumValues takes priority over enumValues
78
+ const rawValues: Array<string | number | boolean> = schema.namedEnumValues?.length
79
+ ? schema.namedEnumValues.map((v) => v.value)
80
+ : (schema.enumValues ?? []).filter((v): v is string | number | boolean => v !== null)
81
+
82
+ if (rawValues.length > 0 && rawValues.every((v) => typeof v === 'string')) {
83
+ expr = `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(', ')}])`
84
+ } else if (rawValues.length > 0) {
85
+ const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`)
86
+ expr = literals.length === 1 ? literals[0]! : `z.union([${literals.join(', ')}])`
87
+ } else {
88
+ expr = 'z.string()'
89
+ }
90
+ break
91
+ }
76
92
  case 'integer':
77
93
  expr = 'z.coerce.number()'
78
94
  break
package/src/presets.ts DELETED
@@ -1,25 +0,0 @@
1
- import { definePresets } from '@kubb/core'
2
- import { mcpGenerator } from './generators/mcpGenerator.tsx'
3
- import { serverGenerator } from './generators/serverGenerator.tsx'
4
- import { serverGeneratorLegacy } from './generators/serverGeneratorLegacy.tsx'
5
- import { resolverMcp } from './resolvers/resolverMcp.ts'
6
- import type { ResolverMcp } from './types.ts'
7
-
8
- /**
9
- * Built-in preset registry for `@kubb/plugin-mcp`.
10
- *
11
- * - `default` — v5 naming with individual zod schemas and per-status responses.
12
- * - `kubbV4` — legacy naming with grouped zod schemas and combined responses.
13
- */
14
- export const presets = definePresets<ResolverMcp>({
15
- default: {
16
- name: 'default',
17
- resolver: resolverMcp,
18
- generators: [mcpGenerator, serverGenerator],
19
- },
20
- kubbV4: {
21
- name: 'kubbV4',
22
- resolver: resolverMcp,
23
- generators: [mcpGenerator, serverGeneratorLegacy],
24
- },
25
- })