Skip to main content

What the MCP 2026-07-28 Spec Broke: Your Server's Auth Fix Checklist

10 min readDora NodaDora Noda
Share
On this page

On July 28, 2026, the Model Context Protocol's largest spec revision since launch became the current protocol version — and every remote MCP server turned into a formal OAuth 2.1 resource server overnight. No more bring-your-own-token. No more skipping auth because your server only talks to internal agents. Across the Tier 1 SDKs, the ecosystem was already running at close to half a billion downloads a month when the button got pushed, so "overnight" here means a very large number of working servers woke up non-compliant.

Here is the part that stings: the spec hardened authorization, but from the operator's chair it reads as breakage. The server you shipped in June still runs. It just fails closed now — clients that speak 2026-07-28 expect metadata endpoints, audience-bound tokens, and issuer checks your server never had, and they refuse to proceed without them. Until the metadata is in place, "the spec hardened auth" and "my working server broke" are the same sentence.

This post is the fix checklist for one concrete server: the deploy-authority MCP server on a self-hosted PaaS, the one whose tools can promote a build to production, living at the canonical URI https://mcp.example.com. Every artifact below is shown against that server. Scope note: the same revision also retired the initialize handshake and Mcp-Session-Id in favor of a stateless core — that is a separate migration, covered by the SDK migration notes, and this post stays strictly on the authorization half.

The checklist

Six items. If you only have ten minutes, do items 1–3 and your server stops failing closed for the common case; items 4–6 close the holes that remain.

#FixExact artifactFails how if missing
1Publish RFC 9728 protected-resource metadataGET /.well-known/oauth-protected-resource returning resource, authorization_servers, scopes_supportedClients cannot discover your authorization server; connection setup dead-ends
2Point 401s at the metadataWWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource" on every unauthenticated responseClients never find item 1; same dead-end, one step earlier
3Enforce RFC 8707 audience bindingRequire resource=https://mcp.example.com on authorize and token requests; reject tokens whose aud is not your URITokens minted for another server replay against yours — including your deploy tools
4Validate RFC 9207 issAuthorization server returns iss; clients validate it before redeeming the codeAuthorization-server mix-up: a code from an attacker's AS redeems against yours
5Bind credentials to the issuing ASKey stored clients, tokens, and PKCE state per issuer; re-register on AS change, never silently migrateStale credentials get reused against the wrong issuer after an AS move
6Plan DCR → CIMDAccept Client ID Metadata Documents; keep DCR only as fallback inside its 12-month windowNothing breaks today — but new clients prefer CIMD, and DCR will be removed

The rest of this post expands each row: what to build, what attack it kills, and what it costs on a small fleet.

1–2. Publish the metadata, then point at it (RFC 9728)

Under 2026-07-28, an MCP server MUST implement OAuth 2.0 Protected Resource Metadata (RFC 9728). Concretely, https://mcp.example.com/.well-known/oauth-protected-resource must answer with a JSON document shaped like this:

json
{
  "resource": "https://mcp.example.com",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["mcp:tools:read", "mcp:tools:execute"],
  "bearer_methods_supported": ["header"]
}

That document is the entire bootstrap chain: a client that has never seen your server learns from it which authorization server to talk to and which scopes exist. But clients only read it if they can find it, which is why the second artifact matters just as much — every unauthenticated response from your server must carry the pointer:

text
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"

Miss either half and the symptom is identical: the client's OAuth flow never starts. This is the single most common "it worked in June" failure, and it is also why deployments that split the resource server and the authorization server across hosts broke hardest. There is a live report of claude mcp login ignoring the RFC 9728 discovery chain and assuming the MCP server's own origin is also the authorization server — silently targeting the wrong host for /authorize whenever the two are split. If your AS lives on a different host than your MCP endpoint, verify the discovery chain end to end against a real client. Spec-correct metadata is necessary but not sufficient when clients take shortcuts.

For the deploy-authority server, both artifacts are nearly free: the metadata document is a static JSON file behind your existing TLS termination, and the 401 header is one line in your auth middleware. No new service, no new dependency.

3. Bind every token to your server — and reject the rest (RFC 8707)

This is the item with teeth. Clients MUST send the RFC 8707 resource parameter — set to your canonical URI — on both the authorization request and the token request, so the authorization server can stamp the issued token as valid only for https://mcp.example.com. Your server, in turn, MUST reject any token not issued for itself: check the audience claim and refuse anything else.

The attack this kills is cross-server token replay, and it is worth stating plainly. Before audience binding, a token was a bearer token in the worst sense: whoever held it could present it anywhere that trusted the issuing AS. A token minted for a harmless internal tool server replays verbatim against your deploy-authority server, and suddenly "can read the staging logs" becomes "can promote to production." The resource parameter plus server-side aud enforcement turns that token into a single-destination credential — useless anywhere except the server named on it.

Note the failure direction the spec chose: fail closed. A token without a recognizable audience is not "unscoped but usable," it is rejected. That is exactly the behavior operators experience as breakage — clients that never sent resource= get 401s from servers that newly enforce aud — but there is no safe fail-open here. A deploy tool that accepts unscoped tokens is a confused deputy waiting for its moment.

Implementation-wise this is a validation rule, not a new endpoint: parse the JWT (or introspection response), compare aud against your canonical URI, reject on mismatch. The canonical URI must match exactly what clients send as resource. Trailing-slash and path-prefix mismatches (https://mcp.example.com vs https://mcp.example.com/mcp) are the classic self-inflicted outage here — so pick one string, document it next to the metadata doc, and treat any deviation as a bug in whichever side deviates.

4–5. Close the mix-up holes (RFC 9207 iss + issuer-bound credentials)

Two related changes harden the client side of the flow, and as the server operator you need to require them, not just support them.

First, authorization servers should return the iss parameter on the authorization response (RFC 9207, via spec proposal SEP-2468), and clients MUST validate it against the expected issuer before redeeming the code. This closes the authorization-server mix-up attack: in a multi-IdP setup, an attacker-controlled authorization server can otherwise capture a code minted by an honest server and get the victim client to redeem it in the wrong place. The iss check makes the code single-origin as well as single-destination — it only redeems where it was minted.

Second, client credentials are now bound to the issuer that minted them (SEP-2352): no reuse across authorization servers. Stored clients, tokens, and PKCE state must be keyed per issuer, and migrating to a different authorization server means existing clients re-register rather than getting a silent handoff. If you have ever moved an IdP and watched everything "just keep working" because credentials floated across, that era is over by design — the silent handoff was the vulnerability.

For your deploy-authority server, the actionable part is on the requirements side: your client documentation and your server's token acceptance policy must assume iss validation and per-issuer credential keying. When you rotate or replace your own authorization server, plan the client re-registration as part of the move — the spec guarantees your clients will need it.

6. DCR is deprecated: plan the move to CIMD

Dynamic Client Registration (RFC 7591) — clients self-registering with the authorization server at connect time — is formally deprecated in favor of Client ID Metadata Documents (CIMD), where the client_id itself is an HTTPS URL pointing at a JSON metadata document the AS fetches and validates on demand. DCR keeps working for backward compatibility inside the spec's twelve-month minimum deprecation window, so this item is a plan, not a fire.

The priority order for new clients is: pre-registered credentials first, then CIMD, then DCR fallback, then ask the user. CIMD also structurally fixes DCR's client-litter problem — every connect-time registration mints a stored client record, and operators have reported runaway accumulation (one team found an agent client had registered five separate clients in ten days). A fetched metadata URL leaves no litter because there is nothing to store.

One adjacent fix ships in the same revision and is worth knowing even though it touches DCR rather than replacing it: clients now set application_type during registration (SEP-837), which stops authorization servers from rejecting localhost redirects for desktop and CLI apps. If your team runs a CLI deploy client against the MCP server and has ever eaten a mysterious redirect_uri error, this is likely the fix you were waiting for — make sure your authorization server honors it.

What the checklist costs on a small fleet

Ranked by effort, for the deploy-authority server on a Cluster-API-managed fleet:

  • Trivial (hours): the static RFC 9728 document, the 401 WWW-Authenticate header, and the aud equality check. One JSON file, one middleware line, one comparison. There is no reason these are not done this week.
  • Small (days): requiring resource= end to end and verifying the discovery chain against every real client your team uses — including the ones that take shortcuts around RFC 9728. Budget the time for client testing, not server code.
  • The actual decision (weeks, and the only one with architecture in it): your authorization server. Same-origin-behind-your-gateway keeps discovery simple and matches what shortcut-taking clients assume; a split AS host is spec-clean but must be verified against each client. Either way, the AS you pick must support CIMD advertisement (client_id_metadata_document_supported) or you inherit DCR as your permanent fallback, plus iss emission and application_type handling. That vendor checklist matters more than any single line of server code in this post.

Notice what the ranking implies: the spec's "hardening" is mostly cheap on the server side. The failures operators feel are discovery and assumption failures — a missing pointer, a mismatched URI string, an AS that cannot emit what clients now require — not deep cryptographic work. The threat model did the breaking, deliberately: a deploy-authority token that replays across servers or redeems across issuers is worse than a 401, and the spec authors chose the 401.

Fail closed on purpose

The through-line of all six items is that 2026-07-28 removed the comfortable middle where auth was present but unenforced. Metadata that exists but is not pointed at might as well not exist. Tokens that carry an audience nobody checks might as well not carry one. The revision's answer to every one of these is the same: require it, validate it, reject without it — and give operators a twelve-month window on the one item (DCR) where immediate enforcement would strand working clients. With all four Tier 1 SDKs (TypeScript, Python, Go, C#) already speaking the new revision, the ecosystem side of the migration is done; what remains is server operators walking the checklist.

Start with the metadata doc and the 401 header today. Your deploy tools will thank you the next time a token shows up where it was never meant to be.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. If your agents deploy through MCP, the servers they talk to should fail closed like this one. Star the repo on GitHub or deploy your first app today.

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide