mcp-expose 1.0.0 → 1.1.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/CHANGELOG.md +36 -1
- package/README.md +181 -32
- package/dist/adonisjs/index.cjs +366 -6
- package/dist/adonisjs/index.cjs.map +1 -1
- package/dist/adonisjs/index.d.cts +2 -1
- package/dist/adonisjs/index.d.ts +2 -1
- package/dist/adonisjs/index.js +366 -6
- package/dist/adonisjs/index.js.map +1 -1
- package/dist/express/index.cjs +364 -7
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +4 -2
- package/dist/express/index.d.ts +4 -2
- package/dist/express/index.js +364 -7
- package/dist/express/index.js.map +1 -1
- package/dist/fastify/index.cjs +373 -6
- package/dist/fastify/index.cjs.map +1 -1
- package/dist/fastify/index.d.cts +2 -1
- package/dist/fastify/index.d.ts +2 -1
- package/dist/fastify/index.js +373 -6
- package/dist/fastify/index.js.map +1 -1
- package/dist/hono/index.cjs +367 -6
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.d.cts +2 -1
- package/dist/hono/index.d.ts +2 -1
- package/dist/hono/index.js +367 -6
- package/dist/hono/index.js.map +1 -1
- package/dist/index.cjs +371 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js +367 -31
- package/dist/index.js.map +1 -1
- package/dist/koa/index.cjs +366 -6
- package/dist/koa/index.cjs.map +1 -1
- package/dist/koa/index.d.cts +2 -1
- package/dist/koa/index.d.ts +2 -1
- package/dist/koa/index.js +366 -6
- package/dist/koa/index.js.map +1 -1
- package/dist/nestjs/index.cjs +381 -7
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +21 -7
- package/dist/nestjs/index.d.ts +21 -7
- package/dist/nestjs/index.js +382 -8
- package/dist/nestjs/index.js.map +1 -1
- package/dist/oauth/index.cjs +214 -0
- package/dist/oauth/index.cjs.map +1 -0
- package/dist/oauth/index.d.cts +69 -0
- package/dist/oauth/index.d.ts +69 -0
- package/dist/oauth/index.js +177 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth-8RmQamGP.d.cts +75 -0
- package/dist/oauth-8RmQamGP.d.ts +75 -0
- package/dist/{server-fzkb-wJo.d.ts → server--mQVQq3A.d.cts} +81 -1
- package/dist/{server-fzkb-wJo.d.cts → server-BPDTsT2o.d.ts} +81 -1
- package/package.json +22 -2
package/dist/adonisjs/index.cjs
CHANGED
|
@@ -98,6 +98,38 @@ function loopbackBaseUrl(req) {
|
|
|
98
98
|
}
|
|
99
99
|
__name(loopbackBaseUrl, "loopbackBaseUrl");
|
|
100
100
|
|
|
101
|
+
// src/core/result.ts
|
|
102
|
+
function isToolResult(value) {
|
|
103
|
+
const content = value?.content;
|
|
104
|
+
return !!value && typeof value === "object" && Array.isArray(content) && content.length > 0 && content.every((c) => !!c && typeof c === "object" && typeof c.type === "string");
|
|
105
|
+
}
|
|
106
|
+
__name(isToolResult, "isToolResult");
|
|
107
|
+
function toToolResult(value) {
|
|
108
|
+
if (isToolResult(value)) return value;
|
|
109
|
+
if (typeof value === "string") return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: value
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
const text = JSON.stringify(value ?? null);
|
|
118
|
+
const result = {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
};
|
|
126
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
127
|
+
result.structuredContent = value;
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
__name(toToolResult, "toToolResult");
|
|
132
|
+
|
|
101
133
|
// src/core/schema.ts
|
|
102
134
|
function isStandard(s) {
|
|
103
135
|
return typeof s === "object" && s !== null && "~standard" in s;
|
|
@@ -318,9 +350,7 @@ function splitArgs(args, mapping) {
|
|
|
318
350
|
}
|
|
319
351
|
__name(splitArgs, "splitArgs");
|
|
320
352
|
function responseToResult(res, maxChars) {
|
|
321
|
-
|
|
322
|
-
if (text.length > maxChars) text = `${text.slice(0, maxChars)}
|
|
323
|
-
\u2026[truncated ${text.length - maxChars} chars]`;
|
|
353
|
+
const text = truncate(res.body, maxChars);
|
|
324
354
|
const isError = res.status >= 400;
|
|
325
355
|
const result = {
|
|
326
356
|
content: [
|
|
@@ -345,6 +375,58 @@ ${text}` : text || `HTTP ${res.status}`
|
|
|
345
375
|
return result;
|
|
346
376
|
}
|
|
347
377
|
__name(responseToResult, "responseToResult");
|
|
378
|
+
function safeDecode(segment) {
|
|
379
|
+
try {
|
|
380
|
+
return decodeURIComponent(segment);
|
|
381
|
+
} catch {
|
|
382
|
+
return segment;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
__name(safeDecode, "safeDecode");
|
|
386
|
+
function truncate(text, maxChars) {
|
|
387
|
+
return text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
388
|
+
\u2026[truncated ${text.length - maxChars} chars]` : text;
|
|
389
|
+
}
|
|
390
|
+
__name(truncate, "truncate");
|
|
391
|
+
function toRouteToolResponse(res) {
|
|
392
|
+
let json;
|
|
393
|
+
if ((res.headers["content-type"] ?? "").includes("json") && res.body) {
|
|
394
|
+
try {
|
|
395
|
+
json = JSON.parse(res.body);
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
...res,
|
|
401
|
+
json,
|
|
402
|
+
ok: res.status < 400
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
__name(toRouteToolResponse, "toRouteToolResponse");
|
|
406
|
+
function transformedResult(value, res, maxChars) {
|
|
407
|
+
const explicit = isToolResult(value);
|
|
408
|
+
const result = toToolResult(value);
|
|
409
|
+
if (!explicit && !res.ok) {
|
|
410
|
+
result.isError = true;
|
|
411
|
+
delete result.structuredContent;
|
|
412
|
+
}
|
|
413
|
+
let truncated = false;
|
|
414
|
+
const content = result.content.map((c) => {
|
|
415
|
+
if (c.type !== "text" || c.text.length <= maxChars) return c;
|
|
416
|
+
truncated = true;
|
|
417
|
+
return {
|
|
418
|
+
...c,
|
|
419
|
+
text: truncate(c.text, maxChars)
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
if (!truncated) return result;
|
|
423
|
+
const { structuredContent: _dropped, ...rest } = result;
|
|
424
|
+
return {
|
|
425
|
+
...rest,
|
|
426
|
+
content
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
__name(transformedResult, "transformedResult");
|
|
348
430
|
function pickForwardHeaders(ctx, names) {
|
|
349
431
|
const out = {};
|
|
350
432
|
for (const name of names) {
|
|
@@ -360,6 +442,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
360
442
|
const { schema, mapping } = buildInputSchema(route, opts);
|
|
361
443
|
const forward = serverOpts.forwardHeaders ?? DEFAULT_FORWARD_HEADERS;
|
|
362
444
|
const maxChars = serverOpts.maxResponseChars ?? 1e5;
|
|
445
|
+
const transform = opts.transformResponse ?? serverOpts.transformResponse;
|
|
363
446
|
return {
|
|
364
447
|
name,
|
|
365
448
|
title: opts.title,
|
|
@@ -369,6 +452,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
369
452
|
...defaultAnnotations(method),
|
|
370
453
|
...opts.annotations
|
|
371
454
|
},
|
|
455
|
+
scopes: opts.scopes,
|
|
372
456
|
validate: toValidator(opts.input),
|
|
373
457
|
async handler(args, ctx) {
|
|
374
458
|
const missing = opts.input ? void 0 : checkRequired(schema, args);
|
|
@@ -404,19 +488,167 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
404
488
|
]
|
|
405
489
|
};
|
|
406
490
|
}
|
|
407
|
-
const
|
|
491
|
+
const request = {
|
|
408
492
|
method,
|
|
409
493
|
path,
|
|
410
494
|
query,
|
|
411
495
|
body,
|
|
412
496
|
headers
|
|
413
|
-
}
|
|
414
|
-
|
|
497
|
+
};
|
|
498
|
+
const res = await dispatch(request, ctx);
|
|
499
|
+
if (!transform) return responseToResult(res, maxChars);
|
|
500
|
+
const { "content-type": _ct, ...baseHeaders } = headers;
|
|
501
|
+
const hookCtx = {
|
|
502
|
+
tool: name,
|
|
503
|
+
args,
|
|
504
|
+
request,
|
|
505
|
+
toolContext: ctx,
|
|
506
|
+
defaultResult: /* @__PURE__ */ __name(() => responseToResult(res, maxChars), "defaultResult"),
|
|
507
|
+
async callRoute(r) {
|
|
508
|
+
if (!r.path.startsWith("/")) {
|
|
509
|
+
throw new Error(`callRoute: path must start with "/", got ${JSON.stringify(r.path)}`);
|
|
510
|
+
}
|
|
511
|
+
const params = r.params ?? {};
|
|
512
|
+
const missing2 = pathParams(r.path).filter((p) => params[p] === void 0 && !r.path.includes(`:${p}?`));
|
|
513
|
+
if (missing2.length) throw new Error(`callRoute: missing params for ${r.path}: ${missing2.join(", ")}`);
|
|
514
|
+
const subPath = interpolatePath(r.path, params);
|
|
515
|
+
if (subPath.split("/").some((seg) => [
|
|
516
|
+
".",
|
|
517
|
+
".."
|
|
518
|
+
].includes(safeDecode(seg)))) {
|
|
519
|
+
throw new Error(`callRoute: path must not contain "." or ".." segments: ${JSON.stringify(subPath)}`);
|
|
520
|
+
}
|
|
521
|
+
const sub = {
|
|
522
|
+
method: (r.method ?? "GET").toUpperCase(),
|
|
523
|
+
path: subPath,
|
|
524
|
+
query: r.query ?? {},
|
|
525
|
+
body: r.body,
|
|
526
|
+
headers: {
|
|
527
|
+
...baseHeaders,
|
|
528
|
+
...r.headers
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
if (sub.body !== void 0) sub.headers["content-type"] = "application/json";
|
|
532
|
+
return toRouteToolResponse(await dispatch(sub, ctx));
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
const response = toRouteToolResponse(res);
|
|
536
|
+
const value = await transform(response, hookCtx);
|
|
537
|
+
return value === void 0 ? responseToResult(res, maxChars) : transformedResult(value, response, maxChars);
|
|
415
538
|
}
|
|
416
539
|
};
|
|
417
540
|
}
|
|
418
541
|
__name(createRouteTool, "createRouteTool");
|
|
419
542
|
|
|
543
|
+
// src/core/oauth.ts
|
|
544
|
+
var PROTECTED_RESOURCE_WELL_KNOWN = "/.well-known/oauth-protected-resource";
|
|
545
|
+
function protectedResourceMetadataPaths(resource) {
|
|
546
|
+
const pathname = new URL(resource).pathname.replace(/\/+$/, "");
|
|
547
|
+
return pathname ? [
|
|
548
|
+
PROTECTED_RESOURCE_WELL_KNOWN + pathname,
|
|
549
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
550
|
+
] : [
|
|
551
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
__name(protectedResourceMetadataPaths, "protectedResourceMetadataPaths");
|
|
555
|
+
function protectedResourceMetadataUrl(resource) {
|
|
556
|
+
return new URL(protectedResourceMetadataPaths(resource)[0], resource).href;
|
|
557
|
+
}
|
|
558
|
+
__name(protectedResourceMetadataUrl, "protectedResourceMetadataUrl");
|
|
559
|
+
function validateOAuthOptions(o) {
|
|
560
|
+
let url;
|
|
561
|
+
try {
|
|
562
|
+
url = new URL(o.resource);
|
|
563
|
+
} catch {
|
|
564
|
+
throw new Error(`mcp-expose: oauth.resource must be an absolute URL, got ${JSON.stringify(o.resource)}`);
|
|
565
|
+
}
|
|
566
|
+
if (url.hash) throw new Error("mcp-expose: oauth.resource must not contain a fragment");
|
|
567
|
+
if (!o.authorizationServers?.length) throw new Error("mcp-expose: oauth.authorizationServers must not be empty");
|
|
568
|
+
if (typeof o.verifyToken !== "function") throw new Error("mcp-expose: oauth.verifyToken is required");
|
|
569
|
+
}
|
|
570
|
+
__name(validateOAuthOptions, "validateOAuthOptions");
|
|
571
|
+
var quote = /* @__PURE__ */ __name((v) => `"${v.replace(/["\\]/g, "\\$&")}"`, "quote");
|
|
572
|
+
function bearerChallenge(resource, params = {}) {
|
|
573
|
+
const parts = [];
|
|
574
|
+
if (params.error) parts.push(`error=${quote(params.error)}`);
|
|
575
|
+
if (params.errorDescription) parts.push(`error_description=${quote(params.errorDescription)}`);
|
|
576
|
+
if (params.scope?.length) parts.push(`scope=${quote(params.scope.join(" "))}`);
|
|
577
|
+
parts.push(`resource_metadata=${quote(protectedResourceMetadataUrl(resource))}`);
|
|
578
|
+
return `Bearer ${parts.join(", ")}`;
|
|
579
|
+
}
|
|
580
|
+
__name(bearerChallenge, "bearerChallenge");
|
|
581
|
+
function bearerToken(headers) {
|
|
582
|
+
const m = /^Bearer[ ]+([^\s,]+)\s*$/i.exec(headers.authorization ?? "");
|
|
583
|
+
return m?.[1];
|
|
584
|
+
}
|
|
585
|
+
__name(bearerToken, "bearerToken");
|
|
586
|
+
async function authenticate(o, headers) {
|
|
587
|
+
const token = bearerToken(headers);
|
|
588
|
+
if (!token) {
|
|
589
|
+
return {
|
|
590
|
+
ok: false,
|
|
591
|
+
status: 401,
|
|
592
|
+
error: "unauthorized",
|
|
593
|
+
description: "Authorization required",
|
|
594
|
+
challenge: bearerChallenge(o.resource, {
|
|
595
|
+
scope: o.requiredScopes
|
|
596
|
+
})
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
let auth;
|
|
600
|
+
try {
|
|
601
|
+
auth = await o.verifyToken(token, {
|
|
602
|
+
resource: o.resource,
|
|
603
|
+
headers
|
|
604
|
+
});
|
|
605
|
+
} catch {
|
|
606
|
+
auth = void 0;
|
|
607
|
+
}
|
|
608
|
+
if (!auth) return deny(o, 401, "invalid_token", "The access token is invalid");
|
|
609
|
+
if (auth.expiresAt !== void 0 && auth.expiresAt * 1e3 <= Date.now()) {
|
|
610
|
+
return deny(o, 401, "invalid_token", "The access token has expired");
|
|
611
|
+
}
|
|
612
|
+
const missing = missingScopes(auth, o.requiredScopes);
|
|
613
|
+
if (missing.length) {
|
|
614
|
+
const scope = [
|
|
615
|
+
.../* @__PURE__ */ new Set([
|
|
616
|
+
...auth.scopes ?? [],
|
|
617
|
+
...o.requiredScopes ?? []
|
|
618
|
+
])
|
|
619
|
+
];
|
|
620
|
+
return deny(o, 403, "insufficient_scope", `Missing scope: ${missing.join(" ")}`, scope);
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
ok: true,
|
|
624
|
+
auth: {
|
|
625
|
+
...auth,
|
|
626
|
+
scopes: auth.scopes ?? []
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
__name(authenticate, "authenticate");
|
|
631
|
+
function deny(o, status, error, description, scope) {
|
|
632
|
+
return {
|
|
633
|
+
ok: false,
|
|
634
|
+
status,
|
|
635
|
+
error,
|
|
636
|
+
description,
|
|
637
|
+
challenge: bearerChallenge(o.resource, {
|
|
638
|
+
error,
|
|
639
|
+
errorDescription: description,
|
|
640
|
+
scope
|
|
641
|
+
})
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
__name(deny, "deny");
|
|
645
|
+
function missingScopes(auth, required) {
|
|
646
|
+
if (!required?.length) return [];
|
|
647
|
+
const granted = new Set(auth?.scopes ?? []);
|
|
648
|
+
return required.filter((s) => !granted.has(s));
|
|
649
|
+
}
|
|
650
|
+
__name(missingScopes, "missingScopes");
|
|
651
|
+
|
|
420
652
|
// src/core/server.ts
|
|
421
653
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
422
654
|
"2025-11-25",
|
|
@@ -451,6 +683,7 @@ var McpServer = class {
|
|
|
451
683
|
loaders = [];
|
|
452
684
|
constructor(options) {
|
|
453
685
|
this.options = options;
|
|
686
|
+
if (options.oauth) validateOAuthOptions(options.oauth);
|
|
454
687
|
}
|
|
455
688
|
/** Register a tool. Throws on duplicate names. */
|
|
456
689
|
addTool(tool) {
|
|
@@ -585,6 +818,79 @@ var McpServer = class {
|
|
|
585
818
|
return (allowed ?? []).includes(origin);
|
|
586
819
|
}
|
|
587
820
|
/**
|
|
821
|
+
* Paths on which adapters serve the OAuth Protected Resource Metadata
|
|
822
|
+
* (empty when `oauth` is not configured). They must be public: no auth middleware.
|
|
823
|
+
*/
|
|
824
|
+
get oauthMetadataPaths() {
|
|
825
|
+
return this.options.oauth ? protectedResourceMetadataPaths(this.options.oauth.resource) : [];
|
|
826
|
+
}
|
|
827
|
+
/** The OAuth Protected Resource Metadata document (RFC 9728). */
|
|
828
|
+
async protectedResourceMetadata() {
|
|
829
|
+
const o = this.options.oauth;
|
|
830
|
+
if (!o) throw new Error("mcp-expose: the oauth option is not configured");
|
|
831
|
+
await this.load();
|
|
832
|
+
const scopes = o.scopesSupported ?? [
|
|
833
|
+
.../* @__PURE__ */ new Set([
|
|
834
|
+
...o.requiredScopes ?? [],
|
|
835
|
+
...[
|
|
836
|
+
...this.tools.values()
|
|
837
|
+
].flatMap((t) => t.scopes ?? [])
|
|
838
|
+
])
|
|
839
|
+
];
|
|
840
|
+
return {
|
|
841
|
+
resource: o.resource,
|
|
842
|
+
authorization_servers: o.authorizationServers,
|
|
843
|
+
...scopes.length ? {
|
|
844
|
+
scopes_supported: scopes
|
|
845
|
+
} : {},
|
|
846
|
+
bearer_methods_supported: [
|
|
847
|
+
"header"
|
|
848
|
+
],
|
|
849
|
+
...o.resourceName ? {
|
|
850
|
+
resource_name: o.resourceName
|
|
851
|
+
} : {},
|
|
852
|
+
...o.resourceDocumentation ? {
|
|
853
|
+
resource_documentation: o.resourceDocumentation
|
|
854
|
+
} : {},
|
|
855
|
+
...o.metadata
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/** HTTP handler for the metadata paths. Public and CORS-enabled so browser-based clients can read it. */
|
|
859
|
+
async handleMetadataHttp(req) {
|
|
860
|
+
const cors = {
|
|
861
|
+
"access-control-allow-origin": "*",
|
|
862
|
+
"access-control-allow-methods": "GET, OPTIONS"
|
|
863
|
+
};
|
|
864
|
+
const method = req.method.toUpperCase();
|
|
865
|
+
if (!this.options.oauth) return {
|
|
866
|
+
status: 404,
|
|
867
|
+
headers: {}
|
|
868
|
+
};
|
|
869
|
+
if (method === "OPTIONS") return {
|
|
870
|
+
status: 204,
|
|
871
|
+
headers: {
|
|
872
|
+
...cors,
|
|
873
|
+
"access-control-allow-headers": "*"
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
if (method !== "GET" && method !== "HEAD") return {
|
|
877
|
+
status: 405,
|
|
878
|
+
headers: {
|
|
879
|
+
allow: "GET, OPTIONS"
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
const body = JSON.stringify(await this.protectedResourceMetadata());
|
|
883
|
+
return {
|
|
884
|
+
status: 200,
|
|
885
|
+
headers: {
|
|
886
|
+
...cors,
|
|
887
|
+
"content-type": "application/json",
|
|
888
|
+
"cache-control": "public, max-age=3600"
|
|
889
|
+
},
|
|
890
|
+
body: method === "HEAD" ? void 0 : body
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
588
894
|
* Framework-neutral Streamable HTTP handler. Adapters convert their request
|
|
589
895
|
* into McpHttpRequest, call this, and write the McpHttpResponse back.
|
|
590
896
|
*/
|
|
@@ -599,6 +905,15 @@ var McpServer = class {
|
|
|
599
905
|
body: JSON.stringify(fail(null, INVALID_REQUEST, "Origin not allowed"))
|
|
600
906
|
};
|
|
601
907
|
}
|
|
908
|
+
const oauth = this.options.oauth;
|
|
909
|
+
if (oauth) {
|
|
910
|
+
const outcome = await authenticate(oauth, req.headers);
|
|
911
|
+
if (!outcome.ok) return authError(outcome);
|
|
912
|
+
ctx = {
|
|
913
|
+
...ctx,
|
|
914
|
+
auth: outcome.auth
|
|
915
|
+
};
|
|
916
|
+
}
|
|
602
917
|
if (req.method.toUpperCase() !== "POST") {
|
|
603
918
|
return {
|
|
604
919
|
status: 405,
|
|
@@ -619,6 +934,27 @@ var McpServer = class {
|
|
|
619
934
|
};
|
|
620
935
|
}
|
|
621
936
|
}
|
|
937
|
+
if (oauth) {
|
|
938
|
+
await this.load();
|
|
939
|
+
const calls = Array.isArray(body) ? body : [
|
|
940
|
+
body
|
|
941
|
+
];
|
|
942
|
+
for (const m of calls) {
|
|
943
|
+
if (m?.method !== "tools/call") continue;
|
|
944
|
+
const tool = this.tools.get(m.params?.name);
|
|
945
|
+
const missing = missingScopes(ctx.auth, tool?.scopes);
|
|
946
|
+
if (missing.length) {
|
|
947
|
+
const scope = [
|
|
948
|
+
.../* @__PURE__ */ new Set([
|
|
949
|
+
...ctx.auth?.scopes ?? [],
|
|
950
|
+
...oauth.requiredScopes ?? [],
|
|
951
|
+
...tool?.scopes ?? []
|
|
952
|
+
])
|
|
953
|
+
];
|
|
954
|
+
return authError(deny(oauth, 403, "insufficient_scope", `Tool "${tool.name}" needs scope: ${missing.join(" ")}`, scope));
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
622
958
|
const response = await this.handleMessage(body, ctx);
|
|
623
959
|
if (response === null) return {
|
|
624
960
|
status: 202,
|
|
@@ -631,6 +967,20 @@ var McpServer = class {
|
|
|
631
967
|
};
|
|
632
968
|
}
|
|
633
969
|
};
|
|
970
|
+
function authError(o) {
|
|
971
|
+
return {
|
|
972
|
+
status: o.status,
|
|
973
|
+
headers: {
|
|
974
|
+
"content-type": "application/json",
|
|
975
|
+
"www-authenticate": o.challenge
|
|
976
|
+
},
|
|
977
|
+
body: JSON.stringify({
|
|
978
|
+
error: o.error,
|
|
979
|
+
error_description: o.description
|
|
980
|
+
})
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
__name(authError, "authError");
|
|
634
984
|
|
|
635
985
|
// src/adonisjs/index.ts
|
|
636
986
|
var marked = /* @__PURE__ */ new Map();
|
|
@@ -697,6 +1047,16 @@ function mountMcp(router, options) {
|
|
|
697
1047
|
});
|
|
698
1048
|
route.as("mcp_expose.endpoint");
|
|
699
1049
|
options.configureRoute?.(route);
|
|
1050
|
+
server.oauthMetadataPaths.forEach((p, i) => {
|
|
1051
|
+
router.any(p, async (ctx) => {
|
|
1052
|
+
const out = await server.handleMetadataHttp({
|
|
1053
|
+
method: ctx.request.method()
|
|
1054
|
+
});
|
|
1055
|
+
ctx.response.status(out.status);
|
|
1056
|
+
for (const [k, v] of Object.entries(out.headers)) ctx.response.header(k, v);
|
|
1057
|
+
ctx.response.send(out.body ?? "");
|
|
1058
|
+
}).as(`mcp_expose.oauth_metadata_${i}`);
|
|
1059
|
+
});
|
|
700
1060
|
return server;
|
|
701
1061
|
}
|
|
702
1062
|
__name(mountMcp, "mountMcp");
|