mcp-trellis 0.2.1 → 2.1.0
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 +70 -620
- package/dist/adapters/node-request.d.ts +53 -0
- package/dist/adapters/node-request.d.ts.map +1 -0
- package/dist/adapters/node-request.js +165 -0
- package/dist/adapters/node-request.js.map +1 -0
- package/dist/adapters/node.d.ts +16 -24
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +69 -88
- package/dist/adapters/node.js.map +1 -1
- package/dist/advanced.d.ts +18 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +18 -0
- package/dist/advanced.js.map +1 -0
- package/dist/app-options.d.ts +132 -0
- package/dist/app-options.d.ts.map +1 -0
- package/dist/app-options.js +2 -0
- package/dist/app-options.js.map +1 -0
- package/dist/app.d.ts +2 -66
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +150 -57
- package/dist/app.js.map +1 -1
- package/dist/audit.d.ts +5 -5
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +19 -8
- package/dist/audit.js.map +1 -1
- package/dist/auth/bearer.d.ts +4 -1
- package/dist/auth/bearer.d.ts.map +1 -1
- package/dist/auth/bearer.js +25 -5
- package/dist/auth/bearer.js.map +1 -1
- package/dist/clients.d.ts.map +1 -1
- package/dist/clients.js +3 -6
- package/dist/clients.js.map +1 -1
- package/dist/deprecated.d.ts +42 -0
- package/dist/deprecated.d.ts.map +1 -0
- package/dist/deprecated.js +35 -0
- package/dist/deprecated.js.map +1 -0
- package/dist/http/body.d.ts +69 -0
- package/dist/http/body.d.ts.map +1 -0
- package/dist/http/body.js +165 -0
- package/dist/http/body.js.map +1 -0
- package/dist/http/http.d.ts +42 -0
- package/dist/http/http.d.ts.map +1 -0
- package/dist/http/http.js +100 -0
- package/dist/http/http.js.map +1 -0
- package/dist/http/origins.d.ts +37 -0
- package/dist/http/origins.d.ts.map +1 -0
- package/dist/http/origins.js +87 -0
- package/dist/http/origins.js.map +1 -0
- package/dist/mcp/dispatch.d.ts +7 -0
- package/dist/mcp/dispatch.d.ts.map +1 -0
- package/dist/mcp/dispatch.js +99 -0
- package/dist/mcp/dispatch.js.map +1 -0
- package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +10 -0
- package/dist/mcp/jsonrpc.d.ts.map +1 -0
- package/dist/mcp/jsonrpc.js +88 -0
- package/dist/mcp/jsonrpc.js.map +1 -0
- package/dist/mcp/methods.d.ts +133 -0
- package/dist/mcp/methods.d.ts.map +1 -0
- package/dist/mcp/methods.js +84 -0
- package/dist/mcp/methods.js.map +1 -0
- package/dist/mcp/protocol.d.ts +16 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.js +22 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/registry.d.ts +58 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +81 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/rpc-handlers.d.ts +11 -0
- package/dist/mcp/rpc-handlers.d.ts.map +1 -0
- package/dist/mcp/rpc-handlers.js +133 -0
- package/dist/mcp/rpc-handlers.js.map +1 -0
- package/dist/{tools.d.ts → mcp/tools.d.ts} +30 -2
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +166 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/{validate.d.ts → mcp/validate.d.ts} +24 -12
- package/dist/mcp/validate.d.ts.map +1 -0
- package/dist/mcp/validate.js +186 -0
- package/dist/mcp/validate.js.map +1 -0
- package/dist/oauth/audit.d.ts +20 -0
- package/dist/oauth/audit.d.ts.map +1 -0
- package/dist/oauth/audit.js +29 -0
- package/dist/oauth/audit.js.map +1 -0
- package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
- package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd-parse.js +70 -0
- package/dist/oauth/cimd/cimd-parse.js.map +1 -0
- package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
- package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd-ssrf.js +193 -0
- package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
- package/dist/oauth/cimd/cimd.d.ts +36 -0
- package/dist/oauth/cimd/cimd.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd.js +132 -0
- package/dist/oauth/cimd/cimd.js.map +1 -0
- package/dist/oauth/cimd/types.d.ts +12 -0
- package/dist/oauth/cimd/types.d.ts.map +1 -0
- package/dist/oauth/cimd/types.js +3 -0
- package/dist/oauth/cimd/types.js.map +1 -0
- package/dist/oauth/config.d.ts +51 -0
- package/dist/oauth/config.d.ts.map +1 -0
- package/dist/oauth/config.js +108 -0
- package/dist/oauth/config.js.map +1 -0
- package/dist/oauth/constants.d.ts +15 -0
- package/dist/oauth/constants.d.ts.map +1 -1
- package/dist/oauth/constants.js +15 -0
- package/dist/oauth/constants.js.map +1 -1
- package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +1 -2
- package/dist/oauth/crypto/base64url.d.ts.map +1 -0
- package/dist/oauth/{base64url.js → crypto/base64url.js} +1 -5
- package/dist/oauth/crypto/base64url.js.map +1 -0
- package/dist/oauth/crypto/clientauth.d.ts +13 -0
- package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
- package/dist/oauth/crypto/clientauth.js +195 -0
- package/dist/oauth/crypto/clientauth.js.map +1 -0
- package/dist/oauth/crypto/codes.d.ts +65 -0
- package/dist/oauth/crypto/codes.d.ts.map +1 -0
- package/dist/oauth/crypto/codes.js +121 -0
- package/dist/oauth/crypto/codes.js.map +1 -0
- package/dist/oauth/crypto/hkdf.d.ts +13 -0
- package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
- package/dist/oauth/crypto/hkdf.js +30 -0
- package/dist/oauth/crypto/hkdf.js.map +1 -0
- package/dist/oauth/crypto/keycache.d.ts +19 -0
- package/dist/oauth/crypto/keycache.d.ts.map +1 -0
- package/dist/oauth/crypto/keycache.js +44 -0
- package/dist/oauth/crypto/keycache.js.map +1 -0
- package/dist/oauth/crypto/pkce.d.ts.map +1 -0
- package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
- package/dist/oauth/crypto/pkce.js.map +1 -0
- package/dist/oauth/crypto/sealed.d.ts +26 -0
- package/dist/oauth/crypto/sealed.d.ts.map +1 -0
- package/dist/oauth/crypto/sealed.js +65 -0
- package/dist/oauth/crypto/sealed.js.map +1 -0
- package/dist/oauth/crypto/secrethash.d.ts +58 -0
- package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
- package/dist/oauth/crypto/secrethash.js +82 -0
- package/dist/oauth/crypto/secrethash.js.map +1 -0
- package/dist/oauth/deprecated.d.ts +52 -0
- package/dist/oauth/deprecated.d.ts.map +1 -0
- package/dist/oauth/deprecated.js +49 -0
- package/dist/oauth/deprecated.js.map +1 -0
- package/dist/oauth/endpoints/authorize.d.ts +3 -0
- package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
- package/dist/oauth/endpoints/authorize.js +209 -0
- package/dist/oauth/endpoints/authorize.js.map +1 -0
- package/dist/oauth/endpoints/callback.d.ts +20 -0
- package/dist/oauth/endpoints/callback.d.ts.map +1 -0
- package/dist/oauth/endpoints/callback.js +31 -0
- package/dist/oauth/endpoints/callback.js.map +1 -0
- package/dist/oauth/endpoints/consent-page.d.ts +5 -0
- package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
- package/dist/oauth/endpoints/consent-page.js +63 -0
- package/dist/oauth/endpoints/consent-page.js.map +1 -0
- package/dist/oauth/endpoints/consent.d.ts +29 -0
- package/dist/oauth/endpoints/consent.d.ts.map +1 -0
- package/dist/oauth/endpoints/consent.js +98 -0
- package/dist/oauth/endpoints/consent.js.map +1 -0
- package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +14 -8
- package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
- package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
- package/dist/oauth/endpoints/metadata.js.map +1 -0
- package/dist/oauth/endpoints/register.d.ts +10 -0
- package/dist/oauth/endpoints/register.d.ts.map +1 -0
- package/dist/oauth/endpoints/register.js +96 -0
- package/dist/oauth/endpoints/register.js.map +1 -0
- package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
- package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
- package/dist/oauth/{revoke.js → endpoints/revoke.js} +11 -9
- package/dist/oauth/endpoints/revoke.js.map +1 -0
- package/dist/oauth/endpoints/token.d.ts +3 -0
- package/dist/oauth/endpoints/token.d.ts.map +1 -0
- package/dist/oauth/{token.js → endpoints/token.js} +33 -47
- package/dist/oauth/endpoints/token.js.map +1 -0
- package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
- package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
- package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -7
- package/dist/oauth/endpoints/wellknown.js.map +1 -0
- package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +6 -1
- package/dist/oauth/policy/redirect.d.ts.map +1 -0
- package/dist/oauth/policy/redirect.js +39 -0
- package/dist/oauth/policy/redirect.js.map +1 -0
- package/dist/oauth/policy/reqbody.d.ts +18 -0
- package/dist/oauth/policy/reqbody.d.ts.map +1 -0
- package/dist/oauth/policy/reqbody.js +68 -0
- package/dist/oauth/policy/reqbody.js.map +1 -0
- package/dist/oauth/policy/resource.d.ts +37 -0
- package/dist/oauth/policy/resource.d.ts.map +1 -0
- package/dist/oauth/policy/resource.js +74 -0
- package/dist/oauth/policy/resource.js.map +1 -0
- package/dist/oauth/policy/scope.d.ts +20 -0
- package/dist/oauth/policy/scope.d.ts.map +1 -0
- package/dist/oauth/policy/scope.js +35 -0
- package/dist/oauth/policy/scope.js.map +1 -0
- package/dist/oauth/router.d.ts +12 -12
- package/dist/oauth/router.d.ts.map +1 -1
- package/dist/oauth/router.js +105 -87
- package/dist/oauth/router.js.map +1 -1
- package/dist/oauth/types.d.ts +177 -26
- package/dist/oauth/types.d.ts.map +1 -1
- package/dist/oauth/types.js +1 -27
- package/dist/oauth/types.js.map +1 -1
- package/dist/server.d.ts +14 -13
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -9
- package/dist/server.js.map +1 -1
- package/dist/util/defined.d.ts +11 -0
- package/dist/util/defined.d.ts.map +1 -0
- package/dist/util/defined.js +15 -0
- package/dist/util/defined.js.map +1 -0
- package/dist/util/json.d.ts +3 -0
- package/dist/util/json.d.ts.map +1 -0
- package/dist/util/json.js +3 -0
- package/dist/util/json.js.map +1 -0
- package/docs/ROADMAP.md +283 -0
- package/docs/compatibility.md +84 -0
- package/docs/diagrams/mcp-trellis-architecture.html +305 -0
- package/docs/guide.md +441 -0
- package/docs/reference.md +210 -0
- package/docs/security.md +112 -0
- package/docs/troubleshooting.md +19 -0
- package/package.json +24 -8
- package/dist/adapters/origins.d.ts +0 -19
- package/dist/adapters/origins.d.ts.map +0 -1
- package/dist/adapters/origins.js +0 -55
- package/dist/adapters/origins.js.map +0 -1
- package/dist/dispatch.d.ts +0 -7
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -79
- package/dist/dispatch.js.map +0 -1
- package/dist/http.d.ts +0 -17
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -38
- package/dist/http.js.map +0 -1
- package/dist/jsonrpc.d.ts.map +0 -1
- package/dist/jsonrpc.js +0 -17
- package/dist/jsonrpc.js.map +0 -1
- package/dist/methods.d.ts +0 -61
- package/dist/methods.d.ts.map +0 -1
- package/dist/methods.js +0 -114
- package/dist/methods.js.map +0 -1
- package/dist/oauth/authorize.d.ts +0 -3
- package/dist/oauth/authorize.d.ts.map +0 -1
- package/dist/oauth/authorize.js +0 -109
- package/dist/oauth/authorize.js.map +0 -1
- package/dist/oauth/base64url.d.ts.map +0 -1
- package/dist/oauth/base64url.js.map +0 -1
- package/dist/oauth/clientauth.d.ts +0 -19
- package/dist/oauth/clientauth.d.ts.map +0 -1
- package/dist/oauth/clientauth.js +0 -76
- package/dist/oauth/clientauth.js.map +0 -1
- package/dist/oauth/codes.d.ts +0 -30
- package/dist/oauth/codes.d.ts.map +0 -1
- package/dist/oauth/codes.js +0 -93
- package/dist/oauth/codes.js.map +0 -1
- package/dist/oauth/metadata.d.ts.map +0 -1
- package/dist/oauth/metadata.js.map +0 -1
- package/dist/oauth/pkce.d.ts.map +0 -1
- package/dist/oauth/pkce.js.map +0 -1
- package/dist/oauth/redirect.d.ts.map +0 -1
- package/dist/oauth/redirect.js +0 -26
- package/dist/oauth/redirect.js.map +0 -1
- package/dist/oauth/resource.d.ts +0 -7
- package/dist/oauth/resource.d.ts.map +0 -1
- package/dist/oauth/resource.js +0 -44
- package/dist/oauth/resource.js.map +0 -1
- package/dist/oauth/revoke.d.ts.map +0 -1
- package/dist/oauth/revoke.js.map +0 -1
- package/dist/oauth/scope.d.ts +0 -11
- package/dist/oauth/scope.d.ts.map +0 -1
- package/dist/oauth/scope.js +0 -24
- package/dist/oauth/scope.js.map +0 -1
- package/dist/oauth/token.d.ts +0 -3
- package/dist/oauth/token.d.ts.map +0 -1
- package/dist/oauth/token.js.map +0 -1
- package/dist/oauth/wellknown.d.ts.map +0 -1
- package/dist/oauth/wellknown.js.map +0 -1
- package/dist/protocol.d.ts +0 -4
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -14
- package/dist/protocol.js.map +0 -1
- package/dist/registry.d.ts +0 -37
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -69
- package/dist/registry.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -97
- package/dist/tools.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js +0 -130
- package/dist/validate.js.map +0 -1
- /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin allowlisting — Host-derived Node origins (multi-tenant) and browser
|
|
3
|
+
* `Origin` headers on MCP (DNS rebinding / CSRF). Same predicate shape as
|
|
4
|
+
* redirect allowlisting — not secret comparison.
|
|
5
|
+
*/
|
|
6
|
+
export type OriginAllowlistOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Exact origins (`https://acme.example.com`) and/or `*.example.com`
|
|
9
|
+
* wildcards. Wildcards match https subdomains only (not the apex, not http).
|
|
10
|
+
* `"*"` admits any origin (loud opt-out).
|
|
11
|
+
*
|
|
12
|
+
* Empty/omitted list → admit all (only safe with a fixed `asNodeHandler`
|
|
13
|
+
* `origin`). Host-derived `asNodeHandler` requires a non-empty list at
|
|
14
|
+
* construction. Resolved origin outside the list → 400; a fixed `origin`
|
|
15
|
+
* absent from a non-empty list throws at construction.
|
|
16
|
+
*/
|
|
17
|
+
allowedOrigins?: string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Host-derived / allowlist Origin check (Node `asNodeHandler` multi-tenant).
|
|
21
|
+
* Empty or omitted `allowedOrigins` → admit all (callers that derive origin
|
|
22
|
+
* from Host must require a non-empty list before reaching here).
|
|
23
|
+
* `"*"` admits any origin.
|
|
24
|
+
*/
|
|
25
|
+
export declare const isAllowedOrigin: (origin: string, options?: OriginAllowlistOptions) => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Browser `Origin` on MCP Streamable HTTP (DNS rebinding / CSRF).
|
|
28
|
+
* No header → allow (native / server-to-server). Empty or omitted allowlist →
|
|
29
|
+
* deny any present Origin (secure default — opposite of `isAllowedOrigin`).
|
|
30
|
+
* `"*"` opts out.
|
|
31
|
+
*/
|
|
32
|
+
export declare const isAllowedRequestOrigin: (originHeader: string | null, allowedRequestOrigins?: string[]) => boolean;
|
|
33
|
+
/** 403 when a present Origin fails the allowlist. `onDenied` runs first (audit). */
|
|
34
|
+
export declare const requestOriginDenied: (request: Request, allowedRequestOrigins: string[] | undefined, onDenied?: () => Promise<void>) => Promise<Response | null>;
|
|
35
|
+
/** When a non-wildcard allowlist admitted this Origin, reflect it on CORS. */
|
|
36
|
+
export declare const reflectCorsIfNeeded: (response: Response, request: Request, allowedRequestOrigins: string[] | undefined) => Response;
|
|
37
|
+
//# sourceMappingURL=origins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../src/http/origins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAyCF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,UAAS,sBAA2B,KAAG,OAMtF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,cAAc,MAAM,GAAG,IAAI,EAC3B,wBAAwB,MAAM,EAAE,KAC/B,OAIF,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,OAAO,EAChB,uBAAuB,MAAM,EAAE,GAAG,SAAS,EAC3C,WAAW,MAAM,OAAO,CAAC,IAAI,CAAC,KAC7B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAMzB,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,GAC9B,UAAU,QAAQ,EAClB,SAAS,OAAO,EAChB,uBAAuB,MAAM,EAAE,GAAG,SAAS,KAC1C,QAMF,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin allowlisting — Host-derived Node origins (multi-tenant) and browser
|
|
3
|
+
* `Origin` headers on MCP (DNS rebinding / CSRF). Same predicate shape as
|
|
4
|
+
* redirect allowlisting — not secret comparison.
|
|
5
|
+
*/
|
|
6
|
+
import { jsonResponse, withReflectedCorsOrigin } from "./http.js";
|
|
7
|
+
const originOf = (value) => {
|
|
8
|
+
try {
|
|
9
|
+
return new URL(value).origin;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const isExactOrigin = (origin, options) => {
|
|
16
|
+
const normalized = originOf(origin);
|
|
17
|
+
if (!normalized)
|
|
18
|
+
return false;
|
|
19
|
+
return Boolean(options.allowedOrigins?.some((entry) => {
|
|
20
|
+
if (entry.startsWith("*."))
|
|
21
|
+
return false;
|
|
22
|
+
return originOf(entry) === normalized;
|
|
23
|
+
}));
|
|
24
|
+
};
|
|
25
|
+
/** `*.example.com` matches `https://acme.example.com`, not the apex, not http. */
|
|
26
|
+
const isWildcardSubdomain = (origin, options) => {
|
|
27
|
+
const patterns = options.allowedOrigins ?? [];
|
|
28
|
+
try {
|
|
29
|
+
const { protocol, hostname } = new URL(origin);
|
|
30
|
+
if (protocol !== "https:")
|
|
31
|
+
return false;
|
|
32
|
+
return patterns.some((entry) => {
|
|
33
|
+
if (!entry.startsWith("*."))
|
|
34
|
+
return false;
|
|
35
|
+
const suffix = entry.slice(1); // ".example.com"
|
|
36
|
+
return hostname.endsWith(suffix) && hostname.length > suffix.length;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const ORIGIN_PREDICATES = [isExactOrigin, isWildcardSubdomain];
|
|
44
|
+
/**
|
|
45
|
+
* Host-derived / allowlist Origin check (Node `asNodeHandler` multi-tenant).
|
|
46
|
+
* Empty or omitted `allowedOrigins` → admit all (callers that derive origin
|
|
47
|
+
* from Host must require a non-empty list before reaching here).
|
|
48
|
+
* `"*"` admits any origin.
|
|
49
|
+
*/
|
|
50
|
+
export const isAllowedOrigin = (origin, options = {}) => {
|
|
51
|
+
if (!options.allowedOrigins || options.allowedOrigins.length === 0) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (options.allowedOrigins.includes("*"))
|
|
55
|
+
return true;
|
|
56
|
+
return ORIGIN_PREDICATES.some((predicate) => predicate(origin, options));
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Browser `Origin` on MCP Streamable HTTP (DNS rebinding / CSRF).
|
|
60
|
+
* No header → allow (native / server-to-server). Empty or omitted allowlist →
|
|
61
|
+
* deny any present Origin (secure default — opposite of `isAllowedOrigin`).
|
|
62
|
+
* `"*"` opts out.
|
|
63
|
+
*/
|
|
64
|
+
export const isAllowedRequestOrigin = (originHeader, allowedRequestOrigins) => {
|
|
65
|
+
if (!originHeader)
|
|
66
|
+
return true;
|
|
67
|
+
if (!allowedRequestOrigins || allowedRequestOrigins.length === 0)
|
|
68
|
+
return false;
|
|
69
|
+
return isAllowedOrigin(originHeader, { allowedOrigins: allowedRequestOrigins });
|
|
70
|
+
};
|
|
71
|
+
/** 403 when a present Origin fails the allowlist. `onDenied` runs first (audit). */
|
|
72
|
+
export const requestOriginDenied = async (request, allowedRequestOrigins, onDenied) => {
|
|
73
|
+
if (isAllowedRequestOrigin(request.headers.get("Origin"), allowedRequestOrigins)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
await onDenied?.();
|
|
77
|
+
return jsonResponse({ data: { error: "origin not allowed" }, status: 403, cors: false });
|
|
78
|
+
};
|
|
79
|
+
/** When a non-wildcard allowlist admitted this Origin, reflect it on CORS. */
|
|
80
|
+
export const reflectCorsIfNeeded = (response, request, allowedRequestOrigins) => {
|
|
81
|
+
const origin = request.headers.get("Origin");
|
|
82
|
+
if (!origin || !allowedRequestOrigins?.length || allowedRequestOrigins.includes("*")) {
|
|
83
|
+
return response;
|
|
84
|
+
}
|
|
85
|
+
return withReflectedCorsOrigin(response, origin);
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=origins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origins.js","sourceRoot":"","sources":["../../src/http/origins.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type McpHandlerOptions } from "./methods.js";
|
|
2
|
+
export type { AuditEntry, McpHandlerOptions, McpPorts, Principal, ServerInfo, } from "./methods.js";
|
|
3
|
+
export type McpHandler = {
|
|
4
|
+
fetch: (request: Request) => Promise<Response>;
|
|
5
|
+
};
|
|
6
|
+
export declare const createMcpHandler: <TCtx>(handlerOptions: McpHandlerOptions<TCtx>) => McpHandler;
|
|
7
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/mcp/dispatch.ts"],"names":[],"mappings":"AAmBA,OAAO,EAIL,KAAK,iBAAiB,EAKvB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,UAAU,GACX,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAChD,CAAC;AAkFF,eAAO,MAAM,gBAAgB,GAAI,IAAI,EAAE,gBAAgB,iBAAiB,CAAC,IAAI,CAAC,KAAG,UAwChF,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { rejectQueryToken } from "../auth/bearer.js";
|
|
2
|
+
import { BodyTooLargeError, DEFAULT_MCP_BODY_LIMIT, readBoundedText } from "../http/body.js";
|
|
3
|
+
import { emptyResponse, errorReason, INTERNAL_ERROR, jsonResponse, methodNotAllowed, optionsResponse, } from "../http/http.js";
|
|
4
|
+
import { reflectCorsIfNeeded, requestOriginDenied } from "../http/origins.js";
|
|
5
|
+
import { JSONRPC_INTERNAL_ERROR, JSONRPC_PARSE_ERROR, JSONRPC_PAYLOAD_TOO_LARGE, protocolVersionHeaderError, rpcError, validateJsonRpcEnvelope, } from "./jsonrpc.js";
|
|
6
|
+
import { DEFAULT_PUBLIC_METHODS, DENIAL_REASONS, isNotification, resolveWwwAuthenticate, safeAudit, unauthorized, } from "./methods.js";
|
|
7
|
+
import { dispatchRpc } from "./rpc-handlers.js";
|
|
8
|
+
const parseBody = async (request) => {
|
|
9
|
+
let text;
|
|
10
|
+
try {
|
|
11
|
+
text = await readBoundedText(request, DEFAULT_MCP_BODY_LIMIT);
|
|
12
|
+
}
|
|
13
|
+
catch (caught) {
|
|
14
|
+
if (caught instanceof BodyTooLargeError) {
|
|
15
|
+
return jsonResponse({
|
|
16
|
+
data: rpcError(null, JSONRPC_PAYLOAD_TOO_LARGE, "Payload too large"),
|
|
17
|
+
status: 413,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return jsonResponse({ data: rpcError(null, JSONRPC_PARSE_ERROR, "Parse error"), status: 400 });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(text);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return jsonResponse({ data: rpcError(null, JSONRPC_PARSE_ERROR, "Parse error"), status: 400 });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/** Transport-level denial: no JSON-RPC method has been parsed yet. */
|
|
30
|
+
const auditDenial = (options, error, startedAt) => safeAudit(options, { method: "", ok: false, error, durationMs: Date.now() - startedAt });
|
|
31
|
+
const denyQueryStringToken = async (request, options, startedAt) => {
|
|
32
|
+
if (rejectQueryToken(new URL(request.url))) {
|
|
33
|
+
await auditDenial(options, DENIAL_REASONS.queryStringToken, startedAt);
|
|
34
|
+
return unauthorized(resolveWwwAuthenticate(options, request), "Token in query string is rejected. Use Authorization Bearer.");
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
38
|
+
const handleGet = async (request, options, startedAt) => {
|
|
39
|
+
const denied = await denyQueryStringToken(request, options, startedAt);
|
|
40
|
+
if (denied)
|
|
41
|
+
return denied;
|
|
42
|
+
if (!request.headers.get("authorization")) {
|
|
43
|
+
await auditDenial(options, DENIAL_REASONS.unauthorized, startedAt);
|
|
44
|
+
return unauthorized(resolveWwwAuthenticate(options, request));
|
|
45
|
+
}
|
|
46
|
+
return methodNotAllowed("Use POST for MCP");
|
|
47
|
+
};
|
|
48
|
+
const handlePost = async (request, options, startedAt) => {
|
|
49
|
+
const denied = await denyQueryStringToken(request, options, startedAt);
|
|
50
|
+
if (denied)
|
|
51
|
+
return denied;
|
|
52
|
+
const parsed = await parseBody(request);
|
|
53
|
+
if (parsed instanceof Response)
|
|
54
|
+
return parsed;
|
|
55
|
+
const envelope = validateJsonRpcEnvelope(parsed);
|
|
56
|
+
if (envelope instanceof Response)
|
|
57
|
+
return envelope;
|
|
58
|
+
const versionError = protocolVersionHeaderError(request, envelope);
|
|
59
|
+
if (versionError)
|
|
60
|
+
return versionError;
|
|
61
|
+
if (isNotification(envelope))
|
|
62
|
+
return emptyResponse(202);
|
|
63
|
+
return dispatchRpc({ req: request, body: envelope, options, startedAt });
|
|
64
|
+
};
|
|
65
|
+
export const createMcpHandler = (handlerOptions) => {
|
|
66
|
+
const options = {
|
|
67
|
+
...handlerOptions,
|
|
68
|
+
publicMethods: handlerOptions.publicMethods ?? DEFAULT_PUBLIC_METHODS,
|
|
69
|
+
};
|
|
70
|
+
const HTTP_METHODS = {
|
|
71
|
+
OPTIONS: () => optionsResponse(),
|
|
72
|
+
GET: (request, startedAt) => handleGet(request, options, startedAt),
|
|
73
|
+
POST: (request, startedAt) => handlePost(request, options, startedAt),
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
fetch: async (request) => {
|
|
77
|
+
const startedAt = Date.now();
|
|
78
|
+
try {
|
|
79
|
+
const originDenied = await requestOriginDenied(request, options.allowedRequestOrigins, () => auditDenial(options, DENIAL_REASONS.originNotAllowed, startedAt));
|
|
80
|
+
if (originDenied)
|
|
81
|
+
return originDenied;
|
|
82
|
+
const handler = HTTP_METHODS[request.method];
|
|
83
|
+
const response = await (handler ? handler(request, startedAt) : methodNotAllowed());
|
|
84
|
+
return reflectCorsIfNeeded(response, request, options.allowedRequestOrigins);
|
|
85
|
+
}
|
|
86
|
+
catch (caught) {
|
|
87
|
+
// A host port (authenticate/context/audit) threw. `fetch` must never
|
|
88
|
+
// reject — the caller gets an honest 500, not an unhandled rejection
|
|
89
|
+
// whose shape depends on which runtime is hosting this.
|
|
90
|
+
await auditDenial(options, errorReason(caught) || INTERNAL_ERROR, startedAt);
|
|
91
|
+
return reflectCorsIfNeeded(jsonResponse({
|
|
92
|
+
data: rpcError(null, JSONRPC_INTERNAL_ERROR, "Internal error"),
|
|
93
|
+
status: 500,
|
|
94
|
+
}), request, options.allowedRequestOrigins);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/mcp/dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,16 @@ export declare const JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
|
27
27
|
export declare const JSONRPC_INVALID_PARAMS = -32602;
|
|
28
28
|
export declare const JSONRPC_INTERNAL_ERROR = -32603;
|
|
29
29
|
export declare const JSONRPC_UNAUTHORIZED = -32001;
|
|
30
|
+
export declare const JSONRPC_PAYLOAD_TOO_LARGE = -32002;
|
|
31
|
+
/** Authenticated but missing required scope — paired with HTTP 403. */
|
|
32
|
+
export declare const JSONRPC_INSUFFICIENT_SCOPE = -32003;
|
|
30
33
|
export declare const rpcResult: (id: JsonRpcId, result: unknown) => JsonRpcSuccess;
|
|
31
34
|
export declare const rpcError: (id: JsonRpcId, code: number, message: string) => JsonRpcFailure;
|
|
35
|
+
/** Batch / jsonrpc version / shape — returns a Response or a validated request. */
|
|
36
|
+
export declare const validateJsonRpcEnvelope: (parsed: unknown) => Response | JsonRpcRequest;
|
|
37
|
+
/**
|
|
38
|
+
* Spec requires `MCP-Protocol-Version` past `initialize`. Unknown → 400.
|
|
39
|
+
* Missing → assume `2025-03-26` (header BC rule; see `protocol.ts`).
|
|
40
|
+
*/
|
|
41
|
+
export declare const protocolVersionHeaderError: (request: Request, jsonRpcRequest: JsonRpcRequest) => Response | null;
|
|
32
42
|
//# sourceMappingURL=jsonrpc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonrpc.d.ts","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE/C,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,cAAc,CAAC;AAE9D,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAC1C,eAAO,MAAM,uBAAuB,SAAS,CAAC;AAC9C,eAAO,MAAM,wBAAwB,SAAS,CAAC;AAC/C,eAAO,MAAM,sBAAsB,SAAS,CAAC;AAC7C,eAAO,MAAM,sBAAsB,SAAS,CAAC;AAC7C,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAC3C,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAChD,uEAAuE;AACvE,eAAO,MAAM,0BAA0B,SAAS,CAAC;AAEjD,eAAO,MAAM,SAAS,GAAI,IAAI,SAAS,EAAE,QAAQ,OAAO,KAAG,cAIzD,CAAC;AAEH,eAAO,MAAM,QAAQ,GAAI,IAAI,SAAS,EAAE,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,cAItE,CAAC;AAwCH,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,OAAO,KAAG,QAAQ,GAAG,cAkBpE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GACrC,SAAS,OAAO,EAChB,gBAAgB,cAAc,KAC7B,QAAQ,GAAG,IAYb,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsonResponse } from "../http/http.js";
|
|
2
|
+
import { PROTOCOL_VERSIONS } from "./protocol.js";
|
|
3
|
+
export const JSONRPC_PARSE_ERROR = -32700;
|
|
4
|
+
export const JSONRPC_INVALID_REQUEST = -32600;
|
|
5
|
+
export const JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
6
|
+
export const JSONRPC_INVALID_PARAMS = -32602;
|
|
7
|
+
export const JSONRPC_INTERNAL_ERROR = -32603;
|
|
8
|
+
export const JSONRPC_UNAUTHORIZED = -32001;
|
|
9
|
+
export const JSONRPC_PAYLOAD_TOO_LARGE = -32002;
|
|
10
|
+
/** Authenticated but missing required scope — paired with HTTP 403. */
|
|
11
|
+
export const JSONRPC_INSUFFICIENT_SCOPE = -32003;
|
|
12
|
+
export const rpcResult = (id, result) => ({
|
|
13
|
+
jsonrpc: "2.0",
|
|
14
|
+
id,
|
|
15
|
+
result,
|
|
16
|
+
});
|
|
17
|
+
export const rpcError = (id, code, message) => ({
|
|
18
|
+
jsonrpc: "2.0",
|
|
19
|
+
id,
|
|
20
|
+
error: { code, message },
|
|
21
|
+
});
|
|
22
|
+
const isJsonRpcId = (id) => id === null || typeof id === "string" || typeof id === "number";
|
|
23
|
+
/**
|
|
24
|
+
* RFC-shaped JSON-RPC id for an error response — omitted → `null`; valid ids
|
|
25
|
+
* echoed; invalid shapes → `null` so we never reflect an illegal id.
|
|
26
|
+
*/
|
|
27
|
+
const errorId = (jsonRpcRequest) => {
|
|
28
|
+
if (jsonRpcRequest.id === undefined)
|
|
29
|
+
return null;
|
|
30
|
+
return isJsonRpcId(jsonRpcRequest.id) ? jsonRpcRequest.id : null;
|
|
31
|
+
};
|
|
32
|
+
/** Runtime JSON-RPC Request shape — types alone do not enforce this. */
|
|
33
|
+
const jsonRpcShapeError = (jsonRpcRequest) => {
|
|
34
|
+
if (jsonRpcRequest.id !== undefined && !isJsonRpcId(jsonRpcRequest.id)) {
|
|
35
|
+
return jsonResponse({
|
|
36
|
+
data: rpcError(null, JSONRPC_INVALID_REQUEST, "id must be string, number, or null"),
|
|
37
|
+
status: 400,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const orphanResult = jsonRpcRequest.method === undefined &&
|
|
41
|
+
jsonRpcRequest.id === undefined &&
|
|
42
|
+
"result" in jsonRpcRequest;
|
|
43
|
+
if (orphanResult)
|
|
44
|
+
return null;
|
|
45
|
+
if (typeof jsonRpcRequest.method !== "string" || jsonRpcRequest.method.length === 0) {
|
|
46
|
+
return jsonResponse({
|
|
47
|
+
data: rpcError(errorId(jsonRpcRequest), JSONRPC_INVALID_REQUEST, "method must be a non-empty string"),
|
|
48
|
+
status: 400,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
/** Batch / jsonrpc version / shape — returns a Response or a validated request. */
|
|
54
|
+
export const validateJsonRpcEnvelope = (parsed) => {
|
|
55
|
+
if (Array.isArray(parsed)) {
|
|
56
|
+
return jsonResponse({
|
|
57
|
+
data: rpcError(null, JSONRPC_INVALID_REQUEST, "Batch requests not supported"),
|
|
58
|
+
status: 400,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!parsed || typeof parsed !== "object") {
|
|
62
|
+
return jsonResponse({ data: rpcError(null, JSONRPC_PARSE_ERROR, "Parse error"), status: 400 });
|
|
63
|
+
}
|
|
64
|
+
const jsonRpcRequest = parsed;
|
|
65
|
+
if (jsonRpcRequest.jsonrpc !== "2.0") {
|
|
66
|
+
return jsonResponse({
|
|
67
|
+
data: rpcError(errorId(jsonRpcRequest), JSONRPC_INVALID_REQUEST, 'jsonrpc must be "2.0"'),
|
|
68
|
+
status: 400,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return jsonRpcShapeError(jsonRpcRequest) ?? jsonRpcRequest;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Spec requires `MCP-Protocol-Version` past `initialize`. Unknown → 400.
|
|
75
|
+
* Missing → assume `2025-03-26` (header BC rule; see `protocol.ts`).
|
|
76
|
+
*/
|
|
77
|
+
export const protocolVersionHeaderError = (request, jsonRpcRequest) => {
|
|
78
|
+
if (jsonRpcRequest.method === "initialize")
|
|
79
|
+
return null;
|
|
80
|
+
const header = request.headers.get("MCP-Protocol-Version");
|
|
81
|
+
if (!header || PROTOCOL_VERSIONS.has(header))
|
|
82
|
+
return null;
|
|
83
|
+
return jsonResponse({
|
|
84
|
+
data: rpcError(errorId(jsonRpcRequest), JSONRPC_INVALID_REQUEST, `Unsupported MCP-Protocol-Version: ${header}`),
|
|
85
|
+
status: 400,
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=jsonrpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonrpc.js","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { type WwwAuthenticateOptions } from "../auth/bearer.js";
|
|
2
|
+
import { type JsonRpcId, type JsonRpcRequest } from "./jsonrpc.js";
|
|
3
|
+
import type { ToolRegistry } from "./registry.js";
|
|
4
|
+
export type Principal = {
|
|
5
|
+
id: string;
|
|
6
|
+
/**
|
|
7
|
+
* Granted scopes. The literal `"*"` is a wildcard that satisfies **every**
|
|
8
|
+
* tool `scope` check (see `hasScope`) — reserve it for trusted internal
|
|
9
|
+
* principals; never mint it for an external connector.
|
|
10
|
+
*/
|
|
11
|
+
scopes: string[];
|
|
12
|
+
/** Optional host claims from `verifyToken` (tenant, plan, role, …). */
|
|
13
|
+
claims?: Record<string, unknown> | undefined;
|
|
14
|
+
};
|
|
15
|
+
/** Per-request metrics emitted through the optional `audit` port. */
|
|
16
|
+
export type AuditEntry = {
|
|
17
|
+
/** JSON-RPC method, or `""` for transport-level rejections made before parsing. */
|
|
18
|
+
method: string;
|
|
19
|
+
tool?: string | undefined;
|
|
20
|
+
principalId?: string | undefined;
|
|
21
|
+
ok: boolean;
|
|
22
|
+
error?: string | undefined;
|
|
23
|
+
durationMs: number;
|
|
24
|
+
};
|
|
25
|
+
export type McpPorts<TCtx> = {
|
|
26
|
+
authenticate: (req: Request, method: string, tool?: string) => Promise<Principal | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Builds the tool context. Called only for a `tools/call` that passed the
|
|
29
|
+
* unknown-tool and scope checks — never for `initialize`, `ping` or
|
|
30
|
+
* `tools/list`. `principal` is null only when a host adds `tools/call`
|
|
31
|
+
* to `publicMethods`.
|
|
32
|
+
*/
|
|
33
|
+
context: (req: Request, principal: Principal | null) => TCtx | Promise<TCtx>;
|
|
34
|
+
/**
|
|
35
|
+
* Opt-in metrics hook. Pass any function to receive tool results and auth
|
|
36
|
+
* denials (bad token, missing scope, query-string token), plus the 500
|
|
37
|
+
* path when a host port throws. Protocol errors (malformed JSON, bad
|
|
38
|
+
* `jsonrpc`, unsupported protocol version, oversized body, batch) are not
|
|
39
|
+
* audited. Omit it and the library stays silent — no stdout, no store.
|
|
40
|
+
* Do what you want with `entry` (DB, APM, admin UI). Throwing never fails
|
|
41
|
+
* the request, and neither does hanging — see
|
|
42
|
+
* `McpHandlerOptions.auditTimeoutMs`.
|
|
43
|
+
*/
|
|
44
|
+
audit?: ((entry: AuditEntry) => void | Promise<void>) | undefined;
|
|
45
|
+
};
|
|
46
|
+
export type ServerInfo = {
|
|
47
|
+
name: string;
|
|
48
|
+
version: string;
|
|
49
|
+
};
|
|
50
|
+
export type McpHandlerOptions<TCtx> = {
|
|
51
|
+
registry: ToolRegistry<TCtx>;
|
|
52
|
+
ports: McpPorts<TCtx>;
|
|
53
|
+
serverInfo: ServerInfo;
|
|
54
|
+
instructions?: string;
|
|
55
|
+
wwwAuthenticate: WwwAuthenticateOptions | ((req: Request) => WwwAuthenticateOptions);
|
|
56
|
+
/** Methods that skip auth (default: initialize, ping, notifications/*). */
|
|
57
|
+
publicMethods?: Set<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Max time to wait for `ports.audit` before giving up on it and
|
|
60
|
+
* responding anyway. Default 1000ms. A hanging audit sink can't fail a
|
|
61
|
+
* request (see `safeAudit`), but without this it could stall one
|
|
62
|
+
* indefinitely — "can't fail" isn't "can't delay."
|
|
63
|
+
*/
|
|
64
|
+
auditTimeoutMs?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Browser `Origin` header allowlist for MCP (DNS rebinding / CSRF).
|
|
67
|
+
* Distinct from Node Host `allowedOrigins` on `asNodeHandler`.
|
|
68
|
+
* No `Origin` → allowed (native clients). Omitted/empty → reject any
|
|
69
|
+
* present `Origin`. `"*"` admits any Origin.
|
|
70
|
+
*/
|
|
71
|
+
allowedRequestOrigins?: string[];
|
|
72
|
+
/**
|
|
73
|
+
* Filter `tools/list` down to tools the calling principal's scopes
|
|
74
|
+
* actually satisfy (an unscoped tool is always listed). Default
|
|
75
|
+
* **false** — every tool is always listed, `tools/call` is what enforces
|
|
76
|
+
* scope (with a **403** `insufficient_scope`, see `insufficientScope`).
|
|
77
|
+
* Turning this on trades that step-up signal away: a client can no
|
|
78
|
+
* longer discover "this tool exists, ask for its scope" from `tools/list`
|
|
79
|
+
* for a scope it doesn't hold — only worth it if listing a tool at all
|
|
80
|
+
* would leak something (its name, description, or schema) you don't want
|
|
81
|
+
* a lesser-scoped principal to see.
|
|
82
|
+
*/
|
|
83
|
+
hideToolsOutsideScope?: boolean;
|
|
84
|
+
};
|
|
85
|
+
/** `McpHandlerOptions` after `createMcpHandler` resolved its defaults. */
|
|
86
|
+
export type ResolvedMcpHandlerOptions<TCtx> = McpHandlerOptions<TCtx> & {
|
|
87
|
+
publicMethods: Set<string>;
|
|
88
|
+
};
|
|
89
|
+
/** `AuditEntry.error` values for requests the handler itself denied. */
|
|
90
|
+
export declare const DENIAL_REASONS: {
|
|
91
|
+
readonly unauthorized: "unauthorized";
|
|
92
|
+
readonly queryStringToken: "query_string_token";
|
|
93
|
+
readonly originNotAllowed: "origin_not_allowed";
|
|
94
|
+
readonly unknownTool: "unknown_tool";
|
|
95
|
+
readonly missingScope: "missing_scope";
|
|
96
|
+
};
|
|
97
|
+
export declare const DEFAULT_PUBLIC_METHODS: Set<string>;
|
|
98
|
+
/**
|
|
99
|
+
* JSON-RPC 2.0: a notification omits `id` (no response). MCP also treats
|
|
100
|
+
* `notifications/*` and legacy `initialized` as notifications even when an
|
|
101
|
+
* `id` is present. Orphan result-shaped bodies (no method/id) get 202 too.
|
|
102
|
+
*/
|
|
103
|
+
export declare const isNotification: (body: JsonRpcRequest) => boolean;
|
|
104
|
+
export declare const resolveWwwAuthenticate: <TCtx>(options: McpHandlerOptions<TCtx>, request: Request) => WwwAuthenticateOptions;
|
|
105
|
+
/**
|
|
106
|
+
* `id` defaults to `null` for the transport-level call sites in
|
|
107
|
+
* `dispatch.ts` — a body hasn't been parsed yet there, so there's no real
|
|
108
|
+
* id to echo. Call sites past that point (inside `dispatchRpc` and the
|
|
109
|
+
* method handlers) have one and must pass it, or a client correlating
|
|
110
|
+
* responses by id sees `null` on every 401 regardless of what it sent.
|
|
111
|
+
*/
|
|
112
|
+
export declare const unauthorized: (wwwAuthenticate: WwwAuthenticateOptions, message?: string, id?: JsonRpcId) => Response;
|
|
113
|
+
/**
|
|
114
|
+
* RFC 6750 §3.1: a request that authenticated but lacks the required scope
|
|
115
|
+
* gets **403**, not 401 — 401 tells the client "authenticate", which a
|
|
116
|
+
* scope-step-up client obeys by re-running the exact same OAuth flow,
|
|
117
|
+
* getting back the exact same scopes, and retrying forever. 403 with
|
|
118
|
+
* `error="insufficient_scope"` says what's actually wrong: re-consent with
|
|
119
|
+
* a broader `scope` request, not re-authenticate.
|
|
120
|
+
*/
|
|
121
|
+
export declare const insufficientScope: (wwwAuthenticate: WwwAuthenticateOptions, scope: string, id?: JsonRpcId) => Response;
|
|
122
|
+
/**
|
|
123
|
+
* Invoke the audit metrics hook, swallowing any failure and racing it
|
|
124
|
+
* against `auditTimeoutMs` so a hanging sink can't stall the response
|
|
125
|
+
* either — "can't fail a request" was already true; this adds "can't
|
|
126
|
+
* delay one" past a bound. The audit call keeps running in the background
|
|
127
|
+
* if the timer wins; its own `catch` below means it can never surface as
|
|
128
|
+
* an unhandled rejection whenever it does finish.
|
|
129
|
+
*/
|
|
130
|
+
export declare const safeAudit: <TCtx>(options: McpHandlerOptions<TCtx>, entry: AuditEntry) => Promise<void>;
|
|
131
|
+
/** True when `scope` is unset, granted, or covered by the `"*"` wildcard scope. */
|
|
132
|
+
export declare const hasScope: (principal: Principal, scope: string | undefined) => boolean;
|
|
133
|
+
//# sourceMappingURL=methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"methods.d.ts","sourceRoot":"","sources":["../../src/mcp/methods.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { wwwAuthenticateHeader } from "../auth/bearer.js";
|
|
2
|
+
import { DEFAULT_AUDIT_TIMEOUT_MS, jsonResponse, runAuditBounded } from "../http/http.js";
|
|
3
|
+
import { JSONRPC_INSUFFICIENT_SCOPE, JSONRPC_UNAUTHORIZED, rpcError, } from "./jsonrpc.js";
|
|
4
|
+
/** `AuditEntry.error` values for requests the handler itself denied. */
|
|
5
|
+
export const DENIAL_REASONS = {
|
|
6
|
+
unauthorized: "unauthorized",
|
|
7
|
+
queryStringToken: "query_string_token",
|
|
8
|
+
originNotAllowed: "origin_not_allowed",
|
|
9
|
+
unknownTool: "unknown_tool",
|
|
10
|
+
missingScope: "missing_scope",
|
|
11
|
+
};
|
|
12
|
+
export const DEFAULT_PUBLIC_METHODS = new Set([
|
|
13
|
+
"initialize",
|
|
14
|
+
"ping",
|
|
15
|
+
"notifications/initialized",
|
|
16
|
+
"initialized",
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* JSON-RPC 2.0: a notification omits `id` (no response). MCP also treats
|
|
20
|
+
* `notifications/*` and legacy `initialized` as notifications even when an
|
|
21
|
+
* `id` is present. Orphan result-shaped bodies (no method/id) get 202 too.
|
|
22
|
+
*/
|
|
23
|
+
export const isNotification = (body) => {
|
|
24
|
+
if (body.id === undefined) {
|
|
25
|
+
return typeof body.method === "string" || (body.method === undefined && "result" in body);
|
|
26
|
+
}
|
|
27
|
+
return ((typeof body.method === "string" && body.method.startsWith("notifications/")) ||
|
|
28
|
+
body.method === "initialized");
|
|
29
|
+
};
|
|
30
|
+
export const resolveWwwAuthenticate = (options, request) => typeof options.wwwAuthenticate === "function"
|
|
31
|
+
? options.wwwAuthenticate(request)
|
|
32
|
+
: options.wwwAuthenticate;
|
|
33
|
+
/**
|
|
34
|
+
* `id` defaults to `null` for the transport-level call sites in
|
|
35
|
+
* `dispatch.ts` — a body hasn't been parsed yet there, so there's no real
|
|
36
|
+
* id to echo. Call sites past that point (inside `dispatchRpc` and the
|
|
37
|
+
* method handlers) have one and must pass it, or a client correlating
|
|
38
|
+
* responses by id sees `null` on every 401 regardless of what it sent.
|
|
39
|
+
*/
|
|
40
|
+
export const unauthorized = (wwwAuthenticate, message = "Unauthorized", id = null) => jsonResponse({
|
|
41
|
+
data: rpcError(id, JSONRPC_UNAUTHORIZED, message),
|
|
42
|
+
status: 401,
|
|
43
|
+
headers: { "WWW-Authenticate": wwwAuthenticateHeader(wwwAuthenticate) },
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* RFC 6750 §3.1: a request that authenticated but lacks the required scope
|
|
47
|
+
* gets **403**, not 401 — 401 tells the client "authenticate", which a
|
|
48
|
+
* scope-step-up client obeys by re-running the exact same OAuth flow,
|
|
49
|
+
* getting back the exact same scopes, and retrying forever. 403 with
|
|
50
|
+
* `error="insufficient_scope"` says what's actually wrong: re-consent with
|
|
51
|
+
* a broader `scope` request, not re-authenticate.
|
|
52
|
+
*/
|
|
53
|
+
export const insufficientScope = (wwwAuthenticate, scope, id = null) => jsonResponse({
|
|
54
|
+
data: rpcError(id, JSONRPC_INSUFFICIENT_SCOPE, `Missing scope: ${scope}`),
|
|
55
|
+
status: 403,
|
|
56
|
+
headers: {
|
|
57
|
+
"WWW-Authenticate": wwwAuthenticateHeader({
|
|
58
|
+
...wwwAuthenticate,
|
|
59
|
+
error: "insufficient_scope",
|
|
60
|
+
scope,
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Invoke the audit metrics hook, swallowing any failure and racing it
|
|
66
|
+
* against `auditTimeoutMs` so a hanging sink can't stall the response
|
|
67
|
+
* either — "can't fail a request" was already true; this adds "can't
|
|
68
|
+
* delay one" past a bound. The audit call keeps running in the background
|
|
69
|
+
* if the timer wins; its own `catch` below means it can never surface as
|
|
70
|
+
* an unhandled rejection whenever it does finish.
|
|
71
|
+
*/
|
|
72
|
+
export const safeAudit = async (options, entry) => {
|
|
73
|
+
const audit = options.ports.audit;
|
|
74
|
+
if (!audit)
|
|
75
|
+
return;
|
|
76
|
+
await runAuditBounded(() => audit(entry), options.auditTimeoutMs ?? DEFAULT_AUDIT_TIMEOUT_MS);
|
|
77
|
+
};
|
|
78
|
+
/** True when `scope` is unset, granted, or covered by the `"*"` wildcard scope. */
|
|
79
|
+
export const hasScope = (principal, scope) => {
|
|
80
|
+
if (!scope)
|
|
81
|
+
return true;
|
|
82
|
+
return principal.scopes.includes(scope) || principal.scopes.includes("*");
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=methods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"methods.js","sourceRoot":"","sources":["../../src/mcp/methods.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EAGpB,QAAQ,GACT,MAAM,cAAc,CAAC;AA8FtB,wEAAwE;AACxE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY,EAAE,cAAc;IAC5B,gBAAgB,EAAE,oBAAoB;IACtC,gBAAgB,EAAE,oBAAoB;IACtC,WAAW,EAAE,cAAc;IAC3B,YAAY,EAAE,eAAe;CACrB,CAAC;AAEX,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IAC5C,YAAY;IACZ,MAAM;IACN,2BAA2B;IAC3B,aAAa;CACd,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAoB,EAAW,EAAE;IAC9D,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,QAAQ,IAAI,IAAI,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,CACL,CAAC,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;QAC7E,IAAI,CAAC,MAAM,KAAK,aAAa,CAC9B,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,OAAgC,EAChC,OAAgB,EACQ,EAAE,CAC1B,OAAO,OAAO,CAAC,eAAe,KAAK,UAAU;IAC3C,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC;IAClC,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC;AAE9B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,eAAuC,EACvC,OAAO,GAAG,cAAc,EACxB,KAAgB,IAAI,EACV,EAAE,CACZ,YAAY,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,oBAAoB,EAAE,OAAO,CAAC;IACjD,MAAM,EAAE,GAAG;IACX,OAAO,EAAE,EAAE,kBAAkB,EAAE,qBAAqB,CAAC,eAAe,CAAC,EAAE;CACxE,CAAC,CAAC;AAEL;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,eAAuC,EACvC,KAAa,EACb,KAAgB,IAAI,EACV,EAAE,CACZ,YAAY,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,0BAA0B,EAAE,kBAAkB,KAAK,EAAE,CAAC;IACzE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,kBAAkB,EAAE,qBAAqB,CAAC;YACxC,GAAG,eAAe;YAClB,KAAK,EAAE,oBAAoB;YAC3B,KAAK;SACN,CAAC;KACH;CACF,CAAC,CAAC;AAEL;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,OAAgC,EAChC,KAAiB,EACF,EAAE;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC,CAAC;AAChG,CAAC,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,SAAoB,EAAE,KAAyB,EAAW,EAAE;IACnF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC5E,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const PROTOCOL_VERSIONS: Set<string>;
|
|
2
|
+
/**
|
|
3
|
+
* Answered on `initialize` when the client's requested version is missing
|
|
4
|
+
* or unrecognized: per the spec, the latest version this server supports.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_PROTOCOL_VERSION = "2025-06-18";
|
|
7
|
+
/**
|
|
8
|
+
* The assumed protocol version for a request that omits the
|
|
9
|
+
* `MCP-Protocol-Version` header — distinct from `DEFAULT_PROTOCOL_VERSION`
|
|
10
|
+
* above. This is the 2025-06-18 spec's own backwards-compatibility rule
|
|
11
|
+
* for the header specifically (introduced in that revision, so a header-less
|
|
12
|
+
* request predates it), not a "pick the newest" preference.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ASSUMED_HEADER_PROTOCOL_VERSION = "2025-03-26";
|
|
15
|
+
export declare const pickProtocolVersion: (params?: Record<string, unknown>) => string;
|
|
16
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/mcp/protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,aAAsD,CAAC;AAErF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,eAAe,CAAC;AAE5D,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAMtE,CAAC"}
|