@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 +100 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +69 -21
- package/dist/index.js +100 -66
- package/dist/index.js.map +1 -1
- package/extension.yaml +600 -147
- package/package.json +7 -7
- package/src/components/McpHandler.tsx +8 -8
- package/src/components/Server.tsx +6 -6
- package/src/generators/mcpGenerator.tsx +11 -2
- package/src/generators/serverGenerator.tsx +15 -12
- package/src/plugin.ts +33 -1
- package/src/resolvers/resolverMcp.ts +8 -4
- package/src/types.ts +18 -12
- package/src/utils.ts +14 -30
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 `
|
|
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 //
|
|
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
|
|
267
|
-
|
|
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 :
|
|
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}}` :
|
|
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) ?
|
|
340
|
+
return Number.isNaN(code) ? null : code;
|
|
340
341
|
}
|
|
341
342
|
function isSuccessStatusCode(statusCode) {
|
|
342
343
|
const code = getStatusCodeNumber(statusCode);
|
|
343
|
-
return code !==
|
|
344
|
+
return code !== null && code >= 200 && code < 300;
|
|
344
345
|
}
|
|
345
346
|
function isErrorStatusCode(statusCode) {
|
|
346
347
|
const code = getStatusCodeNumber(statusCode);
|
|
347
|
-
return code !==
|
|
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) :
|
|
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 :
|
|
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) :
|
|
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) :
|
|
441
|
-
const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) :
|
|
442
|
-
const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) :
|
|
443
|
-
const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` :
|
|
444
|
-
const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" :
|
|
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
|
-
|
|
523
|
-
|
|
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"))
|
|
527
|
-
|
|
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
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
530
|
+
return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
|
|
531
|
+
}
|
|
532
|
+
return "z.string()";
|
|
532
533
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
})) })] }) :
|
|
589
|
+
})) })] }) : null;
|
|
598
590
|
const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
|
|
599
|
-
const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` :
|
|
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
|
|
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) :
|
|
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) :
|
|
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) :
|
|
831
|
-
headerParams: headerParams.length ? resolveParams(headerParams) :
|
|
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(
|
|
860
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
863
861
|
output,
|
|
864
862
|
config
|
|
865
863
|
}),
|
|
866
|
-
footer: resolver.resolveFooter(
|
|
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:
|
|
887
|
-
serverVersion:
|
|
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
|
-
"${
|
|
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
|
-
*
|
|
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
|
-
*
|
|
918
|
+
* @example Resolve a handler name
|
|
919
|
+
* ```ts
|
|
920
|
+
* import { resolverMcp } from '@kubb/plugin-mcp'
|
|
919
921
|
*
|
|
920
|
-
*
|
|
921
|
-
*
|
|
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
|
-
} :
|
|
990
|
+
} : null;
|
|
957
991
|
return {
|
|
958
992
|
name: pluginMcpName,
|
|
959
993
|
options,
|