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/express/index.cjs
CHANGED
|
@@ -118,6 +118,38 @@ function writeNodeResponse(res, out) {
|
|
|
118
118
|
}
|
|
119
119
|
__name(writeNodeResponse, "writeNodeResponse");
|
|
120
120
|
|
|
121
|
+
// src/core/result.ts
|
|
122
|
+
function isToolResult(value) {
|
|
123
|
+
const content = value?.content;
|
|
124
|
+
return !!value && typeof value === "object" && Array.isArray(content) && content.length > 0 && content.every((c) => !!c && typeof c === "object" && typeof c.type === "string");
|
|
125
|
+
}
|
|
126
|
+
__name(isToolResult, "isToolResult");
|
|
127
|
+
function toToolResult(value) {
|
|
128
|
+
if (isToolResult(value)) return value;
|
|
129
|
+
if (typeof value === "string") return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: value
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
};
|
|
137
|
+
const text = JSON.stringify(value ?? null);
|
|
138
|
+
const result = {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: "text",
|
|
142
|
+
text
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
147
|
+
result.structuredContent = value;
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
__name(toToolResult, "toToolResult");
|
|
152
|
+
|
|
121
153
|
// src/core/schema.ts
|
|
122
154
|
function isStandard(s) {
|
|
123
155
|
return typeof s === "object" && s !== null && "~standard" in s;
|
|
@@ -343,9 +375,7 @@ function splitArgs(args, mapping) {
|
|
|
343
375
|
}
|
|
344
376
|
__name(splitArgs, "splitArgs");
|
|
345
377
|
function responseToResult(res, maxChars) {
|
|
346
|
-
|
|
347
|
-
if (text.length > maxChars) text = `${text.slice(0, maxChars)}
|
|
348
|
-
\u2026[truncated ${text.length - maxChars} chars]`;
|
|
378
|
+
const text = truncate(res.body, maxChars);
|
|
349
379
|
const isError = res.status >= 400;
|
|
350
380
|
const result = {
|
|
351
381
|
content: [
|
|
@@ -370,6 +400,58 @@ ${text}` : text || `HTTP ${res.status}`
|
|
|
370
400
|
return result;
|
|
371
401
|
}
|
|
372
402
|
__name(responseToResult, "responseToResult");
|
|
403
|
+
function safeDecode(segment) {
|
|
404
|
+
try {
|
|
405
|
+
return decodeURIComponent(segment);
|
|
406
|
+
} catch {
|
|
407
|
+
return segment;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
__name(safeDecode, "safeDecode");
|
|
411
|
+
function truncate(text, maxChars) {
|
|
412
|
+
return text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
413
|
+
\u2026[truncated ${text.length - maxChars} chars]` : text;
|
|
414
|
+
}
|
|
415
|
+
__name(truncate, "truncate");
|
|
416
|
+
function toRouteToolResponse(res) {
|
|
417
|
+
let json;
|
|
418
|
+
if ((res.headers["content-type"] ?? "").includes("json") && res.body) {
|
|
419
|
+
try {
|
|
420
|
+
json = JSON.parse(res.body);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
...res,
|
|
426
|
+
json,
|
|
427
|
+
ok: res.status < 400
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
__name(toRouteToolResponse, "toRouteToolResponse");
|
|
431
|
+
function transformedResult(value, res, maxChars) {
|
|
432
|
+
const explicit = isToolResult(value);
|
|
433
|
+
const result = toToolResult(value);
|
|
434
|
+
if (!explicit && !res.ok) {
|
|
435
|
+
result.isError = true;
|
|
436
|
+
delete result.structuredContent;
|
|
437
|
+
}
|
|
438
|
+
let truncated = false;
|
|
439
|
+
const content = result.content.map((c) => {
|
|
440
|
+
if (c.type !== "text" || c.text.length <= maxChars) return c;
|
|
441
|
+
truncated = true;
|
|
442
|
+
return {
|
|
443
|
+
...c,
|
|
444
|
+
text: truncate(c.text, maxChars)
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
if (!truncated) return result;
|
|
448
|
+
const { structuredContent: _dropped, ...rest } = result;
|
|
449
|
+
return {
|
|
450
|
+
...rest,
|
|
451
|
+
content
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
__name(transformedResult, "transformedResult");
|
|
373
455
|
function pickForwardHeaders(ctx, names) {
|
|
374
456
|
const out = {};
|
|
375
457
|
for (const name of names) {
|
|
@@ -385,6 +467,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
385
467
|
const { schema, mapping } = buildInputSchema(route, opts);
|
|
386
468
|
const forward = serverOpts.forwardHeaders ?? DEFAULT_FORWARD_HEADERS;
|
|
387
469
|
const maxChars = serverOpts.maxResponseChars ?? 1e5;
|
|
470
|
+
const transform = opts.transformResponse ?? serverOpts.transformResponse;
|
|
388
471
|
return {
|
|
389
472
|
name,
|
|
390
473
|
title: opts.title,
|
|
@@ -394,6 +477,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
394
477
|
...defaultAnnotations(method),
|
|
395
478
|
...opts.annotations
|
|
396
479
|
},
|
|
480
|
+
scopes: opts.scopes,
|
|
397
481
|
validate: toValidator(opts.input),
|
|
398
482
|
async handler(args, ctx) {
|
|
399
483
|
const missing = opts.input ? void 0 : checkRequired(schema, args);
|
|
@@ -429,19 +513,167 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
429
513
|
]
|
|
430
514
|
};
|
|
431
515
|
}
|
|
432
|
-
const
|
|
516
|
+
const request = {
|
|
433
517
|
method,
|
|
434
518
|
path,
|
|
435
519
|
query,
|
|
436
520
|
body,
|
|
437
521
|
headers
|
|
438
|
-
}
|
|
439
|
-
|
|
522
|
+
};
|
|
523
|
+
const res = await dispatch(request, ctx);
|
|
524
|
+
if (!transform) return responseToResult(res, maxChars);
|
|
525
|
+
const { "content-type": _ct, ...baseHeaders } = headers;
|
|
526
|
+
const hookCtx = {
|
|
527
|
+
tool: name,
|
|
528
|
+
args,
|
|
529
|
+
request,
|
|
530
|
+
toolContext: ctx,
|
|
531
|
+
defaultResult: /* @__PURE__ */ __name(() => responseToResult(res, maxChars), "defaultResult"),
|
|
532
|
+
async callRoute(r) {
|
|
533
|
+
if (!r.path.startsWith("/")) {
|
|
534
|
+
throw new Error(`callRoute: path must start with "/", got ${JSON.stringify(r.path)}`);
|
|
535
|
+
}
|
|
536
|
+
const params = r.params ?? {};
|
|
537
|
+
const missing2 = pathParams(r.path).filter((p) => params[p] === void 0 && !r.path.includes(`:${p}?`));
|
|
538
|
+
if (missing2.length) throw new Error(`callRoute: missing params for ${r.path}: ${missing2.join(", ")}`);
|
|
539
|
+
const subPath = interpolatePath(r.path, params);
|
|
540
|
+
if (subPath.split("/").some((seg) => [
|
|
541
|
+
".",
|
|
542
|
+
".."
|
|
543
|
+
].includes(safeDecode(seg)))) {
|
|
544
|
+
throw new Error(`callRoute: path must not contain "." or ".." segments: ${JSON.stringify(subPath)}`);
|
|
545
|
+
}
|
|
546
|
+
const sub = {
|
|
547
|
+
method: (r.method ?? "GET").toUpperCase(),
|
|
548
|
+
path: subPath,
|
|
549
|
+
query: r.query ?? {},
|
|
550
|
+
body: r.body,
|
|
551
|
+
headers: {
|
|
552
|
+
...baseHeaders,
|
|
553
|
+
...r.headers
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
if (sub.body !== void 0) sub.headers["content-type"] = "application/json";
|
|
557
|
+
return toRouteToolResponse(await dispatch(sub, ctx));
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const response = toRouteToolResponse(res);
|
|
561
|
+
const value = await transform(response, hookCtx);
|
|
562
|
+
return value === void 0 ? responseToResult(res, maxChars) : transformedResult(value, response, maxChars);
|
|
440
563
|
}
|
|
441
564
|
};
|
|
442
565
|
}
|
|
443
566
|
__name(createRouteTool, "createRouteTool");
|
|
444
567
|
|
|
568
|
+
// src/core/oauth.ts
|
|
569
|
+
var PROTECTED_RESOURCE_WELL_KNOWN = "/.well-known/oauth-protected-resource";
|
|
570
|
+
function protectedResourceMetadataPaths(resource) {
|
|
571
|
+
const pathname = new URL(resource).pathname.replace(/\/+$/, "");
|
|
572
|
+
return pathname ? [
|
|
573
|
+
PROTECTED_RESOURCE_WELL_KNOWN + pathname,
|
|
574
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
575
|
+
] : [
|
|
576
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
577
|
+
];
|
|
578
|
+
}
|
|
579
|
+
__name(protectedResourceMetadataPaths, "protectedResourceMetadataPaths");
|
|
580
|
+
function protectedResourceMetadataUrl(resource) {
|
|
581
|
+
return new URL(protectedResourceMetadataPaths(resource)[0], resource).href;
|
|
582
|
+
}
|
|
583
|
+
__name(protectedResourceMetadataUrl, "protectedResourceMetadataUrl");
|
|
584
|
+
function validateOAuthOptions(o) {
|
|
585
|
+
let url;
|
|
586
|
+
try {
|
|
587
|
+
url = new URL(o.resource);
|
|
588
|
+
} catch {
|
|
589
|
+
throw new Error(`mcp-expose: oauth.resource must be an absolute URL, got ${JSON.stringify(o.resource)}`);
|
|
590
|
+
}
|
|
591
|
+
if (url.hash) throw new Error("mcp-expose: oauth.resource must not contain a fragment");
|
|
592
|
+
if (!o.authorizationServers?.length) throw new Error("mcp-expose: oauth.authorizationServers must not be empty");
|
|
593
|
+
if (typeof o.verifyToken !== "function") throw new Error("mcp-expose: oauth.verifyToken is required");
|
|
594
|
+
}
|
|
595
|
+
__name(validateOAuthOptions, "validateOAuthOptions");
|
|
596
|
+
var quote = /* @__PURE__ */ __name((v) => `"${v.replace(/["\\]/g, "\\$&")}"`, "quote");
|
|
597
|
+
function bearerChallenge(resource, params = {}) {
|
|
598
|
+
const parts = [];
|
|
599
|
+
if (params.error) parts.push(`error=${quote(params.error)}`);
|
|
600
|
+
if (params.errorDescription) parts.push(`error_description=${quote(params.errorDescription)}`);
|
|
601
|
+
if (params.scope?.length) parts.push(`scope=${quote(params.scope.join(" "))}`);
|
|
602
|
+
parts.push(`resource_metadata=${quote(protectedResourceMetadataUrl(resource))}`);
|
|
603
|
+
return `Bearer ${parts.join(", ")}`;
|
|
604
|
+
}
|
|
605
|
+
__name(bearerChallenge, "bearerChallenge");
|
|
606
|
+
function bearerToken(headers) {
|
|
607
|
+
const m = /^Bearer[ ]+([^\s,]+)\s*$/i.exec(headers.authorization ?? "");
|
|
608
|
+
return m?.[1];
|
|
609
|
+
}
|
|
610
|
+
__name(bearerToken, "bearerToken");
|
|
611
|
+
async function authenticate(o, headers) {
|
|
612
|
+
const token = bearerToken(headers);
|
|
613
|
+
if (!token) {
|
|
614
|
+
return {
|
|
615
|
+
ok: false,
|
|
616
|
+
status: 401,
|
|
617
|
+
error: "unauthorized",
|
|
618
|
+
description: "Authorization required",
|
|
619
|
+
challenge: bearerChallenge(o.resource, {
|
|
620
|
+
scope: o.requiredScopes
|
|
621
|
+
})
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
let auth;
|
|
625
|
+
try {
|
|
626
|
+
auth = await o.verifyToken(token, {
|
|
627
|
+
resource: o.resource,
|
|
628
|
+
headers
|
|
629
|
+
});
|
|
630
|
+
} catch {
|
|
631
|
+
auth = void 0;
|
|
632
|
+
}
|
|
633
|
+
if (!auth) return deny(o, 401, "invalid_token", "The access token is invalid");
|
|
634
|
+
if (auth.expiresAt !== void 0 && auth.expiresAt * 1e3 <= Date.now()) {
|
|
635
|
+
return deny(o, 401, "invalid_token", "The access token has expired");
|
|
636
|
+
}
|
|
637
|
+
const missing = missingScopes(auth, o.requiredScopes);
|
|
638
|
+
if (missing.length) {
|
|
639
|
+
const scope = [
|
|
640
|
+
.../* @__PURE__ */ new Set([
|
|
641
|
+
...auth.scopes ?? [],
|
|
642
|
+
...o.requiredScopes ?? []
|
|
643
|
+
])
|
|
644
|
+
];
|
|
645
|
+
return deny(o, 403, "insufficient_scope", `Missing scope: ${missing.join(" ")}`, scope);
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
ok: true,
|
|
649
|
+
auth: {
|
|
650
|
+
...auth,
|
|
651
|
+
scopes: auth.scopes ?? []
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
__name(authenticate, "authenticate");
|
|
656
|
+
function deny(o, status, error, description, scope) {
|
|
657
|
+
return {
|
|
658
|
+
ok: false,
|
|
659
|
+
status,
|
|
660
|
+
error,
|
|
661
|
+
description,
|
|
662
|
+
challenge: bearerChallenge(o.resource, {
|
|
663
|
+
error,
|
|
664
|
+
errorDescription: description,
|
|
665
|
+
scope
|
|
666
|
+
})
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
__name(deny, "deny");
|
|
670
|
+
function missingScopes(auth, required) {
|
|
671
|
+
if (!required?.length) return [];
|
|
672
|
+
const granted = new Set(auth?.scopes ?? []);
|
|
673
|
+
return required.filter((s) => !granted.has(s));
|
|
674
|
+
}
|
|
675
|
+
__name(missingScopes, "missingScopes");
|
|
676
|
+
|
|
445
677
|
// src/core/server.ts
|
|
446
678
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
447
679
|
"2025-11-25",
|
|
@@ -476,6 +708,7 @@ var McpServer = class {
|
|
|
476
708
|
loaders = [];
|
|
477
709
|
constructor(options) {
|
|
478
710
|
this.options = options;
|
|
711
|
+
if (options.oauth) validateOAuthOptions(options.oauth);
|
|
479
712
|
}
|
|
480
713
|
/** Register a tool. Throws on duplicate names. */
|
|
481
714
|
addTool(tool) {
|
|
@@ -610,6 +843,79 @@ var McpServer = class {
|
|
|
610
843
|
return (allowed ?? []).includes(origin);
|
|
611
844
|
}
|
|
612
845
|
/**
|
|
846
|
+
* Paths on which adapters serve the OAuth Protected Resource Metadata
|
|
847
|
+
* (empty when `oauth` is not configured). They must be public: no auth middleware.
|
|
848
|
+
*/
|
|
849
|
+
get oauthMetadataPaths() {
|
|
850
|
+
return this.options.oauth ? protectedResourceMetadataPaths(this.options.oauth.resource) : [];
|
|
851
|
+
}
|
|
852
|
+
/** The OAuth Protected Resource Metadata document (RFC 9728). */
|
|
853
|
+
async protectedResourceMetadata() {
|
|
854
|
+
const o = this.options.oauth;
|
|
855
|
+
if (!o) throw new Error("mcp-expose: the oauth option is not configured");
|
|
856
|
+
await this.load();
|
|
857
|
+
const scopes = o.scopesSupported ?? [
|
|
858
|
+
.../* @__PURE__ */ new Set([
|
|
859
|
+
...o.requiredScopes ?? [],
|
|
860
|
+
...[
|
|
861
|
+
...this.tools.values()
|
|
862
|
+
].flatMap((t) => t.scopes ?? [])
|
|
863
|
+
])
|
|
864
|
+
];
|
|
865
|
+
return {
|
|
866
|
+
resource: o.resource,
|
|
867
|
+
authorization_servers: o.authorizationServers,
|
|
868
|
+
...scopes.length ? {
|
|
869
|
+
scopes_supported: scopes
|
|
870
|
+
} : {},
|
|
871
|
+
bearer_methods_supported: [
|
|
872
|
+
"header"
|
|
873
|
+
],
|
|
874
|
+
...o.resourceName ? {
|
|
875
|
+
resource_name: o.resourceName
|
|
876
|
+
} : {},
|
|
877
|
+
...o.resourceDocumentation ? {
|
|
878
|
+
resource_documentation: o.resourceDocumentation
|
|
879
|
+
} : {},
|
|
880
|
+
...o.metadata
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
/** HTTP handler for the metadata paths. Public and CORS-enabled so browser-based clients can read it. */
|
|
884
|
+
async handleMetadataHttp(req) {
|
|
885
|
+
const cors = {
|
|
886
|
+
"access-control-allow-origin": "*",
|
|
887
|
+
"access-control-allow-methods": "GET, OPTIONS"
|
|
888
|
+
};
|
|
889
|
+
const method = req.method.toUpperCase();
|
|
890
|
+
if (!this.options.oauth) return {
|
|
891
|
+
status: 404,
|
|
892
|
+
headers: {}
|
|
893
|
+
};
|
|
894
|
+
if (method === "OPTIONS") return {
|
|
895
|
+
status: 204,
|
|
896
|
+
headers: {
|
|
897
|
+
...cors,
|
|
898
|
+
"access-control-allow-headers": "*"
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
if (method !== "GET" && method !== "HEAD") return {
|
|
902
|
+
status: 405,
|
|
903
|
+
headers: {
|
|
904
|
+
allow: "GET, OPTIONS"
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const body = JSON.stringify(await this.protectedResourceMetadata());
|
|
908
|
+
return {
|
|
909
|
+
status: 200,
|
|
910
|
+
headers: {
|
|
911
|
+
...cors,
|
|
912
|
+
"content-type": "application/json",
|
|
913
|
+
"cache-control": "public, max-age=3600"
|
|
914
|
+
},
|
|
915
|
+
body: method === "HEAD" ? void 0 : body
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
613
919
|
* Framework-neutral Streamable HTTP handler. Adapters convert their request
|
|
614
920
|
* into McpHttpRequest, call this, and write the McpHttpResponse back.
|
|
615
921
|
*/
|
|
@@ -624,6 +930,15 @@ var McpServer = class {
|
|
|
624
930
|
body: JSON.stringify(fail(null, INVALID_REQUEST, "Origin not allowed"))
|
|
625
931
|
};
|
|
626
932
|
}
|
|
933
|
+
const oauth = this.options.oauth;
|
|
934
|
+
if (oauth) {
|
|
935
|
+
const outcome = await authenticate(oauth, req.headers);
|
|
936
|
+
if (!outcome.ok) return authError(outcome);
|
|
937
|
+
ctx = {
|
|
938
|
+
...ctx,
|
|
939
|
+
auth: outcome.auth
|
|
940
|
+
};
|
|
941
|
+
}
|
|
627
942
|
if (req.method.toUpperCase() !== "POST") {
|
|
628
943
|
return {
|
|
629
944
|
status: 405,
|
|
@@ -644,6 +959,27 @@ var McpServer = class {
|
|
|
644
959
|
};
|
|
645
960
|
}
|
|
646
961
|
}
|
|
962
|
+
if (oauth) {
|
|
963
|
+
await this.load();
|
|
964
|
+
const calls = Array.isArray(body) ? body : [
|
|
965
|
+
body
|
|
966
|
+
];
|
|
967
|
+
for (const m of calls) {
|
|
968
|
+
if (m?.method !== "tools/call") continue;
|
|
969
|
+
const tool = this.tools.get(m.params?.name);
|
|
970
|
+
const missing = missingScopes(ctx.auth, tool?.scopes);
|
|
971
|
+
if (missing.length) {
|
|
972
|
+
const scope = [
|
|
973
|
+
.../* @__PURE__ */ new Set([
|
|
974
|
+
...ctx.auth?.scopes ?? [],
|
|
975
|
+
...oauth.requiredScopes ?? [],
|
|
976
|
+
...tool?.scopes ?? []
|
|
977
|
+
])
|
|
978
|
+
];
|
|
979
|
+
return authError(deny(oauth, 403, "insufficient_scope", `Tool "${tool.name}" needs scope: ${missing.join(" ")}`, scope));
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
647
983
|
const response = await this.handleMessage(body, ctx);
|
|
648
984
|
if (response === null) return {
|
|
649
985
|
status: 202,
|
|
@@ -656,6 +992,20 @@ var McpServer = class {
|
|
|
656
992
|
};
|
|
657
993
|
}
|
|
658
994
|
};
|
|
995
|
+
function authError(o) {
|
|
996
|
+
return {
|
|
997
|
+
status: o.status,
|
|
998
|
+
headers: {
|
|
999
|
+
"content-type": "application/json",
|
|
1000
|
+
"www-authenticate": o.challenge
|
|
1001
|
+
},
|
|
1002
|
+
body: JSON.stringify({
|
|
1003
|
+
error: o.error,
|
|
1004
|
+
error_description: o.description
|
|
1005
|
+
})
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
__name(authError, "authError");
|
|
659
1009
|
|
|
660
1010
|
// src/express/discover.ts
|
|
661
1011
|
function collectExpress3(routes, out) {
|
|
@@ -777,7 +1127,14 @@ function mountMcp(app, options) {
|
|
|
777
1127
|
writeNodeResponse(res, out);
|
|
778
1128
|
})().catch(next);
|
|
779
1129
|
}, "handler");
|
|
780
|
-
|
|
1130
|
+
const all = app.all;
|
|
1131
|
+
all.call(app, path, ...options.middleware ?? [], handler);
|
|
1132
|
+
const metadata = /* @__PURE__ */ __name((req, res, next) => {
|
|
1133
|
+
server.handleMetadataHttp({
|
|
1134
|
+
method: req.method ?? "GET"
|
|
1135
|
+
}).then((out) => writeNodeResponse(res, out)).catch(next);
|
|
1136
|
+
}, "metadata");
|
|
1137
|
+
for (const p of server.oauthMetadataPaths) all.call(app, p, metadata);
|
|
781
1138
|
return server;
|
|
782
1139
|
}
|
|
783
1140
|
__name(mountMcp, "mountMcp");
|