mcp-trellis 1.0.0 → 2.2.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 +65 -93
- 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 +13 -30
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +55 -101
- 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 -89
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +143 -63
- 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 +19 -4
- package/dist/auth/bearer.js.map +1 -1
- package/dist/auth/signed-token.d.ts +26 -0
- package/dist/auth/signed-token.d.ts.map +1 -0
- package/dist/auth/signed-token.js +96 -0
- package/dist/auth/signed-token.js.map +1 -0
- package/dist/clients.js +1 -1
- 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/{body.d.ts → http/body.d.ts} +27 -6
- package/dist/http/body.d.ts.map +1 -0
- package/dist/{body.js → http/body.js} +33 -13
- 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/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
- 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} +9 -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/{methods.d.ts → mcp/methods.d.ts} +60 -11
- 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/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/{protocol.js → mcp/protocol.js} +1 -3
- 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} +20 -1
- 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} +18 -21
- package/dist/mcp/validate.d.ts.map +1 -0
- package/dist/{validate.js → mcp/validate.js} +59 -41
- 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 +11 -0
- package/dist/oauth/constants.d.ts.map +1 -1
- package/dist/oauth/constants.js +11 -0
- package/dist/oauth/constants.js.map +1 -1
- package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
- package/dist/oauth/crypto/base64url.d.ts.map +1 -0
- package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
- 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/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
- package/dist/oauth/crypto/sealed.d.ts.map +1 -0
- package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
- package/dist/oauth/crypto/sealed.js.map +1 -0
- package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
- package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
- package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
- 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} +8 -2
- 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} +8 -18
- 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} +32 -25
- 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 -9
- package/dist/oauth/endpoints/wellknown.js.map +1 -0
- package/dist/oauth/policy/redirect.d.ts.map +1 -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/{resource.d.ts → policy/resource.d.ts} +1 -1
- package/dist/oauth/policy/resource.d.ts.map +1 -0
- package/dist/oauth/{resource.js → policy/resource.js} +2 -2
- package/dist/oauth/policy/resource.js.map +1 -0
- package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
- package/dist/oauth/policy/scope.d.ts.map +1 -0
- package/dist/oauth/{scope.js → policy/scope.js} +3 -3
- package/dist/oauth/policy/scope.js.map +1 -0
- package/dist/oauth/router.d.ts +10 -13
- package/dist/oauth/router.d.ts.map +1 -1
- package/dist/oauth/router.js +93 -115
- package/dist/oauth/router.js.map +1 -1
- package/dist/oauth/types.d.ts +99 -45
- package/dist/oauth/types.d.ts.map +1 -1
- package/dist/oauth/types.js +1 -90
- package/dist/oauth/types.js.map +1 -1
- package/dist/server.d.ts +13 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -8
- 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 +37 -47
- package/docs/compatibility.md +86 -0
- package/docs/diagrams/demo-tool-result.png +0 -0
- package/docs/diagrams/mcp-trellis-architecture.html +305 -0
- package/docs/guide.md +46 -16
- package/docs/reference.md +55 -22
- package/docs/security.md +58 -9
- package/docs/troubleshooting.md +19 -0
- package/package.json +27 -10
- package/dist/adapters/origins.d.ts +0 -19
- package/dist/adapters/origins.d.ts.map +0 -1
- package/dist/adapters/origins.js +0 -52
- package/dist/adapters/origins.js.map +0 -1
- package/dist/body.d.ts.map +0 -1
- package/dist/body.js.map +0 -1
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -119
- 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 -18
- package/dist/jsonrpc.js.map +0 -1
- package/dist/methods.d.ts.map +0 -1
- package/dist/methods.js +0 -130
- 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 -141
- 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 -24
- package/dist/oauth/clientauth.d.ts.map +0 -1
- package/dist/oauth/clientauth.js +0 -119
- 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 -85
- package/dist/oauth/codes.js.map +0 -1
- package/dist/oauth/consent.d.ts +0 -65
- package/dist/oauth/consent.d.ts.map +0 -1
- package/dist/oauth/consent.js +0 -173
- package/dist/oauth/consent.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.map +0 -1
- package/dist/oauth/reqbody.d.ts +0 -10
- package/dist/oauth/reqbody.d.ts.map +0 -1
- package/dist/oauth/reqbody.js +0 -29
- package/dist/oauth/reqbody.js.map +0 -1
- package/dist/oauth/resource.d.ts.map +0 -1
- 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.map +0 -1
- package/dist/oauth/scope.js.map +0 -1
- package/dist/oauth/sealed.d.ts.map +0 -1
- package/dist/oauth/sealed.js.map +0 -1
- package/dist/oauth/secrethash.d.ts.map +0 -1
- package/dist/oauth/secrethash.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.map +0 -1
- 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 -77
- package/dist/registry.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -98
- package/dist/tools.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
- /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
- /package/dist/oauth/{redirect.js → policy/redirect.js} +0 -0
package/docs/reference.md
CHANGED
|
@@ -7,9 +7,10 @@ Ports and recipes: [guide.md](guide.md). Security: [security.md](security.md).
|
|
|
7
7
|
|
|
8
8
|
| Import | What you get |
|
|
9
9
|
|--------|----------------|
|
|
10
|
-
| `mcp-trellis` | `createMcpApp`, client profiles, MCP handler, tool registry, bearer helpers,
|
|
11
|
-
| `mcp-trellis/oauth` | OAuth 2.1 AS router,
|
|
10
|
+
| `mcp-trellis` | `createMcpApp`, client profiles, MCP handler, tool registry, bearer helpers, JSON-RPC helpers |
|
|
11
|
+
| `mcp-trellis/oauth` | OAuth 2.1 AS router, discovery metadata, resource and redirect policy, client-secret hashing |
|
|
12
12
|
| `mcp-trellis/node` | `asNodeHandler` + `resolveOrigin` for Node `(req, res)` |
|
|
13
|
+
| `mcp-trellis/advanced` | Low-level primitives: PKCE, auth codes, client auth, scope/resource parsing, schema validation, HTTP utils. Less stable — may change in a minor release |
|
|
13
14
|
|
|
14
15
|
`createMcpApp` is the batteries-included layer. `createMcpHandler` and
|
|
15
16
|
`createOAuthRouter` remain exported and unchanged — reach for them when you want
|
|
@@ -24,7 +25,7 @@ With defaults `resourcePath: "/mcp"` and `oauthPath: "/mcp/oauth"`:
|
|
|
24
25
|
|------|---------|
|
|
25
26
|
| `/.well-known/oauth-protected-resource` (+ `/mcp`) | Protected resource metadata |
|
|
26
27
|
| `/.well-known/oauth-authorization-server` (+ `/mcp`) | Authorization server metadata |
|
|
27
|
-
| `/mcp/oauth/register` | Dynamic client registration (unmounted when DCR is off) |
|
|
28
|
+
| `/mcp/oauth/register` | Dynamic client registration (unmounted when DCR is off). `redirect_uris`, if supplied, must be an array of at most 10 entries — a malformed body, a non-object body, a non-array `redirect_uris`, or too many entries all return **400** `invalid_client_metadata` (RFC 7591 §3.2.2) rather than silently falling back to the default callback. `redirect_uris` omitted entirely still falls back as documented below |
|
|
28
29
|
| `/mcp/oauth/authorize` | Authorization endpoint — GET only. Renders a consent interstitial rather than redirecting directly; see `/consent` below and [security.md](security.md). Once `redirect_uri` is validated, every remaining error redirects to it with `?error=…` (RFC 6749 §4.1.2.1) rather than returning JSON; an invalid `redirect_uri` itself, and an oversized `state`, are the two exceptions and stay direct 400s |
|
|
29
30
|
| `/mcp/oauth/consent` | POST only. Approves or denies a consent ticket issued by `/authorize` and, on approval, issues the code and redirects to `redirect_uri` |
|
|
30
31
|
| `/mcp/oauth/token` | Token endpoint. JSON or form body |
|
|
@@ -37,7 +38,7 @@ With defaults `resourcePath: "/mcp"` and `oauthPath: "/mcp/oauth"`:
|
|
|
37
38
|
| `initialize` | public | Negotiates protocol version; returns `capabilities.tools` |
|
|
38
39
|
| `ping` | public | Empty result |
|
|
39
40
|
| `tools/list` | Bearer | Lists registry entries |
|
|
40
|
-
| `tools/call` | Bearer + scope | Runs the tool; missing scope →
|
|
41
|
+
| `tools/call` | Bearer + scope | Runs the tool; no Bearer at all → 401, authenticated but missing scope → 403, `params.name` naming no registered tool → `-32602` (protocol error, not a tool result — see `docs/guide.md`) |
|
|
41
42
|
| `notifications/*` | public | HTTP 202 empty body |
|
|
42
43
|
|
|
43
44
|
Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
|
|
@@ -49,6 +50,7 @@ Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
|
|
|
49
50
|
| **202** | Notification (empty body) |
|
|
50
51
|
| **400** | Parse error, batch array, invalid request, `jsonrpc` not `"2.0"`, unsupported `MCP-Protocol-Version` |
|
|
51
52
|
| **401** | Missing/invalid Bearer; includes `WWW-Authenticate`; echoes the real request id, not `null` |
|
|
53
|
+
| **403** | Authenticated, but the token's scopes don't include the tool's `scope`; `WWW-Authenticate` carries `error="insufficient_scope"` and `scope="<required>"` (RFC 6750 §3.1) |
|
|
52
54
|
| **405** | Wrong HTTP verb on MCP |
|
|
53
55
|
| **413** | Request body over the cap — `/mcp` (1 MiB default), `/token`/`/revoke`/`/register` (64 KiB default) |
|
|
54
56
|
|
|
@@ -57,7 +59,7 @@ Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
|
|
|
57
59
|
| `-32700` | `JSONRPC_PARSE_ERROR` |
|
|
58
60
|
| `-32600` | `JSONRPC_INVALID_REQUEST` (batches) |
|
|
59
61
|
| `-32601` | `JSONRPC_METHOD_NOT_FOUND` |
|
|
60
|
-
| `-32602` | `JSONRPC_INVALID_PARAMS` |
|
|
62
|
+
| `-32602` | `JSONRPC_INVALID_PARAMS` (unrecognized `tools/call` `params.name`) |
|
|
61
63
|
| `-32603` | `JSONRPC_INTERNAL_ERROR` |
|
|
62
64
|
| `-32001` | `JSONRPC_UNAUTHORIZED` |
|
|
63
65
|
|
|
@@ -72,9 +74,11 @@ Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
|
|
|
72
74
|
| `instructions` | no | Returned on `initialize` |
|
|
73
75
|
| `publicMethods` | no | Default: `initialize`, `ping`, notifications |
|
|
74
76
|
| `auditTimeoutMs` | no | Max time to wait for `ports.audit` before responding anyway. Default 1000ms — see [security.md](security.md) |
|
|
77
|
+
| `allowedRequestOrigins` | no | Browser `Origin` allowlist. Omitted → reject any present `Origin`; no header → allow; `"*"` opts out. Distinct from Node Host `allowedOrigins` |
|
|
75
78
|
|
|
76
79
|
`ports.audit` fires on tool results and on auth denials (bad token, missing
|
|
77
|
-
scope, query-string token
|
|
80
|
+
scope, query-string token, rejected browser Origin as `origin_not_allowed`),
|
|
81
|
+
plus the 500 path when a host port throws.
|
|
78
82
|
Malformed JSON, bad `jsonrpc`, unsupported `MCP-Protocol-Version`, oversized
|
|
79
83
|
bodies, and batch requests return JSON-RPC client errors without an audit
|
|
80
84
|
entry — those are protocol errors, not auth denials.
|
|
@@ -91,9 +95,10 @@ entry — those are protocol errors, not auth denials.
|
|
|
91
95
|
| `defaultScopes` | full `scopes` | Scopes granted when a client omits `scope` entirely. **Required at construction** once `scopes` has more than one entry — the router throws rather than silently granting everything advertised. Must be a subset of `scopes` |
|
|
92
96
|
| `tokenEndpointAuthMethods` | `["none"]` | Advertised client auth methods |
|
|
93
97
|
| `allowUnregisteredClients` | `true` | `false` requires `clientStore`, unmounts DCR, and rejects unknown `client_id`s (`unauthorized_client` / `invalid_client`) |
|
|
94
|
-
| `requireRegisteredClients` | `true` | Reject any `client_id` that isn't `clientStore`-resolved or self-sealed via this server's own `/register` (see [security.md](security.md)). DCR stays mounted, unlike `allowUnregisteredClients: false` — a client just can't invent an id out of thin air |
|
|
98
|
+
| `requireRegisteredClients` | `true` | Reject any `client_id` that isn't `clientStore`-resolved or self-sealed via this server's own `/register` (see [security.md](security.md)). DCR stays mounted, unlike `allowUnregisteredClients: false` — a client just can't invent an id out of thin air. Enforced at `/authorize`, and at `/token`'s `refresh_token` grant and `/revoke` (the `authorization_code` grant's `client_id` is already bound into the sealed code at `/authorize` time, so it's covered indirectly there) |
|
|
95
99
|
| `redirect` | see below | Redirect URI allowlist |
|
|
96
100
|
| `consent` | see below | Consent/approval policy for `/authorize` |
|
|
101
|
+
| `auditTimeoutMs` | `1000` | Max time to wait for `ports.audit` before responding anyway — same as MCP |
|
|
97
102
|
|
|
98
103
|
**`redirect` (`RedirectAllowlistOptions`):**
|
|
99
104
|
|
|
@@ -129,7 +134,17 @@ validated against their own bound `redirectUris`.
|
|
|
129
134
|
| `allowLoopback` | `true` | Allow loopback redirects — see the narrowed `http:`-only, no-`localhost` behavior in [security.md](security.md) |
|
|
130
135
|
| `consent` | built-in interstitial | Same as `OAuthRouterOptions.consent` — override the approval page or pre-approve specific client ids |
|
|
131
136
|
| `requireRegisteredClients` | `true` | Same as `OAuthRouterOptions.requireRegisteredClients` — inventing a public `client_id` is rejected unless it comes from `clientStore` or this server's own `/register` |
|
|
132
|
-
| `instructions`, `validateArgs`, `onToolError`, `context`, `audit`, `auditTimeoutMs` | — | Passed through
|
|
137
|
+
| `instructions`, `validateArgs`, `onToolError`, `context`, `audit`, `auditTimeoutMs`, `allowedRequestOrigins` | — | Passed through to the MCP handler. Top-level `audit` is a unified MCP+OAuth sink (`source`); `auth.audit` overrides OAuth only. `auditTimeoutMs` applies to both |
|
|
138
|
+
| `hideToolsOutsideScope` | `false` | Filter `tools/list` to tools the calling principal's scopes actually satisfy (an unscoped tool, or one with `scope: null`, is always listed). `tools/call` already enforces scope on its own (403 `insufficient_scope`) regardless of this setting — turning it on only changes what's discoverable via `tools/list` |
|
|
139
|
+
|
|
140
|
+
**A tool's `scope` (`ToolDef.scope`):** required scope, `null` for "any authenticated
|
|
141
|
+
principal, deliberately" or omitted for the same at runtime. On a server
|
|
142
|
+
advertising more than one `scopes` entry, `createMcpApp` **refuses to
|
|
143
|
+
construct** if any tool omits `scope` — an omission there means "any
|
|
144
|
+
authenticated principal, including one holding none of the advertised
|
|
145
|
+
scopes," which is rarely the intent once there's more than one scope to
|
|
146
|
+
have gotten wrong. Pass `scope: null` to state the omission on purpose.
|
|
147
|
+
A single-scope server has no such ambiguity and is unaffected.
|
|
133
148
|
|
|
134
149
|
## Exports
|
|
135
150
|
|
|
@@ -137,14 +152,15 @@ validated against their own bound `redirectUris`.
|
|
|
137
152
|
<summary><code>mcp-trellis</code></summary>
|
|
138
153
|
|
|
139
154
|
- `createMcpApp`, `createMcpHandler`, `createToolRegistry`
|
|
140
|
-
- `consoleAudit` — convenience `audit` sink that logs to the console (pass any function for metrics / DB / APM)
|
|
141
|
-
- `
|
|
155
|
+
- `consoleAudit` — convenience unified `audit` sink that logs to the console (pass any function for metrics / DB / APM)
|
|
156
|
+
- `UnifiedAuditEntry` — `{ source: "mcp" } & AuditEntry` \| `{ source: "oauth" } & OAuthAuditEntry`
|
|
157
|
+
- `defineTool`, `apiTool` — typed, validated tool authoring on top of `ToolDef`. `apiTool`'s `timeoutMs` (default 30000, or `false` to disable) and `maxResponseBytes` (default 1 MiB) bound the upstream call; either surfaces as `isError: true`, not a thrown exception
|
|
142
158
|
- `CLIENT_PROFILES`, `DEFAULT_CLIENTS`, `authMethodsFor`, `redirectUrisFor`, `preRegisteredClients`, `hasDynamicClient`
|
|
143
|
-
- `parseBearer`, `
|
|
144
|
-
- `
|
|
159
|
+
- `parseBearer`, `wwwAuthenticateHeader`, `rejectQueryToken`
|
|
160
|
+
- `signedTokenAuth` — one-secret convenience `McpAppAuth` (HMAC access tokens via HKDF; no refresh/revoke). Type `SignedTokenAuthOptions`
|
|
145
161
|
- `rpcResult`, `rpcError`, JSON-RPC error constants
|
|
146
|
-
- `
|
|
147
|
-
- `jsonResponse
|
|
162
|
+
- `PROTOCOL_VERSIONS`, `DEFAULT_PROTOCOL_VERSION`
|
|
163
|
+
- `jsonResponse({ data, status?, headers?, cors? })` — type `JsonResponseInput` exported
|
|
148
164
|
- Types: `McpApp`, `McpAppOptions`, `McpAppAuth`, `VerifiedToken`, `ClientName`, `ClientProfile`, `McpHandler`, `McpHandlerOptions`, `McpPorts`, `Principal`, `AuditEntry`, `ServerInfo`, `ToolDef`, `ToolHandler`, `ToolResult`, `ToolRegistry`, `JsonSchema`, `StandardSchemaV1`, `DefineToolOptions`, `ApiToolOptions`, `ApiRequest`, `ClientStore`, `RegisteredClient`, `CodeStore`, `MintAccessTokenInput`, `RefreshAccessTokenInput`, `RevokeTokenInput`, `MintedToken`, `OAuthUser`, …
|
|
149
165
|
|
|
150
166
|
</details>
|
|
@@ -154,25 +170,42 @@ validated against their own bound `redirectUris`.
|
|
|
154
170
|
|
|
155
171
|
- `createOAuthRouter`
|
|
156
172
|
- `authorizationServerMetadata`, `protectedResourceMetadata`, `mcpWwwAuthenticate`
|
|
157
|
-
- `canonicalResource`, `resourcesEqual`, `
|
|
173
|
+
- `canonicalResource`, `resourcesEqual`, `DEFAULT_RESOURCE_PATH`
|
|
158
174
|
- `isAllowedRedirectUri`, `CLAUDE_CALLBACK`
|
|
159
|
-
- `
|
|
160
|
-
- `verifyPkceS256`, `sha256Base64Url`, `randomBase64Url`
|
|
161
|
-
- `readClientAuth`, `firstClientAuthError`, `unregisteredClientsAllowed`
|
|
162
|
-
- `parseScope`, `formatScope`, `requestedScopes`, `firstScopeError`, `scopeErrorInfo`
|
|
163
|
-
- `defaultScopes`, `registeredClientsRequired` — resolve the effective option value, same pattern as `unregisteredClientsAllowed`
|
|
175
|
+
- `isCimdClientId`, `resolveCimdClient`
|
|
164
176
|
- `hashClientSecret`, `verifyClientSecret` — for `ClientStore.secretHash` (hash once at registration time; the library verifies)
|
|
165
|
-
- `buildErrorRedirectUrl` — the RFC 6749 §4.1.2.1 helper `/authorize` uses internally, exported for hosts composing their own authorize flow via [compose the primitives](guide.md#advanced-compose-the-primitives)
|
|
166
|
-
- `normalizeConfiguredPath` — strips a trailing slash from a configured path (`resourcePath`, `oauthPath`); `createOAuthRouter` applies this to `resourcePath` internally, exported for hosts composing their own routing
|
|
167
177
|
- `GRANT_TYPES`, `OAUTH_ERRORS`, `DEFAULT_SCOPE`, `TOKEN_ENDPOINT_AUTH_METHODS`
|
|
168
178
|
- Types: `OAuthUser`, `MintedToken`, `OAuthPorts`, `OAuthAuditEntry`, `OAuthRouterOptions`, `OAuthErrorInfo`, `AuthCodeRecord`, `CodeStore`, `ClientStore`, `RegisteredClient`, `ClientAssertion`, `ClientAuth`, `TokenEndpointAuthMethod`, `MintAccessTokenInput`, `RefreshAccessTokenInput`, `RevokeTokenInput`, `ConsentOptions`, `ConsentRequest`
|
|
169
179
|
|
|
170
180
|
</details>
|
|
171
181
|
|
|
182
|
+
<details>
|
|
183
|
+
<summary><code>mcp-trellis/advanced</code></summary>
|
|
184
|
+
|
|
185
|
+
Building blocks for hosts assembling their own transport or OAuth flow. Less stable than the entries above.
|
|
186
|
+
|
|
187
|
+
- `timingSafeEqual`, `matchesAny`
|
|
188
|
+
- `validateAgainstSchema`, `JSON_SCHEMA_TYPES`, `SUPPORTED_SCHEMA_KEYWORDS`, `IGNORED_SCHEMA_KEYWORDS`, `unsupportedKeywords`, `missingObjectType`
|
|
189
|
+
- `pickProtocolVersion`, `ASSUMED_HEADER_PROTOCOL_VERSION`
|
|
190
|
+
- `emptyResponse`, `optionsResponse`, `corsHeaders`, `methodNotAllowed`
|
|
191
|
+
- `issueAuthCode`, `consumeAuthCode` (pass `codeStore`; omitting it is deprecated), `newClientId`
|
|
192
|
+
- `verifyPkceS256`, `sha256Base64Url`, `randomBase64Url`
|
|
193
|
+
- `readClientAuth`, `firstClientAuthError`
|
|
194
|
+
- `firstResourceError`, `resourceErrorInfo`, `normalizeConfiguredPath`
|
|
195
|
+
- `parseScope`, `formatScope`, `requestedScopes`, `firstScopeError`, `scopeErrorInfo`
|
|
196
|
+
- `advertisedScopes`, `defaultScopes`, `registeredClientsRequired`, `unregisteredClientsAllowed` — resolve the effective option value
|
|
197
|
+
- `buildErrorRedirectUrl` — the RFC 6749 §4.1.2.1 error-redirect builder `/authorize` uses
|
|
198
|
+
- Types: `AuthCodeRecord`, `CodeStore`, `ClientAuth`
|
|
199
|
+
|
|
200
|
+
**Deprecated in 2.1:** every symbol above is still importable from `mcp-trellis` or `mcp-trellis/oauth`, where it used to live, as a `@deprecated` alias. Those aliases are removed in 3.0.
|
|
201
|
+
|
|
202
|
+
</details>
|
|
203
|
+
|
|
172
204
|
<details>
|
|
173
205
|
<summary><code>mcp-trellis/node</code></summary>
|
|
174
206
|
|
|
175
207
|
- `asNodeHandler`, `resolveOrigin`, `isAllowedOrigin`, `toWebRequest`, `sendWebResponse`, `readNodeBody`
|
|
176
208
|
- Types: `NodeRequestLike`, `NodeResponseLike`, `ToWebRequestOptions`, `ResolveOriginOptions`, `AsNodeHandlerOptions`, `OriginAllowlistOptions`
|
|
209
|
+
- `InvalidOriginError` — thrown by `resolveOrigin` for a Host/`X-Forwarded-*` header that can't safely become an origin (missing, carrying a path/query/fragment/credentials, malformed, or a non-`http(s)` scheme). `asNodeHandler` answers it with **400**, not the generic 500 it gives an actual unexpected failure
|
|
177
210
|
|
|
178
211
|
</details>
|
package/docs/security.md
CHANGED
|
@@ -11,7 +11,7 @@ Ports and audience wiring: [guide.md](guide.md). Roadmap: [ROADMAP.md](ROADMAP.m
|
|
|
11
11
|
- `jsonrpc` must be exactly `"2.0"` on every `/mcp` request → `-32600` otherwise, echoing the request id when one was present
|
|
12
12
|
- `MCP-Protocol-Version` header validated on every request past `initialize` (which is what negotiates it) — unsupported → 400; missing → assumed `2025-03-26` per the spec's own backwards-compatibility rule
|
|
13
13
|
- Query-string tokens rejected (`token` and RFC 6750 `access_token`)
|
|
14
|
-
- `tools/call` checks `principal.scopes` (`*` = all), and the OAuth grant that produced them is validated at `authorize`
|
|
14
|
+
- `tools/call` checks `principal.scopes` (`*` = all — a wildcard that passes **every** tool scope check; reserve it for trusted internal principals and never mint it for an external connector), and the OAuth grant that produced them is validated at `authorize`
|
|
15
15
|
- With `createMcpApp`, tokens whose audience is not this server's canonical resource are rejected by the library
|
|
16
16
|
- **`fetch` never rejects.** If a host port you supply (`authenticate`, `context`, `resolveUser`, `mintAccessToken`, `clientStore`, …) throws, `createMcpHandler`, `createOAuthRouter`, and `createMcpApp` all catch it and return a real `500` (`-32603` for MCP, `server_error` for OAuth) instead of an unhandled rejection — whose shape and whether it even reaches the client varies by runtime. `asNodeHandler` catches independently too, since on raw `http.createServer` a rejection there leaves the socket open with no response ever sent.
|
|
17
17
|
|
|
@@ -21,30 +21,79 @@ This is a **library** threat model, not a third-party audit badge.
|
|
|
21
21
|
|
|
22
22
|
| Risk | Mitigation |
|
|
23
23
|
|------|------------|
|
|
24
|
-
| Attacker-driven cross-site authorization (a logged-in user visits an attacker page and is silently walked through `/authorize`, catching the code on an attacker-controlled `redirect_uri`) | **`/authorize` never issues a code directly.** A resolved session renders a consent interstitial (built in, hardened — CSP, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, escaped reflection — or your own via `consent.render`) and only issues a code after an explicit `POST ${oauthPath}/consent` approval. The approval ticket is sealed (AES-GCM, HKDF-derived per-type key, type bound in as AEAD data — see `sealed.ts`), single-use, 5-minute TTL, and re-checked against the resolved user at redemption; it can never be redeemed as an auth code or vice versa. PKCE alone does **not** defend against this — it proves possession of the verifier, not identity of a legitimate client — so this is a distinct control, not a restatement of PKCE. `preApprovedClientIds` skips the screen only for ids `clientStore` actually resolves; a DCR or self-invented id is never trusted to skip it. |
|
|
24
|
+
| Attacker-driven cross-site authorization (a logged-in user visits an attacker page and is silently walked through `/authorize`, catching the code on an attacker-controlled `redirect_uri`) | **`/authorize` never issues a code directly.** A resolved session renders a consent interstitial (built in, hardened — CSP, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, escaped reflection — or your own via `consent.render`) and only issues a code after an explicit `POST ${oauthPath}/consent` approval. The approval ticket is sealed (AES-GCM, HKDF-derived per-type key, type bound in as AEAD data — see `sealed.ts`), single-use, 5-minute TTL, and re-checked against the resolved user at redemption; it can never be redeemed as an auth code or vice versa. PKCE alone does **not** defend against this — it proves possession of the verifier, not identity of a legitimate client — so this is a distinct control, not a restatement of PKCE. `preApprovedClientIds` skips the screen only for ids `clientStore` actually resolves; a DCR or self-invented id is never trusted to skip it. The built-in interstitial's CSP scopes `form-action` to `'self'` plus the validated `redirect_uri`'s own origin — Chromium enforces `form-action` across the redirect the approval POST produces, not just its immediate target, so a custom `consent.render` that sets its own CSP must do the same or the callback to the client's own origin gets silently blocked. |
|
|
25
25
|
| Confused deputy (token usable at the wrong MCP) | RFC 8707: `resource` required on authorize + token; bound into the auth code; must equal this AS's canonical resource (`canonicalResource(origin, resourcePath)`). Passed into `mintAccessToken` / `refreshAccessToken` so you can embed `aud`. |
|
|
26
26
|
| Refresh token replayed at another resource on the same AS | Library cannot inspect opaque refresh tokens — **`refreshAccessToken` MUST reject tokens not originally issued for the given `resource`** |
|
|
27
27
|
| Audience not enforced at the RS | **`createMcpApp` enforces this** — `verifyToken` returns the token's `audience` and the library rejects any mismatch against `canonicalResource(origin, resourcePath)`. With bare `createMcpHandler`, the check is yours. |
|
|
28
28
|
| Open redirect after login | Your login page must validate `next` is same-origin before redirecting |
|
|
29
29
|
| Auth-code replay across instances | Pass a shared `codeStore`; in-memory `jti` (the auth code's single-use id) map is process-local only |
|
|
30
30
|
| Origin spoofing on Node | Pass explicit `origin`, or `trustProxy: true` only behind a proxy that strips client `X-Forwarded-*`. Host-derived origin **requires** non-empty `allowedOrigins` on `asNodeHandler` (`["*"]` to opt out loudly) |
|
|
31
|
+
| A Host/`X-Forwarded-*` header carrying a path, query, fragment, or embedded credentials (`evil.test/../trusted.test`, `user:pass@evil.test`) building an origin string that merely looks well-formed | `resolveOrigin` always normalizes and validates through `URL`, rejecting any of those (plus a non-`http(s)` `X-Forwarded-Proto`) with `InvalidOriginError` — `asNodeHandler` answers it **400**, not the generic 500 an actual unexpected failure gets |
|
|
32
|
+
| Browser `Origin` on MCP (DNS rebinding / CSRF) | `allowedRequestOrigins` on `createMcpHandler` / `createMcpApp`. No `Origin` header → allowed (native clients). Omitted allowlist → **reject** any present `Origin`. `"*"` opts out. Distinct from Node Host `allowedOrigins` |
|
|
31
33
|
| Scope escalation at authorize | Requested `scope` is validated against the advertised `scopes` and rejected with `invalid_scope`; the grant is bound into the auth code and handed to `mintAccessToken` |
|
|
34
|
+
| An unscoped tool silently callable by any authenticated principal, including one holding none of the advertised scopes — the gap `defaultScopes: []` alone doesn't close, since that only governs the token grant, not per-tool enforcement | `createMcpApp` refuses to construct once `scopes` has more than one entry if any tool omits `ToolDef.scope` — pass `scope: null` to state the omission on purpose. A single-scope server has no such ambiguity and is unaffected. `tools/call` itself always enforces whatever `scope` a tool does declare, independent of this guard (403 `insufficient_scope`) |
|
|
35
|
+
| Scope reduction lost on refresh | Optional `scope` on the refresh token request is validated against advertised scopes and forwarded on `RefreshAccessTokenInput.scope`. **`refreshAccessToken` MUST enforce ⊆ originally granted scopes** (opaque RT — same pattern as `resource`). The token response omits `scope` when neither the port nor the grant supplies one (RFC 6749 §5.1) — clients must not assume the field is always present |
|
|
32
36
|
| Stolen access or refresh token | Host `revokeToken` (denylist or equivalent) that `verifyToken` and `refreshAccessToken` consult. Unknown / wrong-client tokens MUST no-op, not throw. Well-formed authenticated revoke is 200 even if the token is unknown |
|
|
33
37
|
| Stolen confidential-client secret | `clientStore.verifySecret` owns comparison — store hashes, not plaintext. The library never sees or persists credentials. **Prefer `clientStore.secretHash`** instead: return a stored hash from `hashClientSecret` and the library does the comparison with the same constant-time primitive it uses everywhere else, rather than a hand-rolled `stored === presented` in your own `verifySecret` being a timing oracle with no warning |
|
|
34
|
-
| An unauthenticated caller enumerating which `client_id`s a locked-down server knows, via distinguishable `/token`/`/revoke` error text or response timing | Every client-auth failure at `/token` and `/revoke` collapses to one `invalid_client` / "client authentication failed" — previously up to five distinguishable messages named exactly why (wrong method, missing secret, misconfigured store, unknown id). An unknown `client_id` on a locked-down server (`allowUnregisteredClients: false`)
|
|
38
|
+
| An unauthenticated caller enumerating which `client_id`s a locked-down server knows, via distinguishable `/token`/`/revoke` error text or response timing | Every client-auth failure at `/token` and `/revoke` collapses to one `invalid_client` / "client authentication failed" — previously up to five distinguishable messages named exactly why (wrong method, missing secret, misconfigured store, unknown id). Every rejection path in `firstClientAuthError` for a client_id that *is* known — wrong auth method, no secret presented, wrong secret, no `secretHash`/`verifySecret` on file — runs the same fixed-cost dummy or real comparison before returning, so response timing alone can't tell "known client, wrong method" apart from "known client, wrong secret." An unknown `client_id` on a locked-down server (`allowUnregisteredClients: false`) pays the identical fixed cost, so the crypto-verification cost itself can't distinguish "unknown" from "known, wrong secret" either. `/authorize`'s `unauthorized_client` is unchanged and stays descriptive — a human debugging a browser flow, not a bare API response, and a locked-down server already announces the same fact by omitting `registration_endpoint` |
|
|
35
39
|
| Auth codes readable by anyone who sees one — browser history, `Referer`, proxy logs | Auth codes are sealed (AES-GCM, `sealed.ts`), not signed-only. Legacy v1 (HMAC-signed, not encrypted) codes are **rejected** since 1.0 — complete any rolling deploy before upgrading past 0.4.x |
|
|
36
40
|
| An upstream API's error body leaking into the model or the audit log via `apiTool` | Default is status-only — the upstream response body is never forwarded on a non-2xx result, since it's a return value `onToolError` redaction never sees. Use `onError` for (a redacted version of) the body |
|
|
37
41
|
| `/authorize` errors delivered as a bare JSON body a connector can't act on | RFC 6749 §4.1.2.1: once `redirect_uri` is validated, every remaining error (`unsupported_response_type`, PKCE, `invalid_scope`, `invalid_target`, …) redirects to the client's own callback with `?error=…&state=…` instead. `redirect_uri` validity itself, and an oversized `state`, stay direct JSON — the URI is what's in question in the first case, and reflecting an oversized `state` into its own error redirect would recreate the DoS the length cap exists to prevent |
|
|
38
|
-
| DCR-issued `client_id` reused with a substituted `redirect_uri` | `/register` no longer returns a bare random id — the `client_id` **is** a sealed, self-verifying assertion of the exact `redirect_uris` it registered (`sealed.ts`, type `"client"`), with zero server-side storage. `/authorize` unseals it and binds the request to that list, the same protection a stored registration would give. This does not require the *identity* of the client to be verified — an attacker can still call `/register` and mint their own sealed id — so it stops URI substitution on a legitimate connector's id, not impersonation; the consent step above is what stops impersonation. `requireRegisteredClients` defaults to **true** since 1.0 (reject any `client_id` that isn't `clientStore`-resolved or self-sealed); set `false` only to accept invented public ids.
|
|
42
|
+
| DCR-issued `client_id` reused with a substituted `redirect_uri` | `/register` no longer returns a bare random id — the `client_id` **is** a sealed, self-verifying assertion of the exact `redirect_uris` it registered (`sealed.ts`, type `"client"`), with zero server-side storage. `/authorize` unseals it and binds the request to that list, the same protection a stored registration would give. This does not require the *identity* of the client to be verified — an attacker can still call `/register` and mint their own sealed id — so it stops URI substitution on a legitimate connector's id, not impersonation; the consent step above is what stops impersonation. `requireRegisteredClients` defaults to **true** since 1.0 (reject any `client_id` that isn't `clientStore`-resolved or self-sealed); set `false` only to accept invented public ids. Enforced at `/authorize`, and — since 1.2.0 — at `/token`'s `refresh_token` grant and at `/revoke` too, closing a gap where an invented `client_id` that never went through `/authorize` could still authenticate on either (the `authorization_code` grant was never actually exposed by this: a redeemable code's `client_id` is bound into its sealed payload at `/authorize` time, where the check already ran). Opt into CIMD with `cimd: true` for URL-shaped client_ids (off by default) |
|
|
43
|
+
| CIMD fetch as SSRF proxy (attacker supplies `https://…` `client_id`) | **Off by default** (`cimd: true` to enable). When on: HTTPS-only fetch, private/loopback/link-local/NAT64/`::ffff:` blocked (incl. bracketed IPv6 **and full-form mapped encodings DNS may return**, e.g. `0:0:0:0:0:ffff:7f00:1`), DNS lookup required before fetch and re-checked after the body, unparseable IPv6 lookup results fail closed, 5 KB stream-capped body / 10 s per hop covering the body read / 3 same-origin redirects (cross-origin redirects rejected), public clients only, `http:` redirect_uris loopback-only (`127.0.0.1` / `[::1]`, never `localhost`). Also blocked: `192.0.0/24`, `198.18/15`, multicast/reserved `224/4`–`240/4`, IPv6 multicast `ff00::/8`, 6to4 `2002::/16`, IPv4-compatible `::a.b.c.d` (and full-form private embeds). `/token` and `/revoke` **re-resolve** CIMD — never accept URL-shaped ids by shape alone; pass `cimdCache` (strongly recommended) or every refresh/revoke re-fetches the document, and a failed re-resolve is audited as `cimd_resolve_failed`. **Ceiling:** TCP connect is not pinned to the looked-up addresses (zero-dep Web `fetch`); classic DNS rebinding between lookup and connect remains possible until a custom dispatcher pins the IP — do not treat CIMD as production-complete SSRF defense until then |
|
|
39
44
|
| Naming only confidential clients doesn't actually block public ones | `clients: ["gemini"]` (or `allowUnregisteredClients: false`) is **enforced**: DCR unmounted, dropped from AS metadata, unresolved `client_id` rejected with `unauthorized_client` at `authorize` and `invalid_client` at `token` and `revoke` |
|
|
40
45
|
| DCR / token-endpoint abuse | Edge or reverse-proxy rate limiting on `/register` and `/token`, **plus** a library-enforced request body cap (see "Also designed in") — the library does not rate-limit by request count, but an unbounded body was previously a memory-exhaustion vector on its own regardless of rate |
|
|
41
|
-
| A weak or copy-pasted `codeSecret` (it can forge an auth code — or, since 0.3.0, a consent ticket or DCR client assertion — for any userId/scope/resource) | Validated at the point of use: a string form throws at `createOAuthRouter` construction, a function form on every call. Minimum 32 characters, and the exact literals this package's own docs and examples
|
|
46
|
+
| A weak or copy-pasted `codeSecret` (it can forge an auth code — or, since 0.3.0, a consent ticket or DCR client assertion — for any userId/scope/resource) | Validated at the point of use: a string or array form throws at `createOAuthRouter` construction (every array entry checked, and the array itself must be non-empty), a function form on every call. Minimum 32 characters per entry, and the exact literals this package's own docs and examples have published are denylisted by name, as is any secret containing the examples' `do-not-reuse` marker — copy-paste is the realistic failure mode. The examples themselves read `MCP_SECRET` (or `OAUTH_CODE_SECRET` for the advanced multi-secret recipes) from the environment and ship no usable default, and a test fails if any `codeSecret` literal in `examples/`, `README.md` or `docs/` is one the library would accept. This only catches the obvious cases; a leaked or brute-forceable 32+ character secret is still a full bypass, same as before |
|
|
42
47
|
| An operator unable to diagnose *why* a client was rejected, once `/token`/`/revoke` collapsed every failure to one generic `invalid_client` (see the enumeration-oracle row above) | `OAuthPorts.audit` (and `McpAppAuth.audit` on `createMcpApp`) — opt-in, sees the real reason (`"client requires client_secret_basic, got none"`, a rejected `codeSecret`'s actual message, …) that never reaches the caller. Same "never fails the request" guarantee as the MCP-side `audit` port |
|
|
43
|
-
| A hanging `ports.audit` sink stalling every audited response indefinitely | `
|
|
48
|
+
| A hanging `ports.audit` sink stalling every audited response indefinitely | `auditTimeoutMs` (default 1000ms) on both `McpHandlerOptions` and `OAuthRouterOptions` races the audit call against a timeout; "can't fail a request" already held, this adds "can't delay one" past a bound. A slow sink keeps running in the background and its own internal `catch` means it can never surface as an unhandled rejection later |
|
|
44
49
|
|
|
45
|
-
**Also designed in:** PKCE S256 (timing-safe, length-capped); auth codes, consent tickets, and DCR client assertions are all AES-GCM sealed with domain-separated keys (`sealed.ts`) so none of the three token types can be presented as another, and each binds `userId`/`clientId`/`redirectUri`/challenge/`resource` as applicable; redirect allowlist + DCR filter; a `redirect_uri` carrying a fragment or embedded credentials is rejected outright; no query-string tokens (`token` and `access_token`); honest `grant_types_supported`; `/token` and `/revoke` omit CORS `*` and both accept JSON or form bodies; request bodies are capped (1 MiB on `/mcp`, 64 KiB on `/token`/`/revoke`/`/register`) and a chunked body with no declared length is aborted mid-stream past the cap, not buffered first; audit fires on auth/scope denial as well as tool results; 401 responses echo the real JSON-RPC request id instead of hardcoding `null`; `createOAuthRouter` refuses to construct if `oauthPath` and `resourcePath` would collide, or if `resourcePath` starts with `/.well-known`; `resourcePath` is normalized once at construction (`normalizeConfiguredPath`) so a trailing slash can't make `/authorize`, `/token`, and route matching disagree with each other about what the canonical resource is.
|
|
50
|
+
**Also designed in:** PKCE S256 (timing-safe, length-capped; `code_challenge` must be a 43-character base64url digest at `/authorize`); auth codes, consent tickets, and DCR client assertions are all AES-GCM sealed with domain-separated keys (`sealed.ts`) so none of the three token types can be presented as another, and each binds `userId`/`clientId`/`redirectUri`/challenge/`resource` as applicable; redirect allowlist + DCR filter; a `redirect_uri` carrying a fragment or embedded credentials is rejected outright; no query-string tokens (`token` and `access_token`); honest `grant_types_supported`; `/token` and `/revoke` omit CORS `*` and both accept JSON or form bodies; request bodies are capped (1 MiB on `/mcp`, 64 KiB on `/token`/`/revoke`/`/register`) and a chunked body with no declared length is aborted mid-stream past the cap, not buffered first; audit fires on auth/scope denial as well as tool results; a scope-missing `tools/call` from an authenticated principal is **403** `insufficient_scope` (RFC 6750 §3.1), not a 401 that would just send the client back through the same authorize flow forever; 401/403 responses echo the real JSON-RPC request id instead of hardcoding `null`; `createOAuthRouter` refuses to construct if `oauthPath` and `resourcePath` would collide, or if `resourcePath` starts with `/.well-known`; `resourcePath` is normalized once at construction (`normalizeConfiguredPath`) so a trailing slash can't make `/authorize`, `/token`, and route matching disagree with each other about what the canonical resource is.
|
|
46
51
|
|
|
47
|
-
**Known limits:** in-memory jti map is process-local (multi-instance needs `codeStore`); the loopback redirect allowlist permits any path or port on `127.0.0.1`/`[::1]` over `http:` only — `localhost` and `https:` loopback are deliberately not accepted (RFC 8252 §8.3: `localhost` is DNS-resolvable, not a loopback guarantee, and native-app loopback redirects are `http:` only); DCR binds a client to its own `redirect_uris` via a sealed assertion but does not verify the *registrant's identity*
|
|
52
|
+
**Known limits:** in-memory jti map is process-local (multi-instance needs `codeStore`); the loopback redirect allowlist permits any path or port on `127.0.0.1`/`[::1]` over `http:` only — `localhost` and `https:` loopback are deliberately not accepted (RFC 8252 §8.3: `localhost` is DNS-resolvable, not a loopback guarantee, and native-app loopback redirects are `http:` only); DCR binds a client to its own `redirect_uris` via a sealed assertion but does not verify the *registrant's identity* (opt into CIMD with `cimd: true`); CIMD DNS checks do not pin the TCP connect (see CIMD SSRF row).
|
|
53
|
+
|
|
54
|
+
## Rotating codeSecret
|
|
55
|
+
|
|
56
|
+
`codeSecret` is the one secret behind auth codes, consent tickets, DCR
|
|
57
|
+
client assertions, and (when `clientStore.secretHash` is used)
|
|
58
|
+
confidential-client secret hashes. Before 1.2.0 it was a single string —
|
|
59
|
+
rotating it invalidated every DCR-issued `client_id` still in a connector's
|
|
60
|
+
hands and every stored `secretHash`, with no way to phase the change in.
|
|
61
|
+
|
|
62
|
+
Since 1.2.0, `codeSecret` also accepts an array:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
codeSecret: [newSecret, oldSecret],
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- The **first** entry is the one used to seal new auth codes, consent
|
|
69
|
+
tickets, and DCR client assertions, and to hash a client secret passed to
|
|
70
|
+
`hashClientSecret`.
|
|
71
|
+
- **Every** entry is tried, in order, when unsealing or verifying existing
|
|
72
|
+
material — so a DCR `client_id` sealed under `oldSecret`, or a
|
|
73
|
+
`secretHash` computed with it, keeps working.
|
|
74
|
+
|
|
75
|
+
To rotate:
|
|
76
|
+
|
|
77
|
+
1. Deploy with `codeSecret: [newSecret, oldSecret]`.
|
|
78
|
+
2. Watch `ports.audit` for `{ event: "legacy_code_secret_used", reason }` —
|
|
79
|
+
it fires whenever anything but the first entry is the one that actually
|
|
80
|
+
verified (a `client` assertion, `code`, `consent` ticket, or
|
|
81
|
+
`client_secret_hash`, named in `reason`). Once it stops firing for a
|
|
82
|
+
given `reason`, nothing still depends on `oldSecret` for that purpose.
|
|
83
|
+
3. Drop `oldSecret`: `codeSecret: newSecret` (or `[newSecret]`).
|
|
84
|
+
|
|
85
|
+
A DCR `client_id` is only as durable as the key it was sealed with — once
|
|
86
|
+
you drop a key, every `client_id` sealed under it (and only it) stops
|
|
87
|
+
authorizing, and the connector must re-register. `client_secret_hash`
|
|
88
|
+
entries you control the storage for: re-hash them under the new primary key
|
|
89
|
+
(`hashClientSecret(secret, newSecret)`) before dropping the old one, rather
|
|
90
|
+
than waiting for the audit event, if you want zero downtime for confidential
|
|
91
|
+
clients. Auth codes and consent tickets are short-lived (10 and 5 minutes
|
|
92
|
+
respectively) and need no such care — anything issued before rotation has
|
|
93
|
+
long expired by the time you'd reasonably drop a key.
|
|
94
|
+
|
|
95
|
+
`hashClientSecret` itself is unchanged: call it once, at registration time,
|
|
96
|
+
with whichever `codeSecret` value is currently primary.
|
|
48
97
|
|
|
49
98
|
## Not in scope
|
|
50
99
|
|
|
@@ -60,4 +109,4 @@ Explicitly **out** of this package (do not expect parity with full MCP hosts or
|
|
|
60
109
|
- **No rate limiting** — put quotas on `/register` and `/token` at the edge or reverse proxy
|
|
61
110
|
- **No paid external security audit** claimed here — see Threat model above
|
|
62
111
|
|
|
63
|
-
See [ROADMAP.md](ROADMAP.md) for what is next —
|
|
112
|
+
See [ROADMAP.md](ROADMAP.md) for what is next — the `2026-07-28` stateless core is the open protocol item; CIMD is shipped.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Connector troubleshooting
|
|
2
|
+
|
|
3
|
+
| Symptom | Check | Next step |
|
|
4
|
+
|---|---|---|
|
|
5
|
+
| Client cannot discover auth | Is `/.well-known/oauth-protected-resource/mcp` reachable on the public origin? | Route discovery paths and `/mcp/oauth/*` through the same proxy as `/mcp`. |
|
|
6
|
+
| Discovery advertises localhost | Node adapter origin differs from public URL | Set the exact public HTTPS origin; restart the app. |
|
|
7
|
+
| `invalid_client` | Missing registration, wrong configured profile, or missing `clientStore` | Use DCR for public clients or resolve the pre-registered client; keep registration enforcement on. |
|
|
8
|
+
| Callback rejected | Callback is not an exact allowlisted URI | Add the callback shown by the client to `extraRedirectUris`; do not allow arbitrary hosts. |
|
|
9
|
+
| Browser shows an approval page | A login session exists but consent is still required | Approve in the browser. Do not expect `/authorize` to redirect immediately. |
|
|
10
|
+
| Login loops | Cookie domain/path/Secure flags do not match public host | Inspect your app's session handling and use a consistent origin. |
|
|
11
|
+
| `invalid_grant` | Expired/replayed code, wrong verifier or resource | Start a fresh authorization flow. Keep verifier and resource consistent. |
|
|
12
|
+
| Tool request returns 401 | Token expired, verification failed, or audience mismatch | Verify signature/expiry in app auth and return the requested MCP audience. |
|
|
13
|
+
| Tool request returns 403 | Tool scope missing | Grant and return the required scopes from token verification. |
|
|
14
|
+
| Wrong user's data | Data query ignores authenticated identity | Filter by verified principal and tenant; never trust a tool argument for ownership. |
|
|
15
|
+
| Switching login does not switch connector user | Connector retains its previously issued token | Disconnect and reauthorize with the intended account. |
|
|
16
|
+
| Initialization works but tools fail | Initialize is public | Test authenticated `tools/call`; initialize alone proves no auth flow. |
|
|
17
|
+
| Client requests an unsupported protocol | Package supports the revisions in `src/mcp/protocol.ts` | Record negotiation; do not claim a live pass from a successful HTTP status alone. |
|
|
18
|
+
|
|
19
|
+
See [security responsibilities](security.md) before replacing production token verification with example code. Share sanitized errors in an issue, never access tokens or session cookies.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-trellis",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Add authenticated remote MCP connectors to existing TypeScript SaaS apps using your existing users and auth.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -25,7 +25,13 @@
|
|
|
25
25
|
"model-context-protocol",
|
|
26
26
|
"cloudflare-workers",
|
|
27
27
|
"authorization-server",
|
|
28
|
-
"connector"
|
|
28
|
+
"connector",
|
|
29
|
+
"typescript",
|
|
30
|
+
"mcp-server",
|
|
31
|
+
"saas",
|
|
32
|
+
"remote-mcp",
|
|
33
|
+
"byo-auth",
|
|
34
|
+
"ai-connector"
|
|
29
35
|
],
|
|
30
36
|
"types": "./dist/server.d.ts",
|
|
31
37
|
"exports": {
|
|
@@ -40,32 +46,43 @@
|
|
|
40
46
|
"./node": {
|
|
41
47
|
"types": "./dist/adapters/node.d.ts",
|
|
42
48
|
"import": "./dist/adapters/node.js"
|
|
49
|
+
},
|
|
50
|
+
"./advanced": {
|
|
51
|
+
"types": "./dist/advanced.d.ts",
|
|
52
|
+
"import": "./dist/advanced.js"
|
|
43
53
|
}
|
|
44
54
|
},
|
|
45
55
|
"files": [
|
|
46
56
|
"dist",
|
|
47
|
-
"docs",
|
|
57
|
+
"docs/*.md",
|
|
58
|
+
"docs/diagrams/**",
|
|
48
59
|
"README.md",
|
|
49
60
|
"LICENSE"
|
|
50
61
|
],
|
|
51
62
|
"publishConfig": {
|
|
52
|
-
"access": "public"
|
|
63
|
+
"access": "public",
|
|
64
|
+
"provenance": true
|
|
53
65
|
},
|
|
54
66
|
"scripts": {
|
|
55
67
|
"build": "tsc -p tsconfig.build.json",
|
|
56
|
-
"test": "tsx --test test/oauth
|
|
68
|
+
"test": "tsx --test test/oauth/**/*.test.ts test/mcp/*.test.ts test/adapters/*.test.ts test/auth/*.test.ts test/util/*.test.ts test/*.test.ts",
|
|
57
69
|
"test:e2e": "tsx --test test/e2e/*.test.ts",
|
|
58
|
-
"test:coverage": "node
|
|
70
|
+
"test:coverage": "node scripts/test-coverage.mjs",
|
|
59
71
|
"typecheck": "tsc --noEmit",
|
|
72
|
+
"check:quickstart": "node scripts/check-quickstart.mjs",
|
|
60
73
|
"lint": "biome check .",
|
|
61
|
-
"prepublishOnly": "npm run build && npm test"
|
|
74
|
+
"prepublishOnly": "npm run build && npm test",
|
|
75
|
+
"demo": "npm run build && tsx examples/saas-demo/server.ts",
|
|
76
|
+
"smoke:pack": "node scripts/clean-npm-smoke.mjs",
|
|
77
|
+
"pretypecheck": "npm run build",
|
|
78
|
+
"pretest:e2e": "npm run build"
|
|
62
79
|
},
|
|
63
80
|
"engines": {
|
|
64
81
|
"node": ">=20"
|
|
65
82
|
},
|
|
66
83
|
"devDependencies": {
|
|
67
|
-
"@biomejs/biome": "2.5.
|
|
68
|
-
"@types/node": "^
|
|
84
|
+
"@biomejs/biome": "2.5.11",
|
|
85
|
+
"@types/node": "^26.4.1",
|
|
69
86
|
"tsx": "^4.19.3",
|
|
70
87
|
"typescript": "^5.8.2"
|
|
71
88
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Origin allowlist for Host-derived Node origins (multi-tenant SaaS).
|
|
3
|
-
* Same predicate shape as redirect allowlisting — not secret comparison.
|
|
4
|
-
*/
|
|
5
|
-
export type OriginAllowlistOptions = {
|
|
6
|
-
/**
|
|
7
|
-
* Exact origins (`https://acme.example.com`) and/or `*.example.com`
|
|
8
|
-
* wildcards. Wildcards match https subdomains only (not the apex, not http).
|
|
9
|
-
* `"*"` admits any origin (loud opt-out).
|
|
10
|
-
*
|
|
11
|
-
* Empty/omitted list → admit all (only safe with a fixed `asNodeHandler`
|
|
12
|
-
* `origin`). Host-derived `asNodeHandler` requires a non-empty list at
|
|
13
|
-
* construction. Resolved origin outside the list → 400; a fixed `origin`
|
|
14
|
-
* absent from a non-empty list throws at construction.
|
|
15
|
-
*/
|
|
16
|
-
allowedOrigins?: string[];
|
|
17
|
-
};
|
|
18
|
-
export declare const isAllowedOrigin: (origin: string, options?: OriginAllowlistOptions) => boolean;
|
|
19
|
-
//# sourceMappingURL=origins.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../src/adapters/origins.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAyCF,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,UAAS,sBAA2B,KAAG,OAQtF,CAAC"}
|
package/dist/adapters/origins.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Origin allowlist for Host-derived Node origins (multi-tenant SaaS).
|
|
3
|
-
* Same predicate shape as redirect allowlisting — not secret comparison.
|
|
4
|
-
*/
|
|
5
|
-
const originOf = (value) => {
|
|
6
|
-
try {
|
|
7
|
-
return new URL(value).origin;
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
const isExactOrigin = (origin, options) => {
|
|
14
|
-
const normalized = originOf(origin);
|
|
15
|
-
if (!normalized)
|
|
16
|
-
return false;
|
|
17
|
-
return Boolean(options.allowedOrigins?.some((entry) => {
|
|
18
|
-
if (entry.startsWith("*."))
|
|
19
|
-
return false;
|
|
20
|
-
return originOf(entry) === normalized;
|
|
21
|
-
}));
|
|
22
|
-
};
|
|
23
|
-
/** `*.example.com` matches `https://acme.example.com`, not the apex, not http. */
|
|
24
|
-
const isWildcardSubdomain = (origin, options) => {
|
|
25
|
-
const patterns = options.allowedOrigins ?? [];
|
|
26
|
-
try {
|
|
27
|
-
const { protocol, hostname } = new URL(origin);
|
|
28
|
-
if (protocol !== "https:")
|
|
29
|
-
return false;
|
|
30
|
-
return patterns.some((entry) => {
|
|
31
|
-
if (!entry.startsWith("*."))
|
|
32
|
-
return false;
|
|
33
|
-
const suffix = entry.slice(1); // ".example.com"
|
|
34
|
-
return hostname.endsWith(suffix) && hostname.length > suffix.length;
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
const ORIGIN_PREDICATES = [isExactOrigin, isWildcardSubdomain];
|
|
42
|
-
export const isAllowedOrigin = (origin, options = {}) => {
|
|
43
|
-
// Empty/omitted: admit all — callers that derive origin from Host must
|
|
44
|
-
// require a non-empty list before reaching here (see asNodeHandler).
|
|
45
|
-
if (!options.allowedOrigins || options.allowedOrigins.length === 0) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
if (options.allowedOrigins.includes("*"))
|
|
49
|
-
return true;
|
|
50
|
-
return ORIGIN_PREDICATES.some((predicate) => predicate(origin, options));
|
|
51
|
-
};
|
|
52
|
-
//# sourceMappingURL=origins.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"origins.js","sourceRoot":"","sources":["../../src/adapters/origins.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAiB,EAAE;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,aAAa,GAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;IACzD,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,OAAO,CACZ,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACrC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC;IACxC,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,kFAAkF;AAClF,MAAM,mBAAmB,GAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACxC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;YAChD,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACtE,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAsB,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;AAElF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAE,UAAkC,EAAE,EAAW,EAAE;IAC/F,uEAAuE;IACvE,qEAAqE;IACrE,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC"}
|
package/dist/body.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAItC;AAED,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,UAAY,CAAC;AAEhD,uGAAuG;AACvG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAuB/C;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,OAAO,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAgCxF,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,+EAA+E;IAC/E,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC7C,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;IACzD,KAAK,IAAI,IAAI,CAAC;IACd,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAC7E,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,gBAG6B,CAAC;AA+DvF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,gBAAgB,GAAG,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,EAC7D,UAAU,MAAM,EAChB,UAAS,sBAA2B,KACnC,OAAO,CAAC,UAAU,CAQpB,CAAC"}
|
package/dist/body.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"body.js","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"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"}
|
package/dist/dispatch.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAkBA,OAAO,EAIL,KAAK,iBAAiB,EAIvB,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;AAiIF,eAAO,MAAM,gBAAgB,GAAI,IAAI,EAAE,SAAS,iBAAiB,CAAC,IAAI,CAAC,KAAG,UAyBzE,CAAC"}
|