How a Streamable HTTP MCP server decides whether you're even allowed to call a tool — OAuth 2.1, decoded from a real challenge against a production server.
In Lesson 2 we called get-env
on our local server and it just answered — anyone who can reach the port can run any tool. That's
fine for a reference server on 127.0.0.1. It is not fine for a server on the
public internet holding your company's data. This lesson is about the gate: how an MCP server says
"prove who you are first," and exactly what bytes cross the wire to satisfy it.
MCP invents no auth of its own — it is plain OAuth 2.1. The MCP server plays the role of an OAuth Resource Server: it doesn't take your password, it demands a Bearer access token and, if you don't have one, tells you exactly where to go get it. Everything below is OAuth machinery layered on top of the Streamable HTTP transport you already know.spec
Authentication vs authorization. Authn = who you are
(do you hold a valid token?). Authz = what you may do (does that token carry the
scopes for this tool?). MCP leans on OAuth for both. Watch for it: a missing token is a
401; a valid token lacking the right scope is a 403.
The spec makes HTTP authorization OPTIONAL: a server MAY require it, MAY not.
(stdio servers don't use it at all — they get credentials from the environment.) The fastest way to
feel this is to send the same initialize to two real servers. Our local
everything server requires nothing:
Now the identical request to Linear's production MCP server
(https://mcp.linear.app/mcp) — a real capture:
401 is not a dead end — it's a treasure map
A bare 401 would just say "no." The MCP/OAuth twist is the
WWW-Authenticate header carrying resource_metadata=… —
a URL that tells the client how to get authorized. This pointer is the heart of
RFC 9728 (Protected Resource Metadata), and it is what lets a generic MCP client
authenticate against a server it has never seen before, with zero manual configuration.RFC 9728
The client GETs the URL from that header. No auth needed — discovery is public.
Real capture:
Three fields do the heavy lifting. authorization_servers names who issues tokens
for this resource (it need not be the same host as the MCP server — separation of concerns).
resource is the server's canonical identifier; remember it, it returns in
Section 5. And scopes_supported is the menu of permissions — the vocabulary that later
gates individual tools.
The client now knows the authorization server is https://mcp.linear.app. It fetches
that server's metadata to learn its endpoints — again public, again a real capture:
PKCE is required. code_challenge_methods_supported: ["S256"] means
every client — even a confidential one — must use Proof Key for Code Exchange: it sends a SHA-256
hash of a random secret up front and the raw secret at token time, so a stolen authorization
code is useless to a thief. And there's no pre-registration step: a
registration_endpoint (RFC 7591 Dynamic Client Registration) lets a client the server
has never met register itself on the fly. This is why you can point a brand-new MCP client at any
compliant server and have it work without an admin issuing you a client_id by hand.spec
resource bindingNow the client has every endpoint it needs. The next steps involve a human in a browser, so we
describe the shape rather than capture them — but every value below is built deterministically from
the metadata you just saw. First the client (after registering) sends the user's browser to the
authorization_endpoint:
The user logs in and consents in their browser; the server redirects back to
redirect_uri with a one-time code. The client exchanges it — proving it
started the flow by revealing the PKCE code_verifier — at the token endpoint:
resource parameter is the most important field here
That resource=https://mcp.linear.app/mcp (RFC 8707 Resource Indicators) tells the
authorization server: "mint a token whose audience is this one MCP server." The 2025 MCP
spec makes this a MUST, and it closes the confused-deputy hole: without
it, a malicious MCP server could take the token you handed it and replay it against a
different server that trusts the same issuer. Bound to an audience, a token leaked to
server A is rejected by server B. The flip side is a matching rule for servers: a resource server
MUST validate that a presented token was actually issued for it and
MUST NOT pass tokens through to upstream APIs. "It's a valid token" is never
enough — it has to be a valid token for this audience.spec
With a token in hand, the client retries the very first request — now it carries one extra header,
and this time it gets a session instead of a 401:
Don't conflate them. Authorization: Bearer answers "who are you and what may
you do?" — it's checked on every request and is pure OAuth. Mcp-Session-Id
(Lesson 1) answers "which ongoing conversation is this?" — it's MCP transport state, not a
credential. A token can outlive many sessions; a session is meaningless without the token that
earned it. From here on, every tools/call rides with the Authorization
header, and the server checks the token's scopes against the tool before running it —
that's where read vs write finally bites.
Same lesson as the Origin check in Lesson 1: the spec is full of
MUSTs here — require PKCE, validate token audience, never pass tokens through — but
those bind implementers, not the server in front of you. Plenty of real MCP servers ship
with auth misconfigured or audience validation skipped. When you read auth traffic, verify the gate
is actually doing its job; don't assume a 401 on the front door means the locks
inside are sound.
You don't need a Linear account — the challenge and the whole discovery chain are public. Walk the map by hand:
Predict before you run: which step needs a token, and which are public? Then
try the same discover against our local everything server
(http://127.0.0.1:3001/mcp) — what happens, and what does that tell you about its
security posture?
401 with WWW-Authenticate: Bearer … resource_metadata="…". What is that resource_metadata URL for?resource parameter (RFC 8707) a MUST on the authorization and token requests?Authorization: Bearer and Mcp-Session-Id differ?MCP Specification 2025-11-25 — Base / Authorization.
You've now seen the real 401 challenge, both .well-known metadata
documents, and where PKCE and the resource parameter fit. Read the "Authorization Server
Discovery" and "Security Considerations" sections — and skim RFC 9728
(Protected Resource Metadata) and RFC 8707
(Resource Indicators), the two RFCs doing the new work here.