* Presence-gated authenticator for all `/mcp-strict*` routes. Accepts a lazy * signing authenticator so each route uses its own capability instance * (covers_content_digest varies per route and is baked at init time). * * Delegates to `requireAuthenticatedOrSigned` (5.7): bypass on valid bearer,
(lazyAuth: () => Authenticator)
| 187 | * surfaces RFC 9421 error codes on bad signatures. |
| 188 | */ |
| 189 | function buildStrictModeAuthenticator(lazyAuth: () => Authenticator): Authenticator | null { |
| 190 | const bearerAuth = buildBearerAuthenticator(); |
| 191 | if (!bearerAuth) return null; |
| 192 | return enforceSigningWhenWebhookAuthPresent(requireAuthenticatedOrSigned({ |
| 193 | signature: lazyAuth(), |
| 194 | fallback: bearerAuth, |
| 195 | requiredFor: STRICT_REQUIRED_FOR, |
| 196 | resolveOperation: (req) => { |
| 197 | // rawBody is populated by the production http.ts `verify` callback. |
| 198 | // Fall back to req.body (already-parsed by express.json) when rawBody |
| 199 | // is absent — e.g. in test harnesses that skip the verify callback. |
| 200 | // Safe here because resolveOperation drives only the required_for |
| 201 | // routing decision, not cryptographic verification. |
| 202 | const raw = (req as { rawBody?: string }).rawBody; |
| 203 | try { |
| 204 | const body = raw |
| 205 | ? JSON.parse(raw) as { method?: string; params?: { name?: string } } |
| 206 | : (req as { body?: { method?: string; params?: { name?: string } } }).body; |
| 207 | if (body && body.method === 'tools/call' && typeof body.params?.name === 'string') { |
| 208 | return body.params.name; |
| 209 | } |
| 210 | } catch { |
| 211 | // Transport rejects malformed JSON downstream. |
| 212 | } |
| 213 | return undefined; |
| 214 | }, |
| 215 | })); |
| 216 | } |
| 217 | |
| 218 | const defaultAuthenticator = buildDefaultAuthenticator(); |
| 219 | const strictAuthenticator = buildStrictModeAuthenticator(lazyStrictSigningAuth); |
no test coverage detected