@kubb/plugin-mcp 5.2.0-canary.20261014T071450 → 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 = [
@@ -496,23 +439,30 @@ return server`
496
439
  * operation, shared so the selection rules and diagnostics stay in one place.
497
440
  *
498
441
  * Every client runtime lives in a dedicated client plugin, so a consumer always calls a registered
499
- * contract client plugin (plugin-fetch or plugin-axios) and never emits its own inline client:
442
+ * contract client plugin (plugin-fetch, plugin-axios, or plugin-client) and never emits its own
443
+ * inline client:
500
444
  *
501
445
  * - `contract` — a registered contract client plugin owns the `<op>` functions and the consumer
502
446
  * imports and calls them.
503
447
  * - `error` — no client plugin is registered, several are registered without a selector, the
504
448
  * requested one is missing, or `client` is not a known selector.
505
449
  *
506
- * The `client` string selects explicitly (`'fetch'` / `'axios'`); when it is unset a lone registered
507
- * contract client plugin is picked up automatically.
450
+ * The `client` string selects explicitly (`'fetch'` / `'axios'` / `'client'`); when it is unset a
451
+ * lone registered contract client plugin is picked up automatically.
508
452
  */
509
453
  const pluginFetchName = "plugin-fetch";
510
454
  const pluginAxiosName = "plugin-axios";
455
+ const pluginClientName = "plugin-client";
511
456
  const selectorToPlugin = {
512
457
  fetch: pluginFetchName,
513
- axios: pluginAxiosName
458
+ axios: pluginAxiosName,
459
+ client: pluginClientName
514
460
  };
515
- const contractPlugins = [pluginFetchName, pluginAxiosName];
461
+ const contractPlugins = [
462
+ pluginFetchName,
463
+ pluginAxiosName,
464
+ pluginClientName
465
+ ];
516
466
  /**
517
467
  * Applies the `client` resolution rules. See the module comment for the strategy shape.
518
468
  *
@@ -525,17 +475,19 @@ const contractPlugins = [pluginFetchName, pluginAxiosName];
525
475
  * @example
526
476
  * ```ts
527
477
  * resolveClient({ client: undefined, pluginNames: ['plugin-ts'] })
528
- * // { kind: 'error', message: 'No client plugin is registered…' }
478
+ * // { kind: 'error', code: 'KUBB_MISSING_DEPENDENCY', message: 'No client plugin is registered.', help: 'Add …' }
529
479
  * ```
530
480
  */
531
481
  function resolveClient(options) {
532
482
  const { client, pluginNames } = options;
533
483
  const has = (name) => pluginNames.includes(name);
534
- if (client === "fetch" || client === "axios") {
484
+ if (client === "fetch" || client === "axios" || client === "client") {
535
485
  const pluginName = selectorToPlugin[client];
536
486
  if (!has(pluginName)) return {
537
487
  kind: "error",
538
- 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.`
539
491
  };
540
492
  return {
541
493
  kind: "contract",
@@ -544,7 +496,9 @@ function resolveClient(options) {
544
496
  }
545
497
  if (client !== void 0) return {
546
498
  kind: "error",
547
- message: `Option \`client\` is ${typeof client === "string" ? `'${client}'` : String(client)}, but it must be one of 'fetch', 'axios'. 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."
548
502
  };
549
503
  const registered = contractPlugins.filter(has);
550
504
  if (registered.length === 1) return {
@@ -553,36 +507,59 @@ function resolveClient(options) {
553
507
  };
554
508
  if (registered.length > 1) return {
555
509
  kind: "error",
556
- message: `Multiple client plugins are registered (${registered.map((name) => `\`@kubb/${name}\``).join(", ")}). Set \`client: 'fetch' | 'axios'\` 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."
557
513
  };
558
514
  return {
559
515
  kind: "error",
560
- message: "No client plugin is registered. Add `@kubb/plugin-axios` or `@kubb/plugin-fetch` 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."
561
519
  };
562
520
  }
563
521
  /**
564
- * Resolves the contract client during a consumer plugin's setup hook. Extracts the plugin names
565
- * from the raw `plugins` config, applies {@link resolveClient}, and throws the diagnostic on a
566
- * 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.
567
526
  */
568
527
  function resolveContractClient(options) {
569
- const { client, plugins = [] } = options;
528
+ const { plugin, client, plugins = [] } = options;
570
529
  const resolved = resolveClient({
571
530
  client,
572
531
  pluginNames: plugins.map((plugin) => plugin.name).filter((name) => Boolean(name))
573
532
  });
574
- 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
+ }
575
544
  return resolved;
576
545
  }
577
546
  //#endregion
578
547
  //#region ../../internals/client/src/resolveClientOperation.ts
548
+ function isRelativeImport(importPath) {
549
+ return importPath.startsWith(".");
550
+ }
551
+ function resolveImportPath(options) {
552
+ const { importPath, operationFile } = options;
553
+ return isRelativeImport(importPath) ? node_path.default.resolve(node_path.default.dirname(operationFile), importPath) : importPath;
554
+ }
579
555
  /**
580
556
  * Resolves the contract client `<op>` a consumer (query hook, MCP handler) imports, by looking up
581
557
  * the registered contract client plugin's resolver and output. Works for any contract client plugin
582
- * (plugin-fetch or plugin-axios). Returns `null` when no contract plugin is in play (the inline
558
+ * (plugin-fetch, plugin-axios, or plugin-client). Returns `null` when no contract plugin is in play (the inline
583
559
  * path), or when the client plugin's `exclude` / `include` leaves this operation out, since it then
584
- * emits no `<op>` to import. The plugin injects `.kubb/client.ts` at the global output root, the
585
- * same path consumers read `RequestConfig` / `ResponseErrorConfig` from.
560
+ * emits no `<op>` to import. plugin-fetch / plugin-axios inject `.kubb/client.ts` at the global
561
+ * output root, the same path consumers read `RequestConfig` / `ResponseErrorConfig` from.
562
+ * With plugin-client they come from the user's `importPath` module instead.
586
563
  *
587
564
  * The result is cached in the current node's `cache` (`ctx.cache`) under the client plugin's name,
588
565
  * so several dependents reading the same client plugin for one operation in a single pass
@@ -613,10 +590,15 @@ function resolveClientOperation(options) {
613
590
  output: operationOptions.output ?? output,
614
591
  group: operationOptions.group ?? void 0
615
592
  });
593
+ const { importPath } = operationOptions;
616
594
  return {
617
595
  name: resolver.name(node.operationId),
618
596
  path: file.path,
619
- clientPath: node_path.default.resolve(root, ".kubb/client.ts"),
597
+ clientPath: importPath === void 0 ? node_path.default.resolve(root, ".kubb/client.ts") : resolveImportPath({
598
+ importPath,
599
+ operationFile: file.path
600
+ }),
601
+ clientPathIsFile: importPath === void 0 || isRelativeImport(importPath),
620
602
  returnType: operationOptions.returnType ?? "full"
621
603
  };
622
604
  });
@@ -651,10 +633,7 @@ const mcpGenerator = (0, kubb_kit.defineGenerator)({
651
633
  const meta = {
652
634
  name: resolver.handler.name(node),
653
635
  file: resolver.file({
654
- name: node.operationId,
655
- extname: ".ts",
656
- tag: node.tags[0] ?? "default",
657
- path: node.path,
636
+ ...operationFileEntry(node, node.operationId),
658
637
  root,
659
638
  output,
660
639
  group: group ?? void 0
@@ -733,22 +712,14 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
733
712
  const { output, group } = ctx.options;
734
713
  const pluginZod = ctx.requirePlugin(_kubb_plugin_zod.pluginZodName);
735
714
  const zodResolver = driver.getResolver(_kubb_plugin_zod.pluginZodName);
736
- const name = "server";
737
- const serverFile = {
738
- baseName: "server.ts",
739
- 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),
740
718
  meta: { pluginName: plugin.name }
741
- };
742
- const startFile = {
743
- baseName: "start.ts",
744
- path: node_path.default.resolve(root, output.path, "start.ts"),
745
- meta: { pluginName: plugin.name }
746
- };
747
- const jsonFile = {
748
- baseName: ".mcp.json",
749
- path: node_path.default.resolve(root, output.path, ".mcp.json"),
750
- meta: { pluginName: plugin.name }
751
- };
719
+ });
720
+ const serverFile = fileAt("server.ts");
721
+ const startFile = fileAt("start.ts");
722
+ const jsonFile = fileAt(".mcp.json");
752
723
  const mcpConfig = { mcpServers: { [ctx.meta.title || "server"]: {
753
724
  type: "stdio",
754
725
  command: "npx",
@@ -756,26 +727,21 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
756
727
  } } };
757
728
  const operationsMapped = nodes.filter(kubb_kit.ast.isHttpOperationNode).map((node) => {
758
729
  const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
730
+ const fileEntry = operationFileEntry(node, node.operationId);
759
731
  const mcpFile = resolver.file({
760
- name: node.operationId,
761
- extname: ".ts",
762
- tag: node.tags[0] ?? "default",
763
- path: node.path,
732
+ ...fileEntry,
764
733
  root,
765
734
  output,
766
735
  group: group ?? void 0
767
736
  });
768
737
  const zodFile = zodResolver.file({
769
- name: node.operationId,
770
- extname: ".ts",
771
- tag: node.tags[0] ?? "default",
772
- path: node.path,
738
+ ...fileEntry,
773
739
  root,
774
740
  output: pluginZod.options?.output ?? output,
775
741
  group: pluginZod.options?.group ?? void 0
776
742
  });
777
743
  const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null;
778
- const successStatus = findSuccessStatusCode(node.responses);
744
+ const successStatus = getPrimarySuccessResponse(node)?.statusCode;
779
745
  const responseName = successStatus ? zodResolver.response.status(node, successStatus) : null;
780
746
  const resolveParams = (params) => params.map((p) => ({
781
747
  name: p.name,
@@ -798,8 +764,7 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
798
764
  requestName,
799
765
  responseName,
800
766
  file: zodFile
801
- },
802
- node
767
+ }
803
768
  };
804
769
  });
805
770
  const imports = operationsMapped.flatMap(({ mcp, zod }) => {
@@ -897,7 +862,7 @@ const serverGenerator = (0, kubb_kit.defineGenerator)({
897
862
  path: jsonFile.path,
898
863
  meta: jsonFile.meta,
899
864
  children: /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
900
- name,
865
+ name: "server",
901
866
  children: `${JSON.stringify(mcpConfig, null, 2)}\n`
902
867
  })
903
868
  })
@@ -912,7 +877,7 @@ var compatibility = {
912
877
  };
913
878
  //#endregion
914
879
  //#region package.json
915
- var version = "5.2.0-canary.20261014T071450";
880
+ var version = "5.2.1";
916
881
  //#endregion
917
882
  //#region src/resolvers/resolverMcp.ts
918
883
  /**
@@ -998,6 +963,7 @@ const pluginMcp = (0, kubb_kit.defineExtension)({
998
963
  override,
999
964
  group: createGroupConfig(group),
1000
965
  client: resolveContractClient({
966
+ plugin: pluginMcpName,
1001
967
  client,
1002
968
  plugins: (0, kubb_kit.useConfig)().plugins
1003
969
  }),