@kubb/plugin-mcp 5.0.0-beta.42 → 5.0.0-beta.56
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 +102 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -12
- package/dist/index.js +103 -168
- package/dist/index.js.map +1 -1
- package/package.json +9 -17
- package/src/components/McpHandler.tsx +12 -23
- package/src/generators/mcpGenerator.tsx +2 -2
- package/src/generators/serverGenerator.tsx +2 -2
- package/src/plugin.ts +2 -2
- package/src/resolvers/resolverMcp.ts +2 -2
- package/src/types.ts +8 -12
- package/extension.yaml +0 -943
package/dist/index.cjs
CHANGED
|
@@ -47,36 +47,45 @@ let _kubb_plugin_client_templates_config_source = require("@kubb/plugin-client/t
|
|
|
47
47
|
function toCamelOrPascal(text, pascal) {
|
|
48
48
|
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
49
49
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
50
|
-
|
|
51
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
50
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
52
51
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
53
52
|
}
|
|
54
53
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
54
|
+
* Converts `text` to camelCase.
|
|
55
|
+
*
|
|
56
|
+
* @example Word boundaries
|
|
57
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
58
58
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
59
|
+
* @example With a prefix
|
|
60
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
61
61
|
*/
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
62
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
63
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
65
64
|
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region ../../internals/utils/src/fs.ts
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
68
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
69
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
70
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
69
71
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
72
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
73
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
74
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
75
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
76
|
+
*
|
|
77
|
+
* @example Nested path from a dotted name
|
|
78
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
79
|
+
*
|
|
80
|
+
* @example PascalCase the final segment
|
|
81
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
82
|
+
*
|
|
83
|
+
* @example Suffix applied to the final segment only
|
|
84
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
73
85
|
*/
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
suffix
|
|
78
|
-
} : {}));
|
|
79
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
86
|
+
function toFilePath(name, caseLast = camelCase) {
|
|
87
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
88
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
80
89
|
}
|
|
81
90
|
//#endregion
|
|
82
91
|
//#region ../../internals/utils/src/reserved.ts
|
|
@@ -182,99 +191,80 @@ function isValidVarName(name) {
|
|
|
182
191
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
183
192
|
}
|
|
184
193
|
//#endregion
|
|
185
|
-
//#region ../../internals/utils/src/
|
|
194
|
+
//#region ../../internals/utils/src/url.ts
|
|
195
|
+
function transformParam(raw, casing) {
|
|
196
|
+
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
197
|
+
return casing === "camelcase" ? camelCase(param) : param;
|
|
198
|
+
}
|
|
199
|
+
function toParamsObject(path, { replacer, casing } = {}) {
|
|
200
|
+
const params = {};
|
|
201
|
+
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
202
|
+
const param = transformParam(match[1], casing);
|
|
203
|
+
const key = replacer ? replacer(param) : param;
|
|
204
|
+
params[key] = key;
|
|
205
|
+
}
|
|
206
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
207
|
+
}
|
|
186
208
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* const p = new URLPath('/pet/{petId}')
|
|
191
|
-
* p.URL // '/pet/:petId'
|
|
192
|
-
* p.template // '`/pet/${petId}`'
|
|
209
|
+
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
193
210
|
*/
|
|
194
|
-
var
|
|
211
|
+
var Url = class Url {
|
|
195
212
|
/**
|
|
196
|
-
*
|
|
197
|
-
*/
|
|
198
|
-
path;
|
|
199
|
-
#options;
|
|
200
|
-
constructor(path, options = {}) {
|
|
201
|
-
this.path = path;
|
|
202
|
-
this.#options = options;
|
|
203
|
-
}
|
|
204
|
-
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
213
|
+
* Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
|
|
205
214
|
*
|
|
206
215
|
* @example
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* ```
|
|
216
|
+
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
217
|
+
* Url.canParse('/pet/{petId}') // false
|
|
210
218
|
*/
|
|
211
|
-
|
|
212
|
-
return
|
|
219
|
+
static canParse(url, base) {
|
|
220
|
+
return URL.canParse(url, base);
|
|
213
221
|
}
|
|
214
|
-
/**
|
|
222
|
+
/**
|
|
223
|
+
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
215
224
|
*
|
|
216
225
|
* @example
|
|
217
|
-
*
|
|
218
|
-
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
219
|
-
* new URLPath('/pet/{petId}').isURL // false
|
|
220
|
-
* ```
|
|
226
|
+
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
221
227
|
*/
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return !!new URL(this.path).href;
|
|
225
|
-
} catch {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
+
static toPath(path) {
|
|
229
|
+
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
228
230
|
}
|
|
229
231
|
/**
|
|
230
|
-
* Converts
|
|
232
|
+
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
233
|
+
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
234
|
+
* and `casing` controls parameter identifier casing.
|
|
231
235
|
*
|
|
232
236
|
* @example
|
|
233
|
-
*
|
|
234
|
-
* new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
|
|
235
|
-
*/
|
|
236
|
-
get template() {
|
|
237
|
-
return this.toTemplateString();
|
|
238
|
-
}
|
|
239
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
237
|
+
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
240
238
|
*
|
|
241
239
|
* @example
|
|
242
|
-
*
|
|
243
|
-
* new URLPath('/pet/{petId}').object
|
|
244
|
-
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
245
|
-
* ```
|
|
240
|
+
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
246
241
|
*/
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
static toTemplateString(path, { prefix, replacer, casing } = {}) {
|
|
243
|
+
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
244
|
+
if (i % 2 === 0) return part;
|
|
245
|
+
const param = transformParam(part, casing);
|
|
246
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
247
|
+
}).join("");
|
|
248
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
249
249
|
}
|
|
250
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* Returns the path and its extracted params as a structured `URLObject`, or as a stringified
|
|
252
|
+
* expression when `stringify` is set.
|
|
251
253
|
*
|
|
252
254
|
* @example
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
* new URLPath('/pet').params // null
|
|
256
|
-
* ```
|
|
257
|
-
*/
|
|
258
|
-
get params() {
|
|
259
|
-
return this.toParamsObject();
|
|
260
|
-
}
|
|
261
|
-
#transformParam(raw) {
|
|
262
|
-
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
263
|
-
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
255
|
+
* Url.toObject('/pet/{petId}')
|
|
256
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
267
257
|
*/
|
|
268
|
-
|
|
269
|
-
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
270
|
-
const raw = match[1];
|
|
271
|
-
fn(raw, this.#transformParam(raw));
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
toObject({ type = "path", replacer, stringify } = {}) {
|
|
258
|
+
static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
|
|
275
259
|
const object = {
|
|
276
|
-
url: type === "path" ?
|
|
277
|
-
|
|
260
|
+
url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
|
|
261
|
+
replacer,
|
|
262
|
+
casing
|
|
263
|
+
}),
|
|
264
|
+
params: toParamsObject(path, {
|
|
265
|
+
replacer,
|
|
266
|
+
casing
|
|
267
|
+
})
|
|
278
268
|
};
|
|
279
269
|
if (stringify) {
|
|
280
270
|
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
@@ -283,57 +273,13 @@ var URLPath = class {
|
|
|
283
273
|
}
|
|
284
274
|
return object;
|
|
285
275
|
}
|
|
286
|
-
/**
|
|
287
|
-
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
288
|
-
* An optional `replacer` can transform each extracted parameter name before interpolation.
|
|
289
|
-
*
|
|
290
|
-
* @example
|
|
291
|
-
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
292
|
-
*/
|
|
293
|
-
toTemplateString({ prefix, replacer } = {}) {
|
|
294
|
-
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
295
|
-
if (i % 2 === 0) return part;
|
|
296
|
-
const param = this.#transformParam(part);
|
|
297
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
298
|
-
}).join("");
|
|
299
|
-
return `\`${prefix ?? ""}${result}\``;
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
303
|
-
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
304
|
-
* Returns `undefined` when no path parameters are found.
|
|
305
|
-
*
|
|
306
|
-
* @example
|
|
307
|
-
* ```ts
|
|
308
|
-
* new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
|
|
309
|
-
* // { petId: 'petId', tagId: 'tagId' }
|
|
310
|
-
* ```
|
|
311
|
-
*/
|
|
312
|
-
toParamsObject(replacer) {
|
|
313
|
-
const params = {};
|
|
314
|
-
this.#eachParam((_raw, param) => {
|
|
315
|
-
const key = replacer ? replacer(param) : param;
|
|
316
|
-
params[key] = key;
|
|
317
|
-
});
|
|
318
|
-
return Object.keys(params).length > 0 ? params : null;
|
|
319
|
-
}
|
|
320
|
-
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
321
|
-
*
|
|
322
|
-
* @example
|
|
323
|
-
* ```ts
|
|
324
|
-
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
325
|
-
* ```
|
|
326
|
-
*/
|
|
327
|
-
toURLPath() {
|
|
328
|
-
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
329
|
-
}
|
|
330
276
|
};
|
|
331
277
|
//#endregion
|
|
332
278
|
//#region ../../internals/shared/src/operation.ts
|
|
333
279
|
function getOperationLink(node, link) {
|
|
334
280
|
if (!link) return null;
|
|
335
281
|
if (typeof link === "function") return link(node) ?? null;
|
|
336
|
-
if (link === "urlPath") return node.path ? `{@link ${
|
|
282
|
+
if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
|
|
337
283
|
return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
|
|
338
284
|
}
|
|
339
285
|
function buildOperationComments(node, options = {}) {
|
|
@@ -423,26 +369,24 @@ function findSuccessStatusCode(responses) {
|
|
|
423
369
|
* shared default naming so every plugin groups output consistently:
|
|
424
370
|
*
|
|
425
371
|
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
426
|
-
* - other groups use
|
|
372
|
+
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
427
373
|
*
|
|
428
374
|
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
429
375
|
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
430
376
|
* per-plugin convention.
|
|
431
377
|
*
|
|
432
378
|
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
433
|
-
* @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
|
|
434
379
|
*
|
|
435
380
|
* @example
|
|
436
381
|
* ```ts
|
|
437
|
-
* createGroupConfig(group
|
|
438
|
-
* createGroupConfig(group, { suffix: 'Requests' }) // plugin-cypress, plugin-mcp
|
|
382
|
+
* createGroupConfig(group) // shared across every plugin
|
|
439
383
|
* ```
|
|
440
384
|
*/
|
|
441
|
-
function createGroupConfig(group
|
|
385
|
+
function createGroupConfig(group) {
|
|
442
386
|
if (!group) return null;
|
|
443
387
|
const defaultName = (ctx) => {
|
|
444
388
|
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
445
|
-
return
|
|
389
|
+
return camelCase(ctx.group);
|
|
446
390
|
};
|
|
447
391
|
return {
|
|
448
392
|
...group,
|
|
@@ -479,7 +423,6 @@ function buildRemappingCode(mapping, varName, sourceName) {
|
|
|
479
423
|
const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
|
|
480
424
|
function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasing }) {
|
|
481
425
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
482
|
-
const urlPath = new URLPath(node.path);
|
|
483
426
|
const contentType = node.requestBody?.content?.[0]?.contentType;
|
|
484
427
|
const isFormData = contentType === "multipart/form-data";
|
|
485
428
|
const { query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
|
|
@@ -507,28 +450,20 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
|
|
|
507
450
|
const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null, contentTypeHeader].filter(Boolean);
|
|
508
451
|
const fetchConfig = [];
|
|
509
452
|
fetchConfig.push(`method: ${JSON.stringify(node.method.toUpperCase())}`);
|
|
510
|
-
fetchConfig.push(`url: ${
|
|
453
|
+
fetchConfig.push(`url: ${Url.toTemplateString(node.path)}`);
|
|
511
454
|
if (baseURL) fetchConfig.push(`baseURL: \`${baseURL}\``);
|
|
512
455
|
if (queryParams.length) fetchConfig.push(queryParamsMapping ? "params: mappedParams" : "params");
|
|
513
456
|
if (requestName) fetchConfig.push(`data: ${isFormData ? "formData as FormData" : "requestData"}`);
|
|
514
457
|
if (headers.length) fetchConfig.push(`headers: { ${headers.join(", ")} }`);
|
|
515
|
-
const callToolResult =
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
content: [
|
|
525
|
-
{
|
|
526
|
-
type: 'text',
|
|
527
|
-
text: JSON.stringify(res)
|
|
528
|
-
}
|
|
529
|
-
],
|
|
530
|
-
structuredContent: { data: res.data }
|
|
531
|
-
}`;
|
|
458
|
+
const callToolResult = `return {
|
|
459
|
+
content: [
|
|
460
|
+
{
|
|
461
|
+
type: 'text',
|
|
462
|
+
text: JSON.stringify(${dataReturnType === "data" ? "res.data" : "res"})
|
|
463
|
+
}
|
|
464
|
+
],
|
|
465
|
+
structuredContent: { data: res.data }
|
|
466
|
+
}`;
|
|
532
467
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
533
468
|
name,
|
|
534
469
|
isExportable: true,
|
|
@@ -703,7 +638,7 @@ return server`
|
|
|
703
638
|
*/
|
|
704
639
|
const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
705
640
|
name: "mcp",
|
|
706
|
-
renderer: _kubb_renderer_jsx.
|
|
641
|
+
renderer: _kubb_renderer_jsx.jsxRenderer,
|
|
707
642
|
operation(node, ctx) {
|
|
708
643
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
709
644
|
const { resolver, driver, root } = ctx;
|
|
@@ -833,7 +768,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
833
768
|
*/
|
|
834
769
|
const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
835
770
|
name: "operations",
|
|
836
|
-
renderer: _kubb_renderer_jsx.
|
|
771
|
+
renderer: _kubb_renderer_jsx.jsxRenderer,
|
|
837
772
|
operations(nodes, ctx) {
|
|
838
773
|
const { config, resolver, plugin, driver, root } = ctx;
|
|
839
774
|
const { output, paramsCasing, group } = ctx.options;
|
|
@@ -1001,7 +936,7 @@ const resolverMcp = (0, _kubb_core.defineResolver)(() => ({
|
|
|
1001
936
|
name: "default",
|
|
1002
937
|
pluginName: "plugin-mcp",
|
|
1003
938
|
default(name, type) {
|
|
1004
|
-
if (type === "file") return
|
|
939
|
+
if (type === "file") return toFilePath(name);
|
|
1005
940
|
return camelCase(name, { suffix: "handler" });
|
|
1006
941
|
},
|
|
1007
942
|
resolveName(name) {
|
|
@@ -1052,11 +987,11 @@ const pluginMcpName = "plugin-mcp";
|
|
|
1052
987
|
const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
|
|
1053
988
|
const { output = {
|
|
1054
989
|
path: "mcp",
|
|
1055
|
-
|
|
990
|
+
barrel: { type: "named" }
|
|
1056
991
|
}, group, exclude = [], include, override = [], paramsCasing, client, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
|
|
1057
992
|
const clientName = client?.client ?? "axios";
|
|
1058
993
|
const clientImportPath = client?.importPath ?? (!client?.bundle ? `@kubb/plugin-client/clients/${clientName}` : void 0);
|
|
1059
|
-
const groupConfig = createGroupConfig(group
|
|
994
|
+
const groupConfig = createGroupConfig(group);
|
|
1060
995
|
return {
|
|
1061
996
|
name: pluginMcpName,
|
|
1062
997
|
options,
|