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.js
CHANGED
|
@@ -75,6 +75,38 @@ function loopbackBaseUrl(req) {
|
|
|
75
75
|
}
|
|
76
76
|
__name(loopbackBaseUrl, "loopbackBaseUrl");
|
|
77
77
|
|
|
78
|
+
// src/core/result.ts
|
|
79
|
+
function isToolResult(value) {
|
|
80
|
+
const content = value?.content;
|
|
81
|
+
return !!value && typeof value === "object" && Array.isArray(content) && content.length > 0 && content.every((c) => !!c && typeof c === "object" && typeof c.type === "string");
|
|
82
|
+
}
|
|
83
|
+
__name(isToolResult, "isToolResult");
|
|
84
|
+
function toToolResult(value) {
|
|
85
|
+
if (isToolResult(value)) return value;
|
|
86
|
+
if (typeof value === "string") return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: value
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
const text = JSON.stringify(value ?? null);
|
|
95
|
+
const result = {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
104
|
+
result.structuredContent = value;
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
__name(toToolResult, "toToolResult");
|
|
109
|
+
|
|
78
110
|
// src/core/schema.ts
|
|
79
111
|
function isStandard(s) {
|
|
80
112
|
return typeof s === "object" && s !== null && "~standard" in s;
|
|
@@ -295,9 +327,7 @@ function splitArgs(args, mapping) {
|
|
|
295
327
|
}
|
|
296
328
|
__name(splitArgs, "splitArgs");
|
|
297
329
|
function responseToResult(res, maxChars) {
|
|
298
|
-
|
|
299
|
-
if (text.length > maxChars) text = `${text.slice(0, maxChars)}
|
|
300
|
-
\u2026[truncated ${text.length - maxChars} chars]`;
|
|
330
|
+
const text = truncate(res.body, maxChars);
|
|
301
331
|
const isError = res.status >= 400;
|
|
302
332
|
const result = {
|
|
303
333
|
content: [
|
|
@@ -322,6 +352,58 @@ ${text}` : text || `HTTP ${res.status}`
|
|
|
322
352
|
return result;
|
|
323
353
|
}
|
|
324
354
|
__name(responseToResult, "responseToResult");
|
|
355
|
+
function safeDecode(segment) {
|
|
356
|
+
try {
|
|
357
|
+
return decodeURIComponent(segment);
|
|
358
|
+
} catch {
|
|
359
|
+
return segment;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
__name(safeDecode, "safeDecode");
|
|
363
|
+
function truncate(text, maxChars) {
|
|
364
|
+
return text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
365
|
+
\u2026[truncated ${text.length - maxChars} chars]` : text;
|
|
366
|
+
}
|
|
367
|
+
__name(truncate, "truncate");
|
|
368
|
+
function toRouteToolResponse(res) {
|
|
369
|
+
let json;
|
|
370
|
+
if ((res.headers["content-type"] ?? "").includes("json") && res.body) {
|
|
371
|
+
try {
|
|
372
|
+
json = JSON.parse(res.body);
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
...res,
|
|
378
|
+
json,
|
|
379
|
+
ok: res.status < 400
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
__name(toRouteToolResponse, "toRouteToolResponse");
|
|
383
|
+
function transformedResult(value, res, maxChars) {
|
|
384
|
+
const explicit = isToolResult(value);
|
|
385
|
+
const result = toToolResult(value);
|
|
386
|
+
if (!explicit && !res.ok) {
|
|
387
|
+
result.isError = true;
|
|
388
|
+
delete result.structuredContent;
|
|
389
|
+
}
|
|
390
|
+
let truncated = false;
|
|
391
|
+
const content = result.content.map((c) => {
|
|
392
|
+
if (c.type !== "text" || c.text.length <= maxChars) return c;
|
|
393
|
+
truncated = true;
|
|
394
|
+
return {
|
|
395
|
+
...c,
|
|
396
|
+
text: truncate(c.text, maxChars)
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
if (!truncated) return result;
|
|
400
|
+
const { structuredContent: _dropped, ...rest } = result;
|
|
401
|
+
return {
|
|
402
|
+
...rest,
|
|
403
|
+
content
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
__name(transformedResult, "transformedResult");
|
|
325
407
|
function pickForwardHeaders(ctx, names) {
|
|
326
408
|
const out = {};
|
|
327
409
|
for (const name of names) {
|
|
@@ -337,6 +419,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
337
419
|
const { schema, mapping } = buildInputSchema(route, opts);
|
|
338
420
|
const forward = serverOpts.forwardHeaders ?? DEFAULT_FORWARD_HEADERS;
|
|
339
421
|
const maxChars = serverOpts.maxResponseChars ?? 1e5;
|
|
422
|
+
const transform = opts.transformResponse ?? serverOpts.transformResponse;
|
|
340
423
|
return {
|
|
341
424
|
name,
|
|
342
425
|
title: opts.title,
|
|
@@ -346,6 +429,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
346
429
|
...defaultAnnotations(method),
|
|
347
430
|
...opts.annotations
|
|
348
431
|
},
|
|
432
|
+
scopes: opts.scopes,
|
|
349
433
|
validate: toValidator(opts.input),
|
|
350
434
|
async handler(args, ctx) {
|
|
351
435
|
const missing = opts.input ? void 0 : checkRequired(schema, args);
|
|
@@ -381,19 +465,167 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
381
465
|
]
|
|
382
466
|
};
|
|
383
467
|
}
|
|
384
|
-
const
|
|
468
|
+
const request = {
|
|
385
469
|
method,
|
|
386
470
|
path,
|
|
387
471
|
query,
|
|
388
472
|
body,
|
|
389
473
|
headers
|
|
390
|
-
}
|
|
391
|
-
|
|
474
|
+
};
|
|
475
|
+
const res = await dispatch(request, ctx);
|
|
476
|
+
if (!transform) return responseToResult(res, maxChars);
|
|
477
|
+
const { "content-type": _ct, ...baseHeaders } = headers;
|
|
478
|
+
const hookCtx = {
|
|
479
|
+
tool: name,
|
|
480
|
+
args,
|
|
481
|
+
request,
|
|
482
|
+
toolContext: ctx,
|
|
483
|
+
defaultResult: /* @__PURE__ */ __name(() => responseToResult(res, maxChars), "defaultResult"),
|
|
484
|
+
async callRoute(r) {
|
|
485
|
+
if (!r.path.startsWith("/")) {
|
|
486
|
+
throw new Error(`callRoute: path must start with "/", got ${JSON.stringify(r.path)}`);
|
|
487
|
+
}
|
|
488
|
+
const params = r.params ?? {};
|
|
489
|
+
const missing2 = pathParams(r.path).filter((p) => params[p] === void 0 && !r.path.includes(`:${p}?`));
|
|
490
|
+
if (missing2.length) throw new Error(`callRoute: missing params for ${r.path}: ${missing2.join(", ")}`);
|
|
491
|
+
const subPath = interpolatePath(r.path, params);
|
|
492
|
+
if (subPath.split("/").some((seg) => [
|
|
493
|
+
".",
|
|
494
|
+
".."
|
|
495
|
+
].includes(safeDecode(seg)))) {
|
|
496
|
+
throw new Error(`callRoute: path must not contain "." or ".." segments: ${JSON.stringify(subPath)}`);
|
|
497
|
+
}
|
|
498
|
+
const sub = {
|
|
499
|
+
method: (r.method ?? "GET").toUpperCase(),
|
|
500
|
+
path: subPath,
|
|
501
|
+
query: r.query ?? {},
|
|
502
|
+
body: r.body,
|
|
503
|
+
headers: {
|
|
504
|
+
...baseHeaders,
|
|
505
|
+
...r.headers
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
if (sub.body !== void 0) sub.headers["content-type"] = "application/json";
|
|
509
|
+
return toRouteToolResponse(await dispatch(sub, ctx));
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const response = toRouteToolResponse(res);
|
|
513
|
+
const value = await transform(response, hookCtx);
|
|
514
|
+
return value === void 0 ? responseToResult(res, maxChars) : transformedResult(value, response, maxChars);
|
|
392
515
|
}
|
|
393
516
|
};
|
|
394
517
|
}
|
|
395
518
|
__name(createRouteTool, "createRouteTool");
|
|
396
519
|
|
|
520
|
+
// src/core/oauth.ts
|
|
521
|
+
var PROTECTED_RESOURCE_WELL_KNOWN = "/.well-known/oauth-protected-resource";
|
|
522
|
+
function protectedResourceMetadataPaths(resource) {
|
|
523
|
+
const pathname = new URL(resource).pathname.replace(/\/+$/, "");
|
|
524
|
+
return pathname ? [
|
|
525
|
+
PROTECTED_RESOURCE_WELL_KNOWN + pathname,
|
|
526
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
527
|
+
] : [
|
|
528
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
529
|
+
];
|
|
530
|
+
}
|
|
531
|
+
__name(protectedResourceMetadataPaths, "protectedResourceMetadataPaths");
|
|
532
|
+
function protectedResourceMetadataUrl(resource) {
|
|
533
|
+
return new URL(protectedResourceMetadataPaths(resource)[0], resource).href;
|
|
534
|
+
}
|
|
535
|
+
__name(protectedResourceMetadataUrl, "protectedResourceMetadataUrl");
|
|
536
|
+
function validateOAuthOptions(o) {
|
|
537
|
+
let url;
|
|
538
|
+
try {
|
|
539
|
+
url = new URL(o.resource);
|
|
540
|
+
} catch {
|
|
541
|
+
throw new Error(`mcp-expose: oauth.resource must be an absolute URL, got ${JSON.stringify(o.resource)}`);
|
|
542
|
+
}
|
|
543
|
+
if (url.hash) throw new Error("mcp-expose: oauth.resource must not contain a fragment");
|
|
544
|
+
if (!o.authorizationServers?.length) throw new Error("mcp-expose: oauth.authorizationServers must not be empty");
|
|
545
|
+
if (typeof o.verifyToken !== "function") throw new Error("mcp-expose: oauth.verifyToken is required");
|
|
546
|
+
}
|
|
547
|
+
__name(validateOAuthOptions, "validateOAuthOptions");
|
|
548
|
+
var quote = /* @__PURE__ */ __name((v) => `"${v.replace(/["\\]/g, "\\$&")}"`, "quote");
|
|
549
|
+
function bearerChallenge(resource, params = {}) {
|
|
550
|
+
const parts = [];
|
|
551
|
+
if (params.error) parts.push(`error=${quote(params.error)}`);
|
|
552
|
+
if (params.errorDescription) parts.push(`error_description=${quote(params.errorDescription)}`);
|
|
553
|
+
if (params.scope?.length) parts.push(`scope=${quote(params.scope.join(" "))}`);
|
|
554
|
+
parts.push(`resource_metadata=${quote(protectedResourceMetadataUrl(resource))}`);
|
|
555
|
+
return `Bearer ${parts.join(", ")}`;
|
|
556
|
+
}
|
|
557
|
+
__name(bearerChallenge, "bearerChallenge");
|
|
558
|
+
function bearerToken(headers) {
|
|
559
|
+
const m = /^Bearer[ ]+([^\s,]+)\s*$/i.exec(headers.authorization ?? "");
|
|
560
|
+
return m?.[1];
|
|
561
|
+
}
|
|
562
|
+
__name(bearerToken, "bearerToken");
|
|
563
|
+
async function authenticate(o, headers) {
|
|
564
|
+
const token = bearerToken(headers);
|
|
565
|
+
if (!token) {
|
|
566
|
+
return {
|
|
567
|
+
ok: false,
|
|
568
|
+
status: 401,
|
|
569
|
+
error: "unauthorized",
|
|
570
|
+
description: "Authorization required",
|
|
571
|
+
challenge: bearerChallenge(o.resource, {
|
|
572
|
+
scope: o.requiredScopes
|
|
573
|
+
})
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
let auth;
|
|
577
|
+
try {
|
|
578
|
+
auth = await o.verifyToken(token, {
|
|
579
|
+
resource: o.resource,
|
|
580
|
+
headers
|
|
581
|
+
});
|
|
582
|
+
} catch {
|
|
583
|
+
auth = void 0;
|
|
584
|
+
}
|
|
585
|
+
if (!auth) return deny(o, 401, "invalid_token", "The access token is invalid");
|
|
586
|
+
if (auth.expiresAt !== void 0 && auth.expiresAt * 1e3 <= Date.now()) {
|
|
587
|
+
return deny(o, 401, "invalid_token", "The access token has expired");
|
|
588
|
+
}
|
|
589
|
+
const missing = missingScopes(auth, o.requiredScopes);
|
|
590
|
+
if (missing.length) {
|
|
591
|
+
const scope = [
|
|
592
|
+
.../* @__PURE__ */ new Set([
|
|
593
|
+
...auth.scopes ?? [],
|
|
594
|
+
...o.requiredScopes ?? []
|
|
595
|
+
])
|
|
596
|
+
];
|
|
597
|
+
return deny(o, 403, "insufficient_scope", `Missing scope: ${missing.join(" ")}`, scope);
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
ok: true,
|
|
601
|
+
auth: {
|
|
602
|
+
...auth,
|
|
603
|
+
scopes: auth.scopes ?? []
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
__name(authenticate, "authenticate");
|
|
608
|
+
function deny(o, status, error, description, scope) {
|
|
609
|
+
return {
|
|
610
|
+
ok: false,
|
|
611
|
+
status,
|
|
612
|
+
error,
|
|
613
|
+
description,
|
|
614
|
+
challenge: bearerChallenge(o.resource, {
|
|
615
|
+
error,
|
|
616
|
+
errorDescription: description,
|
|
617
|
+
scope
|
|
618
|
+
})
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
__name(deny, "deny");
|
|
622
|
+
function missingScopes(auth, required) {
|
|
623
|
+
if (!required?.length) return [];
|
|
624
|
+
const granted = new Set(auth?.scopes ?? []);
|
|
625
|
+
return required.filter((s) => !granted.has(s));
|
|
626
|
+
}
|
|
627
|
+
__name(missingScopes, "missingScopes");
|
|
628
|
+
|
|
397
629
|
// src/core/server.ts
|
|
398
630
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
399
631
|
"2025-11-25",
|
|
@@ -428,6 +660,7 @@ var McpServer = class {
|
|
|
428
660
|
loaders = [];
|
|
429
661
|
constructor(options) {
|
|
430
662
|
this.options = options;
|
|
663
|
+
if (options.oauth) validateOAuthOptions(options.oauth);
|
|
431
664
|
}
|
|
432
665
|
/** Register a tool. Throws on duplicate names. */
|
|
433
666
|
addTool(tool) {
|
|
@@ -562,6 +795,79 @@ var McpServer = class {
|
|
|
562
795
|
return (allowed ?? []).includes(origin);
|
|
563
796
|
}
|
|
564
797
|
/**
|
|
798
|
+
* Paths on which adapters serve the OAuth Protected Resource Metadata
|
|
799
|
+
* (empty when `oauth` is not configured). They must be public: no auth middleware.
|
|
800
|
+
*/
|
|
801
|
+
get oauthMetadataPaths() {
|
|
802
|
+
return this.options.oauth ? protectedResourceMetadataPaths(this.options.oauth.resource) : [];
|
|
803
|
+
}
|
|
804
|
+
/** The OAuth Protected Resource Metadata document (RFC 9728). */
|
|
805
|
+
async protectedResourceMetadata() {
|
|
806
|
+
const o = this.options.oauth;
|
|
807
|
+
if (!o) throw new Error("mcp-expose: the oauth option is not configured");
|
|
808
|
+
await this.load();
|
|
809
|
+
const scopes = o.scopesSupported ?? [
|
|
810
|
+
.../* @__PURE__ */ new Set([
|
|
811
|
+
...o.requiredScopes ?? [],
|
|
812
|
+
...[
|
|
813
|
+
...this.tools.values()
|
|
814
|
+
].flatMap((t) => t.scopes ?? [])
|
|
815
|
+
])
|
|
816
|
+
];
|
|
817
|
+
return {
|
|
818
|
+
resource: o.resource,
|
|
819
|
+
authorization_servers: o.authorizationServers,
|
|
820
|
+
...scopes.length ? {
|
|
821
|
+
scopes_supported: scopes
|
|
822
|
+
} : {},
|
|
823
|
+
bearer_methods_supported: [
|
|
824
|
+
"header"
|
|
825
|
+
],
|
|
826
|
+
...o.resourceName ? {
|
|
827
|
+
resource_name: o.resourceName
|
|
828
|
+
} : {},
|
|
829
|
+
...o.resourceDocumentation ? {
|
|
830
|
+
resource_documentation: o.resourceDocumentation
|
|
831
|
+
} : {},
|
|
832
|
+
...o.metadata
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
/** HTTP handler for the metadata paths. Public and CORS-enabled so browser-based clients can read it. */
|
|
836
|
+
async handleMetadataHttp(req) {
|
|
837
|
+
const cors = {
|
|
838
|
+
"access-control-allow-origin": "*",
|
|
839
|
+
"access-control-allow-methods": "GET, OPTIONS"
|
|
840
|
+
};
|
|
841
|
+
const method = req.method.toUpperCase();
|
|
842
|
+
if (!this.options.oauth) return {
|
|
843
|
+
status: 404,
|
|
844
|
+
headers: {}
|
|
845
|
+
};
|
|
846
|
+
if (method === "OPTIONS") return {
|
|
847
|
+
status: 204,
|
|
848
|
+
headers: {
|
|
849
|
+
...cors,
|
|
850
|
+
"access-control-allow-headers": "*"
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
if (method !== "GET" && method !== "HEAD") return {
|
|
854
|
+
status: 405,
|
|
855
|
+
headers: {
|
|
856
|
+
allow: "GET, OPTIONS"
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
const body = JSON.stringify(await this.protectedResourceMetadata());
|
|
860
|
+
return {
|
|
861
|
+
status: 200,
|
|
862
|
+
headers: {
|
|
863
|
+
...cors,
|
|
864
|
+
"content-type": "application/json",
|
|
865
|
+
"cache-control": "public, max-age=3600"
|
|
866
|
+
},
|
|
867
|
+
body: method === "HEAD" ? void 0 : body
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
565
871
|
* Framework-neutral Streamable HTTP handler. Adapters convert their request
|
|
566
872
|
* into McpHttpRequest, call this, and write the McpHttpResponse back.
|
|
567
873
|
*/
|
|
@@ -576,6 +882,15 @@ var McpServer = class {
|
|
|
576
882
|
body: JSON.stringify(fail(null, INVALID_REQUEST, "Origin not allowed"))
|
|
577
883
|
};
|
|
578
884
|
}
|
|
885
|
+
const oauth = this.options.oauth;
|
|
886
|
+
if (oauth) {
|
|
887
|
+
const outcome = await authenticate(oauth, req.headers);
|
|
888
|
+
if (!outcome.ok) return authError(outcome);
|
|
889
|
+
ctx = {
|
|
890
|
+
...ctx,
|
|
891
|
+
auth: outcome.auth
|
|
892
|
+
};
|
|
893
|
+
}
|
|
579
894
|
if (req.method.toUpperCase() !== "POST") {
|
|
580
895
|
return {
|
|
581
896
|
status: 405,
|
|
@@ -596,6 +911,27 @@ var McpServer = class {
|
|
|
596
911
|
};
|
|
597
912
|
}
|
|
598
913
|
}
|
|
914
|
+
if (oauth) {
|
|
915
|
+
await this.load();
|
|
916
|
+
const calls = Array.isArray(body) ? body : [
|
|
917
|
+
body
|
|
918
|
+
];
|
|
919
|
+
for (const m of calls) {
|
|
920
|
+
if (m?.method !== "tools/call") continue;
|
|
921
|
+
const tool = this.tools.get(m.params?.name);
|
|
922
|
+
const missing = missingScopes(ctx.auth, tool?.scopes);
|
|
923
|
+
if (missing.length) {
|
|
924
|
+
const scope = [
|
|
925
|
+
.../* @__PURE__ */ new Set([
|
|
926
|
+
...ctx.auth?.scopes ?? [],
|
|
927
|
+
...oauth.requiredScopes ?? [],
|
|
928
|
+
...tool?.scopes ?? []
|
|
929
|
+
])
|
|
930
|
+
];
|
|
931
|
+
return authError(deny(oauth, 403, "insufficient_scope", `Tool "${tool.name}" needs scope: ${missing.join(" ")}`, scope));
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
599
935
|
const response = await this.handleMessage(body, ctx);
|
|
600
936
|
if (response === null) return {
|
|
601
937
|
status: 202,
|
|
@@ -608,6 +944,20 @@ var McpServer = class {
|
|
|
608
944
|
};
|
|
609
945
|
}
|
|
610
946
|
};
|
|
947
|
+
function authError(o) {
|
|
948
|
+
return {
|
|
949
|
+
status: o.status,
|
|
950
|
+
headers: {
|
|
951
|
+
"content-type": "application/json",
|
|
952
|
+
"www-authenticate": o.challenge
|
|
953
|
+
},
|
|
954
|
+
body: JSON.stringify({
|
|
955
|
+
error: o.error,
|
|
956
|
+
error_description: o.description
|
|
957
|
+
})
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
__name(authError, "authError");
|
|
611
961
|
|
|
612
962
|
// src/adonisjs/index.ts
|
|
613
963
|
var marked = /* @__PURE__ */ new Map();
|
|
@@ -674,6 +1024,16 @@ function mountMcp(router, options) {
|
|
|
674
1024
|
});
|
|
675
1025
|
route.as("mcp_expose.endpoint");
|
|
676
1026
|
options.configureRoute?.(route);
|
|
1027
|
+
server.oauthMetadataPaths.forEach((p, i) => {
|
|
1028
|
+
router.any(p, async (ctx) => {
|
|
1029
|
+
const out = await server.handleMetadataHttp({
|
|
1030
|
+
method: ctx.request.method()
|
|
1031
|
+
});
|
|
1032
|
+
ctx.response.status(out.status);
|
|
1033
|
+
for (const [k, v] of Object.entries(out.headers)) ctx.response.header(k, v);
|
|
1034
|
+
ctx.response.send(out.body ?? "");
|
|
1035
|
+
}).as(`mcp_expose.oauth_metadata_${i}`);
|
|
1036
|
+
});
|
|
677
1037
|
return server;
|
|
678
1038
|
}
|
|
679
1039
|
__name(mountMcp, "mountMcp");
|