@kubb/plugin-mcp 5.0.0-beta.15 → 5.0.0-beta.25

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
@@ -220,12 +220,12 @@ var URLPath = class {
220
220
  get object() {
221
221
  return this.toObject();
222
222
  }
223
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
223
+ /** Returns a map of path parameter names, or `null` when the path has no parameters.
224
224
  *
225
225
  * @example
226
226
  * ```ts
227
227
  * new URLPath('/pet/{petId}').params // { petId: 'petId' }
228
- * new URLPath('/pet').params // undefined
228
+ * new URLPath('/pet').params // null
229
229
  * ```
230
230
  */
231
231
  get params() {
@@ -263,12 +263,13 @@ var URLPath = class {
263
263
  * @example
264
264
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
265
265
  */
266
- toTemplateString({ prefix = "", replacer } = {}) {
267
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
266
+ toTemplateString({ prefix, replacer } = {}) {
267
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
268
268
  if (i % 2 === 0) return part;
269
269
  const param = this.#transformParam(part);
270
270
  return `\${${replacer ? replacer(param) : param}}`;
271
- }).join("")}\``;
271
+ }).join("");
272
+ return `\`${prefix ?? ""}${result}\``;
272
273
  }
273
274
  /**
274
275
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -287,7 +288,7 @@ var URLPath = class {
287
288
  const key = replacer ? replacer(param) : param;
288
289
  params[key] = key;
289
290
  });
290
- return Object.keys(params).length > 0 ? params : void 0;
291
+ return Object.keys(params).length > 0 ? params : null;
291
292
  }
292
293
  /** Converts the OpenAPI path to Express-style colon syntax.
293
294
  *
@@ -303,9 +304,9 @@ var URLPath = class {
303
304
  //#endregion
304
305
  //#region ../../internals/shared/src/operation.ts
305
306
  function getOperationLink(node, link) {
306
- if (!link) return;
307
- if (typeof link === "function") return link(node);
308
- if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : void 0;
307
+ if (!link) return null;
308
+ if (typeof link === "function") return link(node) ?? null;
309
+ if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
309
310
  return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
310
311
  }
311
312
  function buildOperationComments(node, options = {}) {
@@ -336,15 +337,15 @@ function getOperationParameters(node, options = {}) {
336
337
  }
337
338
  function getStatusCodeNumber(statusCode) {
338
339
  const code = Number(statusCode);
339
- return Number.isNaN(code) ? void 0 : code;
340
+ return Number.isNaN(code) ? null : code;
340
341
  }
341
342
  function isSuccessStatusCode(statusCode) {
342
343
  const code = getStatusCodeNumber(statusCode);
343
- return code !== void 0 && code >= 200 && code < 300;
344
+ return code !== null && code >= 200 && code < 300;
344
345
  }
345
346
  function isErrorStatusCode(statusCode) {
346
347
  const code = getStatusCodeNumber(statusCode);
347
- return code !== void 0 && code >= 400;
348
+ return code !== null && code >= 400;
348
349
  }
349
350
  function resolveErrorNames(node, resolver) {
350
351
  return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
@@ -371,7 +372,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
371
372
  ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
372
373
  ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
373
374
  ];
374
- const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0, resolver.resolveResponseName(node)];
375
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
375
376
  const result = (options.order === "body-response-first" ? [
376
377
  ...bodyAndResponseNames,
377
378
  ...paramNames,
@@ -386,6 +387,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
386
387
  }
387
388
  function findSuccessStatusCode(responses) {
388
389
  for (const response of responses) if (isSuccessStatusCode(response.statusCode)) return response.statusCode;
390
+ return null;
389
391
  }
390
392
  //#endregion
391
393
  //#region ../../internals/shared/src/params.ts
@@ -397,10 +399,10 @@ function buildParamsMapping(originalParams, mappedParams) {
397
399
  mapping[param.name] = mappedName;
398
400
  if (param.name !== mappedName) hasChanged = true;
399
401
  });
400
- return hasChanged ? mapping : void 0;
402
+ return hasChanged ? mapping : null;
401
403
  }
402
404
  function buildTransformedParamsMapping(params, transformName) {
403
- if (!params.length) return;
405
+ if (!params.length) return null;
404
406
  return buildParamsMapping(params, params.map((param) => ({
405
407
  ...param,
406
408
  name: transformName(param.name)
@@ -421,7 +423,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
421
423
  const isFormData = contentType === "multipart/form-data";
422
424
  const { query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
423
425
  const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
424
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0;
426
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
425
427
  const responseName = resolver.resolveResponseName(node);
426
428
  const errorResponses = node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => resolver.resolveResponseStatusName(node, r.statusCode));
427
429
  const generics = [
@@ -437,11 +439,11 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
437
439
  });
438
440
  const baseParamsSignature = declarationPrinter.print(paramsNode) ?? "";
439
441
  const paramsSignature = baseParamsSignature ? `${baseParamsSignature}, request: RequestHandlerExtra<ServerRequest, ServerNotification>` : "request: RequestHandlerExtra<ServerRequest, ServerNotification>";
440
- const pathParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalPathParams, camelCase) : void 0;
441
- const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) : void 0;
442
- const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) : void 0;
443
- const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0;
444
- const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : void 0, contentTypeHeader].filter(Boolean);
442
+ const pathParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalPathParams, camelCase) : null;
443
+ const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) : null;
444
+ const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) : null;
445
+ const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null;
446
+ const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null, contentTypeHeader].filter(Boolean);
445
447
  const fetchConfig = [];
446
448
  fetchConfig.push(`method: ${JSON.stringify(node.method.toUpperCase())}`);
447
449
  fetchConfig.push(`url: ${urlPath.template}`);
@@ -519,33 +521,23 @@ function zodGroupExpr(entry) {
519
521
  * Used as fallback when no named zod schema is available for a path parameter.
520
522
  */
521
523
  function zodExprFromSchemaNode(schema) {
522
- let expr;
523
- switch (schema.type) {
524
- case "enum": {
524
+ const baseExpr = (() => {
525
+ if (schema.type === "enum") {
525
526
  const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
526
- if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) expr = `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
527
- else if (rawValues.length > 0) {
527
+ if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) return `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
528
+ if (rawValues.length > 0) {
528
529
  const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
529
- expr = literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
530
- } else expr = "z.string()";
531
- break;
530
+ return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
531
+ }
532
+ return "z.string()";
532
533
  }
533
- case "integer":
534
- expr = "z.coerce.number()";
535
- break;
536
- case "number":
537
- expr = "z.number()";
538
- break;
539
- case "boolean":
540
- expr = "z.boolean()";
541
- break;
542
- case "array":
543
- expr = "z.array(z.unknown())";
544
- break;
545
- default: expr = "z.string()";
546
- }
547
- if (schema.nullable) expr = `${expr}.nullable()`;
548
- return expr;
534
+ if (schema.type === "integer") return "z.coerce.number()";
535
+ if (schema.type === "number") return "z.number()";
536
+ if (schema.type === "boolean") return "z.boolean()";
537
+ if (schema.type === "array") return "z.array(z.unknown())";
538
+ return "z.string()";
539
+ })();
540
+ return schema.nullable ? `${baseExpr}.nullable()` : baseExpr;
549
541
  }
550
542
  //#endregion
551
543
  //#region src/components/Server.tsx
@@ -594,9 +586,9 @@ function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
594
586
  const paramsNode = entries.length ? ast.createFunctionParameters({ params: [ast.createParameterGroup({ properties: entries.map((e) => ast.createFunctionParameter({
595
587
  name: e.key,
596
588
  optional: false
597
- })) })] }) : void 0;
589
+ })) })] }) : null;
598
590
  const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
599
- const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : void 0;
591
+ const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : null;
600
592
  const outputSchema = zod.responseName;
601
593
  const config = [
602
594
  tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
@@ -637,6 +629,12 @@ server.registerTool(${JSON.stringify(tool.name)}, {
637
629
  }
638
630
  //#endregion
639
631
  //#region src/generators/mcpGenerator.tsx
632
+ /**
633
+ * Built-in operation generator for `@kubb/plugin-mcp`. Emits one MCP tool
634
+ * handler per OpenAPI operation, wiring the input Zod schema, the HTTP call,
635
+ * and the response shape into a single function that an MCP server can
636
+ * register as a callable tool.
637
+ */
640
638
  const mcpGenerator = defineGenerator({
641
639
  name: "mcp",
642
640
  renderer: jsxRendererSync,
@@ -660,7 +658,7 @@ const mcpGenerator = defineGenerator({
660
658
  }, {
661
659
  root,
662
660
  output,
663
- group
661
+ group: group ?? void 0
664
662
  }),
665
663
  fileTs: tsResolver.resolveFile({
666
664
  name: node.operationId,
@@ -670,7 +668,7 @@ const mcpGenerator = defineGenerator({
670
668
  }, {
671
669
  root,
672
670
  output: pluginTs.options?.output ?? output,
673
- group: pluginTs.options?.group
671
+ group: pluginTs.options?.group ?? void 0
674
672
  })
675
673
  };
676
674
  return /* @__PURE__ */ jsxs(File, {
@@ -770,7 +768,7 @@ const serverGenerator = defineGenerator({
770
768
  name: "operations",
771
769
  renderer: jsxRendererSync,
772
770
  operations(nodes, ctx) {
773
- const { config, resolver, plugin, driver, root, inputNode } = ctx;
771
+ const { config, resolver, plugin, driver, root } = ctx;
774
772
  const { output, paramsCasing, group } = ctx.options;
775
773
  const pluginZod = driver.getPlugin(pluginZodName);
776
774
  if (!pluginZod) return;
@@ -796,7 +794,7 @@ const serverGenerator = defineGenerator({
796
794
  }, {
797
795
  root,
798
796
  output,
799
- group
797
+ group: group ?? void 0
800
798
  });
801
799
  const zodFile = zodResolver.resolveFile({
802
800
  name: node.operationId,
@@ -806,11 +804,11 @@ const serverGenerator = defineGenerator({
806
804
  }, {
807
805
  root,
808
806
  output: pluginZod.options?.output ?? output,
809
- group: pluginZod.options?.group
807
+ group: pluginZod.options?.group ?? void 0
810
808
  });
811
- const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : void 0;
809
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : null;
812
810
  const successStatus = findSuccessStatusCode(node.responses);
813
- const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : void 0;
811
+ const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : null;
814
812
  const resolveParams = (params) => params.map((p) => ({
815
813
  name: p.name,
816
814
  schemaName: zodResolver.resolveParamName(node, p)
@@ -827,8 +825,8 @@ const serverGenerator = defineGenerator({
827
825
  },
828
826
  zod: {
829
827
  pathParams: resolveParams(pathParams),
830
- queryParams: queryParams.length ? resolveParams(queryParams) : void 0,
831
- headerParams: headerParams.length ? resolveParams(headerParams) : void 0,
828
+ queryParams: queryParams.length ? resolveParams(queryParams) : null,
829
+ headerParams: headerParams.length ? resolveParams(headerParams) : null,
832
830
  requestName,
833
831
  responseName,
834
832
  file: zodFile
@@ -859,11 +857,11 @@ const serverGenerator = defineGenerator({
859
857
  baseName: serverFile.baseName,
860
858
  path: serverFile.path,
861
859
  meta: serverFile.meta,
862
- banner: resolver.resolveBanner(inputNode, {
860
+ banner: resolver.resolveBanner(ctx.meta, {
863
861
  output,
864
862
  config
865
863
  }),
866
- footer: resolver.resolveFooter(inputNode, {
864
+ footer: resolver.resolveFooter(ctx.meta, {
867
865
  output,
868
866
  config
869
867
  }),
@@ -883,8 +881,8 @@ const serverGenerator = defineGenerator({
883
881
  imports,
884
882
  /* @__PURE__ */ jsx(Server, {
885
883
  name,
886
- serverName: inputNode.meta?.title ?? "server",
887
- serverVersion: inputNode.meta?.version ?? "0.0.0",
884
+ serverName: ctx.meta.title ?? "server",
885
+ serverVersion: ctx.meta.version ?? "0.0.0",
888
886
  paramsCasing,
889
887
  operations: operationsMapped
890
888
  })
@@ -898,7 +896,7 @@ const serverGenerator = defineGenerator({
898
896
  children: `
899
897
  {
900
898
  "mcpServers": {
901
- "${inputNode.meta?.title || "server"}": {
899
+ "${ctx.meta.title || "server"}": {
902
900
  "type": "stdio",
903
901
  "command": "npx",
904
902
  "args": ["tsx", "${path.relative(path.dirname(jsonFile.path), serverFile.path)}"]
@@ -913,12 +911,16 @@ const serverGenerator = defineGenerator({
913
911
  //#endregion
914
912
  //#region src/resolvers/resolverMcp.ts
915
913
  /**
916
- * Naming convention resolver for MCP plugin.
914
+ * Default resolver used by `@kubb/plugin-mcp`. Decides the names and file
915
+ * paths for every generated MCP tool handler. Function names get a `Handler`
916
+ * suffix so an operation `addPet` becomes `addPetHandler`.
917
917
  *
918
- * Provides default naming helpers using camelCase with a `handler` suffix for functions.
918
+ * @example Resolve a handler name
919
+ * ```ts
920
+ * import { resolverMcp } from '@kubb/plugin-mcp'
919
921
  *
920
- * @example
921
- * `resolverMcp.default('addPet', 'function') // → 'addPetHandler'`
922
+ * resolverMcp.default('addPet', 'function') // 'addPetHandler'
923
+ * ```
922
924
  */
923
925
  const resolverMcp = defineResolver(() => ({
924
926
  name: "default",
@@ -939,7 +941,39 @@ const resolverMcp = defineResolver(() => ({
939
941
  }));
940
942
  //#endregion
941
943
  //#region src/plugin.ts
944
+ /**
945
+ * Canonical plugin name for `@kubb/plugin-mcp`. Used for driver lookups and
946
+ * cross-plugin dependency references.
947
+ */
942
948
  const pluginMcpName = "plugin-mcp";
949
+ /**
950
+ * Generates a Model Context Protocol (MCP) server from an OpenAPI spec. Every
951
+ * operation becomes a typed MCP tool that AI assistants (Claude Desktop, Claude
952
+ * Code, MCP-compatible clients) can call directly.
953
+ *
954
+ * @example
955
+ * ```ts
956
+ * import { defineConfig } from 'kubb'
957
+ * import { pluginTs } from '@kubb/plugin-ts'
958
+ * import { pluginClient } from '@kubb/plugin-client'
959
+ * import { pluginZod } from '@kubb/plugin-zod'
960
+ * import { pluginMcp } from '@kubb/plugin-mcp'
961
+ *
962
+ * export default defineConfig({
963
+ * input: { path: './petStore.yaml' },
964
+ * output: { path: './src/gen' },
965
+ * plugins: [
966
+ * pluginTs(),
967
+ * pluginClient(),
968
+ * pluginZod(),
969
+ * pluginMcp({
970
+ * output: { path: './mcp' },
971
+ * client: { baseURL: 'https://petstore.swagger.io/v2' },
972
+ * }),
973
+ * ],
974
+ * })
975
+ * ```
976
+ */
943
977
  const pluginMcp = definePlugin((options) => {
944
978
  const { output = {
945
979
  path: "mcp",
@@ -953,7 +987,7 @@ const pluginMcp = definePlugin((options) => {
953
987
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
954
988
  return `${camelCase(ctx.group)}Requests`;
955
989
  }
956
- } : void 0;
990
+ } : null;
957
991
  return {
958
992
  name: pluginMcpName,
959
993
  options,