@kubb/plugin-mcp 5.0.0-beta.3 → 5.0.0-beta.30
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/README.md +25 -5
- package/dist/index.cjs +252 -148
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +78 -22
- package/dist/index.js +253 -149
- package/dist/index.js.map +1 -1
- package/extension.yaml +926 -0
- package/package.json +11 -12
- package/src/components/McpHandler.tsx +14 -20
- package/src/components/Server.tsx +8 -8
- package/src/generators/mcpGenerator.tsx +21 -24
- package/src/generators/serverGenerator.tsx +22 -22
- package/src/plugin.ts +36 -4
- package/src/resolvers/resolverMcp.ts +16 -6
- package/src/types.ts +27 -13
- package/src/utils.ts +15 -80
package/dist/index.cjs
CHANGED
|
@@ -247,16 +247,16 @@ var URLPath = class {
|
|
|
247
247
|
get object() {
|
|
248
248
|
return this.toObject();
|
|
249
249
|
}
|
|
250
|
-
/** Returns a map of path parameter names, or `
|
|
250
|
+
/** Returns a map of path parameter names, or `null` when the path has no parameters.
|
|
251
251
|
*
|
|
252
252
|
* @example
|
|
253
253
|
* ```ts
|
|
254
254
|
* new URLPath('/pet/{petId}').params // { petId: 'petId' }
|
|
255
|
-
* new URLPath('/pet').params //
|
|
255
|
+
* new URLPath('/pet').params // null
|
|
256
256
|
* ```
|
|
257
257
|
*/
|
|
258
258
|
get params() {
|
|
259
|
-
return this.
|
|
259
|
+
return this.toParamsObject();
|
|
260
260
|
}
|
|
261
261
|
#transformParam(raw) {
|
|
262
262
|
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
@@ -274,7 +274,7 @@ var URLPath = class {
|
|
|
274
274
|
toObject({ type = "path", replacer, stringify } = {}) {
|
|
275
275
|
const object = {
|
|
276
276
|
url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
|
|
277
|
-
params: this.
|
|
277
|
+
params: this.toParamsObject()
|
|
278
278
|
};
|
|
279
279
|
if (stringify) {
|
|
280
280
|
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
@@ -290,12 +290,13 @@ var URLPath = class {
|
|
|
290
290
|
* @example
|
|
291
291
|
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
292
292
|
*/
|
|
293
|
-
toTemplateString({ prefix
|
|
294
|
-
|
|
293
|
+
toTemplateString({ prefix, replacer } = {}) {
|
|
294
|
+
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
295
295
|
if (i % 2 === 0) return part;
|
|
296
296
|
const param = this.#transformParam(part);
|
|
297
297
|
return `\${${replacer ? replacer(param) : param}}`;
|
|
298
|
-
}).join("")
|
|
298
|
+
}).join("");
|
|
299
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
299
300
|
}
|
|
300
301
|
/**
|
|
301
302
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
@@ -304,17 +305,17 @@ var URLPath = class {
|
|
|
304
305
|
*
|
|
305
306
|
* @example
|
|
306
307
|
* ```ts
|
|
307
|
-
* new URLPath('/pet/{petId}/tag/{tagId}').
|
|
308
|
+
* new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
|
|
308
309
|
* // { petId: 'petId', tagId: 'tagId' }
|
|
309
310
|
* ```
|
|
310
311
|
*/
|
|
311
|
-
|
|
312
|
+
toParamsObject(replacer) {
|
|
312
313
|
const params = {};
|
|
313
314
|
this.#eachParam((_raw, param) => {
|
|
314
315
|
const key = replacer ? replacer(param) : param;
|
|
315
316
|
params[key] = key;
|
|
316
317
|
});
|
|
317
|
-
return Object.keys(params).length > 0 ? params :
|
|
318
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
318
319
|
}
|
|
319
320
|
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
320
321
|
*
|
|
@@ -328,82 +329,111 @@ var URLPath = class {
|
|
|
328
329
|
}
|
|
329
330
|
};
|
|
330
331
|
//#endregion
|
|
331
|
-
//#region src/
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const code = Number(res.statusCode);
|
|
338
|
-
if (code >= 200 && code < 300) return res.statusCode;
|
|
339
|
-
}
|
|
332
|
+
//#region ../../internals/shared/src/operation.ts
|
|
333
|
+
function getOperationLink(node, link) {
|
|
334
|
+
if (!link) return null;
|
|
335
|
+
if (typeof link === "function") return link(node) ?? null;
|
|
336
|
+
if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
|
|
337
|
+
return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
|
|
340
338
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
* Build JSDoc comment lines from an OperationNode.
|
|
351
|
-
*/
|
|
352
|
-
function getComments(node) {
|
|
353
|
-
return [
|
|
339
|
+
function buildOperationComments(node, options = {}) {
|
|
340
|
+
const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
|
|
341
|
+
const linkComment = getOperationLink(node, link);
|
|
342
|
+
const filteredComments = (linkPosition === "beforeDeprecated" ? [
|
|
343
|
+
node.description && `@description ${node.description}`,
|
|
344
|
+
node.summary && `@summary ${node.summary}`,
|
|
345
|
+
linkComment,
|
|
346
|
+
node.deprecated && "@deprecated"
|
|
347
|
+
] : [
|
|
354
348
|
node.description && `@description ${node.description}`,
|
|
355
349
|
node.summary && `@summary ${node.summary}`,
|
|
356
350
|
node.deprecated && "@deprecated",
|
|
357
|
-
|
|
358
|
-
].filter((
|
|
351
|
+
linkComment
|
|
352
|
+
]).filter((comment) => Boolean(comment));
|
|
353
|
+
if (!splitLines) return filteredComments;
|
|
354
|
+
return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
|
|
359
355
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
for (const p of params) {
|
|
369
|
-
const camelName = camelCase(p.name);
|
|
370
|
-
mapping[p.name] = camelName;
|
|
371
|
-
if (p.name !== camelName) hasDifference = true;
|
|
372
|
-
}
|
|
373
|
-
return hasDifference ? mapping : void 0;
|
|
356
|
+
function getOperationParameters(node, options = {}) {
|
|
357
|
+
const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
|
|
358
|
+
return {
|
|
359
|
+
path: params.filter((param) => param.in === "path"),
|
|
360
|
+
query: params.filter((param) => param.in === "query"),
|
|
361
|
+
header: params.filter((param) => param.in === "header"),
|
|
362
|
+
cookie: params.filter((param) => param.in === "cookie")
|
|
363
|
+
};
|
|
374
364
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
function
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
default: expr = "z.string()";
|
|
365
|
+
function getStatusCodeNumber(statusCode) {
|
|
366
|
+
const code = Number(statusCode);
|
|
367
|
+
return Number.isNaN(code) ? null : code;
|
|
368
|
+
}
|
|
369
|
+
function isSuccessStatusCode(statusCode) {
|
|
370
|
+
const code = getStatusCodeNumber(statusCode);
|
|
371
|
+
return code !== null && code >= 200 && code < 300;
|
|
372
|
+
}
|
|
373
|
+
function isErrorStatusCode(statusCode) {
|
|
374
|
+
const code = getStatusCodeNumber(statusCode);
|
|
375
|
+
return code !== null && code >= 400;
|
|
376
|
+
}
|
|
377
|
+
function resolveErrorNames(node, resolver) {
|
|
378
|
+
return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
379
|
+
}
|
|
380
|
+
function resolveStatusCodeNames(node, resolver) {
|
|
381
|
+
return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
382
|
+
}
|
|
383
|
+
const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
|
|
384
|
+
function resolveOperationTypeNames(node, resolver, options = {}) {
|
|
385
|
+
const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
|
|
386
|
+
let byResolver = typeNamesByResolver.get(resolver);
|
|
387
|
+
if (byResolver) {
|
|
388
|
+
const cached = byResolver.get(cacheKey);
|
|
389
|
+
if (cached) return cached;
|
|
390
|
+
} else {
|
|
391
|
+
byResolver = /* @__PURE__ */ new Map();
|
|
392
|
+
typeNamesByResolver.set(resolver, byResolver);
|
|
404
393
|
}
|
|
405
|
-
|
|
406
|
-
|
|
394
|
+
const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
|
|
395
|
+
const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
|
|
396
|
+
const exclude = new Set(options.exclude ?? []);
|
|
397
|
+
const paramNames = [
|
|
398
|
+
...path.map((param) => resolver.resolvePathParamsName(node, param)),
|
|
399
|
+
...query.map((param) => resolver.resolveQueryParamsName(node, param)),
|
|
400
|
+
...header.map((param) => resolver.resolveHeaderParamsName(node, param))
|
|
401
|
+
];
|
|
402
|
+
const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
|
|
403
|
+
const result = (options.order === "body-response-first" ? [
|
|
404
|
+
...bodyAndResponseNames,
|
|
405
|
+
...paramNames,
|
|
406
|
+
...responseStatusNames
|
|
407
|
+
] : [
|
|
408
|
+
...paramNames,
|
|
409
|
+
...bodyAndResponseNames,
|
|
410
|
+
...responseStatusNames
|
|
411
|
+
]).filter((name) => Boolean(name) && !exclude.has(name));
|
|
412
|
+
byResolver.set(cacheKey, result);
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
function findSuccessStatusCode(responses) {
|
|
416
|
+
for (const response of responses) if (isSuccessStatusCode(response.statusCode)) return response.statusCode;
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region ../../internals/shared/src/params.ts
|
|
421
|
+
function buildParamsMapping(originalParams, mappedParams) {
|
|
422
|
+
const mapping = {};
|
|
423
|
+
let hasChanged = false;
|
|
424
|
+
originalParams.forEach((param, i) => {
|
|
425
|
+
const mappedName = mappedParams[i]?.name ?? param.name;
|
|
426
|
+
mapping[param.name] = mappedName;
|
|
427
|
+
if (param.name !== mappedName) hasChanged = true;
|
|
428
|
+
});
|
|
429
|
+
return hasChanged ? mapping : null;
|
|
430
|
+
}
|
|
431
|
+
function buildTransformedParamsMapping(params, transformName) {
|
|
432
|
+
if (!params.length) return null;
|
|
433
|
+
return buildParamsMapping(params, params.map((param) => ({
|
|
434
|
+
...param,
|
|
435
|
+
name: transformName(param.name)
|
|
436
|
+
})));
|
|
407
437
|
}
|
|
408
438
|
//#endregion
|
|
409
439
|
//#region src/components/McpHandler.tsx
|
|
@@ -418,13 +448,9 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
|
|
|
418
448
|
const urlPath = new URLPath(node.path);
|
|
419
449
|
const contentType = node.requestBody?.content?.[0]?.contentType;
|
|
420
450
|
const isFormData = contentType === "multipart/form-data";
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
const originalPathParams = node.parameters.filter((p) => p.in === "path");
|
|
425
|
-
const originalQueryParams = node.parameters.filter((p) => p.in === "query");
|
|
426
|
-
const originalHeaderParams = node.parameters.filter((p) => p.in === "header");
|
|
427
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0;
|
|
451
|
+
const { query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
|
|
452
|
+
const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
|
|
453
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
|
|
428
454
|
const responseName = resolver.resolveResponseName(node);
|
|
429
455
|
const errorResponses = node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => resolver.resolveResponseStatusName(node, r.statusCode));
|
|
430
456
|
const generics = [
|
|
@@ -440,11 +466,11 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
|
|
|
440
466
|
});
|
|
441
467
|
const baseParamsSignature = declarationPrinter.print(paramsNode) ?? "";
|
|
442
468
|
const paramsSignature = baseParamsSignature ? `${baseParamsSignature}, request: RequestHandlerExtra<ServerRequest, ServerNotification>` : "request: RequestHandlerExtra<ServerRequest, ServerNotification>";
|
|
443
|
-
const pathParamsMapping = paramsCasing ?
|
|
444
|
-
const queryParamsMapping = paramsCasing ?
|
|
445
|
-
const headerParamsMapping = paramsCasing ?
|
|
446
|
-
const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` :
|
|
447
|
-
const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" :
|
|
469
|
+
const pathParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalPathParams, camelCase) : null;
|
|
470
|
+
const queryParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalQueryParams, camelCase) : null;
|
|
471
|
+
const headerParamsMapping = paramsCasing ? buildTransformedParamsMapping(originalHeaderParams, camelCase) : null;
|
|
472
|
+
const contentTypeHeader = contentType && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null;
|
|
473
|
+
const headers = [headerParams.length ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null, contentTypeHeader].filter(Boolean);
|
|
448
474
|
const fetchConfig = [];
|
|
449
475
|
fetchConfig.push(`method: ${JSON.stringify(node.method.toUpperCase())}`);
|
|
450
476
|
fetchConfig.push(`url: ${urlPath.template}`);
|
|
@@ -478,7 +504,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
|
|
|
478
504
|
async: true,
|
|
479
505
|
export: true,
|
|
480
506
|
params: paramsSignature,
|
|
481
|
-
JSDoc: { comments:
|
|
507
|
+
JSDoc: { comments: buildOperationComments(node) },
|
|
482
508
|
returnType: "Promise<CallToolResult>",
|
|
483
509
|
children: [
|
|
484
510
|
"",
|
|
@@ -500,7 +526,7 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
|
|
|
500
526
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
|
|
501
527
|
isFormData && requestName && "const formData = buildFormData(requestData)",
|
|
502
528
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
|
|
503
|
-
`const res = await
|
|
529
|
+
`const res = await client<${generics.join(", ")}>({ ${fetchConfig.join(", ")} }, request)`,
|
|
504
530
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
|
|
505
531
|
callToolResult
|
|
506
532
|
]
|
|
@@ -508,6 +534,39 @@ function McpHandler({ name, node, resolver, baseURL, dataReturnType, paramsCasin
|
|
|
508
534
|
});
|
|
509
535
|
}
|
|
510
536
|
//#endregion
|
|
537
|
+
//#region src/utils.ts
|
|
538
|
+
/**
|
|
539
|
+
* Render a group param value — compose individual schemas into `z.object({ ... })`,
|
|
540
|
+
* or use a schema name string directly.
|
|
541
|
+
*/
|
|
542
|
+
function zodGroupExpr(entry) {
|
|
543
|
+
if (typeof entry === "string") return entry;
|
|
544
|
+
return `z.object({ ${entry.map((p) => `${JSON.stringify(p.name)}: ${p.schemaName}`).join(", ")} })`;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Convert a SchemaNode type to an inline Zod expression string.
|
|
548
|
+
* Used as fallback when no named zod schema is available for a path parameter.
|
|
549
|
+
*/
|
|
550
|
+
function zodExprFromSchemaNode(schema) {
|
|
551
|
+
const baseExpr = (() => {
|
|
552
|
+
if (schema.type === "enum") {
|
|
553
|
+
const rawValues = schema.namedEnumValues?.length ? schema.namedEnumValues.map((v) => v.value) : (schema.enumValues ?? []).filter((v) => v !== null);
|
|
554
|
+
if (rawValues.length > 0 && rawValues.every((v) => typeof v === "string")) return `z.enum([${rawValues.map((v) => JSON.stringify(v)).join(", ")}])`;
|
|
555
|
+
if (rawValues.length > 0) {
|
|
556
|
+
const literals = rawValues.map((v) => `z.literal(${JSON.stringify(v)})`);
|
|
557
|
+
return literals.length === 1 ? literals[0] : `z.union([${literals.join(", ")}])`;
|
|
558
|
+
}
|
|
559
|
+
return "z.string()";
|
|
560
|
+
}
|
|
561
|
+
if (schema.type === "integer") return "z.coerce.number()";
|
|
562
|
+
if (schema.type === "number") return "z.number()";
|
|
563
|
+
if (schema.type === "boolean") return "z.boolean()";
|
|
564
|
+
if (schema.type === "array") return "z.array(z.unknown())";
|
|
565
|
+
return "z.string()";
|
|
566
|
+
})();
|
|
567
|
+
return schema.nullable ? `${baseExpr}.nullable()` : baseExpr;
|
|
568
|
+
}
|
|
569
|
+
//#endregion
|
|
511
570
|
//#region src/components/Server.tsx
|
|
512
571
|
const keysPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "keys" });
|
|
513
572
|
function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
|
|
@@ -527,7 +586,7 @@ function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
|
|
|
527
586
|
`
|
|
528
587
|
}),
|
|
529
588
|
operations.map(({ tool, mcp, zod, node }) => {
|
|
530
|
-
const pathParams =
|
|
589
|
+
const { path: pathParams } = getOperationParameters(node, { paramsCasing });
|
|
531
590
|
const pathEntries = [];
|
|
532
591
|
const otherEntries = [];
|
|
533
592
|
for (const p of pathParams) {
|
|
@@ -554,9 +613,9 @@ function Server({ name, serverName, serverVersion, paramsCasing, operations }) {
|
|
|
554
613
|
const paramsNode = entries.length ? _kubb_core.ast.createFunctionParameters({ params: [_kubb_core.ast.createParameterGroup({ properties: entries.map((e) => _kubb_core.ast.createFunctionParameter({
|
|
555
614
|
name: e.key,
|
|
556
615
|
optional: false
|
|
557
|
-
})) })] }) :
|
|
616
|
+
})) })] }) : null;
|
|
558
617
|
const destructured = paramsNode ? keysPrinter.print(paramsNode) ?? "" : "";
|
|
559
|
-
const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` :
|
|
618
|
+
const inputSchema = entries.length ? `{ ${entries.map((e) => `${e.key}: ${e.value}`).join(", ")} }` : null;
|
|
560
619
|
const outputSchema = zod.responseName;
|
|
561
620
|
const config = [
|
|
562
621
|
tool.title ? `title: ${JSON.stringify(tool.title)}` : null,
|
|
@@ -597,29 +656,27 @@ server.registerTool(${JSON.stringify(tool.name)}, {
|
|
|
597
656
|
}
|
|
598
657
|
//#endregion
|
|
599
658
|
//#region src/generators/mcpGenerator.tsx
|
|
659
|
+
/**
|
|
660
|
+
* Built-in operation generator for `@kubb/plugin-mcp`. Emits one MCP tool
|
|
661
|
+
* handler per OpenAPI operation, wiring the input Zod schema, the HTTP call,
|
|
662
|
+
* and the response shape into a single function that an MCP server can
|
|
663
|
+
* register as a callable tool.
|
|
664
|
+
*/
|
|
600
665
|
const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
601
666
|
name: "mcp",
|
|
602
|
-
renderer: _kubb_renderer_jsx.
|
|
667
|
+
renderer: _kubb_renderer_jsx.jsxRendererSync,
|
|
603
668
|
operation(node, ctx) {
|
|
604
669
|
const { resolver, driver, root } = ctx;
|
|
605
670
|
const { output, client, paramsCasing, group } = ctx.options;
|
|
606
671
|
const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
|
|
607
672
|
if (!pluginTs) return null;
|
|
608
673
|
const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const importedTypeNames = [
|
|
614
|
-
...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
|
|
615
|
-
...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
|
|
616
|
-
...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
|
|
617
|
-
node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
|
|
618
|
-
tsResolver.resolveResponseName(node),
|
|
619
|
-
...node.responses.filter((r) => Number(r.statusCode) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
|
|
620
|
-
].filter(Boolean);
|
|
674
|
+
const importedTypeNames = resolveOperationTypeNames(node, tsResolver, {
|
|
675
|
+
paramsCasing,
|
|
676
|
+
responseStatusNames: "error"
|
|
677
|
+
});
|
|
621
678
|
const meta = {
|
|
622
|
-
name: resolver.
|
|
679
|
+
name: resolver.resolveHandlerName(node),
|
|
623
680
|
file: resolver.resolveFile({
|
|
624
681
|
name: node.operationId,
|
|
625
682
|
extname: ".ts",
|
|
@@ -628,7 +685,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
628
685
|
}, {
|
|
629
686
|
root,
|
|
630
687
|
output,
|
|
631
|
-
group
|
|
688
|
+
group: group ?? void 0
|
|
632
689
|
}),
|
|
633
690
|
fileTs: tsResolver.resolveFile({
|
|
634
691
|
name: node.operationId,
|
|
@@ -638,7 +695,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
638
695
|
}, {
|
|
639
696
|
root,
|
|
640
697
|
output: pluginTs.options?.output ?? output,
|
|
641
|
-
group: pluginTs.options?.group
|
|
698
|
+
group: pluginTs.options?.group ?? void 0
|
|
642
699
|
})
|
|
643
700
|
};
|
|
644
701
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
|
|
@@ -682,7 +739,7 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
682
739
|
isTypeOnly: true
|
|
683
740
|
}),
|
|
684
741
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
685
|
-
name: "
|
|
742
|
+
name: "client",
|
|
686
743
|
path: client.importPath
|
|
687
744
|
}),
|
|
688
745
|
client.dataReturnType === "full" && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
@@ -698,18 +755,18 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
698
755
|
"ResponseErrorConfig"
|
|
699
756
|
],
|
|
700
757
|
root: meta.file.path,
|
|
701
|
-
path: node_path.default.resolve(root, ".kubb/
|
|
758
|
+
path: node_path.default.resolve(root, ".kubb/client.ts"),
|
|
702
759
|
isTypeOnly: true
|
|
703
760
|
}),
|
|
704
761
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
705
|
-
name: ["
|
|
762
|
+
name: ["client"],
|
|
706
763
|
root: meta.file.path,
|
|
707
|
-
path: node_path.default.resolve(root, ".kubb/
|
|
764
|
+
path: node_path.default.resolve(root, ".kubb/client.ts")
|
|
708
765
|
}),
|
|
709
766
|
client.dataReturnType === "full" && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
710
767
|
name: ["ResponseConfig"],
|
|
711
768
|
root: meta.file.path,
|
|
712
|
-
path: node_path.default.resolve(root, ".kubb/
|
|
769
|
+
path: node_path.default.resolve(root, ".kubb/client.ts"),
|
|
713
770
|
isTypeOnly: true
|
|
714
771
|
})
|
|
715
772
|
] }),
|
|
@@ -736,9 +793,9 @@ const mcpGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
736
793
|
*/
|
|
737
794
|
const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
738
795
|
name: "operations",
|
|
739
|
-
renderer: _kubb_renderer_jsx.
|
|
796
|
+
renderer: _kubb_renderer_jsx.jsxRendererSync,
|
|
740
797
|
operations(nodes, ctx) {
|
|
741
|
-
const {
|
|
798
|
+
const { config, resolver, plugin, driver, root } = ctx;
|
|
742
799
|
const { output, paramsCasing, group } = ctx.options;
|
|
743
800
|
const pluginZod = driver.getPlugin(_kubb_plugin_zod.pluginZodName);
|
|
744
801
|
if (!pluginZod) return;
|
|
@@ -755,10 +812,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
755
812
|
meta: { pluginName: plugin.name }
|
|
756
813
|
};
|
|
757
814
|
const operationsMapped = nodes.map((node) => {
|
|
758
|
-
const
|
|
759
|
-
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
760
|
-
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
761
|
-
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
815
|
+
const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node, { paramsCasing });
|
|
762
816
|
const mcpFile = resolver.resolveFile({
|
|
763
817
|
name: node.operationId,
|
|
764
818
|
extname: ".ts",
|
|
@@ -767,7 +821,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
767
821
|
}, {
|
|
768
822
|
root,
|
|
769
823
|
output,
|
|
770
|
-
group
|
|
824
|
+
group: group ?? void 0
|
|
771
825
|
});
|
|
772
826
|
const zodFile = zodResolver.resolveFile({
|
|
773
827
|
name: node.operationId,
|
|
@@ -777,11 +831,11 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
777
831
|
}, {
|
|
778
832
|
root,
|
|
779
833
|
output: pluginZod.options?.output ?? output,
|
|
780
|
-
group: pluginZod.options?.group
|
|
834
|
+
group: pluginZod.options?.group ?? void 0
|
|
781
835
|
});
|
|
782
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) :
|
|
836
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName(node) : null;
|
|
783
837
|
const successStatus = findSuccessStatusCode(node.responses);
|
|
784
|
-
const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) :
|
|
838
|
+
const responseName = successStatus ? zodResolver.resolveResponseStatusName(node, successStatus) : null;
|
|
785
839
|
const resolveParams = (params) => params.map((p) => ({
|
|
786
840
|
name: p.name,
|
|
787
841
|
schemaName: zodResolver.resolveParamName(node, p)
|
|
@@ -793,13 +847,13 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
793
847
|
description: node.description || `Make a ${node.method.toUpperCase()} request to ${node.path}`
|
|
794
848
|
},
|
|
795
849
|
mcp: {
|
|
796
|
-
name: resolver.
|
|
850
|
+
name: resolver.resolveHandlerName(node),
|
|
797
851
|
file: mcpFile
|
|
798
852
|
},
|
|
799
853
|
zod: {
|
|
800
854
|
pathParams: resolveParams(pathParams),
|
|
801
|
-
queryParams: queryParams.length ? resolveParams(queryParams) :
|
|
802
|
-
headerParams: headerParams.length ? resolveParams(headerParams) :
|
|
855
|
+
queryParams: queryParams.length ? resolveParams(queryParams) : null,
|
|
856
|
+
headerParams: headerParams.length ? resolveParams(headerParams) : null,
|
|
803
857
|
requestName,
|
|
804
858
|
responseName,
|
|
805
859
|
file: zodFile
|
|
@@ -814,7 +868,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
814
868
|
...(zod.headerParams ?? []).map((p) => p.schemaName),
|
|
815
869
|
zod.requestName,
|
|
816
870
|
zod.responseName
|
|
817
|
-
].filter(Boolean);
|
|
871
|
+
].filter((name) => Boolean(name));
|
|
818
872
|
const uniqueNames = [...new Set(zodNames)].sort();
|
|
819
873
|
return [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
820
874
|
name: [mcp.name],
|
|
@@ -830,13 +884,21 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
830
884
|
baseName: serverFile.baseName,
|
|
831
885
|
path: serverFile.path,
|
|
832
886
|
meta: serverFile.meta,
|
|
833
|
-
banner: resolver.resolveBanner(
|
|
887
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
834
888
|
output,
|
|
835
|
-
config
|
|
889
|
+
config,
|
|
890
|
+
file: {
|
|
891
|
+
path: serverFile.path,
|
|
892
|
+
baseName: serverFile.baseName
|
|
893
|
+
}
|
|
836
894
|
}),
|
|
837
|
-
footer: resolver.resolveFooter(
|
|
895
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
838
896
|
output,
|
|
839
|
-
config
|
|
897
|
+
config,
|
|
898
|
+
file: {
|
|
899
|
+
path: serverFile.path,
|
|
900
|
+
baseName: serverFile.baseName
|
|
901
|
+
}
|
|
840
902
|
}),
|
|
841
903
|
children: [
|
|
842
904
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
@@ -854,8 +916,8 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
854
916
|
imports,
|
|
855
917
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Server, {
|
|
856
918
|
name,
|
|
857
|
-
serverName:
|
|
858
|
-
serverVersion:
|
|
919
|
+
serverName: ctx.meta.title ?? "server",
|
|
920
|
+
serverVersion: ctx.meta.version ?? "0.0.0",
|
|
859
921
|
paramsCasing,
|
|
860
922
|
operations: operationsMapped
|
|
861
923
|
})
|
|
@@ -869,7 +931,7 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
869
931
|
children: `
|
|
870
932
|
{
|
|
871
933
|
"mcpServers": {
|
|
872
|
-
"${
|
|
934
|
+
"${ctx.meta.title || "server"}": {
|
|
873
935
|
"type": "stdio",
|
|
874
936
|
"command": "npx",
|
|
875
937
|
"args": ["tsx", "${node_path.default.relative(node_path.default.dirname(jsonFile.path), serverFile.path)}"]
|
|
@@ -884,14 +946,18 @@ const serverGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
884
946
|
//#endregion
|
|
885
947
|
//#region src/resolvers/resolverMcp.ts
|
|
886
948
|
/**
|
|
887
|
-
*
|
|
949
|
+
* Default resolver used by `@kubb/plugin-mcp`. Decides the names and file
|
|
950
|
+
* paths for every generated MCP tool handler. Function names get a `Handler`
|
|
951
|
+
* suffix so an operation `addPet` becomes `addPetHandler`.
|
|
888
952
|
*
|
|
889
|
-
*
|
|
953
|
+
* @example Resolve a handler name
|
|
954
|
+
* ```ts
|
|
955
|
+
* import { resolverMcp } from '@kubb/plugin-mcp'
|
|
890
956
|
*
|
|
891
|
-
*
|
|
892
|
-
*
|
|
957
|
+
* resolverMcp.default('addPet', 'function') // 'addPetHandler'
|
|
958
|
+
* ```
|
|
893
959
|
*/
|
|
894
|
-
const resolverMcp = (0, _kubb_core.defineResolver)((
|
|
960
|
+
const resolverMcp = (0, _kubb_core.defineResolver)(() => ({
|
|
895
961
|
name: "default",
|
|
896
962
|
pluginName: "plugin-mcp",
|
|
897
963
|
default(name, type) {
|
|
@@ -899,12 +965,50 @@ const resolverMcp = (0, _kubb_core.defineResolver)((ctx) => ({
|
|
|
899
965
|
return camelCase(name, { suffix: "handler" });
|
|
900
966
|
},
|
|
901
967
|
resolveName(name) {
|
|
902
|
-
return
|
|
968
|
+
return this.default(name, "function");
|
|
969
|
+
},
|
|
970
|
+
resolvePathName(name, type) {
|
|
971
|
+
return this.default(name, type);
|
|
972
|
+
},
|
|
973
|
+
resolveHandlerName(node) {
|
|
974
|
+
return this.resolveName(node.operationId);
|
|
903
975
|
}
|
|
904
976
|
}));
|
|
905
977
|
//#endregion
|
|
906
978
|
//#region src/plugin.ts
|
|
979
|
+
/**
|
|
980
|
+
* Canonical plugin name for `@kubb/plugin-mcp`. Used for driver lookups and
|
|
981
|
+
* cross-plugin dependency references.
|
|
982
|
+
*/
|
|
907
983
|
const pluginMcpName = "plugin-mcp";
|
|
984
|
+
/**
|
|
985
|
+
* Generates a Model Context Protocol (MCP) server from an OpenAPI spec. Every
|
|
986
|
+
* operation becomes a typed MCP tool that AI assistants (Claude Desktop, Claude
|
|
987
|
+
* Code, MCP-compatible clients) can call directly.
|
|
988
|
+
*
|
|
989
|
+
* @example
|
|
990
|
+
* ```ts
|
|
991
|
+
* import { defineConfig } from 'kubb'
|
|
992
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
993
|
+
* import { pluginClient } from '@kubb/plugin-client'
|
|
994
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
995
|
+
* import { pluginMcp } from '@kubb/plugin-mcp'
|
|
996
|
+
*
|
|
997
|
+
* export default defineConfig({
|
|
998
|
+
* input: { path: './petStore.yaml' },
|
|
999
|
+
* output: { path: './src/gen' },
|
|
1000
|
+
* plugins: [
|
|
1001
|
+
* pluginTs(),
|
|
1002
|
+
* pluginClient(),
|
|
1003
|
+
* pluginZod(),
|
|
1004
|
+
* pluginMcp({
|
|
1005
|
+
* output: { path: './mcp' },
|
|
1006
|
+
* client: { baseURL: 'https://petstore.swagger.io/v2' },
|
|
1007
|
+
* }),
|
|
1008
|
+
* ],
|
|
1009
|
+
* })
|
|
1010
|
+
* ```
|
|
1011
|
+
*/
|
|
908
1012
|
const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
|
|
909
1013
|
const { output = {
|
|
910
1014
|
path: "mcp",
|
|
@@ -918,7 +1022,7 @@ const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
|
|
|
918
1022
|
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
919
1023
|
return `${camelCase(ctx.group)}Requests`;
|
|
920
1024
|
}
|
|
921
|
-
} :
|
|
1025
|
+
} : null;
|
|
922
1026
|
return {
|
|
923
1027
|
name: pluginMcpName,
|
|
924
1028
|
options,
|
|
@@ -954,10 +1058,10 @@ const pluginMcp = (0, _kubb_core.definePlugin)((options) => {
|
|
|
954
1058
|
const root = node_path$1.default.resolve(ctx.config.root, ctx.config.output.path);
|
|
955
1059
|
const hasClientPlugin = ctx.config.plugins?.some((p) => p.name === _kubb_plugin_client.pluginClientName);
|
|
956
1060
|
if (client?.bundle && !hasClientPlugin && !clientImportPath) ctx.injectFile({
|
|
957
|
-
baseName: "
|
|
958
|
-
path: node_path$1.default.resolve(root, ".kubb/
|
|
1061
|
+
baseName: "client.ts",
|
|
1062
|
+
path: node_path$1.default.resolve(root, ".kubb/client.ts"),
|
|
959
1063
|
sources: [_kubb_core.ast.createSource({
|
|
960
|
-
name: "
|
|
1064
|
+
name: "client",
|
|
961
1065
|
nodes: [_kubb_core.ast.createText(clientName === "fetch" ? _kubb_plugin_client_templates_clients_fetch_source.source : _kubb_plugin_client_templates_clients_axios_source.source)],
|
|
962
1066
|
isExportable: true,
|
|
963
1067
|
isIndexable: true
|