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/koa/index.cjs
CHANGED
|
@@ -116,6 +116,38 @@ function loopbackBaseUrl(req) {
|
|
|
116
116
|
}
|
|
117
117
|
__name(loopbackBaseUrl, "loopbackBaseUrl");
|
|
118
118
|
|
|
119
|
+
// src/core/result.ts
|
|
120
|
+
function isToolResult(value) {
|
|
121
|
+
const content = value?.content;
|
|
122
|
+
return !!value && typeof value === "object" && Array.isArray(content) && content.length > 0 && content.every((c) => !!c && typeof c === "object" && typeof c.type === "string");
|
|
123
|
+
}
|
|
124
|
+
__name(isToolResult, "isToolResult");
|
|
125
|
+
function toToolResult(value) {
|
|
126
|
+
if (isToolResult(value)) return value;
|
|
127
|
+
if (typeof value === "string") return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: value
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
};
|
|
135
|
+
const text = JSON.stringify(value ?? null);
|
|
136
|
+
const result = {
|
|
137
|
+
content: [
|
|
138
|
+
{
|
|
139
|
+
type: "text",
|
|
140
|
+
text
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
};
|
|
144
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
145
|
+
result.structuredContent = value;
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
__name(toToolResult, "toToolResult");
|
|
150
|
+
|
|
119
151
|
// src/core/schema.ts
|
|
120
152
|
function isStandard(s) {
|
|
121
153
|
return typeof s === "object" && s !== null && "~standard" in s;
|
|
@@ -336,9 +368,7 @@ function splitArgs(args, mapping) {
|
|
|
336
368
|
}
|
|
337
369
|
__name(splitArgs, "splitArgs");
|
|
338
370
|
function responseToResult(res, maxChars) {
|
|
339
|
-
|
|
340
|
-
if (text.length > maxChars) text = `${text.slice(0, maxChars)}
|
|
341
|
-
\u2026[truncated ${text.length - maxChars} chars]`;
|
|
371
|
+
const text = truncate(res.body, maxChars);
|
|
342
372
|
const isError = res.status >= 400;
|
|
343
373
|
const result = {
|
|
344
374
|
content: [
|
|
@@ -363,6 +393,58 @@ ${text}` : text || `HTTP ${res.status}`
|
|
|
363
393
|
return result;
|
|
364
394
|
}
|
|
365
395
|
__name(responseToResult, "responseToResult");
|
|
396
|
+
function safeDecode(segment) {
|
|
397
|
+
try {
|
|
398
|
+
return decodeURIComponent(segment);
|
|
399
|
+
} catch {
|
|
400
|
+
return segment;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
__name(safeDecode, "safeDecode");
|
|
404
|
+
function truncate(text, maxChars) {
|
|
405
|
+
return text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
406
|
+
\u2026[truncated ${text.length - maxChars} chars]` : text;
|
|
407
|
+
}
|
|
408
|
+
__name(truncate, "truncate");
|
|
409
|
+
function toRouteToolResponse(res) {
|
|
410
|
+
let json;
|
|
411
|
+
if ((res.headers["content-type"] ?? "").includes("json") && res.body) {
|
|
412
|
+
try {
|
|
413
|
+
json = JSON.parse(res.body);
|
|
414
|
+
} catch {
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
...res,
|
|
419
|
+
json,
|
|
420
|
+
ok: res.status < 400
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
__name(toRouteToolResponse, "toRouteToolResponse");
|
|
424
|
+
function transformedResult(value, res, maxChars) {
|
|
425
|
+
const explicit = isToolResult(value);
|
|
426
|
+
const result = toToolResult(value);
|
|
427
|
+
if (!explicit && !res.ok) {
|
|
428
|
+
result.isError = true;
|
|
429
|
+
delete result.structuredContent;
|
|
430
|
+
}
|
|
431
|
+
let truncated = false;
|
|
432
|
+
const content = result.content.map((c) => {
|
|
433
|
+
if (c.type !== "text" || c.text.length <= maxChars) return c;
|
|
434
|
+
truncated = true;
|
|
435
|
+
return {
|
|
436
|
+
...c,
|
|
437
|
+
text: truncate(c.text, maxChars)
|
|
438
|
+
};
|
|
439
|
+
});
|
|
440
|
+
if (!truncated) return result;
|
|
441
|
+
const { structuredContent: _dropped, ...rest } = result;
|
|
442
|
+
return {
|
|
443
|
+
...rest,
|
|
444
|
+
content
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
__name(transformedResult, "transformedResult");
|
|
366
448
|
function pickForwardHeaders(ctx, names) {
|
|
367
449
|
const out = {};
|
|
368
450
|
for (const name of names) {
|
|
@@ -378,6 +460,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
378
460
|
const { schema, mapping } = buildInputSchema(route, opts);
|
|
379
461
|
const forward = serverOpts.forwardHeaders ?? DEFAULT_FORWARD_HEADERS;
|
|
380
462
|
const maxChars = serverOpts.maxResponseChars ?? 1e5;
|
|
463
|
+
const transform = opts.transformResponse ?? serverOpts.transformResponse;
|
|
381
464
|
return {
|
|
382
465
|
name,
|
|
383
466
|
title: opts.title,
|
|
@@ -387,6 +470,7 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
387
470
|
...defaultAnnotations(method),
|
|
388
471
|
...opts.annotations
|
|
389
472
|
},
|
|
473
|
+
scopes: opts.scopes,
|
|
390
474
|
validate: toValidator(opts.input),
|
|
391
475
|
async handler(args, ctx) {
|
|
392
476
|
const missing = opts.input ? void 0 : checkRequired(schema, args);
|
|
@@ -422,19 +506,167 @@ function createRouteTool(route, opts, dispatch, serverOpts = {}) {
|
|
|
422
506
|
]
|
|
423
507
|
};
|
|
424
508
|
}
|
|
425
|
-
const
|
|
509
|
+
const request = {
|
|
426
510
|
method,
|
|
427
511
|
path,
|
|
428
512
|
query,
|
|
429
513
|
body,
|
|
430
514
|
headers
|
|
431
|
-
}
|
|
432
|
-
|
|
515
|
+
};
|
|
516
|
+
const res = await dispatch(request, ctx);
|
|
517
|
+
if (!transform) return responseToResult(res, maxChars);
|
|
518
|
+
const { "content-type": _ct, ...baseHeaders } = headers;
|
|
519
|
+
const hookCtx = {
|
|
520
|
+
tool: name,
|
|
521
|
+
args,
|
|
522
|
+
request,
|
|
523
|
+
toolContext: ctx,
|
|
524
|
+
defaultResult: /* @__PURE__ */ __name(() => responseToResult(res, maxChars), "defaultResult"),
|
|
525
|
+
async callRoute(r) {
|
|
526
|
+
if (!r.path.startsWith("/")) {
|
|
527
|
+
throw new Error(`callRoute: path must start with "/", got ${JSON.stringify(r.path)}`);
|
|
528
|
+
}
|
|
529
|
+
const params = r.params ?? {};
|
|
530
|
+
const missing2 = pathParams(r.path).filter((p) => params[p] === void 0 && !r.path.includes(`:${p}?`));
|
|
531
|
+
if (missing2.length) throw new Error(`callRoute: missing params for ${r.path}: ${missing2.join(", ")}`);
|
|
532
|
+
const subPath = interpolatePath(r.path, params);
|
|
533
|
+
if (subPath.split("/").some((seg) => [
|
|
534
|
+
".",
|
|
535
|
+
".."
|
|
536
|
+
].includes(safeDecode(seg)))) {
|
|
537
|
+
throw new Error(`callRoute: path must not contain "." or ".." segments: ${JSON.stringify(subPath)}`);
|
|
538
|
+
}
|
|
539
|
+
const sub = {
|
|
540
|
+
method: (r.method ?? "GET").toUpperCase(),
|
|
541
|
+
path: subPath,
|
|
542
|
+
query: r.query ?? {},
|
|
543
|
+
body: r.body,
|
|
544
|
+
headers: {
|
|
545
|
+
...baseHeaders,
|
|
546
|
+
...r.headers
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
if (sub.body !== void 0) sub.headers["content-type"] = "application/json";
|
|
550
|
+
return toRouteToolResponse(await dispatch(sub, ctx));
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
const response = toRouteToolResponse(res);
|
|
554
|
+
const value = await transform(response, hookCtx);
|
|
555
|
+
return value === void 0 ? responseToResult(res, maxChars) : transformedResult(value, response, maxChars);
|
|
433
556
|
}
|
|
434
557
|
};
|
|
435
558
|
}
|
|
436
559
|
__name(createRouteTool, "createRouteTool");
|
|
437
560
|
|
|
561
|
+
// src/core/oauth.ts
|
|
562
|
+
var PROTECTED_RESOURCE_WELL_KNOWN = "/.well-known/oauth-protected-resource";
|
|
563
|
+
function protectedResourceMetadataPaths(resource) {
|
|
564
|
+
const pathname = new URL(resource).pathname.replace(/\/+$/, "");
|
|
565
|
+
return pathname ? [
|
|
566
|
+
PROTECTED_RESOURCE_WELL_KNOWN + pathname,
|
|
567
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
568
|
+
] : [
|
|
569
|
+
PROTECTED_RESOURCE_WELL_KNOWN
|
|
570
|
+
];
|
|
571
|
+
}
|
|
572
|
+
__name(protectedResourceMetadataPaths, "protectedResourceMetadataPaths");
|
|
573
|
+
function protectedResourceMetadataUrl(resource) {
|
|
574
|
+
return new URL(protectedResourceMetadataPaths(resource)[0], resource).href;
|
|
575
|
+
}
|
|
576
|
+
__name(protectedResourceMetadataUrl, "protectedResourceMetadataUrl");
|
|
577
|
+
function validateOAuthOptions(o) {
|
|
578
|
+
let url;
|
|
579
|
+
try {
|
|
580
|
+
url = new URL(o.resource);
|
|
581
|
+
} catch {
|
|
582
|
+
throw new Error(`mcp-expose: oauth.resource must be an absolute URL, got ${JSON.stringify(o.resource)}`);
|
|
583
|
+
}
|
|
584
|
+
if (url.hash) throw new Error("mcp-expose: oauth.resource must not contain a fragment");
|
|
585
|
+
if (!o.authorizationServers?.length) throw new Error("mcp-expose: oauth.authorizationServers must not be empty");
|
|
586
|
+
if (typeof o.verifyToken !== "function") throw new Error("mcp-expose: oauth.verifyToken is required");
|
|
587
|
+
}
|
|
588
|
+
__name(validateOAuthOptions, "validateOAuthOptions");
|
|
589
|
+
var quote = /* @__PURE__ */ __name((v) => `"${v.replace(/["\\]/g, "\\$&")}"`, "quote");
|
|
590
|
+
function bearerChallenge(resource, params = {}) {
|
|
591
|
+
const parts = [];
|
|
592
|
+
if (params.error) parts.push(`error=${quote(params.error)}`);
|
|
593
|
+
if (params.errorDescription) parts.push(`error_description=${quote(params.errorDescription)}`);
|
|
594
|
+
if (params.scope?.length) parts.push(`scope=${quote(params.scope.join(" "))}`);
|
|
595
|
+
parts.push(`resource_metadata=${quote(protectedResourceMetadataUrl(resource))}`);
|
|
596
|
+
return `Bearer ${parts.join(", ")}`;
|
|
597
|
+
}
|
|
598
|
+
__name(bearerChallenge, "bearerChallenge");
|
|
599
|
+
function bearerToken(headers) {
|
|
600
|
+
const m = /^Bearer[ ]+([^\s,]+)\s*$/i.exec(headers.authorization ?? "");
|
|
601
|
+
return m?.[1];
|
|
602
|
+
}
|
|
603
|
+
__name(bearerToken, "bearerToken");
|
|
604
|
+
async function authenticate(o, headers) {
|
|
605
|
+
const token = bearerToken(headers);
|
|
606
|
+
if (!token) {
|
|
607
|
+
return {
|
|
608
|
+
ok: false,
|
|
609
|
+
status: 401,
|
|
610
|
+
error: "unauthorized",
|
|
611
|
+
description: "Authorization required",
|
|
612
|
+
challenge: bearerChallenge(o.resource, {
|
|
613
|
+
scope: o.requiredScopes
|
|
614
|
+
})
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
let auth;
|
|
618
|
+
try {
|
|
619
|
+
auth = await o.verifyToken(token, {
|
|
620
|
+
resource: o.resource,
|
|
621
|
+
headers
|
|
622
|
+
});
|
|
623
|
+
} catch {
|
|
624
|
+
auth = void 0;
|
|
625
|
+
}
|
|
626
|
+
if (!auth) return deny(o, 401, "invalid_token", "The access token is invalid");
|
|
627
|
+
if (auth.expiresAt !== void 0 && auth.expiresAt * 1e3 <= Date.now()) {
|
|
628
|
+
return deny(o, 401, "invalid_token", "The access token has expired");
|
|
629
|
+
}
|
|
630
|
+
const missing = missingScopes(auth, o.requiredScopes);
|
|
631
|
+
if (missing.length) {
|
|
632
|
+
const scope = [
|
|
633
|
+
.../* @__PURE__ */ new Set([
|
|
634
|
+
...auth.scopes ?? [],
|
|
635
|
+
...o.requiredScopes ?? []
|
|
636
|
+
])
|
|
637
|
+
];
|
|
638
|
+
return deny(o, 403, "insufficient_scope", `Missing scope: ${missing.join(" ")}`, scope);
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
ok: true,
|
|
642
|
+
auth: {
|
|
643
|
+
...auth,
|
|
644
|
+
scopes: auth.scopes ?? []
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
__name(authenticate, "authenticate");
|
|
649
|
+
function deny(o, status, error, description, scope) {
|
|
650
|
+
return {
|
|
651
|
+
ok: false,
|
|
652
|
+
status,
|
|
653
|
+
error,
|
|
654
|
+
description,
|
|
655
|
+
challenge: bearerChallenge(o.resource, {
|
|
656
|
+
error,
|
|
657
|
+
errorDescription: description,
|
|
658
|
+
scope
|
|
659
|
+
})
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
__name(deny, "deny");
|
|
663
|
+
function missingScopes(auth, required) {
|
|
664
|
+
if (!required?.length) return [];
|
|
665
|
+
const granted = new Set(auth?.scopes ?? []);
|
|
666
|
+
return required.filter((s) => !granted.has(s));
|
|
667
|
+
}
|
|
668
|
+
__name(missingScopes, "missingScopes");
|
|
669
|
+
|
|
438
670
|
// src/core/server.ts
|
|
439
671
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
440
672
|
"2025-11-25",
|
|
@@ -469,6 +701,7 @@ var McpServer = class {
|
|
|
469
701
|
loaders = [];
|
|
470
702
|
constructor(options) {
|
|
471
703
|
this.options = options;
|
|
704
|
+
if (options.oauth) validateOAuthOptions(options.oauth);
|
|
472
705
|
}
|
|
473
706
|
/** Register a tool. Throws on duplicate names. */
|
|
474
707
|
addTool(tool) {
|
|
@@ -603,6 +836,79 @@ var McpServer = class {
|
|
|
603
836
|
return (allowed ?? []).includes(origin);
|
|
604
837
|
}
|
|
605
838
|
/**
|
|
839
|
+
* Paths on which adapters serve the OAuth Protected Resource Metadata
|
|
840
|
+
* (empty when `oauth` is not configured). They must be public: no auth middleware.
|
|
841
|
+
*/
|
|
842
|
+
get oauthMetadataPaths() {
|
|
843
|
+
return this.options.oauth ? protectedResourceMetadataPaths(this.options.oauth.resource) : [];
|
|
844
|
+
}
|
|
845
|
+
/** The OAuth Protected Resource Metadata document (RFC 9728). */
|
|
846
|
+
async protectedResourceMetadata() {
|
|
847
|
+
const o = this.options.oauth;
|
|
848
|
+
if (!o) throw new Error("mcp-expose: the oauth option is not configured");
|
|
849
|
+
await this.load();
|
|
850
|
+
const scopes = o.scopesSupported ?? [
|
|
851
|
+
.../* @__PURE__ */ new Set([
|
|
852
|
+
...o.requiredScopes ?? [],
|
|
853
|
+
...[
|
|
854
|
+
...this.tools.values()
|
|
855
|
+
].flatMap((t) => t.scopes ?? [])
|
|
856
|
+
])
|
|
857
|
+
];
|
|
858
|
+
return {
|
|
859
|
+
resource: o.resource,
|
|
860
|
+
authorization_servers: o.authorizationServers,
|
|
861
|
+
...scopes.length ? {
|
|
862
|
+
scopes_supported: scopes
|
|
863
|
+
} : {},
|
|
864
|
+
bearer_methods_supported: [
|
|
865
|
+
"header"
|
|
866
|
+
],
|
|
867
|
+
...o.resourceName ? {
|
|
868
|
+
resource_name: o.resourceName
|
|
869
|
+
} : {},
|
|
870
|
+
...o.resourceDocumentation ? {
|
|
871
|
+
resource_documentation: o.resourceDocumentation
|
|
872
|
+
} : {},
|
|
873
|
+
...o.metadata
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
/** HTTP handler for the metadata paths. Public and CORS-enabled so browser-based clients can read it. */
|
|
877
|
+
async handleMetadataHttp(req) {
|
|
878
|
+
const cors = {
|
|
879
|
+
"access-control-allow-origin": "*",
|
|
880
|
+
"access-control-allow-methods": "GET, OPTIONS"
|
|
881
|
+
};
|
|
882
|
+
const method = req.method.toUpperCase();
|
|
883
|
+
if (!this.options.oauth) return {
|
|
884
|
+
status: 404,
|
|
885
|
+
headers: {}
|
|
886
|
+
};
|
|
887
|
+
if (method === "OPTIONS") return {
|
|
888
|
+
status: 204,
|
|
889
|
+
headers: {
|
|
890
|
+
...cors,
|
|
891
|
+
"access-control-allow-headers": "*"
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
if (method !== "GET" && method !== "HEAD") return {
|
|
895
|
+
status: 405,
|
|
896
|
+
headers: {
|
|
897
|
+
allow: "GET, OPTIONS"
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
const body = JSON.stringify(await this.protectedResourceMetadata());
|
|
901
|
+
return {
|
|
902
|
+
status: 200,
|
|
903
|
+
headers: {
|
|
904
|
+
...cors,
|
|
905
|
+
"content-type": "application/json",
|
|
906
|
+
"cache-control": "public, max-age=3600"
|
|
907
|
+
},
|
|
908
|
+
body: method === "HEAD" ? void 0 : body
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
606
912
|
* Framework-neutral Streamable HTTP handler. Adapters convert their request
|
|
607
913
|
* into McpHttpRequest, call this, and write the McpHttpResponse back.
|
|
608
914
|
*/
|
|
@@ -617,6 +923,15 @@ var McpServer = class {
|
|
|
617
923
|
body: JSON.stringify(fail(null, INVALID_REQUEST, "Origin not allowed"))
|
|
618
924
|
};
|
|
619
925
|
}
|
|
926
|
+
const oauth = this.options.oauth;
|
|
927
|
+
if (oauth) {
|
|
928
|
+
const outcome = await authenticate(oauth, req.headers);
|
|
929
|
+
if (!outcome.ok) return authError(outcome);
|
|
930
|
+
ctx = {
|
|
931
|
+
...ctx,
|
|
932
|
+
auth: outcome.auth
|
|
933
|
+
};
|
|
934
|
+
}
|
|
620
935
|
if (req.method.toUpperCase() !== "POST") {
|
|
621
936
|
return {
|
|
622
937
|
status: 405,
|
|
@@ -637,6 +952,27 @@ var McpServer = class {
|
|
|
637
952
|
};
|
|
638
953
|
}
|
|
639
954
|
}
|
|
955
|
+
if (oauth) {
|
|
956
|
+
await this.load();
|
|
957
|
+
const calls = Array.isArray(body) ? body : [
|
|
958
|
+
body
|
|
959
|
+
];
|
|
960
|
+
for (const m of calls) {
|
|
961
|
+
if (m?.method !== "tools/call") continue;
|
|
962
|
+
const tool = this.tools.get(m.params?.name);
|
|
963
|
+
const missing = missingScopes(ctx.auth, tool?.scopes);
|
|
964
|
+
if (missing.length) {
|
|
965
|
+
const scope = [
|
|
966
|
+
.../* @__PURE__ */ new Set([
|
|
967
|
+
...ctx.auth?.scopes ?? [],
|
|
968
|
+
...oauth.requiredScopes ?? [],
|
|
969
|
+
...tool?.scopes ?? []
|
|
970
|
+
])
|
|
971
|
+
];
|
|
972
|
+
return authError(deny(oauth, 403, "insufficient_scope", `Tool "${tool.name}" needs scope: ${missing.join(" ")}`, scope));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
640
976
|
const response = await this.handleMessage(body, ctx);
|
|
641
977
|
if (response === null) return {
|
|
642
978
|
status: 202,
|
|
@@ -649,6 +985,20 @@ var McpServer = class {
|
|
|
649
985
|
};
|
|
650
986
|
}
|
|
651
987
|
};
|
|
988
|
+
function authError(o) {
|
|
989
|
+
return {
|
|
990
|
+
status: o.status,
|
|
991
|
+
headers: {
|
|
992
|
+
"content-type": "application/json",
|
|
993
|
+
"www-authenticate": o.challenge
|
|
994
|
+
},
|
|
995
|
+
body: JSON.stringify({
|
|
996
|
+
error: o.error,
|
|
997
|
+
error_description: o.description
|
|
998
|
+
})
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
__name(authError, "authError");
|
|
652
1002
|
|
|
653
1003
|
// src/koa/index.ts
|
|
654
1004
|
function mcpTool(options = {}) {
|
|
@@ -677,7 +1027,17 @@ function createKoaMcp(options) {
|
|
|
677
1027
|
}
|
|
678
1028
|
for (const r of options.routes ?? []) server.addTool(createRouteTool(r, r, dispatch, options));
|
|
679
1029
|
});
|
|
1030
|
+
const metadataPaths = server.oauthMetadataPaths;
|
|
680
1031
|
const middleware = /* @__PURE__ */ __name(async (ctx, next) => {
|
|
1032
|
+
if (metadataPaths.includes(ctx.path)) {
|
|
1033
|
+
const out2 = await server.handleMetadataHttp({
|
|
1034
|
+
method: ctx.method
|
|
1035
|
+
});
|
|
1036
|
+
ctx.status = out2.status;
|
|
1037
|
+
ctx.set(out2.headers);
|
|
1038
|
+
if (out2.body !== void 0) ctx.body = out2.body;
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
681
1041
|
if (ctx.path !== path) return next();
|
|
682
1042
|
const body = ctx.request.body ?? (ctx.method === "POST" ? await readRawBody(ctx.req) : void 0);
|
|
683
1043
|
const toolCtx = {
|