@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.
- package/LICENSE +17 -10
- package/README.md +37 -23
- package/dist/index.cjs +860 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +254 -6
- package/dist/index.js +830 -105
- package/dist/index.js.map +1 -1
- package/package.json +38 -74
- 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 -508
- package/dist/generators.js +0 -2
- package/dist/types-DXZDZ3vf.d.ts +0 -64
- package/src/components/Server.tsx +0 -174
- package/src/components/index.ts +0 -1
- package/src/generators/index.ts +0 -2
- package/src/generators/mcpGenerator.tsx +0 -110
- package/src/generators/serverGenerator.tsx +0 -112
- package/src/index.ts +0 -2
- package/src/plugin.ts +0 -170
- package/src/types.ts +0 -62
- /package/dist/{chunk--u3MIqq1.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,122 +1,847 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
3
|
-
import { n as mcpGenerator, t as serverGenerator } from "./generators-TtEOkDB1.js";
|
|
1
|
+
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
+
import { Resolver, Url, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { source as source$1 } from "@kubb/plugin-client/templates/clients/fetch.source";
|
|
9
|
-
import { source as source$2 } from "@kubb/plugin-client/templates/config.source";
|
|
10
|
-
import { OperationGenerator, pluginOasName } from "@kubb/plugin-oas";
|
|
11
|
-
import { pluginTsName } from "@kubb/plugin-ts";
|
|
4
|
+
import { Const, File, Function, jsxRenderer } from "kubb/jsx";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
|
|
6
|
+
import { createFunctionParameter, createFunctionParameters, createObjectBindingPattern, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
|
|
12
7
|
import { pluginZodName } from "@kubb/plugin-zod";
|
|
8
|
+
//#region ../../internals/shared/src/params.ts
|
|
9
|
+
/**
|
|
10
|
+
* Drops parameters that share the same name, keeping the first.
|
|
11
|
+
*
|
|
12
|
+
* A malformed spec can declare the same parameter name twice within one `in` location. Both would
|
|
13
|
+
* resolve to the same output property, so emitting both would yield an object type with a duplicate
|
|
14
|
+
* member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
|
|
15
|
+
* parameter names flow through unchanged, so no two distinct names ever collide here anymore.
|
|
16
|
+
*/
|
|
17
|
+
function dedupeParams(params) {
|
|
18
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19
|
+
return params.filter((param) => {
|
|
20
|
+
if (seen.has(param.name)) return false;
|
|
21
|
+
seen.add(param.name);
|
|
22
|
+
return true;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region ../../internals/shared/src/operation.ts
|
|
27
|
+
/**
|
|
28
|
+
* Builds the `ResolverFileParams` every operation generator passes to
|
|
29
|
+
* `resolver.file`: a file named `name`, tagged by the operation's first
|
|
30
|
+
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
31
|
+
* that was repeated at dozens of call sites across the client and query plugins.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function operationFileEntry(node, name, extname = ".ts") {
|
|
39
|
+
return {
|
|
40
|
+
name,
|
|
41
|
+
extname,
|
|
42
|
+
tag: node.tags[0] ?? "default",
|
|
43
|
+
path: node.path
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function getOperationLink(node, link) {
|
|
47
|
+
if (!link) return null;
|
|
48
|
+
if (typeof link === "function") return link(node) ?? null;
|
|
49
|
+
return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
|
|
53
|
+
* are present and the union, default, and form-data flags the client uses to pick one.
|
|
54
|
+
*/
|
|
55
|
+
function buildContentTypeInfo(contentTypes) {
|
|
56
|
+
const isMultipleContentTypes = contentTypes.length > 1;
|
|
57
|
+
return {
|
|
58
|
+
contentTypes,
|
|
59
|
+
isMultipleContentTypes,
|
|
60
|
+
contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
|
|
61
|
+
defaultContentType: contentTypes[0] ?? "application/json",
|
|
62
|
+
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function getContentTypeInfo(node) {
|
|
66
|
+
return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The request-body counterpart for the primary success response: the content types it documents and
|
|
70
|
+
* whether several are present, so the client can let a caller pick which one to accept.
|
|
71
|
+
*/
|
|
72
|
+
function getResponseContentTypeInfo(node) {
|
|
73
|
+
return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
|
|
74
|
+
}
|
|
75
|
+
function buildRequestConfigType(node) {
|
|
76
|
+
const request = getContentTypeInfo(node);
|
|
77
|
+
const response = getResponseContentTypeInfo(node);
|
|
78
|
+
const configType = `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`;
|
|
79
|
+
const members = [request.isMultipleContentTypes ? `request?: ${request.contentTypeUnion}` : null, response.isMultipleContentTypes ? `response?: ${response.contentTypeUnion}` : null].filter(Boolean);
|
|
80
|
+
return members.length ? `${configType} & { contentType?: { ${members.join("; ")} } }` : configType;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Which of the grouped request options an operation carries.
|
|
84
|
+
*/
|
|
85
|
+
function getRequestGroups(node) {
|
|
86
|
+
const { path, query, header } = getOperationParameters(node);
|
|
87
|
+
return {
|
|
88
|
+
path: path.length > 0,
|
|
89
|
+
query: query.length > 0,
|
|
90
|
+
body: Boolean(node.requestBody?.content?.[0]?.schema),
|
|
91
|
+
headers: header.length > 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolves which grouped request options an operation carries together with whether each group
|
|
96
|
+
* holds a required member. The grouped parameter stays optional only when nothing inside it is
|
|
97
|
+
* required, matching the generated `RequestConfig` type.
|
|
98
|
+
*/
|
|
99
|
+
function getRequestGroupOptionality(node) {
|
|
100
|
+
const groups = getRequestGroups(node);
|
|
101
|
+
const { path, query, header } = getOperationParameters(node);
|
|
102
|
+
const hasRequiredPath = path.some((param) => param.required);
|
|
103
|
+
const hasRequiredQuery = query.some((param) => param.required);
|
|
104
|
+
const hasRequiredHeader = header.some((param) => param.required);
|
|
105
|
+
return {
|
|
106
|
+
groups,
|
|
107
|
+
hasRequiredPath,
|
|
108
|
+
hasRequiredQuery,
|
|
109
|
+
hasRequiredHeader,
|
|
110
|
+
isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Builds the grouped `{ path, query, body, headers }` parameter for a generated client
|
|
115
|
+
* function, typed from the operation's `Options` (minus `url`). Only the groups the
|
|
116
|
+
* operation actually has are destructured. The trailing `config` parameter carries the
|
|
117
|
+
* runtime `RequestConfig` overrides plus `client`.
|
|
118
|
+
*/
|
|
119
|
+
function buildRequestParamsSignature(node, resolver, options = {}) {
|
|
120
|
+
const { isConfigurable = true } = options;
|
|
121
|
+
const { groups, isOptional } = getRequestGroupOptionality(node);
|
|
122
|
+
const names = [
|
|
123
|
+
"path",
|
|
124
|
+
"query",
|
|
125
|
+
"body",
|
|
126
|
+
"headers"
|
|
127
|
+
].filter((key) => groups[key]);
|
|
128
|
+
return {
|
|
129
|
+
signature: [names.length > 0 ? `{ ${names.join(", ")} }: ${resolver.response.options(node)}${isOptional ? " = {}" : ""}` : null, isConfigurable ? `config: ${buildRequestConfigType(node)} = {}` : null].filter(Boolean).join(", "),
|
|
130
|
+
groups
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function buildOperationComments(node, options = {}) {
|
|
134
|
+
const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
|
|
135
|
+
const linkComment = getOperationLink(node, link);
|
|
136
|
+
const filteredComments = (linkPosition === "beforeDeprecated" ? [
|
|
137
|
+
node.description && `@description ${node.description}`,
|
|
138
|
+
node.summary && `@summary ${node.summary}`,
|
|
139
|
+
linkComment,
|
|
140
|
+
node.deprecated && "@deprecated"
|
|
141
|
+
] : [
|
|
142
|
+
node.description && `@description ${node.description}`,
|
|
143
|
+
node.summary && `@summary ${node.summary}`,
|
|
144
|
+
node.deprecated && "@deprecated",
|
|
145
|
+
linkComment
|
|
146
|
+
]).filter((comment) => Boolean(comment));
|
|
147
|
+
if (!splitLines) return filteredComments;
|
|
148
|
+
return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
|
|
149
|
+
}
|
|
150
|
+
function getOperationParameters(node) {
|
|
151
|
+
return {
|
|
152
|
+
path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
|
|
153
|
+
query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
|
|
154
|
+
header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
|
|
155
|
+
cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function getStatusCodeNumber(statusCode) {
|
|
159
|
+
const code = Number(statusCode);
|
|
160
|
+
return Number.isNaN(code) ? null : code;
|
|
161
|
+
}
|
|
162
|
+
function isSuccessStatusCode(statusCode) {
|
|
163
|
+
const code = getStatusCodeNumber(statusCode);
|
|
164
|
+
return code !== null && code >= 200 && code < 300;
|
|
165
|
+
}
|
|
166
|
+
function getSuccessResponses(responses) {
|
|
167
|
+
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
168
|
+
}
|
|
169
|
+
function getOperationSuccessResponses(node) {
|
|
170
|
+
return getSuccessResponses(node.responses);
|
|
171
|
+
}
|
|
172
|
+
function getPrimarySuccessResponse(node) {
|
|
173
|
+
return getOperationSuccessResponses(node)[0] ?? null;
|
|
174
|
+
}
|
|
175
|
+
function findSuccessStatusCode(responses) {
|
|
176
|
+
for (const response of responses) if (isSuccessStatusCode(response.statusCode)) return response.statusCode;
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region ../../internals/utils/src/casing.ts
|
|
181
|
+
/**
|
|
182
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
183
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
184
|
+
* and capitalizes each word according to `pascal`.
|
|
185
|
+
*
|
|
186
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
187
|
+
*/
|
|
188
|
+
function toCamelOrPascal(text, pascal) {
|
|
189
|
+
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
190
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
191
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
192
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Converts `text` to camelCase.
|
|
196
|
+
*
|
|
197
|
+
* @example Word boundaries
|
|
198
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
199
|
+
*
|
|
200
|
+
* @example With a prefix
|
|
201
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
202
|
+
*/
|
|
203
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
204
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region ../../internals/shared/src/group.ts
|
|
208
|
+
/**
|
|
209
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
210
|
+
* shared default naming so every plugin groups output consistently:
|
|
211
|
+
*
|
|
212
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
213
|
+
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
214
|
+
*
|
|
215
|
+
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
216
|
+
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
217
|
+
* per-plugin convention.
|
|
218
|
+
*
|
|
219
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* createGroupConfig(group) // shared across every plugin
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
function createGroupConfig(group) {
|
|
227
|
+
if (!group) return null;
|
|
228
|
+
const defaultName = (ctx) => {
|
|
229
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
230
|
+
return camelCase(ctx.group);
|
|
231
|
+
};
|
|
232
|
+
return {
|
|
233
|
+
...group,
|
|
234
|
+
name: group.name ? group.name : defaultName
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/components/McpHandler.tsx
|
|
239
|
+
const requestGroupOrder = [
|
|
240
|
+
"path",
|
|
241
|
+
"query",
|
|
242
|
+
"headers",
|
|
243
|
+
"body"
|
|
244
|
+
];
|
|
245
|
+
function McpHandler({ name, clientName, node, resolver }) {
|
|
246
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
247
|
+
const { signature, groups } = buildRequestParamsSignature(node, resolver, { isConfigurable: false });
|
|
248
|
+
const paramsSignature = [signature, "request: RequestHandlerExtra<ServerRequest, ServerNotification>"].filter(Boolean).join(", ");
|
|
249
|
+
const callArgs = requestGroupOrder.filter((key) => groups[key]);
|
|
250
|
+
const callConfig = callArgs.length ? `{ ${callArgs.join(", ")} }` : "{}";
|
|
251
|
+
return /* @__PURE__ */ jsx(File.Source, {
|
|
252
|
+
name,
|
|
253
|
+
isExportable: true,
|
|
254
|
+
isIndexable: true,
|
|
255
|
+
children: /* @__PURE__ */ jsxs(Function, {
|
|
256
|
+
name,
|
|
257
|
+
async: true,
|
|
258
|
+
export: true,
|
|
259
|
+
params: paramsSignature,
|
|
260
|
+
JSDoc: { comments: buildOperationComments(node) },
|
|
261
|
+
returnType: "Promise<CallToolResult>",
|
|
262
|
+
children: [
|
|
263
|
+
`const res = await ${clientName}(${callConfig})`,
|
|
264
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
265
|
+
`return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: 'text',
|
|
269
|
+
text: JSON.stringify(res.data)
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
structuredContent: { data: res.data }
|
|
273
|
+
}`
|
|
274
|
+
]
|
|
275
|
+
})
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/utils.ts
|
|
280
|
+
/**
|
|
281
|
+
* Render a group param value — compose individual schemas into `z.object({ ... })`,
|
|
282
|
+
* or use a schema name string directly.
|
|
283
|
+
*/
|
|
284
|
+
function zodGroupExpr(entry) {
|
|
285
|
+
if (typeof entry === "string") return entry;
|
|
286
|
+
return `z.object({ ${entry.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Convert a SchemaNode type to an inline Zod expression string.
|
|
290
|
+
* Used as fallback when no named zod schema is available for a path parameter.
|
|
291
|
+
*/
|
|
292
|
+
function zodExprFromSchemaNode(schema) {
|
|
293
|
+
const baseExpr = (() => {
|
|
294
|
+
if (schema.type === "enum") {
|
|
295
|
+
const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
|
|
296
|
+
if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) return `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
|
|
297
|
+
if (rawValues.length > 0) {
|
|
298
|
+
const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
|
|
299
|
+
return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
|
|
300
|
+
}
|
|
301
|
+
return "z.string()";
|
|
302
|
+
}
|
|
303
|
+
if (schema.type === "integer") return "z.coerce.number()";
|
|
304
|
+
if (schema.type === "number") return "z.number()";
|
|
305
|
+
if (schema.type === "boolean") return "z.boolean()";
|
|
306
|
+
if (schema.type === "array") return "z.array(z.unknown())";
|
|
307
|
+
return "z.string()";
|
|
308
|
+
})();
|
|
309
|
+
return schema.nullable ? `${baseExpr}.nullable()` : baseExpr;
|
|
310
|
+
}
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/components/Server.tsx
|
|
313
|
+
const keysPrinter = functionPrinter({ mode: "call" });
|
|
314
|
+
function Server({ name, serverName, serverVersion, operations }) {
|
|
315
|
+
const registrations = operations.map(({ tool, mcp, zod, node }) => {
|
|
316
|
+
const { path: pathParams } = getOperationParameters(node);
|
|
317
|
+
const entries = [];
|
|
318
|
+
if (pathParams.length) {
|
|
319
|
+
const pathFields = pathParams.map((p) => {
|
|
320
|
+
const zodParam = zod.pathParams.find((zp) => zp.name === p.name);
|
|
321
|
+
return `${JSON.stringify(p.name)}: ${zodParam ? zodParam.schemaName : zodExprFromSchemaNode(p.schema)}`;
|
|
322
|
+
}).join(", ");
|
|
323
|
+
entries.push({
|
|
324
|
+
key: "path",
|
|
325
|
+
value: `z.object({ ${pathFields} })`
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (zod.queryParams) entries.push({
|
|
329
|
+
key: "query",
|
|
330
|
+
value: zodGroupExpr(zod.queryParams)
|
|
331
|
+
});
|
|
332
|
+
if (zod.headerParams) entries.push({
|
|
333
|
+
key: "headers",
|
|
334
|
+
value: zodGroupExpr(zod.headerParams)
|
|
335
|
+
});
|
|
336
|
+
if (zod.requestName) entries.push({
|
|
337
|
+
key: "body",
|
|
338
|
+
value: zod.requestName
|
|
339
|
+
});
|
|
340
|
+
const paramsNode = entries.length ? createFunctionParameters({ params: [createFunctionParameter({
|
|
341
|
+
name: createObjectBindingPattern({ elements: entries.map((e) => ({ name: e.key })) }),
|
|
342
|
+
optional: false
|
|
343
|
+
})] }) : null;
|
|
344
|
+
const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
|
|
345
|
+
const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : null;
|
|
346
|
+
const outputSchema = zod.responseName;
|
|
347
|
+
const config = [
|
|
348
|
+
tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
|
|
349
|
+
`description: ${JSON.stringify(tool.description)}`,
|
|
350
|
+
outputSchema ? `outputSchema: { data: ${outputSchema} }` : null
|
|
351
|
+
].filter(Boolean).join(",\n ");
|
|
352
|
+
if (inputSchema) return `
|
|
353
|
+
server.registerTool(${JSON.stringify(tool.name)}, {
|
|
354
|
+
${config},
|
|
355
|
+
inputSchema: ${inputSchema},
|
|
356
|
+
}, async (${destructured}, request) => {
|
|
357
|
+
return ${mcp.name}(${destructured}, request)
|
|
358
|
+
})
|
|
359
|
+
`;
|
|
360
|
+
return `
|
|
361
|
+
server.registerTool(${JSON.stringify(tool.name)}, {
|
|
362
|
+
${config},
|
|
363
|
+
}, async (request) => {
|
|
364
|
+
return ${mcp.name}(request)
|
|
365
|
+
})
|
|
366
|
+
`;
|
|
367
|
+
}).filter(Boolean).join("\n");
|
|
368
|
+
return /* @__PURE__ */ jsxs(File.Source, {
|
|
369
|
+
name,
|
|
370
|
+
isExportable: true,
|
|
371
|
+
isIndexable: true,
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ jsx(Function, {
|
|
374
|
+
name: "getServer",
|
|
375
|
+
export: true,
|
|
376
|
+
children: `const server = new McpServer({
|
|
377
|
+
name: '${serverName}',
|
|
378
|
+
version: '${serverVersion}',
|
|
379
|
+
})
|
|
380
|
+
${registrations}
|
|
381
|
+
return server`
|
|
382
|
+
}),
|
|
383
|
+
/* @__PURE__ */ jsx(Const, {
|
|
384
|
+
name: "server",
|
|
385
|
+
export: true,
|
|
386
|
+
children: "getServer()"
|
|
387
|
+
}),
|
|
388
|
+
/* @__PURE__ */ jsx(Function, {
|
|
389
|
+
name: "startServer",
|
|
390
|
+
async: true,
|
|
391
|
+
export: true,
|
|
392
|
+
children: `try {
|
|
393
|
+
const transport = new StdioServerTransport()
|
|
394
|
+
await server.connect(transport)
|
|
395
|
+
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.error('Failed to start server:', error)
|
|
398
|
+
process.exit(1)
|
|
399
|
+
}`
|
|
400
|
+
})
|
|
401
|
+
]
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region ../../internals/client/src/resolveClient.ts
|
|
406
|
+
/**
|
|
407
|
+
* Resolves which client plugin a consumer (react-query, vue-query, swr, mcp) should call for an
|
|
408
|
+
* operation, shared so the selection rules and diagnostics stay in one place.
|
|
409
|
+
*
|
|
410
|
+
* Every client runtime lives in a dedicated client plugin, so a consumer always calls a registered
|
|
411
|
+
* contract client plugin (plugin-fetch or plugin-axios) and never emits its own inline client:
|
|
412
|
+
*
|
|
413
|
+
* - `contract` — a registered contract client plugin owns the `<op>` functions and the consumer
|
|
414
|
+
* imports and calls them.
|
|
415
|
+
* - `error` — no client plugin is registered, several are registered without a selector, or the
|
|
416
|
+
* requested one is missing.
|
|
417
|
+
*
|
|
418
|
+
* The `client` string selects explicitly (`'fetch'` / `'axios'`); when it is unset a lone registered
|
|
419
|
+
* contract client plugin is picked up automatically.
|
|
420
|
+
*/
|
|
421
|
+
const pluginFetchName = "plugin-fetch";
|
|
422
|
+
const pluginAxiosName = "plugin-axios";
|
|
423
|
+
const selectorToPlugin = {
|
|
424
|
+
fetch: pluginFetchName,
|
|
425
|
+
axios: pluginAxiosName
|
|
426
|
+
};
|
|
427
|
+
const contractPlugins = [pluginFetchName, pluginAxiosName];
|
|
428
|
+
/**
|
|
429
|
+
* Applies the `client` resolution rules. See the module comment for the strategy shape.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```ts
|
|
433
|
+
* resolveClient({ client: 'fetch', pluginNames: ['plugin-ts', 'plugin-fetch'] })
|
|
434
|
+
* // { kind: 'contract', pluginName: 'plugin-fetch' }
|
|
435
|
+
* ```
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```ts
|
|
439
|
+
* resolveClient({ client: undefined, pluginNames: ['plugin-ts'] })
|
|
440
|
+
* // { kind: 'error', message: 'No client plugin is registered…' }
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
function resolveClient(options) {
|
|
444
|
+
const { client, pluginNames } = options;
|
|
445
|
+
const has = (name) => pluginNames.includes(name);
|
|
446
|
+
if (client === "fetch" || client === "axios") {
|
|
447
|
+
const pluginName = selectorToPlugin[client];
|
|
448
|
+
if (!has(pluginName)) return {
|
|
449
|
+
kind: "error",
|
|
450
|
+
message: `\`client: '${client}'\` is set but \`@kubb/plugin-${client}\` is not registered in \`plugins\`. Add it, or drop \`client\` to use a different client plugin.`
|
|
451
|
+
};
|
|
452
|
+
return {
|
|
453
|
+
kind: "contract",
|
|
454
|
+
pluginName
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const registered = contractPlugins.filter(has);
|
|
458
|
+
if (registered.length === 1) return {
|
|
459
|
+
kind: "contract",
|
|
460
|
+
pluginName: registered[0]
|
|
461
|
+
};
|
|
462
|
+
if (registered.length > 1) return {
|
|
463
|
+
kind: "error",
|
|
464
|
+
message: `Multiple client plugins are registered (${registered.map((name) => `\`@kubb/${name}\``).join(", ")}). Set \`client: 'fetch' | 'axios'\` to choose which client the hooks call, or register a single client plugin.`
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
kind: "error",
|
|
468
|
+
message: "No client plugin is registered. Add `@kubb/plugin-axios` or `@kubb/plugin-fetch` to `plugins` so the generated code has an HTTP client to call."
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Resolves the contract client during a consumer plugin's setup hook. Extracts the plugin names
|
|
473
|
+
* from the raw `plugins` config, applies {@link resolveClient}, and throws the diagnostic on a
|
|
474
|
+
* misconfiguration so every consumer fails fast with the same message.
|
|
475
|
+
*/
|
|
476
|
+
function resolveContractClient(options) {
|
|
477
|
+
const { client, plugins = [] } = options;
|
|
478
|
+
const resolved = resolveClient({
|
|
479
|
+
client,
|
|
480
|
+
pluginNames: plugins.map((plugin) => plugin.name).filter((name) => Boolean(name))
|
|
481
|
+
});
|
|
482
|
+
if (resolved.kind === "error") throw new Error(resolved.message);
|
|
483
|
+
return resolved;
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region ../../internals/client/src/resolveClientOperation.ts
|
|
487
|
+
/**
|
|
488
|
+
* Resolves the contract client `<op>` a consumer (query hook, MCP handler) imports, by looking up
|
|
489
|
+
* the registered contract client plugin's resolver and output. Works for any contract client plugin
|
|
490
|
+
* (plugin-fetch or plugin-axios). Returns `null` when no contract plugin is in play (the inline
|
|
491
|
+
* path). The plugin injects `.kubb/client.ts` at the global output root, the same path consumers
|
|
492
|
+
* read `RequestConfig` / `ResponseErrorConfig` from.
|
|
493
|
+
*/
|
|
494
|
+
function resolveClientOperation(options) {
|
|
495
|
+
const { clientPlugin, driver, node, root, output } = options;
|
|
496
|
+
if (!clientPlugin) return null;
|
|
497
|
+
const resolver = driver.getResolver(clientPlugin.pluginName);
|
|
498
|
+
const plugin = driver.getPlugin(clientPlugin.pluginName);
|
|
499
|
+
const file = resolver.file({
|
|
500
|
+
...operationFileEntry(node, node.operationId),
|
|
501
|
+
root,
|
|
502
|
+
output: plugin?.options?.output ?? output,
|
|
503
|
+
group: plugin?.options?.group ?? void 0
|
|
504
|
+
});
|
|
505
|
+
return {
|
|
506
|
+
name: resolver.name(node.operationId),
|
|
507
|
+
path: file.path,
|
|
508
|
+
clientPath: path.resolve(root, ".kubb/client.ts")
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/generators/mcpGenerator.tsx
|
|
513
|
+
/**
|
|
514
|
+
* Built-in operation generator for `@kubb/plugin-mcp`. Emits one MCP tool
|
|
515
|
+
* handler per OpenAPI operation. The handler takes the grouped `{ path, query,
|
|
516
|
+
* headers, body }` config and forwards it to the registered client plugin's
|
|
517
|
+
* (`@kubb/plugin-axios` / `@kubb/plugin-fetch`) `<op>` function.
|
|
518
|
+
*/
|
|
519
|
+
const mcpGenerator = defineGenerator({
|
|
520
|
+
name: "mcp",
|
|
521
|
+
renderer: jsxRenderer,
|
|
522
|
+
operation(node, ctx) {
|
|
523
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
524
|
+
const { resolver, driver, root } = ctx;
|
|
525
|
+
const { output, client, group } = ctx.options;
|
|
526
|
+
const pluginTs = driver.getPlugin(pluginTsName);
|
|
527
|
+
if (!pluginTs) return null;
|
|
528
|
+
const tsResolver = driver.getResolver(pluginTsName);
|
|
529
|
+
const contractOp = resolveClientOperation({
|
|
530
|
+
clientPlugin: { pluginName: client.pluginName },
|
|
531
|
+
driver,
|
|
532
|
+
node,
|
|
533
|
+
root,
|
|
534
|
+
output
|
|
535
|
+
});
|
|
536
|
+
if (!contractOp) return null;
|
|
537
|
+
const optionsName = tsResolver.response.options(node);
|
|
538
|
+
const meta = {
|
|
539
|
+
name: resolver.handler.name(node),
|
|
540
|
+
file: resolver.file({
|
|
541
|
+
name: node.operationId,
|
|
542
|
+
extname: ".ts",
|
|
543
|
+
tag: node.tags[0] ?? "default",
|
|
544
|
+
path: node.path,
|
|
545
|
+
root,
|
|
546
|
+
output,
|
|
547
|
+
group: group ?? void 0
|
|
548
|
+
}),
|
|
549
|
+
fileTs: tsResolver.file({
|
|
550
|
+
name: node.operationId,
|
|
551
|
+
extname: ".ts",
|
|
552
|
+
tag: node.tags[0] ?? "default",
|
|
553
|
+
path: node.path,
|
|
554
|
+
root,
|
|
555
|
+
output: pluginTs.options?.output ?? output,
|
|
556
|
+
group: pluginTs.options?.group ?? void 0
|
|
557
|
+
})
|
|
558
|
+
};
|
|
559
|
+
return /* @__PURE__ */ jsxs(File, {
|
|
560
|
+
baseName: meta.file.baseName,
|
|
561
|
+
path: meta.file.path,
|
|
562
|
+
meta: meta.file.meta,
|
|
563
|
+
children: [
|
|
564
|
+
meta.fileTs && optionsName && /* @__PURE__ */ jsx(File.Import, {
|
|
565
|
+
name: [optionsName],
|
|
566
|
+
root: meta.file.path,
|
|
567
|
+
path: meta.fileTs.path,
|
|
568
|
+
isTypeOnly: true
|
|
569
|
+
}),
|
|
570
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
571
|
+
name: [
|
|
572
|
+
"CallToolResult",
|
|
573
|
+
"ServerNotification",
|
|
574
|
+
"ServerRequest"
|
|
575
|
+
],
|
|
576
|
+
path: "@modelcontextprotocol/sdk/types",
|
|
577
|
+
isTypeOnly: true
|
|
578
|
+
}),
|
|
579
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
580
|
+
name: ["RequestHandlerExtra"],
|
|
581
|
+
path: "@modelcontextprotocol/sdk/shared/protocol",
|
|
582
|
+
isTypeOnly: true
|
|
583
|
+
}),
|
|
584
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
585
|
+
name: [contractOp.name],
|
|
586
|
+
root: meta.file.path,
|
|
587
|
+
path: contractOp.path
|
|
588
|
+
}),
|
|
589
|
+
/* @__PURE__ */ jsx(McpHandler, {
|
|
590
|
+
name: meta.name,
|
|
591
|
+
clientName: contractOp.name,
|
|
592
|
+
node,
|
|
593
|
+
resolver: tsResolver
|
|
594
|
+
})
|
|
595
|
+
]
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/generators/serverGenerator.tsx
|
|
601
|
+
/**
|
|
602
|
+
* Default v5 server generator for `@kubb/plugin-mcp`.
|
|
603
|
+
*
|
|
604
|
+
* Uses individual zod schemas for each param (e.g. `createPetsPathUuidSchema`, `createPetsQueryOffsetSchema`)
|
|
605
|
+
* and `resolveResponseStatusName` for per-status response schemas.
|
|
606
|
+
* Query and header params are composed into `z.object({ ... })` from individual schemas.
|
|
607
|
+
*/
|
|
608
|
+
const serverGenerator = defineGenerator({
|
|
609
|
+
name: "operations",
|
|
610
|
+
renderer: jsxRenderer,
|
|
611
|
+
operations(nodes, ctx) {
|
|
612
|
+
const { config, resolver, plugin, driver, root } = ctx;
|
|
613
|
+
const { output, group } = ctx.options;
|
|
614
|
+
const pluginZod = driver.getPlugin(pluginZodName);
|
|
615
|
+
if (!pluginZod) return;
|
|
616
|
+
const zodResolver = driver.getResolver(pluginZodName);
|
|
617
|
+
const name = "server";
|
|
618
|
+
const serverFile = {
|
|
619
|
+
baseName: "server.ts",
|
|
620
|
+
path: path.resolve(root, output.path, "server.ts"),
|
|
621
|
+
meta: { pluginName: plugin.name }
|
|
622
|
+
};
|
|
623
|
+
const jsonFile = {
|
|
624
|
+
baseName: ".mcp.json",
|
|
625
|
+
path: path.resolve(root, output.path, ".mcp.json"),
|
|
626
|
+
meta: { pluginName: plugin.name }
|
|
627
|
+
};
|
|
628
|
+
const operationsMapped = nodes.filter(ast.isHttpOperationNode).map((node) => {
|
|
629
|
+
const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
|
|
630
|
+
const mcpFile = resolver.file({
|
|
631
|
+
name: node.operationId,
|
|
632
|
+
extname: ".ts",
|
|
633
|
+
tag: node.tags[0] ?? "default",
|
|
634
|
+
path: node.path,
|
|
635
|
+
root,
|
|
636
|
+
output,
|
|
637
|
+
group: group ?? void 0
|
|
638
|
+
});
|
|
639
|
+
const zodFile = zodResolver.file({
|
|
640
|
+
name: node.operationId,
|
|
641
|
+
extname: ".ts",
|
|
642
|
+
tag: node.tags[0] ?? "default",
|
|
643
|
+
path: node.path,
|
|
644
|
+
root,
|
|
645
|
+
output: pluginZod.options?.output ?? output,
|
|
646
|
+
group: pluginZod.options?.group ?? void 0
|
|
647
|
+
});
|
|
648
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null;
|
|
649
|
+
const successStatus = findSuccessStatusCode(node.responses);
|
|
650
|
+
const responseName = successStatus ? zodResolver.response.status(node, successStatus) : null;
|
|
651
|
+
const resolveParams = (params) => params.map((p) => ({
|
|
652
|
+
name: p.name,
|
|
653
|
+
schemaName: zodResolver.param.name(node, p)
|
|
654
|
+
}));
|
|
655
|
+
return {
|
|
656
|
+
tool: {
|
|
657
|
+
name: node.operationId,
|
|
658
|
+
title: node.summary || void 0,
|
|
659
|
+
description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`
|
|
660
|
+
},
|
|
661
|
+
mcp: {
|
|
662
|
+
name: resolver.handler.name(node),
|
|
663
|
+
file: mcpFile
|
|
664
|
+
},
|
|
665
|
+
zod: {
|
|
666
|
+
pathParams: resolveParams(pathParams),
|
|
667
|
+
queryParams: queryParams.length ? resolveParams(queryParams) : null,
|
|
668
|
+
headerParams: headerParams.length ? resolveParams(headerParams) : null,
|
|
669
|
+
requestName,
|
|
670
|
+
responseName,
|
|
671
|
+
file: zodFile
|
|
672
|
+
},
|
|
673
|
+
node
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
const imports = operationsMapped.flatMap(({ mcp, zod }) => {
|
|
677
|
+
const zodNames = [
|
|
678
|
+
...zod.pathParams.map((p) => p.schemaName),
|
|
679
|
+
...(zod.queryParams ?? []).map((p) => p.schemaName),
|
|
680
|
+
...(zod.headerParams ?? []).map((p) => p.schemaName),
|
|
681
|
+
zod.requestName,
|
|
682
|
+
zod.responseName
|
|
683
|
+
].filter((name) => Boolean(name));
|
|
684
|
+
const uniqueNames = [...new Set(zodNames)].sort();
|
|
685
|
+
return [/* @__PURE__ */ jsx(File.Import, {
|
|
686
|
+
name: [mcp.name],
|
|
687
|
+
root: serverFile.path,
|
|
688
|
+
path: mcp.file.path
|
|
689
|
+
}, mcp.name), uniqueNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
|
|
690
|
+
name: uniqueNames,
|
|
691
|
+
root: serverFile.path,
|
|
692
|
+
path: zod.file.path
|
|
693
|
+
}, `zod-${mcp.name}`)].filter(Boolean);
|
|
694
|
+
});
|
|
695
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(File, {
|
|
696
|
+
baseName: serverFile.baseName,
|
|
697
|
+
path: serverFile.path,
|
|
698
|
+
meta: serverFile.meta,
|
|
699
|
+
banner: resolver.default.banner(ctx.meta, {
|
|
700
|
+
output,
|
|
701
|
+
config,
|
|
702
|
+
file: {
|
|
703
|
+
path: serverFile.path,
|
|
704
|
+
baseName: serverFile.baseName
|
|
705
|
+
}
|
|
706
|
+
}),
|
|
707
|
+
footer: resolver.default.footer(ctx.meta, {
|
|
708
|
+
output,
|
|
709
|
+
config,
|
|
710
|
+
file: {
|
|
711
|
+
path: serverFile.path,
|
|
712
|
+
baseName: serverFile.baseName
|
|
713
|
+
}
|
|
714
|
+
}),
|
|
715
|
+
children: [
|
|
716
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
717
|
+
name: ["McpServer"],
|
|
718
|
+
path: "@modelcontextprotocol/sdk/server/mcp"
|
|
719
|
+
}),
|
|
720
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
721
|
+
name: ["z"],
|
|
722
|
+
path: "zod"
|
|
723
|
+
}),
|
|
724
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
725
|
+
name: ["StdioServerTransport"],
|
|
726
|
+
path: "@modelcontextprotocol/sdk/server/stdio"
|
|
727
|
+
}),
|
|
728
|
+
imports,
|
|
729
|
+
/* @__PURE__ */ jsx(Server, {
|
|
730
|
+
name,
|
|
731
|
+
serverName: ctx.meta.title ?? "server",
|
|
732
|
+
serverVersion: ctx.meta.version ?? "0.0.0",
|
|
733
|
+
operations: operationsMapped
|
|
734
|
+
})
|
|
735
|
+
]
|
|
736
|
+
}), /* @__PURE__ */ jsx(File, {
|
|
737
|
+
baseName: jsonFile.baseName,
|
|
738
|
+
path: jsonFile.path,
|
|
739
|
+
meta: jsonFile.meta,
|
|
740
|
+
children: /* @__PURE__ */ jsx(File.Source, {
|
|
741
|
+
name,
|
|
742
|
+
children: `
|
|
743
|
+
{
|
|
744
|
+
"mcpServers": {
|
|
745
|
+
"${ctx.meta.title || "server"}": {
|
|
746
|
+
"type": "stdio",
|
|
747
|
+
"command": "npx",
|
|
748
|
+
"args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
`
|
|
753
|
+
})
|
|
754
|
+
})] });
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/resolvers/resolverMcp.ts
|
|
759
|
+
/**
|
|
760
|
+
* Default resolver used by `@kubb/plugin-mcp`. Decides the names and file
|
|
761
|
+
* paths for every generated MCP tool handler. Function names get a `Handler`
|
|
762
|
+
* suffix so an operation `addPet` becomes `addPetHandler`.
|
|
763
|
+
*
|
|
764
|
+
* @example Resolve a handler name
|
|
765
|
+
* ```ts
|
|
766
|
+
* import { resolverMcp } from '@kubb/plugin-mcp'
|
|
767
|
+
*
|
|
768
|
+
* resolverMcp.name('addPet') // 'addPetHandler'
|
|
769
|
+
* ```
|
|
770
|
+
*/
|
|
771
|
+
const resolverMcp = createResolver({
|
|
772
|
+
pluginName: "plugin-mcp",
|
|
773
|
+
name(name) {
|
|
774
|
+
return camelCase(name, { suffix: "handler" });
|
|
775
|
+
},
|
|
776
|
+
handler: { name(node) {
|
|
777
|
+
return this.name(node.operationId);
|
|
778
|
+
} }
|
|
779
|
+
});
|
|
780
|
+
//#endregion
|
|
13
781
|
//#region src/plugin.ts
|
|
782
|
+
/**
|
|
783
|
+
* Canonical plugin name for `@kubb/plugin-mcp`. Used for driver lookups and
|
|
784
|
+
* cross-plugin dependency references.
|
|
785
|
+
*/
|
|
14
786
|
const pluginMcpName = "plugin-mcp";
|
|
15
|
-
|
|
787
|
+
/**
|
|
788
|
+
* Generates a Model Context Protocol (MCP) server from an OpenAPI spec. Every
|
|
789
|
+
* operation becomes a typed MCP tool that AI assistants (Claude Desktop, Claude
|
|
790
|
+
* Code, MCP-compatible clients) can call directly. Each handler calls a contract
|
|
791
|
+
* client `<op>` from a registered `@kubb/plugin-axios` or `@kubb/plugin-fetch`.
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* ```ts
|
|
795
|
+
* import { defineConfig } from 'kubb/config'
|
|
796
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
797
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
798
|
+
* import { pluginMcp } from '@kubb/plugin-mcp'
|
|
799
|
+
*
|
|
800
|
+
* export default defineConfig({
|
|
801
|
+
* input: './petStore.yaml',
|
|
802
|
+
* output: { path: './src/gen' },
|
|
803
|
+
* plugins: [
|
|
804
|
+
* pluginTs(),
|
|
805
|
+
* pluginZod(),
|
|
806
|
+
* pluginMcp({
|
|
807
|
+
* output: { path: './mcp' },
|
|
808
|
+
* baseURL: 'https://petstore.swagger.io/v2',
|
|
809
|
+
* }),
|
|
810
|
+
* ],
|
|
811
|
+
* })
|
|
812
|
+
* ```
|
|
813
|
+
*/
|
|
814
|
+
const pluginMcp = definePlugin((options) => {
|
|
16
815
|
const { output = {
|
|
17
816
|
path: "mcp",
|
|
18
|
-
|
|
19
|
-
}, group, exclude = [], include, override = [],
|
|
20
|
-
const
|
|
21
|
-
const clientImportPath = client?.importPath ?? (!client?.bundle ? `@kubb/plugin-client/clients/${clientName}` : void 0);
|
|
817
|
+
barrel: { type: "named" }
|
|
818
|
+
}, group, exclude = [], include, override = [], client, resolver: userResolver, macros: userMacros } = options;
|
|
819
|
+
const groupConfig = createGroupConfig(group);
|
|
22
820
|
return {
|
|
23
821
|
name: pluginMcpName,
|
|
24
|
-
options
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
clientType: client?.clientType ?? "function",
|
|
31
|
-
importPath: clientImportPath,
|
|
32
|
-
dataReturnType: client?.dataReturnType ?? "data",
|
|
33
|
-
bundle: client?.bundle,
|
|
34
|
-
baseURL: client?.baseURL,
|
|
35
|
-
paramsCasing: client?.paramsCasing
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
pre: [
|
|
39
|
-
pluginOasName,
|
|
40
|
-
pluginTsName,
|
|
41
|
-
pluginZodName
|
|
42
|
-
].filter(Boolean),
|
|
43
|
-
resolvePath(baseName, pathMode, options) {
|
|
44
|
-
const root = path.resolve(this.config.root, this.config.output.path);
|
|
45
|
-
if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single")
|
|
46
|
-
/**
|
|
47
|
-
* when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
|
|
48
|
-
* Other plugins then need to call addOrAppend instead of just add from the fileManager class
|
|
49
|
-
*/
|
|
50
|
-
return path.resolve(root, output.path);
|
|
51
|
-
if (group && (options?.group?.path || options?.group?.tag)) {
|
|
52
|
-
const groupName = group?.name ? group.name : (ctx) => {
|
|
53
|
-
if (group?.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
54
|
-
return `${camelCase(ctx.group)}Requests`;
|
|
55
|
-
};
|
|
56
|
-
return path.resolve(root, output.path, groupName({ group: group.type === "path" ? options.group.path : options.group.tag }), baseName);
|
|
57
|
-
}
|
|
58
|
-
return path.resolve(root, output.path, baseName);
|
|
59
|
-
},
|
|
60
|
-
resolveName(name, type) {
|
|
61
|
-
const resolvedName = camelCase(name, { isFile: type === "file" });
|
|
62
|
-
if (type) return transformers?.name?.(resolvedName, type) || resolvedName;
|
|
63
|
-
return resolvedName;
|
|
64
|
-
},
|
|
65
|
-
async install() {
|
|
66
|
-
const root = path.resolve(this.config.root, this.config.output.path);
|
|
67
|
-
const mode = getMode(path.resolve(root, output.path));
|
|
68
|
-
const oas = await this.getOas();
|
|
69
|
-
const baseURL = await this.getBaseURL();
|
|
70
|
-
if (baseURL) this.plugin.options.client.baseURL = baseURL;
|
|
71
|
-
const hasClientPlugin = !!this.driver.getPluginByName(pluginClientName);
|
|
72
|
-
if (this.plugin.options.client.bundle && !hasClientPlugin && !this.plugin.options.client.importPath) await this.addFile({
|
|
73
|
-
baseName: "fetch.ts",
|
|
74
|
-
path: path.resolve(root, ".kubb/fetch.ts"),
|
|
75
|
-
sources: [{
|
|
76
|
-
name: "fetch",
|
|
77
|
-
value: this.plugin.options.client.client === "fetch" ? source$1 : source,
|
|
78
|
-
isExportable: true,
|
|
79
|
-
isIndexable: true
|
|
80
|
-
}],
|
|
81
|
-
imports: [],
|
|
82
|
-
exports: []
|
|
83
|
-
});
|
|
84
|
-
if (!hasClientPlugin) await this.addFile({
|
|
85
|
-
baseName: "config.ts",
|
|
86
|
-
path: path.resolve(root, ".kubb/config.ts"),
|
|
87
|
-
sources: [{
|
|
88
|
-
name: "config",
|
|
89
|
-
value: source$2,
|
|
90
|
-
isExportable: false,
|
|
91
|
-
isIndexable: false
|
|
92
|
-
}],
|
|
93
|
-
imports: [],
|
|
94
|
-
exports: []
|
|
95
|
-
});
|
|
96
|
-
const files = await new OperationGenerator(this.plugin.options, {
|
|
97
|
-
fabric: this.fabric,
|
|
98
|
-
oas,
|
|
99
|
-
driver: this.driver,
|
|
100
|
-
events: this.events,
|
|
101
|
-
plugin: this.plugin,
|
|
102
|
-
contentType,
|
|
822
|
+
options,
|
|
823
|
+
dependencies: [pluginTsName, pluginZodName],
|
|
824
|
+
hooks: { "kubb:plugin:setup"(ctx) {
|
|
825
|
+
const resolver = userResolver ? Resolver.merge(resolverMcp, userResolver) : resolverMcp;
|
|
826
|
+
ctx.setOptions({
|
|
827
|
+
output,
|
|
103
828
|
exclude,
|
|
104
829
|
include,
|
|
105
830
|
override,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
output,
|
|
113
|
-
meta: { pluginName: this.plugin.name }
|
|
831
|
+
group: groupConfig,
|
|
832
|
+
client: resolveContractClient({
|
|
833
|
+
client,
|
|
834
|
+
plugins: ctx.config.plugins
|
|
835
|
+
}),
|
|
836
|
+
resolver
|
|
114
837
|
});
|
|
115
|
-
|
|
116
|
-
|
|
838
|
+
ctx.setResolver(resolver);
|
|
839
|
+
if (userMacros?.length) ctx.setMacros(userMacros);
|
|
840
|
+
ctx.addGenerator(mcpGenerator, serverGenerator);
|
|
841
|
+
} }
|
|
117
842
|
};
|
|
118
843
|
});
|
|
119
844
|
//#endregion
|
|
120
|
-
export { pluginMcp, pluginMcpName };
|
|
845
|
+
export { McpHandler, Server, pluginMcp as default, pluginMcp, mcpGenerator, pluginMcpName, resolverMcp, serverGenerator };
|
|
121
846
|
|
|
122
847
|
//# sourceMappingURL=index.js.map
|