Co-authored-by: Paul Carleton <paulc@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1.7 KiB
1.7 KiB
| @modelcontextprotocol/client | @modelcontextprotocol/core |
|---|---|
| minor | minor |
Add DPoP (RFC 9449 / SEP-1932) sender-constrained access token support to the client.
- Opt in by implementing
OAuthClientProvider.dpop()returning aDpopSession(new, along withgenerateDpopKeyPair,accessTokenHash,isDpopNonceChallenge).auth()/exchangeAuthorization/refreshAuthorization/fetchTokenthen sign a DPoP proof into token requests (retrying once on an authorization-serveruse_dpop_noncechallenge, with client authentication re-applied per attempt), andStreamableHTTPClientTransport,SSEClientTransportandwithOAuthpresent atoken_type: "DPoP"access token asAuthorization: DPoP <token>plus a fresh per-request proof, retry a resource-serveruse_dpop_noncechallenge once, and pick up aDPoP-Noncedelivered on any response. Tokens the AS issued asBearerare still presented as Bearer. - DPoP is applied at the fetch layer: the transports wrap their resource-server
fetch(including a caller-suppliedfetch/eventSourceInit.fetch) with the newwithDpopFromProvider(provider)middleware, so proofs are always bound to the request actually sent.withDpop(session, getToken)is exported for callers that manage tokens themselves (e.g. alongside a minimalAuthProvider); theAuthProviderinterface itself is unchanged. auth()now recovers frominvalid_dpop_proofon refresh (e.g. a refresh token bound to a key that is no longer held) by discarding the tokens and re-authorizing, likeinvalid_grant.OAuthErrorCodegainsInvalidDpopProofandUseDpopNonce;extractWWWAuthenticateParamsrecognizes theDPoPchallenge scheme;OAuthMetadataSchemagainsdpop_signing_alg_values_supported.