The request ladder
Three orderings are deliberate
Section titled “Three orderings are deliberate”Authentication precedes the body read. The bearer gate runs before anything parses the POST body. Choosing a per-capability scope still requires reading that body, because the capability name lives in it and not trustworthily in a header.
Header validation precedes both the permission lookup and scope selection.
An untrusted Mcp-Name can talk its way into neither a cheaper scope nor
another capability’s permission. Missing, malformed or dishonest routing headers
are HTTP 400 and never reach a tool. SEP-2243 Base64 sentinel names are decoded
before lookup, so encoding a name cannot downgrade its scope.
Permission precedes scope. An unpermitted caller is never prompted to re-authorise for an action your policy will refuse anyway.
What the body read costs
Section titled “What the body read costs”Only a POST is read, only up to maxRequestBytes (1 MiB by default, over it a
413), and only when its content type is JSON (415 otherwise). A GET carries no
body to check its headers against, so it is held to the baseline rather than to
whatever its headers claim.
The refusals, and what each one means
Section titled “The refusals, and what each one means”| Status | Meaning |
|---|---|
404 |
The path is not the one resourceServerUrl names. The error says which is |
400 |
Routing headers are missing, or they disagree with the body |
401 + WWW-Authenticate |
No valid token. The challenge carries resource_metadata |
403 forbidden / policy_denied |
The token is fine and the policy says no. No Bearer challenge |
403 insufficient_scope |
Permission granted, this token lacks the capability’s scope |
413 / 415 |
The body is over maxRequestBytes, or it is not JSON |
Where the Python package differs
Section titled “Where the Python package differs”The Python package runs the same ladder as SDK middleware
(AuthorizationMiddleware), so the check sits in the SDK’s own chain rather
than inside your handlers. The verifier returns the SDK’s AccessToken, and the
SDK serves the bearer challenge and the protected-resource metadata.
Per-capability scope step-up is npm-only, because the official Python SDK exposes only global HTTP scope requirements today. See Scopes and step-up.