Background
SEP-1932 / RFC 9449 DPoP conformance scenarios landed in the conformance suite. #1138 teaches the harness everything-client to pass auth/dpop via a hand-rolled path. That is not SDK support — apps using AuthorizationCodeHandler + StreamableClientTransport still cannot obtain or present DPoP-bound tokens.
Today setMCPHeaders hardcodes Authorization: Bearer (mcp/streamable.go), and OAuthHandler only exposes TokenSource + Authorize — enough for Bearer, not for a fresh per-request DPoP proof (htm/htu/ath/jti).
Proposal (baseline only; no nonce)
Ship one SDK PR that:
- Helpers (e.g. under
oauthex) — ES256 keypair, proof builder, ath, HTU normalize, RFC-vector tests. No nonce in the public API yet.
- Transport — use
token.Type() (default Bearer); attach per-request proofs without breaking existing OAuthHandler implementors.
AuthorizationCodeHandler — explicit opt-in DPoP config; proof on token exchange + refresh; implement the per-request hook.
- Conformance — rewrite
everything-client auth/dpop to use the SDK path (acceptance test). Keep auth/dpop-nonce in baseline.yml.
Hook shape (prefer non-breaking)
Do not add a method to the OAuthHandler interface (breaking for external implementors). Prefer one of:
- A (preferred): optional interface type-asserted in
setMCPHeaders, e.g. handlers that also implement PrepareRequest(ctx, *http.Request) error get called after Authorization is set.
- B:
StreamableClientTransport.PrepareRequest func field set by the app / handler wiring.
AuthorizationCodeHandler with DPoP enabled would implement A (or set B).
Explicit opt-in only — no auto-enable from dpop_bound_access_tokens_required in v1.
Out of scope (follow later, same pattern)
auth/dpop-nonce (AS/RS use_dpop_nonce retry)
- Server
RequireDPoP middleware
- Legacy SSE client OAuth/DPoP
Related
Background
SEP-1932 / RFC 9449 DPoP conformance scenarios landed in the conformance suite. #1138 teaches the harness
everything-clientto passauth/dpopvia a hand-rolled path. That is not SDK support — apps usingAuthorizationCodeHandler+StreamableClientTransportstill cannot obtain or present DPoP-bound tokens.Today
setMCPHeadershardcodesAuthorization: Bearer(mcp/streamable.go), andOAuthHandleronly exposesTokenSource+Authorize— enough for Bearer, not for a fresh per-requestDPoPproof (htm/htu/ath/jti).Proposal (baseline only; no nonce)
Ship one SDK PR that:
oauthex) — ES256 keypair, proof builder,ath, HTU normalize, RFC-vector tests. Nononcein the public API yet.token.Type()(default Bearer); attach per-request proofs without breaking existingOAuthHandlerimplementors.AuthorizationCodeHandler— explicit opt-inDPoPconfig; proof on token exchange + refresh; implement the per-request hook.everything-clientauth/dpopto use the SDK path (acceptance test). Keepauth/dpop-nonceinbaseline.yml.Hook shape (prefer non-breaking)
Do not add a method to the
OAuthHandlerinterface (breaking for external implementors). Prefer one of:setMCPHeaders, e.g. handlers that also implementPrepareRequest(ctx, *http.Request) errorget called after Authorization is set.StreamableClientTransport.PrepareRequestfunc field set by the app / handler wiring.AuthorizationCodeHandlerwith DPoP enabled would implement A (or set B).Explicit opt-in only — no auto-enable from
dpop_bound_access_tokens_requiredin v1.Out of scope (follow later, same pattern)
auth/dpop-nonce(AS/RSuse_dpop_nonceretry)RequireDPoPmiddlewareRelated
auth/dpop,auth/dpop-nonce