@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.cjs CHANGED
@@ -247,12 +247,12 @@ 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() {
@@ -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.
@@ -314,7 +315,7 @@ var URLPath = class {
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
  *
@@ -330,9 +331,9 @@ var URLPath = class {
330
331
  //#endregion
331
332
  //#region ../../internals/shared/src/operation.ts
332
333
  function getOperationLink(node, link) {
333
- if (!link) return;
334
- if (typeof link === "function") return link(node);
335
- if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : void 0;
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;
336
337
  return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
337
338
  }
338
339
  function buildOperationComments(node, options = {}) {
@@ -363,15 +364,15 @@ function getOperationParameters(node, options = {}) {
363
364
  }
364
365
  function getStatusCodeNumber(statusCode) {
365
366
  const code = Number(statusCode);
366
- return Number.isNaN(code) ? void 0 : code;
367
+ return Number.isNaN(code) ? null : code;
367
368
  }
368
369
  function isSuccessStatusCode(statusCode) {
369
370
  const code = getStatusCodeNumber(statusCode);
370
- return code !== void 0 && code >= 200 && code < 300;
371
+ return code !== null && code >= 200 && code < 300;
371
372
  }
372
373
  function isErrorStatusCode(statusCode) {
373
374
  const code = getStatusCodeNumber(statusCode);
374
- return code !== void 0 && code >= 400;
375
+ return code !== null && code >= 400;
375
376
  }
376
377
  function resolveErrorNames(node, resolver) {
377
378
  return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
@@ -398,7 +399,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
398
399
  ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
399
400
  ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
400
401
  ];
401
- const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0, resolver.resolveResponseName(node)];
402
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
402
403
  const result = (options.order === "body-response-first" ? [
403
404
  ...bodyAndResponseNames,
404
405
  ...paramNames,
@@ -413,6 +414,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
413
414
  }
414
415
  function findSuccessStatusCode(responses) {
415
416
  for (const response of responses) if (isSuccessStatusCode(response.statusCode)) return response.statusCode;
417
+ return null;
416
418
  }
417
419
  //#endregion
418
420
  //#region ../../internals/shared/src/params.ts
@@ -424,10 +426,10 @@ function buildParamsMapping(originalParams, mappedParams) {
424
426
  mapping[param.name] = mappedName;
425
427
  if (param.name !== mappedName) hasChanged = true;
426
428
  });
427
- return hasChanged ? mapping : void 0;
429
+ return hasChanged ? mapping : null;
428
430
  }
429
431
  function buildTransformedParamsMapping(params, transformName) {
430
- if (!params.length) return;
432
+ if (!params.length) return null;
431
433
  return buildParamsMapping(params, params.map((param) => ({
432
434
  ...param,
433
435
  name: transformName(param.name)
@@ -448,7 +450,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
448
450
  const isFormData = contentType === "multipart/form-data";
449
451
  const { query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
450
452
  const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
451
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0;
453
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
452
454
  const responseName = resolver.resolveResponseName(node);
453
455
  const errorResponses = node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => resolver.resolveResponseStatusName(node, r.statusCode));
454
456
  const generics = [
@@ -464,11 +466,11 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
464
466
  });
465
467
  const baseParamsSignature = declarationPrinter.print(paramsNode) ?? "";
466
468
  const paramsSignature = baseParamsSignature ? `${baseParamsSignature}, request: RequestHandlerExtra<ServerRequest, ServerNotification>` : "request: RequestHandlerExtra<ServerRequest, ServerNotification>";
467
- const pathParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalPathParams, camelCase) : void 0;
468
- const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) : void 0;
469
- const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) : void 0;
470
- const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0;
471
- 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);
472
474
  const fetchConfig = [];
473
475
  fetchConfig.push(`method: ${JSON.stringify(node.method.toUpperCase())}`);
474
476
  fetchConfig.push(`url: ${urlPath.template}`);
@@ -546,33 +548,23 @@ function zodGroupExpr(entry) {
546
548
  * Used as fallback when no named zod schema is available for a path parameter.
547
549
  */
548
550
  function zodExprFromSchemaNode(schema) {
549
- let expr;
550
- switch (schema.type) {
551
- case "enum": {
551
+ const baseExpr = (() => {
552
+ if (schema.type === "enum") {
552
553
  const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
553
- if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) expr = `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
554
- else if (rawValues.length > 0) {
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) {
555
556
  const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
556
- expr = literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
557
- } else expr = "z.string()";
558
- break;
557
+ return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
558
+ }
559
+ return "z.string()";
559
560
  }
560
- case "integer":
561
- expr = "z.coerce.number()";
562
- break;
563
- case "number":
564
- expr = "z.number()";
565
- break;
566
- case "boolean":
567
- expr = "z.boolean()";
568
- break;
569
- case "array":
570
- expr = "z.array(z.unknown())";
571
- break;
572
- default: expr = "z.string()";
573
- }
574
- if (schema.nullable) expr = `${expr}.nullable()`;
575
- return expr;
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;
576
568
  }
577
569
  //#endregion
578
570
  //#region src/components/Server.tsx
@@ -621,9 +613,9 @@ function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
621
613
  const paramsNode = entries.length ? _kubb_core.ast.createFunctionParameters({ params: [_kubb_core.ast.createParameterGroup({ properties: entries.map((e) => _kubb_core.ast.createFunctionParameter({
622
614
  name: e.key,
623
615
  optional: false
624
- })) })] }) : void 0;
616
+ })) })] }) : null;
625
617
  const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
626
- 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;
627
619
  const outputSchema = zod.responseName;
628
620
  const config = [
629
621
  tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
@@ -664,6 +656,12 @@ server.registerTool(${JSON.stringify(tool.name)}, {
664
656
  }
665
657
  //#endregion
666
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
+ */
667
665
  const mcpGenerator = (0, _kubb_core.defineGenerator)({
668
666
  name: "mcp",
669
667
  renderer: _kubb_renderer_jsx.jsxRendererSync,
@@ -687,7 +685,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
687
685
  }, {
688
686
  root,
689
687
  output,
690
- group
688
+ group: group ?? void 0
691
689
  }),
692
690
  fileTs: tsResolver.resolveFile({
693
691
  name: node.operationId,
@@ -697,7 +695,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
697
695
  }, {
698
696
  root,
699
697
  output: pluginTs.options?.output ?? output,
700
- group: pluginTs.options?.group
698
+ group: pluginTs.options?.group ?? void 0
701
699
  })
702
700
  };
703
701
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
@@ -797,7 +795,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
797
795
  name: "operations",
798
796
  renderer: _kubb_renderer_jsx.jsxRendererSync,
799
797
  operations(nodes, ctx) {
800
- const { config, resolver, plugin, driver, root, inputNode } = ctx;
798
+ const { config, resolver, plugin, driver, root } = ctx;
801
799
  const { output, paramsCasing, group } = ctx.options;
802
800
  const pluginZod = driver.getPlugin(_kubb_plugin_zod.pluginZodName);
803
801
  if (!pluginZod) return;
@@ -823,7 +821,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
823
821
  }, {
824
822
  root,
825
823
  output,
826
- group
824
+ group: group ?? void 0
827
825
  });
828
826
  const zodFile = zodResolver.resolveFile({
829
827
  name: node.operationId,
@@ -833,11 +831,11 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
833
831
  }, {
834
832
  root,
835
833
  output: pluginZod.options?.output ?? output,
836
- group: pluginZod.options?.group
834
+ group: pluginZod.options?.group ?? void 0
837
835
  });
838
- const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : void 0;
836
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : null;
839
837
  const successStatus = findSuccessStatusCode(node.responses);
840
- const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : void 0;
838
+ const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : null;
841
839
  const resolveParams = (params) => params.map((p) => ({
842
840
  name: p.name,
843
841
  schemaName: zodResolver.resolveParamName(node, p)
@@ -854,8 +852,8 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
854
852
  },
855
853
  zod: {
856
854
  pathParams: resolveParams(pathParams),
857
- queryParams: queryParams.length ? resolveParams(queryParams) : void 0,
858
- headerParams: headerParams.length ? resolveParams(headerParams) : void 0,
855
+ queryParams: queryParams.length ? resolveParams(queryParams) : null,
856
+ headerParams: headerParams.length ? resolveParams(headerParams) : null,
859
857
  requestName,
860
858
  responseName,
861
859
  file: zodFile
@@ -886,11 +884,11 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
886
884
  baseName: serverFile.baseName,
887
885
  path: serverFile.path,
888
886
  meta: serverFile.meta,
889
- banner: resolver.resolveBanner(inputNode, {
887
+ banner: resolver.resolveBanner(ctx.meta, {
890
888
  output,
891
889
  config
892
890
  }),
893
- footer: resolver.resolveFooter(inputNode, {
891
+ footer: resolver.resolveFooter(ctx.meta, {
894
892
  output,
895
893
  config
896
894
  }),
@@ -910,8 +908,8 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
910
908
  imports,
911
909
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Server, {
912
910
  name,
913
- serverName: inputNode.meta?.title ?? "server",
914
- serverVersion: inputNode.meta?.version ?? "0.0.0",
911
+ serverName: ctx.meta.title ?? "server",
912
+ serverVersion: ctx.meta.version ?? "0.0.0",
915
913
  paramsCasing,
916
914
  operations: operationsMapped
917
915
  })
@@ -925,7 +923,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
925
923
  children: `
926
924
  {
927
925
  "mcpServers": {
928
- "${inputNode.meta?.title || "server"}": {
926
+ "${ctx.meta.title || "server"}": {
929
927
  "type": "stdio",
930
928
  "command": "npx",
931
929
  "args": ["tsx", "${node_path.default.relative(node_path.default.dirname(jsonFile.path), serverFile.path)}"]
@@ -940,12 +938,16 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
940
938
  //#endregion
941
939
  //#region src/resolvers/resolverMcp.ts
942
940
  /**
943
- * Naming convention resolver for MCP plugin.
941
+ * Default resolver used by `@kubb/plugin-mcp`. Decides the names and file
942
+ * paths for every generated MCP tool handler. Function names get a `Handler`
943
+ * suffix so an operation `addPet` becomes `addPetHandler`.
944
944
  *
945
- * Provides default naming helpers using camelCase with a `handler` suffix for functions.
945
+ * @example Resolve a handler name
946
+ * ```ts
947
+ * import { resolverMcp } from '@kubb/plugin-mcp'
946
948
  *
947
- * @example
948
- * `resolverMcp.default('addPet', 'function') // → 'addPetHandler'`
949
+ * resolverMcp.default('addPet', 'function') // 'addPetHandler'
950
+ * ```
949
951
  */
950
952
  const resolverMcp = (0, _kubb_core.defineResolver)(() => ({
951
953
  name: "default",
@@ -966,7 +968,39 @@ const resolverMcp = (0, _kubb_core.defineResolver)(() => ({
966
968
  }));
967
969
  //#endregion
968
970
  //#region src/plugin.ts
971
+ /**
972
+ * Canonical plugin name for `@kubb/plugin-mcp`. Used for driver lookups and
973
+ * cross-plugin dependency references.
974
+ */
969
975
  const pluginMcpName = "plugin-mcp";
976
+ /**
977
+ * Generates a Model Context Protocol (MCP) server from an OpenAPI spec. Every
978
+ * operation becomes a typed MCP tool that AI assistants (Claude Desktop, Claude
979
+ * Code, MCP-compatible clients) can call directly.
980
+ *
981
+ * @example
982
+ * ```ts
983
+ * import { defineConfig } from 'kubb'
984
+ * import { pluginTs } from '@kubb/plugin-ts'
985
+ * import { pluginClient } from '@kubb/plugin-client'
986
+ * import { pluginZod } from '@kubb/plugin-zod'
987
+ * import { pluginMcp } from '@kubb/plugin-mcp'
988
+ *
989
+ * export default defineConfig({
990
+ * input: { path: './petStore.yaml' },
991
+ * output: { path: './src/gen' },
992
+ * plugins: [
993
+ * pluginTs(),
994
+ * pluginClient(),
995
+ * pluginZod(),
996
+ * pluginMcp({
997
+ * output: { path: './mcp' },
998
+ * client: { baseURL: 'https://petstore.swagger.io/v2' },
999
+ * }),
1000
+ * ],
1001
+ * })
1002
+ * ```
1003
+ */
970
1004
  const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
971
1005
  const { output = {
972
1006
  path: "mcp",
@@ -980,7 +1014,7 @@ const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
980
1014
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
981
1015
  return `${camelCase(ctx.group)}Requests`;
982
1016
  }
983
- } : void 0;
1017
+ } : null;
984
1018
  return {
985
1019
  name: pluginMcpName,
986
1020
  options,