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
package/docs/security.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Protocol promises, library threat model, and explicit non-goals.
|
|
4
|
+
Ports and audience wiring: [guide.md](guide.md). Roadmap: [ROADMAP.md](ROADMAP.md).
|
|
5
|
+
|
|
6
|
+
## Protocol promises
|
|
7
|
+
|
|
8
|
+
- Notifications → HTTP **202** with an empty body
|
|
9
|
+
- Batch arrays → `-32600` (MCP 2025-06-18)
|
|
10
|
+
- Protocol version negotiated from client `initialize` params — an unrecognized or missing request falls back to the **newest** supported revision, not the oldest
|
|
11
|
+
- `jsonrpc` must be exactly `"2.0"` on every `/mcp` request → `-32600` otherwise, echoing the request id when one was present
|
|
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
|
+
- Query-string tokens rejected (`token` and RFC 6750 `access_token`)
|
|
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
|
+
- With `createMcpApp`, tokens whose audience is not this server's canonical resource are rejected by the library
|
|
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
|
+
|
|
18
|
+
## Threat model (library)
|
|
19
|
+
|
|
20
|
+
This is a **library** threat model, not a third-party audit badge.
|
|
21
|
+
|
|
22
|
+
| Risk | Mitigation |
|
|
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. 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
|
+
| 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
|
+
| 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
|
+
| 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
|
+
| Open redirect after login | Your login page must validate `next` is same-origin before redirecting |
|
|
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
|
+
| 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` |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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` |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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` |
|
|
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 |
|
|
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 `OAUTH_CODE_SECRET` / `ACCESS_TOKEN_SECRET` 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 |
|
|
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 |
|
|
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 |
|
|
49
|
+
|
|
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.
|
|
51
|
+
|
|
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.
|
|
97
|
+
|
|
98
|
+
## Not in scope
|
|
99
|
+
|
|
100
|
+
Explicitly **out** of this package (do not expect parity with full MCP hosts or enterprise AS products):
|
|
101
|
+
|
|
102
|
+
- **Tools only** — no `resources/*`, no `prompts/*`, and capabilities advertise tools alone
|
|
103
|
+
- **No server-initiated messages** — Streamable HTTP is served as a single JSON response per POST (which the spec permits, and which Gemini Enterprise requires). No SSE response streams, so no `notifications/progress` and no long-running tool keepalive
|
|
104
|
+
- **No sessions** — removed from the protocol in `2026-07-28` anyway
|
|
105
|
+
- **No enterprise-managed auth** — no ID-JAG, no IdP product integration; you wire login + minting
|
|
106
|
+
- **No login UI, no token store, no sessions** — the consent/approval screen at `/authorize` *is* shipped (built in, overridable via `consent.render`), because it's a security control determined entirely by protocol data the library already holds (`client_id`, `scope`, `redirect_uri`, `resource`), not by your IdP or branding. Login is the boundary: who the user is, and how they prove it, stays yours
|
|
107
|
+
- **No stdio** transport; no batch JSON-RPC arrays
|
|
108
|
+
- **No DPoP, PAR, or `client_credentials`** — this AS is authorization_code (+ optional refresh and revoke)
|
|
109
|
+
- **No rate limiting** — put quotas on `/register` and `/token` at the edge or reverse proxy
|
|
110
|
+
- **No paid external security audit** claimed here — see Threat model above
|
|
111
|
+
|
|
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.1.0",
|
|
4
|
+
"description": "Build secure remote MCP servers for Claude, ChatGPT and Gemini using your existing auth.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"model-context-protocol",
|
|
26
26
|
"cloudflare-workers",
|
|
27
27
|
"authorization-server",
|
|
28
|
-
"connector"
|
|
28
|
+
"connector",
|
|
29
|
+
"typescript",
|
|
30
|
+
"mcp-server"
|
|
29
31
|
],
|
|
30
32
|
"types": "./dist/server.d.ts",
|
|
31
33
|
"exports": {
|
|
@@ -40,29 +42,43 @@
|
|
|
40
42
|
"./node": {
|
|
41
43
|
"types": "./dist/adapters/node.d.ts",
|
|
42
44
|
"import": "./dist/adapters/node.js"
|
|
45
|
+
},
|
|
46
|
+
"./advanced": {
|
|
47
|
+
"types": "./dist/advanced.d.ts",
|
|
48
|
+
"import": "./dist/advanced.js"
|
|
43
49
|
}
|
|
44
50
|
},
|
|
45
51
|
"files": [
|
|
46
52
|
"dist",
|
|
47
|
-
"docs
|
|
53
|
+
"docs/*.md",
|
|
54
|
+
"docs/diagrams/**",
|
|
48
55
|
"README.md",
|
|
49
56
|
"LICENSE"
|
|
50
57
|
],
|
|
51
58
|
"publishConfig": {
|
|
52
|
-
"access": "public"
|
|
59
|
+
"access": "public",
|
|
60
|
+
"provenance": true
|
|
53
61
|
},
|
|
54
62
|
"scripts": {
|
|
55
63
|
"build": "tsc -p tsconfig.build.json",
|
|
56
|
-
"test": "tsx --test test/*.test.ts",
|
|
64
|
+
"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
65
|
"test:e2e": "tsx --test test/e2e/*.test.ts",
|
|
66
|
+
"test:coverage": "node scripts/test-coverage.mjs",
|
|
58
67
|
"typecheck": "tsc --noEmit",
|
|
59
|
-
"
|
|
68
|
+
"check:quickstart": "node scripts/check-quickstart.mjs",
|
|
69
|
+
"lint": "biome check .",
|
|
70
|
+
"prepublishOnly": "npm run build && npm test",
|
|
71
|
+
"demo": "npm run build && tsx examples/saas-demo/server.ts",
|
|
72
|
+
"smoke:pack": "node scripts/clean-npm-smoke.mjs",
|
|
73
|
+
"pretypecheck": "npm run build",
|
|
74
|
+
"pretest:e2e": "npm run build"
|
|
60
75
|
},
|
|
61
76
|
"engines": {
|
|
62
77
|
"node": ">=20"
|
|
63
78
|
},
|
|
64
79
|
"devDependencies": {
|
|
65
|
-
"@
|
|
80
|
+
"@biomejs/biome": "2.5.11",
|
|
81
|
+
"@types/node": "^26.4.1",
|
|
66
82
|
"tsx": "^4.19.3",
|
|
67
83
|
"typescript": "^5.8.2"
|
|
68
84
|
}
|
|
@@ -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;AA+CF,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,EACd,UAAS,sBAA2B,KACnC,OAQF,CAAC"}
|
package/dist/adapters/origins.js
DELETED
|
@@ -1,55 +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 = [
|
|
42
|
-
isExactOrigin,
|
|
43
|
-
isWildcardSubdomain,
|
|
44
|
-
];
|
|
45
|
-
export const isAllowedOrigin = (origin, options = {}) => {
|
|
46
|
-
// Empty/omitted: admit all — callers that derive origin from Host must
|
|
47
|
-
// require a non-empty list before reaching here (see asNodeHandler).
|
|
48
|
-
if (!options.allowedOrigins || options.allowedOrigins.length === 0) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
if (options.allowedOrigins.includes("*"))
|
|
52
|
-
return true;
|
|
53
|
-
return ORIGIN_PREDICATES.some((predicate) => predicate(origin, options));
|
|
54
|
-
};
|
|
55
|
-
//# sourceMappingURL=origins.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"origins.js","sourceRoot":"","sources":["../../src/adapters/origins.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,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;IAC3C,aAAa;IACb,mBAAmB;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAAc,EACd,UAAkC,EAAE,EAC3B,EAAE;IACX,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/dispatch.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type McpHandlerOptions } from "./methods.js";
|
|
2
|
-
export type { Principal, AuditEntry, McpPorts, ServerInfo, McpHandlerOptions, } from "./methods.js";
|
|
3
|
-
export type McpHandler = {
|
|
4
|
-
fetch: (request: Request) => Promise<Response>;
|
|
5
|
-
};
|
|
6
|
-
export declare const createMcpHandler: <TCtx>(options: McpHandlerOptions<TCtx>) => McpHandler;
|
|
7
|
-
//# sourceMappingURL=dispatch.d.ts.map
|
package/dist/dispatch.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAeA,OAAO,EAOL,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,SAAS,EACT,UAAU,EACV,QAAQ,EACR,UAAU,EACV,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAChD,CAAC;AA+FF,eAAO,MAAM,gBAAgB,GAAI,IAAI,EACnC,SAAS,iBAAiB,CAAC,IAAI,CAAC,KAC/B,UA8BF,CAAC"}
|
package/dist/dispatch.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_REQUEST, JSONRPC_PARSE_ERROR, rpcError, } from "./jsonrpc.js";
|
|
2
|
-
import { INTERNAL_ERROR, emptyResponse, jsonResponse, methodNotAllowed, optionsResponse, } from "./http.js";
|
|
3
|
-
import { rejectQueryToken } from "./auth/bearer.js";
|
|
4
|
-
import { DEFAULT_PUBLIC_METHODS, dispatchRpc, isNotification, resolveWww, safeAudit, unauthorized, } from "./methods.js";
|
|
5
|
-
const parseBody = async (request) => {
|
|
6
|
-
try {
|
|
7
|
-
return await request.json();
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return jsonResponse(rpcError(null, JSONRPC_PARSE_ERROR, "Parse error"), 400);
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
/** Transport-level denial: no JSON-RPC method has been parsed yet. */
|
|
14
|
-
const auditDenial = (options, error) => safeAudit(options, { method: "", ok: false, error, durationMs: 0 });
|
|
15
|
-
const guardPost = async (request, options) => {
|
|
16
|
-
if (rejectQueryToken(new URL(request.url))) {
|
|
17
|
-
await auditDenial(options, "query_string_token");
|
|
18
|
-
return unauthorized(resolveWww(options, request), "Token in query string is rejected. Use Authorization Bearer.");
|
|
19
|
-
}
|
|
20
|
-
return null;
|
|
21
|
-
};
|
|
22
|
-
const handleGet = async (request, options) => {
|
|
23
|
-
if (rejectQueryToken(new URL(request.url))) {
|
|
24
|
-
await auditDenial(options, "query_string_token");
|
|
25
|
-
return jsonResponse({
|
|
26
|
-
error: "Pass token via Authorization: Bearer header, not query string",
|
|
27
|
-
}, 400);
|
|
28
|
-
}
|
|
29
|
-
if (!request.headers.get("authorization")) {
|
|
30
|
-
await auditDenial(options, "unauthorized");
|
|
31
|
-
return unauthorized(resolveWww(options, request));
|
|
32
|
-
}
|
|
33
|
-
return methodNotAllowed("Use POST for MCP");
|
|
34
|
-
};
|
|
35
|
-
const handlePost = async (request, options, publicMethods) => {
|
|
36
|
-
const denied = await guardPost(request, options);
|
|
37
|
-
if (denied)
|
|
38
|
-
return denied;
|
|
39
|
-
const parsed = await parseBody(request);
|
|
40
|
-
if (parsed instanceof Response)
|
|
41
|
-
return parsed;
|
|
42
|
-
if (Array.isArray(parsed)) {
|
|
43
|
-
return jsonResponse(rpcError(null, JSONRPC_INVALID_REQUEST, "Batch requests not supported"), 400);
|
|
44
|
-
}
|
|
45
|
-
if (!parsed || typeof parsed !== "object") {
|
|
46
|
-
return jsonResponse(rpcError(null, JSONRPC_PARSE_ERROR, "Parse error"), 400);
|
|
47
|
-
}
|
|
48
|
-
const msg = parsed;
|
|
49
|
-
if (isNotification(msg))
|
|
50
|
-
return emptyResponse(202);
|
|
51
|
-
return dispatchRpc({ req: request, body: msg, options, publicMethods });
|
|
52
|
-
};
|
|
53
|
-
export const createMcpHandler = (options) => {
|
|
54
|
-
const publicMethods = options.publicMethods ?? DEFAULT_PUBLIC_METHODS;
|
|
55
|
-
const HTTP_METHODS = {
|
|
56
|
-
OPTIONS: () => optionsResponse(),
|
|
57
|
-
GET: (request) => handleGet(request, options),
|
|
58
|
-
POST: (request) => handlePost(request, options, publicMethods),
|
|
59
|
-
};
|
|
60
|
-
return {
|
|
61
|
-
fetch: async (request) => {
|
|
62
|
-
try {
|
|
63
|
-
const handler = HTTP_METHODS[request.method];
|
|
64
|
-
return await (handler
|
|
65
|
-
? handler(request, options, publicMethods)
|
|
66
|
-
: methodNotAllowed());
|
|
67
|
-
}
|
|
68
|
-
catch (exc) {
|
|
69
|
-
// A host port (authenticate/context/audit) threw. `fetch` must never
|
|
70
|
-
// reject — the caller gets an honest 500, not an unhandled rejection
|
|
71
|
-
// whose shape depends on which runtime is hosting this.
|
|
72
|
-
const error = exc instanceof Error && exc.message ? exc.message : INTERNAL_ERROR;
|
|
73
|
-
await auditDenial(options, error);
|
|
74
|
-
return jsonResponse(rpcError(null, JSONRPC_INTERNAL_ERROR, "Internal error"), 500);
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
//# sourceMappingURL=dispatch.js.map
|
package/dist/dispatch.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/** JSON `error` body / audit reason when a host port throws. */
|
|
2
|
-
export declare const INTERNAL_ERROR = "internal_error";
|
|
3
|
-
export declare const corsHeaders: (extra?: Record<string, string>) => Record<string, string>;
|
|
4
|
-
export type JsonResponseOptions = {
|
|
5
|
-
/** Include permissive CORS headers. Default true (MCP). OAuth token set false. */
|
|
6
|
-
cors?: boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare const jsonResponse: (data: unknown, status?: number, extraHeaders?: Record<string, string>, options?: JsonResponseOptions) => Response;
|
|
9
|
-
export declare const emptyResponse: (status: number) => Response;
|
|
10
|
-
export declare const optionsResponse: (options?: JsonResponseOptions) => Response;
|
|
11
|
-
export declare const methodNotAllowed: (message?: string, options?: JsonResponseOptions) => Response;
|
|
12
|
-
/**
|
|
13
|
-
* OPTIONS → 204; allowed verb → null (continue); otherwise → 405.
|
|
14
|
-
* Shared by OAuth handlers that all need the same method gate.
|
|
15
|
-
*/
|
|
16
|
-
export declare const requireHttpMethod: (request: Request, allowed: ReadonlySet<string>, options?: JsonResponseOptions) => Response | null;
|
|
17
|
-
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,eAAO,MAAM,WAAW,GACtB,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,CAMtB,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,MAAM,OAAO,EACb,eAAY,EACZ,eAAe,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,UAAU,mBAAmB,KAC5B,QAWF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,KAAG,QACS,CAAC;AAEzD,eAAO,MAAM,eAAe,GAAI,UAAU,mBAAmB,KAAG,QAI5D,CAAC;AAEL,eAAO,MAAM,gBAAgB,GAC3B,gBAA8B,EAC9B,UAAU,mBAAmB,KAC5B,QAAqE,CAAC;AAEzE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,OAAO,EAChB,SAAS,WAAW,CAAC,MAAM,CAAC,EAC5B,UAAU,mBAAmB,KAC5B,QAAQ,GAAG,IASb,CAAC"}
|
package/dist/http.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/** JSON `error` body / audit reason when a host port throws. */
|
|
2
|
-
export const INTERNAL_ERROR = "internal_error";
|
|
3
|
-
export const corsHeaders = (extra) => ({
|
|
4
|
-
"Access-Control-Allow-Origin": "*",
|
|
5
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
6
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization, MCP-Protocol-Version",
|
|
7
|
-
...extra,
|
|
8
|
-
});
|
|
9
|
-
export const jsonResponse = (data, status = 200, extraHeaders, options) => {
|
|
10
|
-
const cors = options?.cors !== false;
|
|
11
|
-
return new Response(JSON.stringify(data), {
|
|
12
|
-
status,
|
|
13
|
-
headers: {
|
|
14
|
-
"Content-Type": "application/json",
|
|
15
|
-
"Cache-Control": "no-store",
|
|
16
|
-
...(cors ? corsHeaders() : {}),
|
|
17
|
-
...extraHeaders,
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
export const emptyResponse = (status) => new Response(null, { status, headers: corsHeaders() });
|
|
22
|
-
export const optionsResponse = (options) => new Response(null, {
|
|
23
|
-
status: 204,
|
|
24
|
-
headers: options?.cors === false ? { "Cache-Control": "no-store" } : corsHeaders(),
|
|
25
|
-
});
|
|
26
|
-
export const methodNotAllowed = (message = "Method not allowed", options) => jsonResponse({ error: message }, 405, undefined, options);
|
|
27
|
-
/**
|
|
28
|
-
* OPTIONS → 204; allowed verb → null (continue); otherwise → 405.
|
|
29
|
-
* Shared by OAuth handlers that all need the same method gate.
|
|
30
|
-
*/
|
|
31
|
-
export const requireHttpMethod = (request, allowed, options) => {
|
|
32
|
-
if (request.method === "OPTIONS")
|
|
33
|
-
return optionsResponse(options);
|
|
34
|
-
if (allowed.has(request.method))
|
|
35
|
-
return null;
|
|
36
|
-
return jsonResponse({ detail: "method not allowed" }, 405, undefined, options);
|
|
37
|
-
};
|
|
38
|
-
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,KAA8B,EACN,EAAE,CAAC,CAAC;IAC5B,6BAA6B,EAAE,GAAG;IAClC,8BAA8B,EAAE,oBAAoB;IACpD,8BAA8B,EAC5B,mDAAmD;IACrD,GAAG,KAAK;CACT,CAAC,CAAC;AAOH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAa,EACb,MAAM,GAAG,GAAG,EACZ,YAAqC,EACrC,OAA6B,EACnB,EAAE;IACZ,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,KAAK,KAAK,CAAC;IACrC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,UAAU;YAC3B,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9B,GAAG,YAAY;SAChB;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAc,EAAY,EAAE,CACxD,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAA6B,EAAY,EAAE,CACzE,IAAI,QAAQ,CAAC,IAAI,EAAE;IACjB,MAAM,EAAE,GAAG;IACX,OAAO,EAAE,OAAO,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE;CACnF,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,OAAO,GAAG,oBAAoB,EAC9B,OAA6B,EACnB,EAAE,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAEzE;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,OAAgB,EAChB,OAA4B,EAC5B,OAA6B,EACZ,EAAE;IACnB,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,YAAY,CACjB,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAChC,GAAG,EACH,SAAS,EACT,OAAO,CACR,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/jsonrpc.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsonrpc.d.ts","sourceRoot":"","sources":["../src/jsonrpc.ts"],"names":[],"mappings":"AAAA,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;AAE3C,eAAO,MAAM,SAAS,GACpB,IAAI,SAAS,EACb,QAAQ,OAAO,KACd,cAID,CAAC;AAEH,eAAO,MAAM,QAAQ,GACnB,IAAI,SAAS,EACb,MAAM,MAAM,EACZ,SAAS,MAAM,KACd,cAID,CAAC"}
|
package/dist/jsonrpc.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const JSONRPC_PARSE_ERROR = -32700;
|
|
2
|
-
export const JSONRPC_INVALID_REQUEST = -32600;
|
|
3
|
-
export const JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
4
|
-
export const JSONRPC_INVALID_PARAMS = -32602;
|
|
5
|
-
export const JSONRPC_INTERNAL_ERROR = -32603;
|
|
6
|
-
export const JSONRPC_UNAUTHORIZED = -32001;
|
|
7
|
-
export const rpcResult = (id, result) => ({
|
|
8
|
-
jsonrpc: "2.0",
|
|
9
|
-
id,
|
|
10
|
-
result,
|
|
11
|
-
});
|
|
12
|
-
export const rpcError = (id, code, message) => ({
|
|
13
|
-
jsonrpc: "2.0",
|
|
14
|
-
id,
|
|
15
|
-
error: { code, message },
|
|
16
|
-
});
|
|
17
|
-
//# sourceMappingURL=jsonrpc.js.map
|
package/dist/jsonrpc.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsonrpc.js","sourceRoot":"","sources":["../src/jsonrpc.ts"],"names":[],"mappings":"AAyBA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAK,CAAC;AAC1C,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,KAAK,CAAC;AAC9C,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,KAAK,CAAC;AAC/C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAK,CAAC;AAC7C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAK,CAAC;AAC7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAK,CAAC;AAE3C,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,EAAa,EACb,MAAe,EACC,EAAE,CAAC,CAAC;IACpB,OAAO,EAAE,KAAK;IACd,EAAE;IACF,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,EAAa,EACb,IAAY,EACZ,OAAe,EACC,EAAE,CAAC,CAAC;IACpB,OAAO,EAAE,KAAK;IACd,EAAE;IACF,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;CACzB,CAAC,CAAC"}
|
package/dist/methods.d.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { type JsonRpcRequest } from "./jsonrpc.js";
|
|
2
|
-
import type { ToolRegistry } from "./registry.js";
|
|
3
|
-
import { type WwwAuthenticateOptions } from "./auth/bearer.js";
|
|
4
|
-
export type Principal = {
|
|
5
|
-
id: string;
|
|
6
|
-
scopes: string[];
|
|
7
|
-
/** Optional host claims from `verifyToken` (tenant, plan, role, …). */
|
|
8
|
-
claims?: Record<string, unknown>;
|
|
9
|
-
};
|
|
10
|
-
/** Per-request metrics emitted through the optional `audit` port. */
|
|
11
|
-
export type AuditEntry = {
|
|
12
|
-
/** JSON-RPC method, or `""` for transport-level rejections made before parsing. */
|
|
13
|
-
method: string;
|
|
14
|
-
tool?: string;
|
|
15
|
-
principalId?: string;
|
|
16
|
-
ok: boolean;
|
|
17
|
-
error?: string;
|
|
18
|
-
durationMs: number;
|
|
19
|
-
};
|
|
20
|
-
export type McpPorts<TCtx> = {
|
|
21
|
-
authenticate: (req: Request, method: string, tool?: string) => Promise<Principal | null>;
|
|
22
|
-
context: (req: Request, principal: Principal | null) => TCtx | Promise<TCtx>;
|
|
23
|
-
/**
|
|
24
|
-
* Opt-in metrics hook. Pass any function to receive each tool result and
|
|
25
|
-
* every denial (bad token, missing scope, query-string token). Omit it and
|
|
26
|
-
* the library stays silent — no stdout, no store. Do what you want with
|
|
27
|
-
* `entry` (DB, APM, admin UI). Throwing never fails the request.
|
|
28
|
-
*/
|
|
29
|
-
audit?: (entry: AuditEntry) => void | Promise<void>;
|
|
30
|
-
};
|
|
31
|
-
export type ServerInfo = {
|
|
32
|
-
name: string;
|
|
33
|
-
version: string;
|
|
34
|
-
};
|
|
35
|
-
export type McpHandlerOptions<TCtx> = {
|
|
36
|
-
registry: ToolRegistry<TCtx>;
|
|
37
|
-
ports: McpPorts<TCtx>;
|
|
38
|
-
serverInfo: ServerInfo;
|
|
39
|
-
instructions?: string;
|
|
40
|
-
wwwAuthenticate: WwwAuthenticateOptions | ((req: Request) => WwwAuthenticateOptions);
|
|
41
|
-
/** Methods that skip auth (default: initialize, ping, notifications/*). */
|
|
42
|
-
publicMethods?: Set<string>;
|
|
43
|
-
};
|
|
44
|
-
export declare const DEFAULT_PUBLIC_METHODS: Set<string>;
|
|
45
|
-
export declare const isNotification: (body: JsonRpcRequest) => boolean;
|
|
46
|
-
export declare const resolveWww: <TCtx>(options: McpHandlerOptions<TCtx>, req: Request) => WwwAuthenticateOptions;
|
|
47
|
-
export declare const unauthorized: (www: WwwAuthenticateOptions, message?: string) => Response;
|
|
48
|
-
/**
|
|
49
|
-
* Invoke the audit metrics hook, swallowing any failure.
|
|
50
|
-
* Telemetry must never turn a served response into a transport error —
|
|
51
|
-
* this is the only call path to `ports.audit`.
|
|
52
|
-
*/
|
|
53
|
-
export declare const safeAudit: <TCtx>(options: McpHandlerOptions<TCtx>, entry: AuditEntry) => Promise<void>;
|
|
54
|
-
export declare const hasScope: (principal: Principal, scope: string | undefined) => boolean;
|
|
55
|
-
export declare const dispatchRpc: <TCtx>(input: {
|
|
56
|
-
req: Request;
|
|
57
|
-
body: JsonRpcRequest;
|
|
58
|
-
options: McpHandlerOptions<TCtx>;
|
|
59
|
-
publicMethods: Set<string>;
|
|
60
|
-
}) => Promise<Response>;
|
|
61
|
-
//# sourceMappingURL=methods.d.ts.map
|
package/dist/methods.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"methods.d.ts","sourceRoot":"","sources":["../src/methods.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EAGpB,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,UAAU,GAAG;IACvB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,QAAQ,CAAC,IAAI,IAAI;IAC3B,YAAY,EAAE,CACZ,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,KACV,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC/B,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,IAAI,IAAI;IACpC,QAAQ,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAC7B,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,EACX,sBAAsB,GACtB,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,sBAAsB,CAAC,CAAC;IAC/C,2EAA2E;IAC3E,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,sBAAsB,aAKjC,CAAC;AAUH,eAAO,MAAM,cAAc,GAAI,MAAM,cAAc,KAAG,OACQ,CAAC;AAE/D,eAAO,MAAM,UAAU,GAAI,IAAI,EAC7B,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAChC,KAAK,OAAO,KACX,sBAG0B,CAAC;AAE9B,eAAO,MAAM,YAAY,GACvB,KAAK,sBAAsB,EAC3B,gBAAwB,KACvB,QAGC,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAU,IAAI,EAClC,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAChC,OAAO,UAAU,KAChB,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,WAAW,SAAS,EACpB,OAAO,MAAM,GAAG,SAAS,KACxB,OAGF,CAAC;AAoEF,eAAO,MAAM,WAAW,GAAU,IAAI,EAAE,OAAO;IAC7C,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACjC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B,KAAG,OAAO,CAAC,QAAQ,CAsCnB,CAAC"}
|