— The MCP 2026-07-28 release candidate closes three separate authorization gaps: SEP-2468 forces MCP clients to validate the OAuth iss claim on every authorization response, SEP-837 makes MCP clients declare an OpenID Connect application_type during Dynamic Client Registration, and the base spec now requires every MCP server to implement OAuth 2.0 Protected Resource Metadata (RFC 9728) for authorization-server discovery. None of that reads like production advice until you run a multi-server gateway — something like the Hermes Tool Gateway we operate daily — where issuer validation collides with your circuit breaker, the new DCR field changes how outbound stdio clients register, and PRM turns your inbound MCP server into an OAuth resource server whether you planned for it or not.
Every migration guide published this month walks the same list: stateless sessions, new error codes, deprecated transports. Ours is not that guide — there is already a solid breaking-changes rundown at MCP Spec Ships July 28 if that is what you need. What none of those checklists cover is what happens when the three auth SEPs hit a gateway that already has a circuit breaker, a tool allowlist, and pinned server versions in production. That is the part we actually had to change.
What “Release Candidate” Means for 2026-07-28
MCP protocol versions are named for the date they lock, not the date they ship. 2026-07-28 is the target version string, and as of this writing it is still in its release-candidate window — not the final spec.[1] Beta SDKs for Python, TypeScript, Go, and C# shipped with the RC, and the maintainers were explicit about the risk window: “public APIs may still change between the betas and the stable releases, so pin exact versions.”[2]
That single line of guidance is the same discipline the Hermes Tool Gateway reference already drills into every mcpServers entry — pin @modelcontextprotocol/[email protected], not @latest, because a patch version silently changed list_directory’s output format and broke a downstream parser. The RC SDKs deserve the identical treatment. Anything you wire up against the 2026-07-28 betas this week goes in with a version pin and a note to re-test at GA, not as a permanent production dependency.
Change 1: Issuer Validation Meets Multi-Server Composition (SEP-2468)
SEP-2468 requires MCP clients to validate the OAuth iss parameter on every authorization response, closing what the spec calls a mix-up attack: a flow where a client associates an authorization response with the wrong authorization server and leaks a token to, or accepts a token from, the wrong identity provider.[3] The draft authorization spec spells out the exact client behavior as a decision table keyed on whether the authorization server advertises authorization_response_iss_parameter_supported and whether iss is present in the response — and in three of the four combinations, the client validates or rejects. Only the combination of no advertised support and no iss present lets the flow proceed unchanged.[6]
This is where a single-client tutorial and a gateway diverge. A single MCP client talks to one authorization server per session. A gateway running the Hermes Tool Gateway’s multi-server composition pattern — filesystem, GitHub, Postgres, and an OAuth-protected remote server like Stripe, all wired into one mcpServers block — is exactly the “single client, many authorization servers” shape SEP-2468 was written for. Before this RC, nothing stopped a bug in your callback router from associating the GitHub app’s authorization code with the Stripe client’s expected issuer. After it, that mismatch is a hard rejection instead of a silent cross-wire.
The behavior client-side looks like this, and both the Go and TypeScript SDK reference implementations follow it exactly — record the expected issuer before redirecting, then compare on the way back:[3]
// Before SEP-2468: authorization code is trusted on arrival
function handleCallback(code, state) {
return exchangeCodeForToken(code, state)
}
// After SEP-2468: issuer is recorded at redirect time and
// checked before the code is ever sent to a token endpoint
function startAuthorization(server) {
pendingFlows.set(state, {
expectedIssuer: server.metadata.issuer, // from validated RFC 8414 metadata
codeVerifier,
})
}
function handleCallback(code, state, iss) {
const flow = pendingFlows.get(state)
if (iss !== undefined && iss !== flow.expectedIssuer) {
throw new Error('issuer mismatch — aborting authorization flow')
}
return exchangeCodeForToken(code, flow.codeVerifier)
}
Here’s the trap: if your gateway’s circuit breaker — the use_gateway block from Pattern 8 of the Hermes reference — counts every failed tool-server interaction toward its failure_threshold without distinguishing an issuer mismatch from a timeout, a host that has not yet updated its callback handler to pass iss through will look identical to a dead upstream. Five rejected callbacks and the circuit opens, the server gets marked unavailable for the full recovery_timeout_s, and the actual problem — a callback signature that needs a new optional argument — never surfaces in the alert. We cover the fix for that specifically in the circuit-breaker section below.
Comments · 0
Beta: comments are stored locally on your device and not visible to other readers.
No comments yet. Be the first to share your thoughts.