@kubb/plugin-mcp 5.0.0-beta.85 → 5.0.0-beta.87

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
@@ -24,12 +24,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
- let node_path = require("node:path");
28
- node_path = __toESM(node_path, 1);
29
27
  let kubb_kit = require("kubb/kit");
30
28
  let kubb_jsx = require("kubb/jsx");
31
29
  let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
32
30
  let _kubb_plugin_ts = require("@kubb/plugin-ts");
31
+ let node_path = require("node:path");
32
+ node_path = __toESM(node_path, 1);
33
33
  let _kubb_plugin_zod = require("@kubb/plugin-zod");
34
34
  //#region ../../internals/utils/src/casing.ts
35
35
  /**
@@ -58,31 +58,6 @@ function camelCase(text, { prefix = "", suffix = "" } = {}) {
58
58
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
59
59
  }
60
60
  //#endregion
61
- //#region ../../internals/utils/src/fs.ts
62
- /**
63
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
64
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
65
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
66
- *
67
- * Empty segments are dropped before joining. They arise when the name starts with a dot
68
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
69
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
70
- * absolute path, letting generated files escape the configured output directory.
71
- *
72
- * @example Nested path from a dotted name
73
- * `toFilePath('pet.petId') // 'pet/petId'`
74
- *
75
- * @example PascalCase the final segment
76
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
77
- *
78
- * @example Suffix applied to the final segment only
79
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
80
- */
81
- function toFilePath(name, caseLast = camelCase) {
82
- const parts = name.split(/\.(?=[a-zA-Z])/);
83
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
84
- }
85
- //#endregion
86
61
  //#region ../../internals/utils/src/reserved.ts
87
62
  /**
88
63
  * JavaScript and Java reserved words.
@@ -183,6 +158,22 @@ const reservedWords = /* @__PURE__ */ new Set([
183
158
  */
184
159
  function isValidVarName(name) {
185
160
  if (!name || reservedWords.has(name)) return false;
161
+ return isIdentifier(name);
162
+ }
163
+ /**
164
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
165
+ *
166
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
167
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
168
+ * deciding whether an object key needs quoting.
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * isIdentifier('name') // true
173
+ * isIdentifier('x-total')// false
174
+ * ```
175
+ */
176
+ function isIdentifier(name) {
186
177
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
187
178
  }
188
179
  //#endregion
@@ -338,13 +329,13 @@ function dedupeByCasedName(params) {
338
329
  //#region ../../internals/shared/src/operation.ts
339
330
  /**
340
331
  * Builds the `ResolverFileParams` every operation generator passes to
341
- * `resolver.resolveFile`: a file named `name`, tagged by the operation's first
332
+ * `resolver.file`: a file named `name`, tagged by the operation's first
342
333
  * tag (or `'default'`), at the operation's path. Centralizes the entry object
343
334
  * that was repeated at dozens of call sites across the client and query plugins.
344
335
  *
345
336
  * @example
346
337
  * ```ts
347
- * resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group })
338
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
348
339
  * ```
349
340
  */
350
341
  function operationFileEntry(node, name, extname = ".ts") {
@@ -361,8 +352,11 @@ function getOperationLink(node, link) {
361
352
  if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
362
353
  return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
363
354
  }
364
- function getContentTypeInfo(node) {
365
- const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
355
+ /**
356
+ * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
357
+ * are present and the union, default, and form-data flags the client uses to pick one.
358
+ */
359
+ function buildContentTypeInfo(contentTypes) {
366
360
  const isMultipleContentTypes = contentTypes.length > 1;
367
361
  return {
368
362
  contentTypes,
@@ -372,20 +366,15 @@ function getContentTypeInfo(node) {
372
366
  hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
373
367
  };
374
368
  }
369
+ function getContentTypeInfo(node) {
370
+ return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
371
+ }
375
372
  /**
376
373
  * The request-body counterpart for the primary success response: the content types it documents and
377
374
  * whether several are present, so the client can let a caller pick which one to accept.
378
375
  */
379
376
  function getResponseContentTypeInfo(node) {
380
- const contentTypes = getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? [];
381
- const isMultipleContentTypes = contentTypes.length > 1;
382
- return {
383
- contentTypes,
384
- isMultipleContentTypes,
385
- contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
386
- defaultContentType: contentTypes[0] ?? "application/json",
387
- hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
388
- };
377
+ return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
389
378
  }
390
379
  function buildRequestConfigType(node) {
391
380
  const request = getContentTypeInfo(node);
@@ -427,7 +416,7 @@ function getRequestGroupOptionality(node) {
427
416
  }
428
417
  /**
429
418
  * Builds the grouped `{ path, query, body, headers }` parameter for a generated client
430
- * function, typed from the operation's `RequestConfig` (minus `url`). Only the groups the
419
+ * function, typed from the operation's `Options` (minus `url`). Only the groups the
431
420
  * operation actually has are destructured. The trailing `config` parameter carries the
432
421
  * runtime `RequestConfig` overrides plus `client`.
433
422
  */
@@ -441,7 +430,7 @@ function buildRequestParamsSignature(node, resolver, options = {}) {
441
430
  "headers"
442
431
  ].filter((key) => groups[key]);
443
432
  return {
444
- signature: [names.length > 0 ? `{ ${names.join(", ")} }: ${resolver.resolveRequestConfigName(node)}${isOptional ? " = {}" : ""}` : null, isConfigurable ? `config: ${buildRequestConfigType(node)} = {}` : null].filter(Boolean).join(", "),
433
+ signature: [names.length > 0 ? `{ ${names.join(", ")} }: ${resolver.response.options(node)}${isOptional ? " = {}" : ""}` : null, isConfigurable ? `config: ${buildRequestConfigType(node)} = {}` : null].filter(Boolean).join(", "),
445
434
  groups
446
435
  };
447
436
  }
@@ -770,15 +759,15 @@ function resolveClientOperation(options) {
770
759
  const { clientPlugin, driver, node, root, output } = options;
771
760
  if (!clientPlugin) return null;
772
761
  const resolver = driver.getResolver(clientPlugin.pluginName);
773
- if (!resolver) return null;
774
762
  const plugin = driver.getPlugin(clientPlugin.pluginName);
775
- const file = resolver.resolveFile(operationFileEntry(node, node.operationId), {
763
+ const file = resolver.file({
764
+ ...operationFileEntry(node, node.operationId),
776
765
  root,
777
766
  output: plugin?.options?.output ?? output,
778
767
  group: plugin?.options?.group ?? void 0
779
768
  });
780
769
  return {
781
- name: resolver.resolveName(node.operationId),
770
+ name: resolver.name(node.operationId),
782
771
  path: file.path,
783
772
  clientPath: node_path.default.resolve(root, ".kubb/client.ts")
784
773
  };
@@ -809,25 +798,23 @@ const mcpGenerator = (0, kubb_kit.defineGenerator)({
809
798
  output
810
799
  });
811
800
  if (!contractOp) return null;
812
- const requestConfigName = tsResolver.resolveRequestConfigName(node);
801
+ const optionsName = tsResolver.response.options(node);
813
802
  const meta = {
814
- name: resolver.resolveHandlerName(node),
815
- file: resolver.resolveFile({
803
+ name: resolver.handler.name(node),
804
+ file: resolver.file({
816
805
  name: node.operationId,
817
806
  extname: ".ts",
818
807
  tag: node.tags[0] ?? "default",
819
- path: node.path
820
- }, {
808
+ path: node.path,
821
809
  root,
822
810
  output,
823
811
  group: group ?? void 0
824
812
  }),
825
- fileTs: tsResolver.resolveFile({
813
+ fileTs: tsResolver.file({
826
814
  name: node.operationId,
827
815
  extname: ".ts",
828
816
  tag: node.tags[0] ?? "default",
829
- path: node.path
830
- }, {
817
+ path: node.path,
831
818
  root,
832
819
  output: pluginTs.options?.output ?? output,
833
820
  group: pluginTs.options?.group ?? void 0
@@ -838,8 +825,8 @@ const mcpGenerator = (0, kubb_kit.defineGenerator)({
838
825
  path: meta.file.path,
839
826
  meta: meta.file.meta,
840
827
  children: [
841
- meta.fileTs && requestConfigName && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
842
- name: [requestConfigName],
828
+ meta.fileTs && optionsName && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
829
+ name: [optionsName],
843
830
  root: meta.file.path,
844
831
  path: meta.fileTs.path,
845
832
  isTypeOnly: true
@@ -904,32 +891,30 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
904
891
  };
905
892
  const operationsMapped = nodes.filter(kubb_kit.ast.isHttpOperationNode).map((node) => {
906
893
  const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
907
- const mcpFile = resolver.resolveFile({
894
+ const mcpFile = resolver.file({
908
895
  name: node.operationId,
909
896
  extname: ".ts",
910
897
  tag: node.tags[0] ?? "default",
911
- path: node.path
912
- }, {
898
+ path: node.path,
913
899
  root,
914
900
  output,
915
901
  group: group ?? void 0
916
902
  });
917
- const zodFile = zodResolver.resolveFile({
903
+ const zodFile = zodResolver.file({
918
904
  name: node.operationId,
919
905
  extname: ".ts",
920
906
  tag: node.tags[0] ?? "default",
921
- path: node.path
922
- }, {
907
+ path: node.path,
923
908
  root,
924
909
  output: pluginZod.options?.output ?? output,
925
910
  group: pluginZod.options?.group ?? void 0
926
911
  });
927
- const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : null;
912
+ const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null;
928
913
  const successStatus = findSuccessStatusCode(node.responses);
929
- const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : null;
914
+ const responseName = successStatus ? zodResolver.response.status(node, successStatus) : null;
930
915
  const resolveParams = (params) => params.map((p) => ({
931
916
  name: p.name,
932
- schemaName: zodResolver.resolveParamName(node, p)
917
+ schemaName: zodResolver.param.name(node, p)
933
918
  }));
934
919
  return {
935
920
  tool: {
@@ -938,7 +923,7 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
938
923
  description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`
939
924
  },
940
925
  mcp: {
941
- name: resolver.resolveHandlerName(node),
926
+ name: resolver.handler.name(node),
942
927
  file: mcpFile
943
928
  },
944
929
  zod: {
@@ -975,7 +960,7 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
975
960
  baseName: serverFile.baseName,
976
961
  path: serverFile.path,
977
962
  meta: serverFile.meta,
978
- banner: resolver.resolveBanner(ctx.meta, {
963
+ banner: resolver.default.banner(ctx.meta, {
979
964
  output,
980
965
  config,
981
966
  file: {
@@ -983,7 +968,7 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
983
968
  baseName: serverFile.baseName
984
969
  }
985
970
  }),
986
- footer: resolver.resolveFooter(ctx.meta, {
971
+ footer: resolver.default.footer(ctx.meta, {
987
972
  output,
988
973
  config,
989
974
  file: {
@@ -1044,26 +1029,18 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
1044
1029
  * ```ts
1045
1030
  * import { resolverMcp } from '@kubb/plugin-mcp'
1046
1031
  *
1047
- * resolverMcp.default('addPet', 'function') // 'addPetHandler'
1032
+ * resolverMcp.name('addPet') // 'addPetHandler'
1048
1033
  * ```
1049
1034
  */
1050
- const resolverMcp = (0, kubb_kit.defineResolver)(() => ({
1051
- name: "default",
1035
+ const resolverMcp = (0, kubb_kit.createResolver)({
1052
1036
  pluginName: "plugin-mcp",
1053
- default(name, type) {
1054
- if (type === "file") return toFilePath(name);
1037
+ name(name) {
1055
1038
  return camelCase(name, { suffix: "handler" });
1056
1039
  },
1057
- resolveName(name) {
1058
- return this.default(name, "function");
1059
- },
1060
- resolvePathName(name, type) {
1061
- return this.default(name, type);
1062
- },
1063
- resolveHandlerName(node) {
1064
- return this.resolveName(node.operationId);
1065
- }
1066
- }));
1040
+ handler: { name(node) {
1041
+ return this.name(node.operationId);
1042
+ } }
1043
+ });
1067
1044
  //#endregion
1068
1045
  //#region src/plugin.ts
1069
1046
  /**
@@ -1109,10 +1086,7 @@ const pluginMcp = (0, kubb_kit.definePlugin)((options) => {
1109
1086
  options,
1110
1087
  dependencies: [_kubb_plugin_ts.pluginTsName, _kubb_plugin_zod.pluginZodName],
1111
1088
  hooks: { "kubb:plugin:setup"(ctx) {
1112
- const resolver = userResolver ? {
1113
- ...resolverMcp,
1114
- ...userResolver
1115
- } : resolverMcp;
1089
+ const resolver = userResolver ? kubb_kit.Resolver.merge(resolverMcp, userResolver) : resolverMcp;
1116
1090
  const resolvedClient = resolveClient({
1117
1091
  client,
1118
1092
  pluginNames: (ctx.config.plugins ?? []).map((p) => p.name).filter((name) => Boolean(name))