@kubb/plugin-mcp 5.0.0-beta.4 → 5.0.0-beta.56

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import "./chunk--u3MIqq1.js";
1
+ import "./chunk-C0LytTxp.js";
2
2
  import path from "node:path";
3
3
  import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
4
4
  import { functionPrinter, pluginTsName } from "@kubb/plugin-ts";
@@ -20,36 +20,45 @@ import { source as source$2 } from "@kubb/plugin-client/templates/config.source"
20
20
  function toCamelOrPascal(text, pascal) {
21
21
  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) => {
22
22
  if (word.length > 1 && word === word.toUpperCase()) return word;
23
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
24
- return word.charAt(0).toUpperCase() + word.slice(1);
23
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
25
24
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
26
25
  }
27
26
  /**
28
- * Splits `text` on `.` and applies `transformPart` to each segment.
29
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
30
- * Segments are joined with `/` to form a file path.
27
+ * Converts `text` to camelCase.
28
+ *
29
+ * @example Word boundaries
30
+ * `camelCase('hello-world') // 'helloWorld'`
31
31
  *
32
- * Only splits on dots followed by a letter so that version numbers
33
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
32
+ * @example With a prefix
33
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
34
34
  */
35
- function applyToFileParts(text, transformPart) {
36
- const parts = text.split(/\.(?=[a-zA-Z])/);
37
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
35
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
36
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
38
37
  }
38
+ //#endregion
39
+ //#region ../../internals/utils/src/fs.ts
39
40
  /**
40
- * Converts `text` to camelCase.
41
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
41
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
42
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
43
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
42
44
  *
43
- * @example
44
- * camelCase('hello-world') // 'helloWorld'
45
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
45
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
46
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
47
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
48
+ * absolute path, letting generated files escape the configured output directory.
49
+ *
50
+ * @example Nested path from a dotted name
51
+ * `toFilePath('pet.petId') // 'pet/petId'`
52
+ *
53
+ * @example PascalCase the final segment
54
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
55
+ *
56
+ * @example Suffix applied to the final segment only
57
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
46
58
  */
47
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
48
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
49
- prefix,
50
- suffix
51
- } : {}));
52
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
59
+ function toFilePath(name, caseLast = camelCase) {
60
+ const parts = name.split(/\.(?=[a-zA-Z])/);
61
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
53
62
  }
54
63
  //#endregion
55
64
  //#region ../../internals/utils/src/reserved.ts
@@ -155,99 +164,80 @@ function isValidVarName(name) {
155
164
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
156
165
  }
157
166
  //#endregion
158
- //#region ../../internals/utils/src/urlPath.ts
167
+ //#region ../../internals/utils/src/url.ts
168
+ function transformParam(raw, casing) {
169
+ const param = isValidVarName(raw) ? raw : camelCase(raw);
170
+ return casing === "camelcase" ? camelCase(param) : param;
171
+ }
172
+ function toParamsObject(path, { replacer, casing } = {}) {
173
+ const params = {};
174
+ for (const match of path.matchAll(/\{([^}]+)\}/g)) {
175
+ const param = transformParam(match[1], casing);
176
+ const key = replacer ? replacer(param) : param;
177
+ params[key] = key;
178
+ }
179
+ return Object.keys(params).length > 0 ? params : null;
180
+ }
159
181
  /**
160
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
161
- *
162
- * @example
163
- * const p = new URLPath('/pet/{petId}')
164
- * p.URL // '/pet/:petId'
165
- * p.template // '`/pet/${petId}`'
182
+ * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
166
183
  */
167
- var URLPath = class {
184
+ var Url = class Url {
168
185
  /**
169
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
170
- */
171
- path;
172
- #options;
173
- constructor(path, options = {}) {
174
- this.path = path;
175
- this.#options = options;
176
- }
177
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
186
+ * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
178
187
  *
179
188
  * @example
180
- * ```ts
181
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
182
- * ```
189
+ * Url.canParse('https://petstore.swagger.io/v2') // true
190
+ * Url.canParse('/pet/{petId}') // false
183
191
  */
184
- get URL() {
185
- return this.toURLPath();
192
+ static canParse(url, base) {
193
+ return URL.canParse(url, base);
186
194
  }
187
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
195
+ /**
196
+ * Converts an OpenAPI/Swagger path to Express-style colon syntax.
188
197
  *
189
198
  * @example
190
- * ```ts
191
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
192
- * new URLPath('/pet/{petId}').isURL // false
193
- * ```
199
+ * Url.toPath('/pet/{petId}') // '/pet/:petId'
194
200
  */
195
- get isURL() {
196
- try {
197
- return !!new URL(this.path).href;
198
- } catch {
199
- return false;
200
- }
201
+ static toPath(path) {
202
+ return path.replace(/\{([^}]+)\}/g, ":$1");
201
203
  }
202
204
  /**
203
- * Converts the OpenAPI path to a TypeScript template literal string.
205
+ * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
206
+ * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
207
+ * and `casing` controls parameter identifier casing.
204
208
  *
205
209
  * @example
206
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
207
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
208
- */
209
- get template() {
210
- return this.toTemplateString();
211
- }
212
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
210
+ * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
213
211
  *
214
212
  * @example
215
- * ```ts
216
- * new URLPath('/pet/{petId}').object
217
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
218
- * ```
213
+ * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
219
214
  */
220
- get object() {
221
- return this.toObject();
215
+ static toTemplateString(path, { prefix, replacer, casing } = {}) {
216
+ const result = path.split(/\{([^}]+)\}/).map((part, i) => {
217
+ if (i % 2 === 0) return part;
218
+ const param = transformParam(part, casing);
219
+ return `\${${replacer ? replacer(param) : param}}`;
220
+ }).join("");
221
+ return `\`${prefix ?? ""}${result}\``;
222
222
  }
223
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
223
+ /**
224
+ * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
225
+ * expression when `stringify` is set.
224
226
  *
225
227
  * @example
226
- * ```ts
227
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
228
- * new URLPath('/pet').params // undefined
229
- * ```
230
- */
231
- get params() {
232
- return this.getParams();
233
- }
234
- #transformParam(raw) {
235
- const param = isValidVarName(raw) ? raw : camelCase(raw);
236
- return this.#options.casing === "camelcase" ? camelCase(param) : param;
237
- }
238
- /**
239
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
228
+ * Url.toObject('/pet/{petId}')
229
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
240
230
  */
241
- #eachParam(fn) {
242
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
243
- const raw = match[1];
244
- fn(raw, this.#transformParam(raw));
245
- }
246
- }
247
- toObject({ type = "path", replacer, stringify } = {}) {
231
+ static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
248
232
  const object = {
249
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
250
- params: this.getParams()
233
+ url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
234
+ replacer,
235
+ casing
236
+ }),
237
+ params: toParamsObject(path, {
238
+ replacer,
239
+ casing
240
+ })
251
241
  };
252
242
  if (stringify) {
253
243
  if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
@@ -256,127 +246,144 @@ var URLPath = class {
256
246
  }
257
247
  return object;
258
248
  }
259
- /**
260
- * Converts the OpenAPI path to a TypeScript template literal string.
261
- * An optional `replacer` can transform each extracted parameter name before interpolation.
262
- *
263
- * @example
264
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
265
- */
266
- toTemplateString({ prefix = "", replacer } = {}) {
267
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
268
- if (i % 2 === 0) return part;
269
- const param = this.#transformParam(part);
270
- return `\${${replacer ? replacer(param) : param}}`;
271
- }).join("")}\``;
272
- }
273
- /**
274
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
275
- * An optional `replacer` transforms each parameter name in both key and value positions.
276
- * Returns `undefined` when no path parameters are found.
277
- *
278
- * @example
279
- * ```ts
280
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
281
- * // { petId: 'petId', tagId: 'tagId' }
282
- * ```
283
- */
284
- getParams(replacer) {
285
- const params = {};
286
- this.#eachParam((_raw, param) => {
287
- const key = replacer ? replacer(param) : param;
288
- params[key] = key;
289
- });
290
- return Object.keys(params).length > 0 ? params : void 0;
291
- }
292
- /** Converts the OpenAPI path to Express-style colon syntax.
293
- *
294
- * @example
295
- * ```ts
296
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
297
- * ```
298
- */
299
- toURLPath() {
300
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
301
- }
302
249
  };
303
250
  //#endregion
304
- //#region src/utils.ts
305
- /**
306
- * Find the first 2xx response status code from an operation's responses.
307
- */
308
- function findSuccessStatusCode(responses) {
309
- for (const res of responses) {
310
- const code = Number(res.statusCode);
311
- if (code >= 200 && code < 300) return res.statusCode;
312
- }
251
+ //#region ../../internals/shared/src/operation.ts
252
+ function getOperationLink(node, link) {
253
+ if (!link) return null;
254
+ if (typeof link === "function") return link(node) ?? null;
255
+ if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
256
+ return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
313
257
  }
314
- /**
315
- * Render a group param value compose individual schemas into `z.object({ ... })`,
316
- * or use a schema name string directly.
317
- */
318
- function zodGroupExpr(entry) {
319
- if (typeof entry === "string") return entry;
320
- return `z.object({ ${entry.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
321
- }
322
- /**
323
- * Build JSDoc comment lines from an OperationNode.
324
- */
325
- function getComments(node) {
326
- return [
258
+ function buildOperationComments(node, options = {}) {
259
+ const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
260
+ const linkComment = getOperationLink(node, link);
261
+ const filteredComments = (linkPosition === "beforeDeprecated" ? [
262
+ node.description && `@description ${node.description}`,
263
+ node.summary && `@summary ${node.summary}`,
264
+ linkComment,
265
+ node.deprecated && "@deprecated"
266
+ ] : [
327
267
  node.description && `@description ${node.description}`,
328
268
  node.summary && `@summary ${node.summary}`,
329
269
  node.deprecated && "@deprecated",
330
- `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`
331
- ].filter((x) => Boolean(x));
270
+ linkComment
271
+ ]).filter((comment) => Boolean(comment));
272
+ if (!splitLines) return filteredComments;
273
+ return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
332
274
  }
333
- /**
334
- * Build a mapping of original param names → camelCase names.
335
- * Returns `undefined` when no names actually change (no remapping needed).
336
- */
337
- function getParamsMapping(params) {
338
- if (!params.length) return;
339
- const mapping = {};
340
- let hasDifference = false;
341
- for (const p of params) {
342
- const camelName = camelCase(p.name);
343
- mapping[p.name] = camelName;
344
- if (p.name !== camelName) hasDifference = true;
275
+ function getOperationParameters(node, options = {}) {
276
+ const params = ast.caseParams(node.parameters, options.paramsCasing);
277
+ return {
278
+ path: params.filter((param) => param.in === "path"),
279
+ query: params.filter((param) => param.in === "query"),
280
+ header: params.filter((param) => param.in === "header"),
281
+ cookie: params.filter((param) => param.in === "cookie")
282
+ };
283
+ }
284
+ function getStatusCodeNumber(statusCode) {
285
+ const code = Number(statusCode);
286
+ return Number.isNaN(code) ? null : code;
287
+ }
288
+ function isSuccessStatusCode(statusCode) {
289
+ const code = getStatusCodeNumber(statusCode);
290
+ return code !== null && code >= 200 && code < 300;
291
+ }
292
+ function isErrorStatusCode(statusCode) {
293
+ const code = getStatusCodeNumber(statusCode);
294
+ return code !== null && code >= 400;
295
+ }
296
+ function resolveErrorNames(node, resolver) {
297
+ return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
298
+ }
299
+ function resolveStatusCodeNames(node, resolver) {
300
+ return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
301
+ }
302
+ const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
303
+ function resolveOperationTypeNames(node, resolver, options = {}) {
304
+ const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
305
+ let byResolver = typeNamesByResolver.get(resolver);
306
+ if (byResolver) {
307
+ const cached = byResolver.get(cacheKey);
308
+ if (cached) return cached;
309
+ } else {
310
+ byResolver = /* @__PURE__ */ new Map();
311
+ typeNamesByResolver.set(resolver, byResolver);
345
312
  }
346
- return hasDifference ? mapping : void 0;
313
+ const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
314
+ const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
315
+ const exclude = new Set(options.exclude ?? []);
316
+ const paramNames = [
317
+ ...path.map((param) => resolver.resolvePathParamsName(node, param)),
318
+ ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
319
+ ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
320
+ ];
321
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
322
+ const result = (options.order === "body-response-first" ? [
323
+ ...bodyAndResponseNames,
324
+ ...paramNames,
325
+ ...responseStatusNames
326
+ ] : [
327
+ ...paramNames,
328
+ ...bodyAndResponseNames,
329
+ ...responseStatusNames
330
+ ]).filter((name) => Boolean(name) && !exclude.has(name));
331
+ byResolver.set(cacheKey, result);
332
+ return result;
333
+ }
334
+ function findSuccessStatusCode(responses) {
335
+ for (const response of responses) if (isSuccessStatusCode(response.statusCode)) return response.statusCode;
336
+ return null;
347
337
  }
338
+ //#endregion
339
+ //#region ../../internals/shared/src/group.ts
348
340
  /**
349
- * Convert a SchemaNode type to an inline Zod expression string.
350
- * Used as fallback when no named zod schema is available for a path parameter.
341
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
342
+ * shared default naming so every plugin groups output consistently:
343
+ *
344
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
345
+ * - other groups use the camelCased group (`pet store` → `petStore`).
346
+ *
347
+ * A user-provided `group.name` always wins over the default namer, so callers stay in
348
+ * control of their output folders. Returns `null` when grouping is disabled, matching the
349
+ * per-plugin convention.
350
+ *
351
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
352
+ *
353
+ * @example
354
+ * ```ts
355
+ * createGroupConfig(group) // shared across every plugin
356
+ * ```
351
357
  */
352
- function zodExprFromSchemaNode(schema) {
353
- let expr;
354
- switch (schema.type) {
355
- case "enum": {
356
- const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
357
- if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) expr = `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
358
- else if (rawValues.length > 0) {
359
- const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
360
- expr = literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
361
- } else expr = "z.string()";
362
- break;
363
- }
364
- case "integer":
365
- expr = "z.coerce.number()";
366
- break;
367
- case "number":
368
- expr = "z.number()";
369
- break;
370
- case "boolean":
371
- expr = "z.boolean()";
372
- break;
373
- case "array":
374
- expr = "z.array(z.unknown())";
375
- break;
376
- default: expr = "z.string()";
377
- }
378
- if (schema.nullable) expr = `${expr}.nullable()`;
379
- return expr;
358
+ function createGroupConfig(group) {
359
+ if (!group) return null;
360
+ const defaultName = (ctx) => {
361
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
362
+ return camelCase(ctx.group);
363
+ };
364
+ return {
365
+ ...group,
366
+ name: group.name ? group.name : defaultName
367
+ };
368
+ }
369
+ //#endregion
370
+ //#region ../../internals/shared/src/params.ts
371
+ function buildParamsMapping(originalParams, mappedParams) {
372
+ const mapping = {};
373
+ let hasChanged = false;
374
+ originalParams.forEach((param, i) => {
375
+ const mappedName = mappedParams[i]?.name ?? param.name;
376
+ mapping[param.name] = mappedName;
377
+ if (param.name !== mappedName) hasChanged = true;
378
+ });
379
+ return hasChanged ? mapping : null;
380
+ }
381
+ function buildTransformedParamsMapping(params, transformName) {
382
+ if (!params.length) return null;
383
+ return buildParamsMapping(params, params.map((param) => ({
384
+ ...param,
385
+ name: transformName(param.name)
386
+ })));
380
387
  }
381
388
  //#endregion
382
389
  //#region src/components/McpHandler.tsx
@@ -388,16 +395,12 @@ function buildRemappingCode(mapping, varName, sourceName) {
388
395
  }
389
396
  const declarationPrinter = functionPrinter({ mode: "declaration" });
390
397
  function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasing }) {
391
- const urlPath = new URLPath(node.path);
398
+ if (!ast.isHttpOperationNode(node)) return null;
392
399
  const contentType = node.requestBody?.content?.[0]?.contentType;
393
400
  const isFormData = contentType === "multipart/form-data";
394
- const casedParams = ast.caseParams(node.parameters, paramsCasing);
395
- const queryParams = casedParams.filter((p) => p.in === "query");
396
- const headerParams = casedParams.filter((p) => p.in === "header");
397
- const originalPathParams = node.parameters.filter((p) => p.in === "path");
398
- const originalQueryParams = node.parameters.filter((p) => p.in === "query");
399
- const originalHeaderParams = node.parameters.filter((p) => p.in === "header");
400
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0;
401
+ const { query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
402
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
403
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
401
404
  const responseName = resolver.resolveResponseName(node);
402
405
  const errorResponses = node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => resolver.resolveResponseStatusName(node, r.statusCode));
403
406
  const generics = [
@@ -413,35 +416,27 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
413
416
  });
414
417
  const baseParamsSignature = declarationPrinter.print(paramsNode) ?? "";
415
418
  const paramsSignature = baseParamsSignature ? `${baseParamsSignature}, request: RequestHandlerExtra<ServerRequest, ServerNotification>` : "request: RequestHandlerExtra<ServerRequest, ServerNotification>";
416
- const pathParamsMapping = paramsCasing ? getParamsMapping(originalPathParams) : void 0;
417
- const queryParamsMapping = paramsCasing ? getParamsMapping(originalQueryParams) : void 0;
418
- const headerParamsMapping = paramsCasing ? getParamsMapping(originalHeaderParams) : void 0;
419
- const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0;
420
- const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : void 0, contentTypeHeader].filter(Boolean);
419
+ const pathParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalPathParams, camelCase) : null;
420
+ const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) : null;
421
+ const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) : null;
422
+ const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null;
423
+ const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null, contentTypeHeader].filter(Boolean);
421
424
  const fetchConfig = [];
422
425
  fetchConfig.push(`method: ${JSON.stringify(node.method.toUpperCase())}`);
423
- fetchConfig.push(`url: ${urlPath.template}`);
426
+ fetchConfig.push(`url: ${Url.toTemplateString(node.path)}`);
424
427
  if (baseURL) fetchConfig.push(`baseURL: \`${baseURL}\``);
425
428
  if (queryParams.length) fetchConfig.push(queryParamsMapping ? "params: mappedParams" : "params");
426
429
  if (requestName) fetchConfig.push(`data: ${isFormData ? "formData as FormData" : "requestData"}`);
427
430
  if (headers.length) fetchConfig.push(`headers: { ${headers.join(", ")} }`);
428
- const callToolResult = dataReturnType === "data" ? `return {
429
- content: [
430
- {
431
- type: 'text',
432
- text: JSON.stringify(res.data)
433
- }
434
- ],
435
- structuredContent: { data: res.data }
436
- }` : `return {
437
- content: [
438
- {
439
- type: 'text',
440
- text: JSON.stringify(res)
441
- }
442
- ],
443
- structuredContent: { data: res.data }
444
- }`;
431
+ const callToolResult = `return {
432
+ content: [
433
+ {
434
+ type: 'text',
435
+ text: JSON.stringify(${dataReturnType === "data" ? "res.data" : "res"})
436
+ }
437
+ ],
438
+ structuredContent: { data: res.data }
439
+ }`;
445
440
  return /* @__PURE__ */ jsx(File.Source, {
446
441
  name,
447
442
  isExportable: true,
@@ -451,7 +446,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
451
446
  async: true,
452
447
  export: true,
453
448
  params: paramsSignature,
454
- JSDoc: { comments: getComments(node) },
449
+ JSDoc: { comments: buildOperationComments(node) },
455
450
  returnType: "Promise<CallToolResult>",
456
451
  children: [
457
452
  "",
@@ -473,7 +468,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
473
468
  /* @__PURE__ */ jsx("br", {}),
474
469
  isFormData && requestName && "const formData = buildFormData(requestData)",
475
470
  /* @__PURE__ */ jsx("br", {}),
476
- `const res = await fetch<${generics.join(", ")}>({ ${fetchConfig.join(", ")} }, request)`,
471
+ `const res = await client<${generics.join(", ")}>({ ${fetchConfig.join(", ")} }, request)`,
477
472
  /* @__PURE__ */ jsx("br", {}),
478
473
  callToolResult
479
474
  ]
@@ -481,62 +476,80 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
481
476
  });
482
477
  }
483
478
  //#endregion
479
+ //#region src/utils.ts
480
+ /**
481
+ * Render a group param value — compose individual schemas into `z.object({ ... })`,
482
+ * or use a schema name string directly.
483
+ */
484
+ function zodGroupExpr(entry) {
485
+ if (typeof entry === "string") return entry;
486
+ return `z.object({ ${entry.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
487
+ }
488
+ /**
489
+ * Convert a SchemaNode type to an inline Zod expression string.
490
+ * Used as fallback when no named zod schema is available for a path parameter.
491
+ */
492
+ function zodExprFromSchemaNode(schema) {
493
+ const baseExpr = (() => {
494
+ if (schema.type === "enum") {
495
+ const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
496
+ if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) return `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
497
+ if (rawValues.length > 0) {
498
+ const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
499
+ return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
500
+ }
501
+ return "z.string()";
502
+ }
503
+ if (schema.type === "integer") return "z.coerce.number()";
504
+ if (schema.type === "number") return "z.number()";
505
+ if (schema.type === "boolean") return "z.boolean()";
506
+ if (schema.type === "array") return "z.array(z.unknown())";
507
+ return "z.string()";
508
+ })();
509
+ return schema.nullable ? `${baseExpr}.nullable()` : baseExpr;
510
+ }
511
+ //#endregion
484
512
  //#region src/components/Server.tsx
485
513
  const keysPrinter = functionPrinter({ mode: "keys" });
486
514
  function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
487
- return /* @__PURE__ */ jsxs(File.Source, {
488
- name,
489
- isExportable: true,
490
- isIndexable: true,
491
- children: [
492
- /* @__PURE__ */ jsx(Const, {
493
- name: "server",
494
- export: true,
495
- children: `
496
- new McpServer({
497
- name: '${serverName}',
498
- version: '${serverVersion}',
499
- })
500
- `
501
- }),
502
- operations.map(({ tool, mcp, zod, node }) => {
503
- const pathParams = ast.caseParams(node.parameters, paramsCasing).filter((p) => p.in === "path");
504
- const pathEntries = [];
505
- const otherEntries = [];
506
- for (const p of pathParams) {
507
- const zodParam = zod.pathParams.find((zp) => zp.name === p.name);
508
- pathEntries.push({
509
- key: p.name,
510
- value: zodParam ? zodParam.schemaName : zodExprFromSchemaNode(p.schema)
511
- });
512
- }
513
- if (zod.requestName) otherEntries.push({
514
- key: "data",
515
- value: zod.requestName
516
- });
517
- if (zod.queryParams) otherEntries.push({
518
- key: "params",
519
- value: zodGroupExpr(zod.queryParams)
520
- });
521
- if (zod.headerParams) otherEntries.push({
522
- key: "headers",
523
- value: zodGroupExpr(zod.headerParams)
524
- });
525
- otherEntries.sort((a, b) => a.key.localeCompare(b.key));
526
- const entries = [...pathEntries, ...otherEntries];
527
- const paramsNode = entries.length ? ast.createFunctionParameters({ params: [ast.createParameterGroup({ properties: entries.map((e) => ast.createFunctionParameter({
528
- name: e.key,
529
- optional: false
530
- })) })] }) : void 0;
531
- const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
532
- const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : void 0;
533
- const outputSchema = zod.responseName;
534
- const config = [
535
- tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
536
- `description: ${JSON.stringify(tool.description)}`,
537
- outputSchema ? `outputSchema: { data: ${outputSchema} }` : null
538
- ].filter(Boolean).join(",\n ");
539
- if (inputSchema) return `
515
+ const registrations = operations.map(({ tool, mcp, zod, node }) => {
516
+ const { path: pathParams } = getOperationParameters(node, { paramsCasing });
517
+ const pathEntries = [];
518
+ const otherEntries = [];
519
+ for (const p of pathParams) {
520
+ const zodParam = zod.pathParams.find((zp) => zp.name === p.name);
521
+ pathEntries.push({
522
+ key: p.name,
523
+ value: zodParam ? zodParam.schemaName : zodExprFromSchemaNode(p.schema)
524
+ });
525
+ }
526
+ if (zod.requestName) otherEntries.push({
527
+ key: "data",
528
+ value: zod.requestName
529
+ });
530
+ if (zod.queryParams) otherEntries.push({
531
+ key: "params",
532
+ value: zodGroupExpr(zod.queryParams)
533
+ });
534
+ if (zod.headerParams) otherEntries.push({
535
+ key: "headers",
536
+ value: zodGroupExpr(zod.headerParams)
537
+ });
538
+ otherEntries.sort((a, b) => a.key.localeCompare(b.key));
539
+ const entries = [...pathEntries, ...otherEntries];
540
+ const paramsNode = entries.length ? ast.createFunctionParameters({ params: [ast.createParameterGroup({ properties: entries.map((e) => ast.createFunctionParameter({
541
+ name: e.key,
542
+ optional: false
543
+ })) })] }) : null;
544
+ const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
545
+ const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : null;
546
+ const outputSchema = zod.responseName;
547
+ const config = [
548
+ tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
549
+ `description: ${JSON.stringify(tool.description)}`,
550
+ outputSchema ? `outputSchema: { data: ${outputSchema} }` : null
551
+ ].filter(Boolean).join(",\n ");
552
+ if (inputSchema) return `
540
553
  server.registerTool(${JSON.stringify(tool.name)}, {
541
554
  ${config},
542
555
  inputSchema: ${inputSchema},
@@ -544,14 +557,34 @@ server.registerTool(${JSON.stringify(tool.name)}, {
544
557
  return ${mcp.name}(${destructured}, request)
545
558
  })
546
559
  `;
547
- return `
560
+ return `
548
561
  server.registerTool(${JSON.stringify(tool.name)}, {
549
562
  ${config},
550
563
  }, async (request) => {
551
564
  return ${mcp.name}(request)
552
565
  })
553
566
  `;
554
- }).filter(Boolean),
567
+ }).filter(Boolean).join("\n");
568
+ return /* @__PURE__ */ jsxs(File.Source, {
569
+ name,
570
+ isExportable: true,
571
+ isIndexable: true,
572
+ children: [
573
+ /* @__PURE__ */ jsx(Function, {
574
+ name: "getServer",
575
+ export: true,
576
+ children: `const server = new McpServer({
577
+ name: '${serverName}',
578
+ version: '${serverVersion}',
579
+ })
580
+ ${registrations}
581
+ return server`
582
+ }),
583
+ /* @__PURE__ */ jsx(Const, {
584
+ name: "server",
585
+ export: true,
586
+ children: "getServer()"
587
+ }),
555
588
  /* @__PURE__ */ jsx(Function, {
556
589
  name: "startServer",
557
590
  async: true,
@@ -570,29 +603,28 @@ server.registerTool(${JSON.stringify(tool.name)}, {
570
603
  }
571
604
  //#endregion
572
605
  //#region src/generators/mcpGenerator.tsx
606
+ /**
607
+ * Built-in operation generator for `@kubb/plugin-mcp`. Emits one MCP tool
608
+ * handler per OpenAPI operation, wiring the input Zod schema, the HTTP call,
609
+ * and the response shape into a single function that an MCP server can
610
+ * register as a callable tool.
611
+ */
573
612
  const mcpGenerator = defineGenerator({
574
613
  name: "mcp",
575
614
  renderer: jsxRenderer,
576
615
  operation(node, ctx) {
616
+ if (!ast.isHttpOperationNode(node)) return null;
577
617
  const { resolver, driver, root } = ctx;
578
618
  const { output, client, paramsCasing, group } = ctx.options;
579
619
  const pluginTs = driver.getPlugin(pluginTsName);
580
620
  if (!pluginTs) return null;
581
621
  const tsResolver = driver.getResolver(pluginTsName);
582
- const casedParams = ast.caseParams(node.parameters, paramsCasing);
583
- const pathParams = casedParams.filter((p) => p.in === "path");
584
- const queryParams = casedParams.filter((p) => p.in === "query");
585
- const headerParams = casedParams.filter((p) => p.in === "header");
586
- const importedTypeNames = [
587
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
588
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
589
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
590
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
591
- tsResolver.resolveResponseName(node),
592
- ...node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
593
- ].filter(Boolean);
622
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
623
+ paramsCasing,
624
+ responseStatusNames: "error"
625
+ });
594
626
  const meta = {
595
- name: resolver.resolveName(node.operationId),
627
+ name: resolver.resolveHandlerName(node),
596
628
  file: resolver.resolveFile({
597
629
  name: node.operationId,
598
630
  extname: ".ts",
@@ -601,7 +633,7 @@ const mcpGenerator = defineGenerator({
601
633
  }, {
602
634
  root,
603
635
  output,
604
- group
636
+ group: group ?? void 0
605
637
  }),
606
638
  fileTs: tsResolver.resolveFile({
607
639
  name: node.operationId,
@@ -611,7 +643,7 @@ const mcpGenerator = defineGenerator({
611
643
  }, {
612
644
  root,
613
645
  output: pluginTs.options?.output ?? output,
614
- group: pluginTs.options?.group
646
+ group: pluginTs.options?.group ?? void 0
615
647
  })
616
648
  };
617
649
  return /* @__PURE__ */ jsxs(File, {
@@ -655,7 +687,7 @@ const mcpGenerator = defineGenerator({
655
687
  isTypeOnly: true
656
688
  }),
657
689
  /* @__PURE__ */ jsx(File.Import, {
658
- name: "fetch",
690
+ name: "client",
659
691
  path: client.importPath
660
692
  }),
661
693
  client.dataReturnType === "full" && /* @__PURE__ */ jsx(File.Import, {
@@ -671,18 +703,18 @@ const mcpGenerator = defineGenerator({
671
703
  "ResponseErrorConfig"
672
704
  ],
673
705
  root: meta.file.path,
674
- path: path.resolve(root, ".kubb/fetch.ts"),
706
+ path: path.resolve(root, ".kubb/client.ts"),
675
707
  isTypeOnly: true
676
708
  }),
677
709
  /* @__PURE__ */ jsx(File.Import, {
678
- name: ["fetch"],
710
+ name: ["client"],
679
711
  root: meta.file.path,
680
- path: path.resolve(root, ".kubb/fetch.ts")
712
+ path: path.resolve(root, ".kubb/client.ts")
681
713
  }),
682
714
  client.dataReturnType === "full" && /* @__PURE__ */ jsx(File.Import, {
683
715
  name: ["ResponseConfig"],
684
716
  root: meta.file.path,
685
- path: path.resolve(root, ".kubb/fetch.ts"),
717
+ path: path.resolve(root, ".kubb/client.ts"),
686
718
  isTypeOnly: true
687
719
  })
688
720
  ] }),
@@ -711,7 +743,7 @@ const serverGenerator = defineGenerator({
711
743
  name: "operations",
712
744
  renderer: jsxRenderer,
713
745
  operations(nodes, ctx) {
714
- const { adapter, config, resolver, plugin, driver, root } = ctx;
746
+ const { config, resolver, plugin, driver, root } = ctx;
715
747
  const { output, paramsCasing, group } = ctx.options;
716
748
  const pluginZod = driver.getPlugin(pluginZodName);
717
749
  if (!pluginZod) return;
@@ -727,11 +759,8 @@ const serverGenerator = defineGenerator({
727
759
  path: path.resolve(root, output.path, ".mcp.json"),
728
760
  meta: { pluginName: plugin.name }
729
761
  };
730
- const operationsMapped = nodes.map((node) => {
731
- const casedParams = ast.caseParams(node.parameters, paramsCasing);
732
- const pathParams = casedParams.filter((p) => p.in === "path");
733
- const queryParams = casedParams.filter((p) => p.in === "query");
734
- const headerParams = casedParams.filter((p) => p.in === "header");
762
+ const operationsMapped = nodes.filter(ast.isHttpOperationNode).map((node) => {
763
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
735
764
  const mcpFile = resolver.resolveFile({
736
765
  name: node.operationId,
737
766
  extname: ".ts",
@@ -740,7 +769,7 @@ const serverGenerator = defineGenerator({
740
769
  }, {
741
770
  root,
742
771
  output,
743
- group
772
+ group: group ?? void 0
744
773
  });
745
774
  const zodFile = zodResolver.resolveFile({
746
775
  name: node.operationId,
@@ -750,11 +779,11 @@ const serverGenerator = defineGenerator({
750
779
  }, {
751
780
  root,
752
781
  output: pluginZod.options?.output ?? output,
753
- group: pluginZod.options?.group
782
+ group: pluginZod.options?.group ?? void 0
754
783
  });
755
- const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : void 0;
784
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : null;
756
785
  const successStatus = findSuccessStatusCode(node.responses);
757
- const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : void 0;
786
+ const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : null;
758
787
  const resolveParams = (params) => params.map((p) => ({
759
788
  name: p.name,
760
789
  schemaName: zodResolver.resolveParamName(node, p)
@@ -766,13 +795,13 @@ const serverGenerator = defineGenerator({
766
795
  description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`
767
796
  },
768
797
  mcp: {
769
- name: resolver.resolveName(node.operationId),
798
+ name: resolver.resolveHandlerName(node),
770
799
  file: mcpFile
771
800
  },
772
801
  zod: {
773
802
  pathParams: resolveParams(pathParams),
774
- queryParams: queryParams.length ? resolveParams(queryParams) : void 0,
775
- headerParams: headerParams.length ? resolveParams(headerParams) : void 0,
803
+ queryParams: queryParams.length ? resolveParams(queryParams) : null,
804
+ headerParams: headerParams.length ? resolveParams(headerParams) : null,
776
805
  requestName,
777
806
  responseName,
778
807
  file: zodFile
@@ -787,7 +816,7 @@ const serverGenerator = defineGenerator({
787
816
  ...(zod.headerParams ?? []).map((p) => p.schemaName),
788
817
  zod.requestName,
789
818
  zod.responseName
790
- ].filter(Boolean);
819
+ ].filter((name) => Boolean(name));
791
820
  const uniqueNames = [...new Set(zodNames)].sort();
792
821
  return [/* @__PURE__ */ jsx(File.Import, {
793
822
  name: [mcp.name],
@@ -803,13 +832,21 @@ const serverGenerator = defineGenerator({
803
832
  baseName: serverFile.baseName,
804
833
  path: serverFile.path,
805
834
  meta: serverFile.meta,
806
- banner: resolver.resolveBanner(adapter.inputNode, {
835
+ banner: resolver.resolveBanner(ctx.meta, {
807
836
  output,
808
- config
837
+ config,
838
+ file: {
839
+ path: serverFile.path,
840
+ baseName: serverFile.baseName
841
+ }
809
842
  }),
810
- footer: resolver.resolveFooter(adapter.inputNode, {
843
+ footer: resolver.resolveFooter(ctx.meta, {
811
844
  output,
812
- config
845
+ config,
846
+ file: {
847
+ path: serverFile.path,
848
+ baseName: serverFile.baseName
849
+ }
813
850
  }),
814
851
  children: [
815
852
  /* @__PURE__ */ jsx(File.Import, {
@@ -827,8 +864,8 @@ const serverGenerator = defineGenerator({
827
864
  imports,
828
865
  /* @__PURE__ */ jsx(Server, {
829
866
  name,
830
- serverName: adapter.inputNode?.meta?.title ?? "server",
831
- serverVersion: adapter.inputNode?.meta?.version ?? "0.0.0",
867
+ serverName: ctx.meta.title ?? "server",
868
+ serverVersion: ctx.meta.version ?? "0.0.0",
832
869
  paramsCasing,
833
870
  operations: operationsMapped
834
871
  })
@@ -842,7 +879,7 @@ const serverGenerator = defineGenerator({
842
879
  children: `
843
880
  {
844
881
  "mcpServers": {
845
- "${adapter.inputNode?.meta?.title || "server"}": {
882
+ "${ctx.meta.title || "server"}": {
846
883
  "type": "stdio",
847
884
  "command": "npx",
848
885
  "args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
@@ -857,41 +894,77 @@ const serverGenerator = defineGenerator({
857
894
  //#endregion
858
895
  //#region src/resolvers/resolverMcp.ts
859
896
  /**
860
- * Naming convention resolver for MCP plugin.
897
+ * Default resolver used by `@kubb/plugin-mcp`. Decides the names and file
898
+ * paths for every generated MCP tool handler. Function names get a `Handler`
899
+ * suffix so an operation `addPet` becomes `addPetHandler`.
861
900
  *
862
- * Provides default naming helpers using camelCase with a `handler` suffix for functions.
901
+ * @example Resolve a handler name
902
+ * ```ts
903
+ * import { resolverMcp } from '@kubb/plugin-mcp'
863
904
  *
864
- * @example
865
- * `resolverMcp.default('addPet', 'function') // → 'addPetHandler'`
905
+ * resolverMcp.default('addPet', 'function') // 'addPetHandler'
906
+ * ```
866
907
  */
867
- const resolverMcp = defineResolver((ctx) => ({
908
+ const resolverMcp = defineResolver(() => ({
868
909
  name: "default",
869
910
  pluginName: "plugin-mcp",
870
911
  default(name, type) {
871
- if (type === "file") return camelCase(name, { isFile: true });
912
+ if (type === "file") return toFilePath(name);
872
913
  return camelCase(name, { suffix: "handler" });
873
914
  },
874
915
  resolveName(name) {
875
- return ctx.default(name, "function");
916
+ return this.default(name, "function");
917
+ },
918
+ resolvePathName(name, type) {
919
+ return this.default(name, type);
920
+ },
921
+ resolveHandlerName(node) {
922
+ return this.resolveName(node.operationId);
876
923
  }
877
924
  }));
878
925
  //#endregion
879
926
  //#region src/plugin.ts
927
+ /**
928
+ * Canonical plugin name for `@kubb/plugin-mcp`. Used for driver lookups and
929
+ * cross-plugin dependency references.
930
+ */
880
931
  const pluginMcpName = "plugin-mcp";
932
+ /**
933
+ * Generates a Model Context Protocol (MCP) server from an OpenAPI spec. Every
934
+ * operation becomes a typed MCP tool that AI assistants (Claude Desktop, Claude
935
+ * Code, MCP-compatible clients) can call directly.
936
+ *
937
+ * @example
938
+ * ```ts
939
+ * import { defineConfig } from 'kubb'
940
+ * import { pluginTs } from '@kubb/plugin-ts'
941
+ * import { pluginClient } from '@kubb/plugin-client'
942
+ * import { pluginZod } from '@kubb/plugin-zod'
943
+ * import { pluginMcp } from '@kubb/plugin-mcp'
944
+ *
945
+ * export default defineConfig({
946
+ * input: { path: './petStore.yaml' },
947
+ * output: { path: './src/gen' },
948
+ * plugins: [
949
+ * pluginTs(),
950
+ * pluginClient(),
951
+ * pluginZod(),
952
+ * pluginMcp({
953
+ * output: { path: './mcp' },
954
+ * client: { baseURL: 'https://petstore.swagger.io/v2' },
955
+ * }),
956
+ * ],
957
+ * })
958
+ * ```
959
+ */
881
960
  const pluginMcp = definePlugin((options) => {
882
961
  const { output = {
883
962
  path: "mcp",
884
- barrelType: "named"
963
+ barrel: { type: "named" }
885
964
  }, group, exclude = [], include, override = [], paramsCasing, client, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
886
965
  const clientName = client?.client ?? "axios";
887
966
  const clientImportPath = client?.importPath ?? (!client?.bundle ? `@kubb/plugin-client/clients/${clientName}` : void 0);
888
- const groupConfig = group ? {
889
- ...group,
890
- name: group.name ? group.name : (ctx) => {
891
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
892
- return `${camelCase(ctx.group)}Requests`;
893
- }
894
- } : void 0;
967
+ const groupConfig = createGroupConfig(group);
895
968
  return {
896
969
  name: pluginMcpName,
897
970
  options,
@@ -927,10 +1000,10 @@ const pluginMcp = definePlugin((options) => {
927
1000
  const root = path.resolve(ctx.config.root, ctx.config.output.path);
928
1001
  const hasClientPlugin = ctx.config.plugins?.some((p) => p.name === pluginClientName);
929
1002
  if (client?.bundle && !hasClientPlugin && !clientImportPath) ctx.injectFile({
930
- baseName: "fetch.ts",
931
- path: path.resolve(root, ".kubb/fetch.ts"),
1003
+ baseName: "client.ts",
1004
+ path: path.resolve(root, ".kubb/client.ts"),
932
1005
  sources: [ast.createSource({
933
- name: "fetch",
1006
+ name: "client",
934
1007
  nodes: [ast.createText(clientName === "fetch" ? source$1 : source)],
935
1008
  isExportable: true,
936
1009
  isIndexable: true