@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 +60 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +13 -16
- package/dist/index.js +60 -86
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
-
import
|
|
3
|
-
import { ast, defineGenerator, definePlugin, defineResolver } from "kubb/kit";
|
|
2
|
+
import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
|
|
4
3
|
import { Const, File, Function, jsxRenderer } from "kubb/jsx";
|
|
5
4
|
import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
|
|
6
5
|
import { createFunctionParameter, createFunctionParameters, createObjectBindingPattern, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
|
|
6
|
+
import path from "node:path";
|
|
7
7
|
import { pluginZodName } from "@kubb/plugin-zod";
|
|
8
8
|
//#region ../../internals/utils/src/casing.ts
|
|
9
9
|
/**
|
|
@@ -32,31 +32,6 @@ function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
|
32
32
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
33
33
|
}
|
|
34
34
|
//#endregion
|
|
35
|
-
//#region ../../internals/utils/src/fs.ts
|
|
36
|
-
/**
|
|
37
|
-
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
38
|
-
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
39
|
-
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
40
|
-
*
|
|
41
|
-
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
42
|
-
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
43
|
-
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
44
|
-
* absolute path, letting generated files escape the configured output directory.
|
|
45
|
-
*
|
|
46
|
-
* @example Nested path from a dotted name
|
|
47
|
-
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
48
|
-
*
|
|
49
|
-
* @example PascalCase the final segment
|
|
50
|
-
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
51
|
-
*
|
|
52
|
-
* @example Suffix applied to the final segment only
|
|
53
|
-
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
54
|
-
*/
|
|
55
|
-
function toFilePath(name, caseLast = camelCase) {
|
|
56
|
-
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
57
|
-
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
58
|
-
}
|
|
59
|
-
//#endregion
|
|
60
35
|
//#region ../../internals/utils/src/reserved.ts
|
|
61
36
|
/**
|
|
62
37
|
* JavaScript and Java reserved words.
|
|
@@ -157,6 +132,22 @@ const reservedWords = /* @__PURE__ */ new Set([
|
|
|
157
132
|
*/
|
|
158
133
|
function isValidVarName(name) {
|
|
159
134
|
if (!name || reservedWords.has(name)) return false;
|
|
135
|
+
return isIdentifier(name);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
139
|
+
*
|
|
140
|
+
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
141
|
+
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
142
|
+
* deciding whether an object key needs quoting.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* isIdentifier('name') // true
|
|
147
|
+
* isIdentifier('x-total')// false
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
function isIdentifier(name) {
|
|
160
151
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
161
152
|
}
|
|
162
153
|
//#endregion
|
|
@@ -312,13 +303,13 @@ function dedupeByCasedName(params) {
|
|
|
312
303
|
//#region ../../internals/shared/src/operation.ts
|
|
313
304
|
/**
|
|
314
305
|
* Builds the `ResolverFileParams` every operation generator passes to
|
|
315
|
-
* `resolver.
|
|
306
|
+
* `resolver.file`: a file named `name`, tagged by the operation's first
|
|
316
307
|
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
317
308
|
* that was repeated at dozens of call sites across the client and query plugins.
|
|
318
309
|
*
|
|
319
310
|
* @example
|
|
320
311
|
* ```ts
|
|
321
|
-
* resolver.
|
|
312
|
+
* resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
|
|
322
313
|
* ```
|
|
323
314
|
*/
|
|
324
315
|
function operationFileEntry(node, name, extname = ".ts") {
|
|
@@ -335,8 +326,11 @@ function getOperationLink(node, link) {
|
|
|
335
326
|
if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
|
|
336
327
|
return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
|
|
337
328
|
}
|
|
338
|
-
|
|
339
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
|
|
331
|
+
* are present and the union, default, and form-data flags the client uses to pick one.
|
|
332
|
+
*/
|
|
333
|
+
function buildContentTypeInfo(contentTypes) {
|
|
340
334
|
const isMultipleContentTypes = contentTypes.length > 1;
|
|
341
335
|
return {
|
|
342
336
|
contentTypes,
|
|
@@ -346,20 +340,15 @@ function getContentTypeInfo(node) {
|
|
|
346
340
|
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
347
341
|
};
|
|
348
342
|
}
|
|
343
|
+
function getContentTypeInfo(node) {
|
|
344
|
+
return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
|
|
345
|
+
}
|
|
349
346
|
/**
|
|
350
347
|
* The request-body counterpart for the primary success response: the content types it documents and
|
|
351
348
|
* whether several are present, so the client can let a caller pick which one to accept.
|
|
352
349
|
*/
|
|
353
350
|
function getResponseContentTypeInfo(node) {
|
|
354
|
-
|
|
355
|
-
const isMultipleContentTypes = contentTypes.length > 1;
|
|
356
|
-
return {
|
|
357
|
-
contentTypes,
|
|
358
|
-
isMultipleContentTypes,
|
|
359
|
-
contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
|
|
360
|
-
defaultContentType: contentTypes[0] ?? "application/json",
|
|
361
|
-
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
362
|
-
};
|
|
351
|
+
return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
|
|
363
352
|
}
|
|
364
353
|
function buildRequestConfigType(node) {
|
|
365
354
|
const request = getContentTypeInfo(node);
|
|
@@ -401,7 +390,7 @@ function getRequestGroupOptionality(node) {
|
|
|
401
390
|
}
|
|
402
391
|
/**
|
|
403
392
|
* Builds the grouped `{ path, query, body, headers }` parameter for a generated client
|
|
404
|
-
* function, typed from the operation's `
|
|
393
|
+
* function, typed from the operation's `Options` (minus `url`). Only the groups the
|
|
405
394
|
* operation actually has are destructured. The trailing `config` parameter carries the
|
|
406
395
|
* runtime `RequestConfig` overrides plus `client`.
|
|
407
396
|
*/
|
|
@@ -415,7 +404,7 @@ function buildRequestParamsSignature(node, resolver, options = {}) {
|
|
|
415
404
|
"headers"
|
|
416
405
|
].filter((key) => groups[key]);
|
|
417
406
|
return {
|
|
418
|
-
signature: [names.length > 0 ? `{ ${names.join(", ")} }: ${resolver.
|
|
407
|
+
signature: [names.length > 0 ? `{ ${names.join(", ")} }: ${resolver.response.options(node)}${isOptional ? " = {}" : ""}` : null, isConfigurable ? `config: ${buildRequestConfigType(node)} = {}` : null].filter(Boolean).join(", "),
|
|
419
408
|
groups
|
|
420
409
|
};
|
|
421
410
|
}
|
|
@@ -744,15 +733,15 @@ function resolveClientOperation(options) {
|
|
|
744
733
|
const { clientPlugin, driver, node, root, output } = options;
|
|
745
734
|
if (!clientPlugin) return null;
|
|
746
735
|
const resolver = driver.getResolver(clientPlugin.pluginName);
|
|
747
|
-
if (!resolver) return null;
|
|
748
736
|
const plugin = driver.getPlugin(clientPlugin.pluginName);
|
|
749
|
-
const file = resolver.
|
|
737
|
+
const file = resolver.file({
|
|
738
|
+
...operationFileEntry(node, node.operationId),
|
|
750
739
|
root,
|
|
751
740
|
output: plugin?.options?.output ?? output,
|
|
752
741
|
group: plugin?.options?.group ?? void 0
|
|
753
742
|
});
|
|
754
743
|
return {
|
|
755
|
-
name: resolver.
|
|
744
|
+
name: resolver.name(node.operationId),
|
|
756
745
|
path: file.path,
|
|
757
746
|
clientPath: path.resolve(root, ".kubb/client.ts")
|
|
758
747
|
};
|
|
@@ -783,25 +772,23 @@ const mcpGenerator = defineGenerator({
|
|
|
783
772
|
output
|
|
784
773
|
});
|
|
785
774
|
if (!contractOp) return null;
|
|
786
|
-
const
|
|
775
|
+
const optionsName = tsResolver.response.options(node);
|
|
787
776
|
const meta = {
|
|
788
|
-
name: resolver.
|
|
789
|
-
file: resolver.
|
|
777
|
+
name: resolver.handler.name(node),
|
|
778
|
+
file: resolver.file({
|
|
790
779
|
name: node.operationId,
|
|
791
780
|
extname: ".ts",
|
|
792
781
|
tag: node.tags[0] ?? "default",
|
|
793
|
-
path: node.path
|
|
794
|
-
}, {
|
|
782
|
+
path: node.path,
|
|
795
783
|
root,
|
|
796
784
|
output,
|
|
797
785
|
group: group ?? void 0
|
|
798
786
|
}),
|
|
799
|
-
fileTs: tsResolver.
|
|
787
|
+
fileTs: tsResolver.file({
|
|
800
788
|
name: node.operationId,
|
|
801
789
|
extname: ".ts",
|
|
802
790
|
tag: node.tags[0] ?? "default",
|
|
803
|
-
path: node.path
|
|
804
|
-
}, {
|
|
791
|
+
path: node.path,
|
|
805
792
|
root,
|
|
806
793
|
output: pluginTs.options?.output ?? output,
|
|
807
794
|
group: pluginTs.options?.group ?? void 0
|
|
@@ -812,8 +799,8 @@ const mcpGenerator = defineGenerator({
|
|
|
812
799
|
path: meta.file.path,
|
|
813
800
|
meta: meta.file.meta,
|
|
814
801
|
children: [
|
|
815
|
-
meta.fileTs &&
|
|
816
|
-
name: [
|
|
802
|
+
meta.fileTs && optionsName && /* @__PURE__ */ jsx(File.Import, {
|
|
803
|
+
name: [optionsName],
|
|
817
804
|
root: meta.file.path,
|
|
818
805
|
path: meta.fileTs.path,
|
|
819
806
|
isTypeOnly: true
|
|
@@ -878,32 +865,30 @@ const serverGenerator = defineGenerator({
|
|
|
878
865
|
};
|
|
879
866
|
const operationsMapped = nodes.filter(ast.isHttpOperationNode).map((node) => {
|
|
880
867
|
const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
|
|
881
|
-
const mcpFile = resolver.
|
|
868
|
+
const mcpFile = resolver.file({
|
|
882
869
|
name: node.operationId,
|
|
883
870
|
extname: ".ts",
|
|
884
871
|
tag: node.tags[0] ?? "default",
|
|
885
|
-
path: node.path
|
|
886
|
-
}, {
|
|
872
|
+
path: node.path,
|
|
887
873
|
root,
|
|
888
874
|
output,
|
|
889
875
|
group: group ?? void 0
|
|
890
876
|
});
|
|
891
|
-
const zodFile = zodResolver.
|
|
877
|
+
const zodFile = zodResolver.file({
|
|
892
878
|
name: node.operationId,
|
|
893
879
|
extname: ".ts",
|
|
894
880
|
tag: node.tags[0] ?? "default",
|
|
895
|
-
path: node.path
|
|
896
|
-
}, {
|
|
881
|
+
path: node.path,
|
|
897
882
|
root,
|
|
898
883
|
output: pluginZod.options?.output ?? output,
|
|
899
884
|
group: pluginZod.options?.group ?? void 0
|
|
900
885
|
});
|
|
901
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.
|
|
886
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null;
|
|
902
887
|
const successStatus = findSuccessStatusCode(node.responses);
|
|
903
|
-
const responseName = successStatus ? zodResolver.
|
|
888
|
+
const responseName = successStatus ? zodResolver.response.status(node, successStatus) : null;
|
|
904
889
|
const resolveParams = (params) => params.map((p) => ({
|
|
905
890
|
name: p.name,
|
|
906
|
-
schemaName: zodResolver.
|
|
891
|
+
schemaName: zodResolver.param.name(node, p)
|
|
907
892
|
}));
|
|
908
893
|
return {
|
|
909
894
|
tool: {
|
|
@@ -912,7 +897,7 @@ const serverGenerator = defineGenerator({
|
|
|
912
897
|
description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`
|
|
913
898
|
},
|
|
914
899
|
mcp: {
|
|
915
|
-
name: resolver.
|
|
900
|
+
name: resolver.handler.name(node),
|
|
916
901
|
file: mcpFile
|
|
917
902
|
},
|
|
918
903
|
zod: {
|
|
@@ -949,7 +934,7 @@ const serverGenerator = defineGenerator({
|
|
|
949
934
|
baseName: serverFile.baseName,
|
|
950
935
|
path: serverFile.path,
|
|
951
936
|
meta: serverFile.meta,
|
|
952
|
-
banner: resolver.
|
|
937
|
+
banner: resolver.default.banner(ctx.meta, {
|
|
953
938
|
output,
|
|
954
939
|
config,
|
|
955
940
|
file: {
|
|
@@ -957,7 +942,7 @@ const serverGenerator = defineGenerator({
|
|
|
957
942
|
baseName: serverFile.baseName
|
|
958
943
|
}
|
|
959
944
|
}),
|
|
960
|
-
footer: resolver.
|
|
945
|
+
footer: resolver.default.footer(ctx.meta, {
|
|
961
946
|
output,
|
|
962
947
|
config,
|
|
963
948
|
file: {
|
|
@@ -1018,26 +1003,18 @@ const serverGenerator = defineGenerator({
|
|
|
1018
1003
|
* ```ts
|
|
1019
1004
|
* import { resolverMcp } from '@kubb/plugin-mcp'
|
|
1020
1005
|
*
|
|
1021
|
-
* resolverMcp.
|
|
1006
|
+
* resolverMcp.name('addPet') // 'addPetHandler'
|
|
1022
1007
|
* ```
|
|
1023
1008
|
*/
|
|
1024
|
-
const resolverMcp =
|
|
1025
|
-
name: "default",
|
|
1009
|
+
const resolverMcp = createResolver({
|
|
1026
1010
|
pluginName: "plugin-mcp",
|
|
1027
|
-
|
|
1028
|
-
if (type === "file") return toFilePath(name);
|
|
1011
|
+
name(name) {
|
|
1029
1012
|
return camelCase(name, { suffix: "handler" });
|
|
1030
1013
|
},
|
|
1031
|
-
|
|
1032
|
-
return this.
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
return this.default(name, type);
|
|
1036
|
-
},
|
|
1037
|
-
resolveHandlerName(node) {
|
|
1038
|
-
return this.resolveName(node.operationId);
|
|
1039
|
-
}
|
|
1040
|
-
}));
|
|
1014
|
+
handler: { name(node) {
|
|
1015
|
+
return this.name(node.operationId);
|
|
1016
|
+
} }
|
|
1017
|
+
});
|
|
1041
1018
|
//#endregion
|
|
1042
1019
|
//#region src/plugin.ts
|
|
1043
1020
|
/**
|
|
@@ -1083,10 +1060,7 @@ const pluginMcp = definePlugin((options) => {
|
|
|
1083
1060
|
options,
|
|
1084
1061
|
dependencies: [pluginTsName, pluginZodName],
|
|
1085
1062
|
hooks: { "kubb:plugin:setup"(ctx) {
|
|
1086
|
-
const resolver = userResolver ?
|
|
1087
|
-
...resolverMcp,
|
|
1088
|
-
...userResolver
|
|
1089
|
-
} : resolverMcp;
|
|
1063
|
+
const resolver = userResolver ? Resolver.merge(resolverMcp, userResolver) : resolverMcp;
|
|
1090
1064
|
const resolvedClient = resolveClient({
|
|
1091
1065
|
client,
|
|
1092
1066
|
pluginNames: (ctx.config.plugins ?? []).map((p) => p.name).filter((name) => Boolean(name))
|