@kubb/plugin-mcp 5.2.0 → 5.2.1

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
@@ -29,26 +29,8 @@ let node_path = require("node:path");
29
29
  node_path = __toESM(node_path, 1);
30
30
  let kubb_jsx = require("kubb/jsx");
31
31
  let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
32
- let _kubb_plugin_ts = require("@kubb/plugin-ts");
33
32
  let _kubb_plugin_zod = require("@kubb/plugin-zod");
34
- //#region ../../internals/shared/src/params.ts
35
- /**
36
- * Drops parameters that share the same name, keeping the first.
37
- *
38
- * A malformed spec can declare the same parameter name twice within one `in` location. Both would
39
- * resolve to the same output property, so emitting both would yield an object type with a duplicate
40
- * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
41
- * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
42
- */
43
- function dedupeParams(params) {
44
- const seen = /* @__PURE__ */ new Set();
45
- return params.filter((param) => {
46
- if (seen.has(param.name)) return false;
47
- seen.add(param.name);
48
- return true;
49
- });
50
- }
51
- //#endregion
33
+ let _kubb_plugin_ts = require("@kubb/plugin-ts");
52
34
  //#region ../../internals/shared/src/operation.ts
53
35
  /**
54
36
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -118,12 +100,12 @@ function getContentTypeInfo(node) {
118
100
  function getResponseContentTypeInfo(node) {
119
101
  return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
120
102
  }
103
+ const requestConfigBaseType = `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`;
121
104
  function buildRequestConfigType(node) {
122
105
  const request = getContentTypeInfo(node);
123
106
  const response = getResponseContentTypeInfo(node);
124
- const configType = `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`;
125
107
  const members = [request.isMultipleContentTypes ? `request?: ${request.contentTypeUnion}` : null, response.isMultipleContentTypes ? `response?: ${response.contentTypeUnion}` : null].filter(Boolean);
126
- return members.length ? `${configType} & { contentType?: { ${members.join("; ")} } }` : configType;
108
+ return members.length ? `${requestConfigBaseType} & { contentType?: { ${members.join("; ")} } }` : requestConfigBaseType;
127
109
  }
128
110
  /**
129
111
  * Which of the grouped request options an operation carries.
@@ -203,11 +185,19 @@ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
203
185
  function getOperationParameters(node) {
204
186
  const cached = operationParameterGroupsByNode.get(node);
205
187
  if (cached) return cached;
188
+ const group = (location) => {
189
+ const seen = /* @__PURE__ */ new Set();
190
+ return node.parameters.filter((param) => {
191
+ if (param.in !== location || seen.has(param.name)) return false;
192
+ seen.add(param.name);
193
+ return true;
194
+ });
195
+ };
206
196
  const groups = {
207
- path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
208
- query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
209
- header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
210
- cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
197
+ path: group("path"),
198
+ query: group("query"),
199
+ header: group("header"),
200
+ cookie: group("cookie")
211
201
  };
212
202
  operationParameterGroupsByNode.set(node, groups);
213
203
  return groups;
@@ -229,10 +219,6 @@ function getOperationSuccessResponses(node) {
229
219
  function getPrimarySuccessResponse(node) {
230
220
  return getOperationSuccessResponses(node)[0] ?? null;
231
221
  }
232
- function findSuccessStatusCode(responses) {
233
- for (const response of responses) if (isSuccessStatusCode(response.statusCode)) return response.statusCode;
234
- return null;
235
- }
236
222
  //#endregion
237
223
  //#region ../../internals/utils/src/casing.ts
238
224
  /**
@@ -277,7 +263,6 @@ function toSlash(p) {
277
263
  * ```
278
264
  */
279
265
  function getRelativePath(rootDir, filePath) {
280
- if (!rootDir || !filePath) throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ""} ${filePath || ""}`);
281
266
  const relativePath = node_path.posix.relative(toSlash(rootDir), toSlash(filePath));
282
267
  return relativePath.startsWith("../") ? relativePath : `./${relativePath}`;
283
268
  }
@@ -321,7 +306,6 @@ const requestGroupOrder = [
321
306
  "body"
322
307
  ];
323
308
  function McpHandler({ name, clientName, node, resolver, returnType = "full" }) {
324
- if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
325
309
  const { signature, groups } = buildRequestParamsSignature(node, resolver, { isConfigurable: false });
326
310
  const paramsSignature = [signature, "ctx: ServerContext"].filter(Boolean).join(", ");
327
311
  const callConfig = `{ ${[
@@ -359,72 +343,31 @@ function McpHandler({ name, clientName, node, resolver, returnType = "full" }) {
359
343
  });
360
344
  }
361
345
  //#endregion
362
- //#region src/utils.ts
363
- /**
364
- * Render a group param value — compose individual schemas into `z.object({ ... })`,
365
- * or use a schema name string directly.
366
- */
367
- function zodGroupExpr(entry) {
368
- if (typeof entry === "string") return entry;
369
- return `z.object({ ${entry.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
370
- }
346
+ //#region src/components/Server.tsx
371
347
  /**
372
- * Convert a SchemaNode type to an inline Zod expression string.
373
- * Used as fallback when no named zod schema is available for a path parameter.
348
+ * Composes individual param schemas into `z.object({ ... })`.
374
349
  */
375
- function zodExprFromSchemaNode(schema) {
376
- const baseExpr = (() => {
377
- if (schema.type === "enum") {
378
- const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
379
- if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) return `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
380
- if (rawValues.length > 0) {
381
- const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
382
- return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
383
- }
384
- return "z.string()";
385
- }
386
- if (schema.type === "integer") return "z.coerce.number()";
387
- if (schema.type === "number") return "z.number()";
388
- if (schema.type === "boolean") return "z.boolean()";
389
- if (schema.type === "array") return "z.array(z.unknown())";
390
- return "z.string()";
391
- })();
392
- return schema.nullable ? `${baseExpr}.nullable()` : baseExpr;
393
- }
394
- //#endregion
395
- //#region src/components/Server.tsx
396
- const keysPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "call" });
350
+ const zodObject = (params) => `z.object({ ${params.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
397
351
  function Server({ serverName, serverVersion, operations }) {
398
- const registrations = operations.map(({ tool, mcp, zod, node }) => {
399
- const { path: pathParams } = getOperationParameters(node);
352
+ const registrations = operations.map(({ tool, mcp, zod }) => {
400
353
  const entries = [];
401
- if (pathParams.length) {
402
- const pathFields = pathParams.map((p) => {
403
- const zodParam = zod.pathParams.find((zp) => zp.name === p.name);
404
- return `${JSON.stringify(p.name)}: ${zodParam ? zodParam.schemaName : zodExprFromSchemaNode(p.schema)}`;
405
- }).join(", ");
406
- entries.push({
407
- key: "path",
408
- value: `z.object({ ${pathFields} })`
409
- });
410
- }
354
+ if (zod.pathParams.length) entries.push({
355
+ key: "path",
356
+ value: zodObject(zod.pathParams)
357
+ });
411
358
  if (zod.queryParams) entries.push({
412
359
  key: "query",
413
- value: zodGroupExpr(zod.queryParams)
360
+ value: zodObject(zod.queryParams)
414
361
  });
415
362
  if (zod.headerParams) entries.push({
416
363
  key: "headers",
417
- value: zodGroupExpr(zod.headerParams)
364
+ value: zodObject(zod.headerParams)
418
365
  });
419
366
  if (zod.requestName) entries.push({
420
367
  key: "body",
421
368
  value: zod.requestName
422
369
  });
423
- const paramsNode = entries.length ? (0, _kubb_plugin_ts.createFunctionParameters)({ params: [(0, _kubb_plugin_ts.createFunctionParameter)({
424
- name: (0, _kubb_plugin_ts.createObjectBindingPattern)({ elements: entries.map((e) => ({ name: e.key })) }),
425
- optional: false
426
- })] }) : null;
427
- const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
370
+ const destructured = `{ ${entries.map((e) => e.key).join(", ")} }`;
428
371
  const inputSchema = entries.length ? `z.object({ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} })` : null;
429
372
  const outputSchema = zod.responseName;
430
373
  const config = [
@@ -532,7 +475,7 @@ const contractPlugins = [
532
475
  * @example
533
476
  * ```ts
534
477
  * resolveClient({ client: undefined, pluginNames: ['plugin-ts'] })
535
- * // { kind: 'error', message: 'No client plugin is registered…' }
478
+ * // { kind: 'error', code: 'KUBB_MISSING_DEPENDENCY', message: 'No client plugin is registered.', help: 'Add …' }
536
479
  * ```
537
480
  */
538
481
  function resolveClient(options) {
@@ -542,7 +485,9 @@ function resolveClient(options) {
542
485
  const pluginName = selectorToPlugin[client];
543
486
  if (!has(pluginName)) return {
544
487
  kind: "error",
545
- message: `\`client: '${client}'\` is set but \`@kubb/plugin-${client}\` is not registered in \`plugins\`. Add it, or drop \`client\` to use a different client plugin.`
488
+ code: kubb_kit.Diagnostics.code.missingDependency,
489
+ message: `\`client: '${client}'\` is set but \`@kubb/plugin-${client}\` is not registered in \`plugins\`.`,
490
+ help: `Add \`@kubb/plugin-${client}\` to \`plugins\`, or drop \`client\` to use a different client plugin.`
546
491
  };
547
492
  return {
548
493
  kind: "contract",
@@ -551,7 +496,9 @@ function resolveClient(options) {
551
496
  }
552
497
  if (client !== void 0) return {
553
498
  kind: "error",
554
- message: `Option \`client\` is ${typeof client === "string" ? `'${client}'` : String(client)}, but it must be one of 'fetch', 'axios', 'client'. Drop \`client\` to use the registered client plugin.`
499
+ code: kubb_kit.Diagnostics.code.invalidPluginOptions,
500
+ message: `Option \`client\` is ${typeof client === "string" ? `'${client}'` : String(client)}, but it must be one of 'fetch', 'axios', 'client'.`,
501
+ help: "Set `client` to one of those values, or drop it to use the registered client plugin."
555
502
  };
556
503
  const registered = contractPlugins.filter(has);
557
504
  if (registered.length === 1) return {
@@ -560,25 +507,40 @@ function resolveClient(options) {
560
507
  };
561
508
  if (registered.length > 1) return {
562
509
  kind: "error",
563
- message: `Multiple client plugins are registered (${registered.map((name) => `\`@kubb/${name}\``).join(", ")}). Set \`client: 'fetch' | 'axios' | 'client'\` to choose which client the hooks call, or register a single client plugin.`
510
+ code: kubb_kit.Diagnostics.code.invalidPluginOptions,
511
+ message: `Multiple client plugins are registered (${registered.map((name) => `\`@kubb/${name}\``).join(", ")}) and \`client\` is not set.`,
512
+ help: "Set `client: 'fetch' | 'axios' | 'client'` to choose which client the hooks call, or register a single client plugin."
564
513
  };
565
514
  return {
566
515
  kind: "error",
567
- message: "No client plugin is registered. Add `@kubb/plugin-axios`, `@kubb/plugin-fetch`, or `@kubb/plugin-client` to `plugins` so the generated code has an HTTP client to call."
516
+ code: kubb_kit.Diagnostics.code.missingDependency,
517
+ message: "No client plugin is registered.",
518
+ help: "Add `@kubb/plugin-fetch`, `@kubb/plugin-axios`, or `@kubb/plugin-client` to `plugins` so the generated code has an HTTP client to call."
568
519
  };
569
520
  }
570
521
  /**
571
- * Resolves the contract client during a consumer plugin's setup hook. Extracts the plugin names
572
- * from the raw `plugins` config, applies {@link resolveClient}, and throws the diagnostic on a
573
- * misconfiguration so every consumer fails fast with the same message.
522
+ * Resolves the contract client during a consumer plugin's setup hook.
523
+ * It extracts the plugin names from the raw `plugins` config and applies {@link resolveClient}.
524
+ * On a misconfiguration it throws a config diagnostic tagged with `plugin`, so every consumer
525
+ * fails fast with the same message.
574
526
  */
575
527
  function resolveContractClient(options) {
576
- const { client, plugins = [] } = options;
528
+ const { plugin, client, plugins = [] } = options;
577
529
  const resolved = resolveClient({
578
530
  client,
579
531
  pluginNames: plugins.map((plugin) => plugin.name).filter((name) => Boolean(name))
580
532
  });
581
- if (resolved.kind === "error") throw new Error(resolved.message);
533
+ if (resolved.kind === "error") {
534
+ const { code, message, help } = resolved;
535
+ throw new kubb_kit.Diagnostics.Error({
536
+ code,
537
+ severity: "error",
538
+ message,
539
+ help,
540
+ location: { kind: "config" },
541
+ plugin
542
+ });
543
+ }
582
544
  return resolved;
583
545
  }
584
546
  //#endregion
@@ -671,10 +633,7 @@ const mcpGenerator = (0, kubb_kit.defineGenerator)({
671
633
  const meta = {
672
634
  name: resolver.handler.name(node),
673
635
  file: resolver.file({
674
- name: node.operationId,
675
- extname: ".ts",
676
- tag: node.tags[0] ?? "default",
677
- path: node.path,
636
+ ...operationFileEntry(node, node.operationId),
678
637
  root,
679
638
  output,
680
639
  group: group ?? void 0
@@ -753,22 +712,14 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
753
712
  const { output, group } = ctx.options;
754
713
  const pluginZod = ctx.requirePlugin(_kubb_plugin_zod.pluginZodName);
755
714
  const zodResolver = driver.getResolver(_kubb_plugin_zod.pluginZodName);
756
- const name = "server";
757
- const serverFile = {
758
- baseName: "server.ts",
759
- path: node_path.default.resolve(root, output.path, "server.ts"),
715
+ const fileAt = (baseName) => ({
716
+ baseName,
717
+ path: node_path.default.resolve(root, output.path, baseName),
760
718
  meta: { pluginName: plugin.name }
761
- };
762
- const startFile = {
763
- baseName: "start.ts",
764
- path: node_path.default.resolve(root, output.path, "start.ts"),
765
- meta: { pluginName: plugin.name }
766
- };
767
- const jsonFile = {
768
- baseName: ".mcp.json",
769
- path: node_path.default.resolve(root, output.path, ".mcp.json"),
770
- meta: { pluginName: plugin.name }
771
- };
719
+ });
720
+ const serverFile = fileAt("server.ts");
721
+ const startFile = fileAt("start.ts");
722
+ const jsonFile = fileAt(".mcp.json");
772
723
  const mcpConfig = { mcpServers: { [ctx.meta.title || "server"]: {
773
724
  type: "stdio",
774
725
  command: "npx",
@@ -776,26 +727,21 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
776
727
  } } };
777
728
  const operationsMapped = nodes.filter(kubb_kit.ast.isHttpOperationNode).map((node) => {
778
729
  const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
730
+ const fileEntry = operationFileEntry(node, node.operationId);
779
731
  const mcpFile = resolver.file({
780
- name: node.operationId,
781
- extname: ".ts",
782
- tag: node.tags[0] ?? "default",
783
- path: node.path,
732
+ ...fileEntry,
784
733
  root,
785
734
  output,
786
735
  group: group ?? void 0
787
736
  });
788
737
  const zodFile = zodResolver.file({
789
- name: node.operationId,
790
- extname: ".ts",
791
- tag: node.tags[0] ?? "default",
792
- path: node.path,
738
+ ...fileEntry,
793
739
  root,
794
740
  output: pluginZod.options?.output ?? output,
795
741
  group: pluginZod.options?.group ?? void 0
796
742
  });
797
743
  const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null;
798
- const successStatus = findSuccessStatusCode(node.responses);
744
+ const successStatus = getPrimarySuccessResponse(node)?.statusCode;
799
745
  const responseName = successStatus ? zodResolver.response.status(node, successStatus) : null;
800
746
  const resolveParams = (params) => params.map((p) => ({
801
747
  name: p.name,
@@ -818,8 +764,7 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
818
764
  requestName,
819
765
  responseName,
820
766
  file: zodFile
821
- },
822
- node
767
+ }
823
768
  };
824
769
  });
825
770
  const imports = operationsMapped.flatMap(({ mcp, zod }) => {
@@ -917,7 +862,7 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
917
862
  path: jsonFile.path,
918
863
  meta: jsonFile.meta,
919
864
  children: /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
920
- name,
865
+ name: "server",
921
866
  children: `${JSON.stringify(mcpConfig, null, 2)}\n`
922
867
  })
923
868
  })
@@ -932,7 +877,7 @@ var compatibility = {
932
877
  };
933
878
  //#endregion
934
879
  //#region package.json
935
- var version = "5.2.0";
880
+ var version = "5.2.1";
936
881
  //#endregion
937
882
  //#region src/resolvers/resolverMcp.ts
938
883
  /**
@@ -1018,6 +963,7 @@ const pluginMcp = (0, kubb_kit.defineExtension)({
1018
963
  override,
1019
964
  group: createGroupConfig(group),
1020
965
  client: resolveContractClient({
966
+ plugin: pluginMcpName,
1021
967
  client,
1022
968
  plugins: (0, kubb_kit.useConfig)().plugins
1023
969
  }),