@kubb/plugin-mcp 5.0.0-beta.3 → 5.0.0-beta.30

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.cjs CHANGED
@@ -247,16 +247,16 @@ var URLPath = class {
247
247
  get object() {
248
248
  return this.toObject();
249
249
  }
250
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
250
+ /** Returns a map of path parameter names, or `null` when the path has no parameters.
251
251
  *
252
252
  * @example
253
253
  * ```ts
254
254
  * new URLPath('/pet/{petId}').params // { petId: 'petId' }
255
- * new URLPath('/pet').params // undefined
255
+ * new URLPath('/pet').params // null
256
256
  * ```
257
257
  */
258
258
  get params() {
259
- return this.getParams();
259
+ return this.toParamsObject();
260
260
  }
261
261
  #transformParam(raw) {
262
262
  const param = isValidVarName(raw) ? raw : camelCase(raw);
@@ -274,7 +274,7 @@ var URLPath = class {
274
274
  toObject({ type = "path", replacer, stringify } = {}) {
275
275
  const object = {
276
276
  url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
277
- params: this.getParams()
277
+ params: this.toParamsObject()
278
278
  };
279
279
  if (stringify) {
280
280
  if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
@@ -290,12 +290,13 @@ var URLPath = class {
290
290
  * @example
291
291
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
292
292
  */
293
- toTemplateString({ prefix = "", replacer } = {}) {
294
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
293
+ toTemplateString({ prefix, replacer } = {}) {
294
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
295
295
  if (i % 2 === 0) return part;
296
296
  const param = this.#transformParam(part);
297
297
  return `\${${replacer ? replacer(param) : param}}`;
298
- }).join("")}\``;
298
+ }).join("");
299
+ return `\`${prefix ?? ""}${result}\``;
299
300
  }
300
301
  /**
301
302
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -304,17 +305,17 @@ var URLPath = class {
304
305
  *
305
306
  * @example
306
307
  * ```ts
307
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
308
+ * new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
308
309
  * // { petId: 'petId', tagId: 'tagId' }
309
310
  * ```
310
311
  */
311
- getParams(replacer) {
312
+ toParamsObject(replacer) {
312
313
  const params = {};
313
314
  this.#eachParam((_raw, param) => {
314
315
  const key = replacer ? replacer(param) : param;
315
316
  params[key] = key;
316
317
  });
317
- return Object.keys(params).length > 0 ? params : void 0;
318
+ return Object.keys(params).length > 0 ? params : null;
318
319
  }
319
320
  /** Converts the OpenAPI path to Express-style colon syntax.
320
321
  *
@@ -328,82 +329,111 @@ var URLPath = class {
328
329
  }
329
330
  };
330
331
  //#endregion
331
- //#region src/utils.ts
332
- /**
333
- * Find the first 2xx response status code from an operation's responses.
334
- */
335
- function findSuccessStatusCode(responses) {
336
- for (const res of responses) {
337
- const code = Number(res.statusCode);
338
- if (code >= 200 && code < 300) return res.statusCode;
339
- }
332
+ //#region ../../internals/shared/src/operation.ts
333
+ function getOperationLink(node, link) {
334
+ if (!link) return null;
335
+ if (typeof link === "function") return link(node) ?? null;
336
+ if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
337
+ return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
340
338
  }
341
- /**
342
- * Render a group param value compose individual schemas into `z.object({ ... })`,
343
- * or use a schema name string directly.
344
- */
345
- function zodGroupExpr(entry) {
346
- if (typeof entry === "string") return entry;
347
- return `z.object({ ${entry.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
348
- }
349
- /**
350
- * Build JSDoc comment lines from an OperationNode.
351
- */
352
- function getComments(node) {
353
- return [
339
+ function buildOperationComments(node, options = {}) {
340
+ const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
341
+ const linkComment = getOperationLink(node, link);
342
+ const filteredComments = (linkPosition === "beforeDeprecated" ? [
343
+ node.description && `@description ${node.description}`,
344
+ node.summary && `@summary ${node.summary}`,
345
+ linkComment,
346
+ node.deprecated && "@deprecated"
347
+ ] : [
354
348
  node.description && `@description ${node.description}`,
355
349
  node.summary && `@summary ${node.summary}`,
356
350
  node.deprecated && "@deprecated",
357
- `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`
358
- ].filter((x) => Boolean(x));
351
+ linkComment
352
+ ]).filter((comment) => Boolean(comment));
353
+ if (!splitLines) return filteredComments;
354
+ return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
359
355
  }
360
- /**
361
- * Build a mapping of original param names → camelCase names.
362
- * Returns `undefined` when no names actually change (no remapping needed).
363
- */
364
- function getParamsMapping(params) {
365
- if (!params.length) return;
366
- const mapping = {};
367
- let hasDifference = false;
368
- for (const p of params) {
369
- const camelName = camelCase(p.name);
370
- mapping[p.name] = camelName;
371
- if (p.name !== camelName) hasDifference = true;
372
- }
373
- return hasDifference ? mapping : void 0;
356
+ function getOperationParameters(node, options = {}) {
357
+ const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
358
+ return {
359
+ path: params.filter((param) => param.in === "path"),
360
+ query: params.filter((param) => param.in === "query"),
361
+ header: params.filter((param) => param.in === "header"),
362
+ cookie: params.filter((param) => param.in === "cookie")
363
+ };
374
364
  }
375
- /**
376
- * Convert a SchemaNode type to an inline Zod expression string.
377
- * Used as fallback when no named zod schema is available for a path parameter.
378
- */
379
- function zodExprFromSchemaNode(schema) {
380
- let expr;
381
- switch (schema.type) {
382
- case "enum": {
383
- const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
384
- if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) expr = `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
385
- else if (rawValues.length > 0) {
386
- const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
387
- expr = literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
388
- } else expr = "z.string()";
389
- break;
390
- }
391
- case "integer":
392
- expr = "z.coerce.number()";
393
- break;
394
- case "number":
395
- expr = "z.number()";
396
- break;
397
- case "boolean":
398
- expr = "z.boolean()";
399
- break;
400
- case "array":
401
- expr = "z.array(z.unknown())";
402
- break;
403
- default: expr = "z.string()";
365
+ function getStatusCodeNumber(statusCode) {
366
+ const code = Number(statusCode);
367
+ return Number.isNaN(code) ? null : code;
368
+ }
369
+ function isSuccessStatusCode(statusCode) {
370
+ const code = getStatusCodeNumber(statusCode);
371
+ return code !== null && code >= 200 && code < 300;
372
+ }
373
+ function isErrorStatusCode(statusCode) {
374
+ const code = getStatusCodeNumber(statusCode);
375
+ return code !== null && code >= 400;
376
+ }
377
+ function resolveErrorNames(node, resolver) {
378
+ return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
379
+ }
380
+ function resolveStatusCodeNames(node, resolver) {
381
+ return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
382
+ }
383
+ const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
384
+ function resolveOperationTypeNames(node, resolver, options = {}) {
385
+ const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
386
+ let byResolver = typeNamesByResolver.get(resolver);
387
+ if (byResolver) {
388
+ const cached = byResolver.get(cacheKey);
389
+ if (cached) return cached;
390
+ } else {
391
+ byResolver = /* @__PURE__ */ new Map();
392
+ typeNamesByResolver.set(resolver, byResolver);
404
393
  }
405
- if (schema.nullable) expr = `${expr}.nullable()`;
406
- return expr;
394
+ const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
395
+ const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
396
+ const exclude = new Set(options.exclude ?? []);
397
+ const paramNames = [
398
+ ...path.map((param) => resolver.resolvePathParamsName(node, param)),
399
+ ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
400
+ ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
401
+ ];
402
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
403
+ const result = (options.order === "body-response-first" ? [
404
+ ...bodyAndResponseNames,
405
+ ...paramNames,
406
+ ...responseStatusNames
407
+ ] : [
408
+ ...paramNames,
409
+ ...bodyAndResponseNames,
410
+ ...responseStatusNames
411
+ ]).filter((name) => Boolean(name) && !exclude.has(name));
412
+ byResolver.set(cacheKey, result);
413
+ return result;
414
+ }
415
+ function findSuccessStatusCode(responses) {
416
+ for (const response of responses) if (isSuccessStatusCode(response.statusCode)) return response.statusCode;
417
+ return null;
418
+ }
419
+ //#endregion
420
+ //#region ../../internals/shared/src/params.ts
421
+ function buildParamsMapping(originalParams, mappedParams) {
422
+ const mapping = {};
423
+ let hasChanged = false;
424
+ originalParams.forEach((param, i) => {
425
+ const mappedName = mappedParams[i]?.name ?? param.name;
426
+ mapping[param.name] = mappedName;
427
+ if (param.name !== mappedName) hasChanged = true;
428
+ });
429
+ return hasChanged ? mapping : null;
430
+ }
431
+ function buildTransformedParamsMapping(params, transformName) {
432
+ if (!params.length) return null;
433
+ return buildParamsMapping(params, params.map((param) => ({
434
+ ...param,
435
+ name: transformName(param.name)
436
+ })));
407
437
  }
408
438
  //#endregion
409
439
  //#region src/components/McpHandler.tsx
@@ -418,13 +448,9 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
418
448
  const urlPath = new URLPath(node.path);
419
449
  const contentType = node.requestBody?.content?.[0]?.contentType;
420
450
  const isFormData = contentType === "multipart/form-data";
421
- const casedParams = _kubb_core.ast.caseParams(node.parameters, paramsCasing);
422
- const queryParams = casedParams.filter((p) => p.in === "query");
423
- const headerParams = casedParams.filter((p) => p.in === "header");
424
- const originalPathParams = node.parameters.filter((p) => p.in === "path");
425
- const originalQueryParams = node.parameters.filter((p) => p.in === "query");
426
- const originalHeaderParams = node.parameters.filter((p) => p.in === "header");
427
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0;
451
+ const { query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
452
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
453
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
428
454
  const responseName = resolver.resolveResponseName(node);
429
455
  const errorResponses = node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => resolver.resolveResponseStatusName(node, r.statusCode));
430
456
  const generics = [
@@ -440,11 +466,11 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
440
466
  });
441
467
  const baseParamsSignature = declarationPrinter.print(paramsNode) ?? "";
442
468
  const paramsSignature = baseParamsSignature ? `${baseParamsSignature}, request: RequestHandlerExtra<ServerRequest, ServerNotification>` : "request: RequestHandlerExtra<ServerRequest, ServerNotification>";
443
- const pathParamsMapping = paramsCasing ? getParamsMapping(originalPathParams) : void 0;
444
- const queryParamsMapping = paramsCasing ? getParamsMapping(originalQueryParams) : void 0;
445
- const headerParamsMapping = paramsCasing ? getParamsMapping(originalHeaderParams) : void 0;
446
- const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0;
447
- const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : void 0, contentTypeHeader].filter(Boolean);
469
+ const pathParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalPathParams, camelCase) : null;
470
+ const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) : null;
471
+ const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) : null;
472
+ const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null;
473
+ const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null, contentTypeHeader].filter(Boolean);
448
474
  const fetchConfig = [];
449
475
  fetchConfig.push(`method: ${JSON.stringify(node.method.toUpperCase())}`);
450
476
  fetchConfig.push(`url: ${urlPath.template}`);
@@ -478,7 +504,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
478
504
  async: true,
479
505
  export: true,
480
506
  params: paramsSignature,
481
- JSDoc: { comments: getComments(node) },
507
+ JSDoc: { comments: buildOperationComments(node) },
482
508
  returnType: "Promise<CallToolResult>",
483
509
  children: [
484
510
  "",
@@ -500,7 +526,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
500
526
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
501
527
  isFormData && requestName && "const formData = buildFormData(requestData)",
502
528
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
503
- `const res = await fetch<${generics.join(", ")}>({ ${fetchConfig.join(", ")} }, request)`,
529
+ `const res = await client<${generics.join(", ")}>({ ${fetchConfig.join(", ")} }, request)`,
504
530
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
505
531
  callToolResult
506
532
  ]
@@ -508,6 +534,39 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
508
534
  });
509
535
  }
510
536
  //#endregion
537
+ //#region src/utils.ts
538
+ /**
539
+ * Render a group param value — compose individual schemas into `z.object({ ... })`,
540
+ * or use a schema name string directly.
541
+ */
542
+ function zodGroupExpr(entry) {
543
+ if (typeof entry === "string") return entry;
544
+ return `z.object({ ${entry.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
545
+ }
546
+ /**
547
+ * Convert a SchemaNode type to an inline Zod expression string.
548
+ * Used as fallback when no named zod schema is available for a path parameter.
549
+ */
550
+ function zodExprFromSchemaNode(schema) {
551
+ const baseExpr = (() => {
552
+ if (schema.type === "enum") {
553
+ const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
554
+ if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) return `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
555
+ if (rawValues.length > 0) {
556
+ const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
557
+ return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
558
+ }
559
+ return "z.string()";
560
+ }
561
+ if (schema.type === "integer") return "z.coerce.number()";
562
+ if (schema.type === "number") return "z.number()";
563
+ if (schema.type === "boolean") return "z.boolean()";
564
+ if (schema.type === "array") return "z.array(z.unknown())";
565
+ return "z.string()";
566
+ })();
567
+ return schema.nullable ? `${baseExpr}.nullable()` : baseExpr;
568
+ }
569
+ //#endregion
511
570
  //#region src/components/Server.tsx
512
571
  const keysPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "keys" });
513
572
  function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
@@ -527,7 +586,7 @@ function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
527
586
  `
528
587
  }),
529
588
  operations.map(({ tool, mcp, zod, node }) => {
530
- const pathParams = _kubb_core.ast.caseParams(node.parameters, paramsCasing).filter((p) => p.in === "path");
589
+ const { path: pathParams } = getOperationParameters(node, { paramsCasing });
531
590
  const pathEntries = [];
532
591
  const otherEntries = [];
533
592
  for (const p of pathParams) {
@@ -554,9 +613,9 @@ function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
554
613
  const paramsNode = entries.length ? _kubb_core.ast.createFunctionParameters({ params: [_kubb_core.ast.createParameterGroup({ properties: entries.map((e) => _kubb_core.ast.createFunctionParameter({
555
614
  name: e.key,
556
615
  optional: false
557
- })) })] }) : void 0;
616
+ })) })] }) : null;
558
617
  const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
559
- const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : void 0;
618
+ const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : null;
560
619
  const outputSchema = zod.responseName;
561
620
  const config = [
562
621
  tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
@@ -597,29 +656,27 @@ server.registerTool(${JSON.stringify(tool.name)}, {
597
656
  }
598
657
  //#endregion
599
658
  //#region src/generators/mcpGenerator.tsx
659
+ /**
660
+ * Built-in operation generator for `@kubb/plugin-mcp`. Emits one MCP tool
661
+ * handler per OpenAPI operation, wiring the input Zod schema, the HTTP call,
662
+ * and the response shape into a single function that an MCP server can
663
+ * register as a callable tool.
664
+ */
600
665
  const mcpGenerator = (0, _kubb_core.defineGenerator)({
601
666
  name: "mcp",
602
- renderer: _kubb_renderer_jsx.jsxRenderer,
667
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
603
668
  operation(node, ctx) {
604
669
  const { resolver, driver, root } = ctx;
605
670
  const { output, client, paramsCasing, group } = ctx.options;
606
671
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
607
672
  if (!pluginTs) return null;
608
673
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
609
- const casedParams = _kubb_core.ast.caseParams(node.parameters, paramsCasing);
610
- const pathParams = casedParams.filter((p) => p.in === "path");
611
- const queryParams = casedParams.filter((p) => p.in === "query");
612
- const headerParams = casedParams.filter((p) => p.in === "header");
613
- const importedTypeNames = [
614
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
615
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
616
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
617
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
618
- tsResolver.resolveResponseName(node),
619
- ...node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
620
- ].filter(Boolean);
674
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
675
+ paramsCasing,
676
+ responseStatusNames: "error"
677
+ });
621
678
  const meta = {
622
- name: resolver.resolveName(node.operationId),
679
+ name: resolver.resolveHandlerName(node),
623
680
  file: resolver.resolveFile({
624
681
  name: node.operationId,
625
682
  extname: ".ts",
@@ -628,7 +685,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
628
685
  }, {
629
686
  root,
630
687
  output,
631
- group
688
+ group: group ?? void 0
632
689
  }),
633
690
  fileTs: tsResolver.resolveFile({
634
691
  name: node.operationId,
@@ -638,7 +695,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
638
695
  }, {
639
696
  root,
640
697
  output: pluginTs.options?.output ?? output,
641
- group: pluginTs.options?.group
698
+ group: pluginTs.options?.group ?? void 0
642
699
  })
643
700
  };
644
701
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
@@ -682,7 +739,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
682
739
  isTypeOnly: true
683
740
  }),
684
741
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
685
- name: "fetch",
742
+ name: "client",
686
743
  path: client.importPath
687
744
  }),
688
745
  client.dataReturnType === "full" && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -698,18 +755,18 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
698
755
  "ResponseErrorConfig"
699
756
  ],
700
757
  root: meta.file.path,
701
- path: node_path.default.resolve(root, ".kubb/fetch.ts"),
758
+ path: node_path.default.resolve(root, ".kubb/client.ts"),
702
759
  isTypeOnly: true
703
760
  }),
704
761
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
705
- name: ["fetch"],
762
+ name: ["client"],
706
763
  root: meta.file.path,
707
- path: node_path.default.resolve(root, ".kubb/fetch.ts")
764
+ path: node_path.default.resolve(root, ".kubb/client.ts")
708
765
  }),
709
766
  client.dataReturnType === "full" && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
710
767
  name: ["ResponseConfig"],
711
768
  root: meta.file.path,
712
- path: node_path.default.resolve(root, ".kubb/fetch.ts"),
769
+ path: node_path.default.resolve(root, ".kubb/client.ts"),
713
770
  isTypeOnly: true
714
771
  })
715
772
  ] }),
@@ -736,9 +793,9 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
736
793
  */
737
794
  const serverGenerator = (0, _kubb_core.defineGenerator)({
738
795
  name: "operations",
739
- renderer: _kubb_renderer_jsx.jsxRenderer,
796
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
740
797
  operations(nodes, ctx) {
741
- const { adapter, config, resolver, plugin, driver, root } = ctx;
798
+ const { config, resolver, plugin, driver, root } = ctx;
742
799
  const { output, paramsCasing, group } = ctx.options;
743
800
  const pluginZod = driver.getPlugin(_kubb_plugin_zod.pluginZodName);
744
801
  if (!pluginZod) return;
@@ -755,10 +812,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
755
812
  meta: { pluginName: plugin.name }
756
813
  };
757
814
  const operationsMapped = nodes.map((node) => {
758
- const casedParams = _kubb_core.ast.caseParams(node.parameters, paramsCasing);
759
- const pathParams = casedParams.filter((p) => p.in === "path");
760
- const queryParams = casedParams.filter((p) => p.in === "query");
761
- const headerParams = casedParams.filter((p) => p.in === "header");
815
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
762
816
  const mcpFile = resolver.resolveFile({
763
817
  name: node.operationId,
764
818
  extname: ".ts",
@@ -767,7 +821,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
767
821
  }, {
768
822
  root,
769
823
  output,
770
- group
824
+ group: group ?? void 0
771
825
  });
772
826
  const zodFile = zodResolver.resolveFile({
773
827
  name: node.operationId,
@@ -777,11 +831,11 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
777
831
  }, {
778
832
  root,
779
833
  output: pluginZod.options?.output ?? output,
780
- group: pluginZod.options?.group
834
+ group: pluginZod.options?.group ?? void 0
781
835
  });
782
- const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : void 0;
836
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : null;
783
837
  const successStatus = findSuccessStatusCode(node.responses);
784
- const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : void 0;
838
+ const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : null;
785
839
  const resolveParams = (params) => params.map((p) => ({
786
840
  name: p.name,
787
841
  schemaName: zodResolver.resolveParamName(node, p)
@@ -793,13 +847,13 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
793
847
  description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`
794
848
  },
795
849
  mcp: {
796
- name: resolver.resolveName(node.operationId),
850
+ name: resolver.resolveHandlerName(node),
797
851
  file: mcpFile
798
852
  },
799
853
  zod: {
800
854
  pathParams: resolveParams(pathParams),
801
- queryParams: queryParams.length ? resolveParams(queryParams) : void 0,
802
- headerParams: headerParams.length ? resolveParams(headerParams) : void 0,
855
+ queryParams: queryParams.length ? resolveParams(queryParams) : null,
856
+ headerParams: headerParams.length ? resolveParams(headerParams) : null,
803
857
  requestName,
804
858
  responseName,
805
859
  file: zodFile
@@ -814,7 +868,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
814
868
  ...(zod.headerParams ?? []).map((p) => p.schemaName),
815
869
  zod.requestName,
816
870
  zod.responseName
817
- ].filter(Boolean);
871
+ ].filter((name) => Boolean(name));
818
872
  const uniqueNames = [...new Set(zodNames)].sort();
819
873
  return [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
820
874
  name: [mcp.name],
@@ -830,13 +884,21 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
830
884
  baseName: serverFile.baseName,
831
885
  path: serverFile.path,
832
886
  meta: serverFile.meta,
833
- banner: resolver.resolveBanner(adapter.inputNode, {
887
+ banner: resolver.resolveBanner(ctx.meta, {
834
888
  output,
835
- config
889
+ config,
890
+ file: {
891
+ path: serverFile.path,
892
+ baseName: serverFile.baseName
893
+ }
836
894
  }),
837
- footer: resolver.resolveFooter(adapter.inputNode, {
895
+ footer: resolver.resolveFooter(ctx.meta, {
838
896
  output,
839
- config
897
+ config,
898
+ file: {
899
+ path: serverFile.path,
900
+ baseName: serverFile.baseName
901
+ }
840
902
  }),
841
903
  children: [
842
904
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -854,8 +916,8 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
854
916
  imports,
855
917
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Server, {
856
918
  name,
857
- serverName: adapter.inputNode?.meta?.title ?? "server",
858
- serverVersion: adapter.inputNode?.meta?.version ?? "0.0.0",
919
+ serverName: ctx.meta.title ?? "server",
920
+ serverVersion: ctx.meta.version ?? "0.0.0",
859
921
  paramsCasing,
860
922
  operations: operationsMapped
861
923
  })
@@ -869,7 +931,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
869
931
  children: `
870
932
  {
871
933
  "mcpServers": {
872
- "${adapter.inputNode?.meta?.title || "server"}": {
934
+ "${ctx.meta.title || "server"}": {
873
935
  "type": "stdio",
874
936
  "command": "npx",
875
937
  "args": ["tsx", "${node_path.default.relative(node_path.default.dirname(jsonFile.path), serverFile.path)}"]
@@ -884,14 +946,18 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
884
946
  //#endregion
885
947
  //#region src/resolvers/resolverMcp.ts
886
948
  /**
887
- * Naming convention resolver for MCP plugin.
949
+ * Default resolver used by `@kubb/plugin-mcp`. Decides the names and file
950
+ * paths for every generated MCP tool handler. Function names get a `Handler`
951
+ * suffix so an operation `addPet` becomes `addPetHandler`.
888
952
  *
889
- * Provides default naming helpers using camelCase with a `handler` suffix for functions.
953
+ * @example Resolve a handler name
954
+ * ```ts
955
+ * import { resolverMcp } from '@kubb/plugin-mcp'
890
956
  *
891
- * @example
892
- * `resolverMcp.default('addPet', 'function') // → 'addPetHandler'`
957
+ * resolverMcp.default('addPet', 'function') // 'addPetHandler'
958
+ * ```
893
959
  */
894
- const resolverMcp = (0, _kubb_core.defineResolver)((ctx) => ({
960
+ const resolverMcp = (0, _kubb_core.defineResolver)(() => ({
895
961
  name: "default",
896
962
  pluginName: "plugin-mcp",
897
963
  default(name, type) {
@@ -899,12 +965,50 @@ const resolverMcp = (0, _kubb_core.defineResolver)((ctx) => ({
899
965
  return camelCase(name, { suffix: "handler" });
900
966
  },
901
967
  resolveName(name) {
902
- return ctx.default(name, "function");
968
+ return this.default(name, "function");
969
+ },
970
+ resolvePathName(name, type) {
971
+ return this.default(name, type);
972
+ },
973
+ resolveHandlerName(node) {
974
+ return this.resolveName(node.operationId);
903
975
  }
904
976
  }));
905
977
  //#endregion
906
978
  //#region src/plugin.ts
979
+ /**
980
+ * Canonical plugin name for `@kubb/plugin-mcp`. Used for driver lookups and
981
+ * cross-plugin dependency references.
982
+ */
907
983
  const pluginMcpName = "plugin-mcp";
984
+ /**
985
+ * Generates a Model Context Protocol (MCP) server from an OpenAPI spec. Every
986
+ * operation becomes a typed MCP tool that AI assistants (Claude Desktop, Claude
987
+ * Code, MCP-compatible clients) can call directly.
988
+ *
989
+ * @example
990
+ * ```ts
991
+ * import { defineConfig } from 'kubb'
992
+ * import { pluginTs } from '@kubb/plugin-ts'
993
+ * import { pluginClient } from '@kubb/plugin-client'
994
+ * import { pluginZod } from '@kubb/plugin-zod'
995
+ * import { pluginMcp } from '@kubb/plugin-mcp'
996
+ *
997
+ * export default defineConfig({
998
+ * input: { path: './petStore.yaml' },
999
+ * output: { path: './src/gen' },
1000
+ * plugins: [
1001
+ * pluginTs(),
1002
+ * pluginClient(),
1003
+ * pluginZod(),
1004
+ * pluginMcp({
1005
+ * output: { path: './mcp' },
1006
+ * client: { baseURL: 'https://petstore.swagger.io/v2' },
1007
+ * }),
1008
+ * ],
1009
+ * })
1010
+ * ```
1011
+ */
908
1012
  const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
909
1013
  const { output = {
910
1014
  path: "mcp",
@@ -918,7 +1022,7 @@ const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
918
1022
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
919
1023
  return `${camelCase(ctx.group)}Requests`;
920
1024
  }
921
- } : void 0;
1025
+ } : null;
922
1026
  return {
923
1027
  name: pluginMcpName,
924
1028
  options,
@@ -954,10 +1058,10 @@ const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
954
1058
  const root = node_path$1.default.resolve(ctx.config.root, ctx.config.output.path);
955
1059
  const hasClientPlugin = ctx.config.plugins?.some((p) => p.name === _kubb_plugin_client.pluginClientName);
956
1060
  if (client?.bundle && !hasClientPlugin && !clientImportPath) ctx.injectFile({
957
- baseName: "fetch.ts",
958
- path: node_path$1.default.resolve(root, ".kubb/fetch.ts"),
1061
+ baseName: "client.ts",
1062
+ path: node_path$1.default.resolve(root, ".kubb/client.ts"),
959
1063
  sources: [_kubb_core.ast.createSource({
960
- name: "fetch",
1064
+ name: "client",
961
1065
  nodes: [_kubb_core.ast.createText(clientName === "fetch" ? _kubb_plugin_client_templates_clients_fetch_source.source : _kubb_plugin_client_templates_clients_axios_source.source)],
962
1066
  isExportable: true,
963
1067
  isIndexable: true