You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SEP-1932
defines an optional MCP authorization extension that adopts OAuth 2.0
Demonstrating Proof of Possession (RFC 9449)
to make access tokens sender-constrained. A DPoP-bound token cannot be used
by anyone who does not also control the private key the token is bound to, which
mitigates token theft and replay.
This issue covers MCP client conformance only. It validates that a client
obtains a DPoP-bound access token, presents it correctly on every MCP request,
constructs well-formed DPoP proofs, handles server-supplied nonce challenges, and
behaves correctly when the token endpoint or MCP server rejects a request.
Key properties of the client role:
The client generates its own key pair and proves possession of the private
key on every request. Unlike credential-based extensions, the scenario does not
supply the proof key — generating and using it correctly is itself under test.
The proposal adopts DPoP as defined in RFC 9449, without MCP-specific
extensions or additional proof material.
The extension is OPTIONAL and additive; it builds on the baseline MCP
authorization requirements, which continue to apply unchanged.
Generating a DPoP key pair and using it to sign proofs.
Sending a DPoP proof to the token endpoint and obtaining a DPoP-bound token.
Presenting the access token with the DPoPAuthorization scheme on MCP requests.
Constructing a fresh, well-formed DPoP proof per request, including the ath
claim when an access token is presented.
Handling authorization-server and resource-server nonce challenges.
Correct error handling when the token endpoint or MCP server rejects a request.
Not in scope (covered elsewhere or by another role):
DPoP proof validation logic and the RFC 9449 §4.3 checklist — these are
resource-server concerns, covered by the server conformance issue.
Authorization-server token binding (cnf/jkt), metadata advertisement, and
token-endpoint nonce issuance behaviour — covered by the authorization-server
conformance issue.
Cryptographic strength policy and signing-key storage — implementation
concerns, not protocol conformance.
The test authorization server and test MCP server are the judges. They
accept the client's proof at face value for structural and possession checks and
use configurable rejection rules to drive the negative cases. The SDK author
writes the thin conformance client.
Changes Required
Test authorization server (createAuthServer)
Accept a DPoP proof on the token request and verify the proof is a well-formed dpop+jwt signed by the embedded jwk with an asymmetric alg.
Issue a DPoP-bound access token (token_type of DPoP); record the bound key
thumbprint so the test MCP server can confirm proof/token key agreement.
Advertise dpop_signing_alg_values_supported in /.well-known/oauth-authorization-server.
Configurable rejection rules to drive client behaviour:
Respond once with 400 use_dpop_nonce + DPoP-Nonce to exercise the
token-endpoint nonce retry.
Respond with invalid_dpop_proof to exercise client error handling.
Append the corresponding conformance checks to the shared checks array.
Test MCP server (createServer)
Require the DPoPAuthorization scheme; reject Bearer presentation of a
DPoP-bound token.
Verify the per-request proof: required claims present (jti, htm, htu, iat), htm/htu match the observed request, ath equals the hash of the
presented token, signature verifies against the embedded jwk, and jti is
fresh relative to previously seen proofs.
Configurable rejection rules:
Issue a 401 with WWW-Authenticate: DPoP ... error="use_dpop_nonce" + DPoP-Nonce to exercise the resource-server nonce retry.
Issue a 401 invalid_token to exercise client non-fallback behaviour.
Append the corresponding conformance checks to the shared checks array.
Register the new scenario in extensionScenariosList (optional protocol
extension).
Acceptance test suite
Helper unit tests, test-server unit tests (one per rejection rule), and
scenario acceptance tests that exercise both a conformant and a deliberately
non-conformant client. See Acceptance Criteria.
Components that do not change
The MCP protocol interaction itself is unchanged — the grant/token type does
not alter the JSON-RPC exchange beyond the Authorization scheme and DPoP
header, which the test MCP server already needs to observe.
Client sends a DPoP proof on the token request: typ=dpop+jwt, asymmetric alg, embedded public jwk (no private key), htm=POST, htu=token
endpoint, jti, iat.
Client obtains and uses a DPoP-bound access token (does not discard the
binding and fall back to a bearer flow).
Client presents the token to the MCP server with Authorization: DPoP <token>
(not Bearer).
Client includes a DPoP proof header on each MCP request with jti, htm, htu, iat.
Resource-access proof htm/htu match the actual MCP request method and
target URI.
Resource-access proof includes ath = base64url(SHA-256(access token)).
Client generates a fresh proof per request (unique jti, current iat).
Client signs with an asymmetric algorithm advertised in dpop_signing_alg_values_supported; never none or a symmetric algorithm.
Token-endpoint nonce challenge: on 400 use_dpop_nonce + DPoP-Nonce, the
client retries the token request with a nonce claim matching the supplied
value.
Resource-server nonce challenge: on 401 ... use_dpop_nonce + DPoP-Nonce,
the client retries the request with a nonce claim matching the supplied
value.
Negative (client error handling)
On invalid_dpop_proof from the token endpoint, the client does not retry
blindly and surfaces a clear, actionable error.
On a non-nonce 401 invalid_token from the MCP server, the client does not
silently fall back to Bearer or another scheme.
Client does not replay a prior proof — each request carries a distinct jti.
Acceptance Criteria
A single scenario file in src/scenarios/client/auth/ implements all checks
above (one scenario, many checks).
Scenario registered in extensionScenariosList.
Every check has both a passing case (conformant client) and a deliberate
failing case (non-conformant client) proven by the automated acceptance test
suite — no check is vacuously passing.
Helper unit tests cover proof parsing, thumbprint, ath, and nonce helpers.
Test-server unit tests cover each rejection rule (correct status, body, and
recorded check ID/status).
The acceptance test suite runs as part of npm test.
Scenario runs through the standard CLI runner
(npx @modelcontextprotocol/conformance client --command "..." --scenario ...);
no parallel entry point is introduced.
Validated against at least one real SDK conformance client before the PR is
submitted; SDK baseline YAMLs updated where existing SDKs do not yet support
DPoP.
Out of Scope
Required jti state-tracking — optional and stateful per RFC 9449 §11.1; not a
client behaviour.
Authorization-server and resource-server validation behaviour — covered by the
separate server and authorization-server conformance issues.
Overview
SEP-1932
defines an optional MCP authorization extension that adopts OAuth 2.0
Demonstrating Proof of Possession (RFC 9449)
to make access tokens sender-constrained. A DPoP-bound token cannot be used
by anyone who does not also control the private key the token is bound to, which
mitigates token theft and replay.
This issue covers MCP client conformance only. It validates that a client
obtains a DPoP-bound access token, presents it correctly on every MCP request,
constructs well-formed DPoP proofs, handles server-supplied nonce challenges, and
behaves correctly when the token endpoint or MCP server rejects a request.
Key properties of the client role:
key on every request. Unlike credential-based extensions, the scenario does not
supply the proof key — generating and using it correctly is itself under test.
extensions or additional proof material.
authorization requirements, which continue to apply unchanged.
Specification References
cnf/jkt)dpop_signing_alg_values_supported): https://datatracker.ietf.org/doc/html/rfc8414Scope
In scope — what the MCP client does:
DPoPAuthorizationscheme on MCP requests.athclaim when an access token is presented.
Not in scope (covered elsewhere or by another role):
resource-server concerns, covered by the server conformance issue.
cnf/jkt), metadata advertisement, andtoken-endpoint nonce issuance behaviour — covered by the authorization-server
conformance issue.
concerns, not protocol conformance.
The test authorization server and test MCP server are the judges. They
accept the client's proof at face value for structural and possession checks and
use configurable rejection rules to drive the negative cases. The SDK author
writes the thin conformance client.
Changes Required
Test authorization server (
createAuthServer)dpop+jwtsigned by the embeddedjwkwith an asymmetricalg.token_typeofDPoP); record the bound keythumbprint so the test MCP server can confirm proof/token key agreement.
dpop_signing_alg_values_supportedin/.well-known/oauth-authorization-server.400 use_dpop_nonce+DPoP-Nonceto exercise thetoken-endpoint nonce retry.
invalid_dpop_proofto exercise client error handling.checksarray.Test MCP server (
createServer)DPoPAuthorizationscheme; rejectBearerpresentation of aDPoP-bound token.
jti,htm,htu,iat),htm/htumatch the observed request,athequals the hash of thepresented token, signature verifies against the embedded
jwk, andjtiisfresh relative to previously seen proofs.
401withWWW-Authenticate: DPoP ... error="use_dpop_nonce"+DPoP-Nonceto exercise the resource-server nonce retry.401 invalid_tokento exercise client non-fallback behaviour.checksarray.Helpers (
helpers/)dpop+jwt, verify signatureagainst embedded
jwk, extract claims).athcomputation helper (base64url(SHA-256(token))).Scenario registration (
src/scenarios/client/auth/index.ts)extensionScenariosList(optional protocolextension).
Acceptance test suite
scenario acceptance tests that exercise both a conformant and a deliberately
non-conformant client. See Acceptance Criteria.
Components that do not change
not alter the JSON-RPC exchange beyond the
Authorizationscheme andDPoPheader, which the test MCP server already needs to observe.
concerns, not client conformance.
Checks to Cover
Positive
typ=dpop+jwt, asymmetricalg, embedded publicjwk(no private key),htm=POST,htu=tokenendpoint,
jti,iat.binding and fall back to a bearer flow).
Authorization: DPoP <token>(not
Bearer).DPoPproof header on each MCP request withjti,htm,htu,iat.htm/htumatch the actual MCP request method andtarget URI.
ath= base64url(SHA-256(access token)).jti, currentiat).dpop_signing_alg_values_supported; nevernoneor a symmetric algorithm.400 use_dpop_nonce+DPoP-Nonce, theclient retries the token request with a
nonceclaim matching the suppliedvalue.
401 ... use_dpop_nonce+DPoP-Nonce,the client retries the request with a
nonceclaim matching the suppliedvalue.
Negative (client error handling)
invalid_dpop_prooffrom the token endpoint, the client does not retryblindly and surfaces a clear, actionable error.
401 invalid_tokenfrom the MCP server, the client does notsilently fall back to
Beareror another scheme.jti.Acceptance Criteria
src/scenarios/client/auth/implements all checksabove (one scenario, many checks).
extensionScenariosList.failing case (non-conformant client) proven by the automated acceptance test
suite — no check is vacuously passing.
ath, and nonce helpers.recorded check ID/status).
npm test.(
npx @modelcontextprotocol/conformance client --command "..." --scenario ...);no parallel entry point is introduced.
submitted; SDK baseline YAMLs updated where existing SDKs do not yet support
DPoP.
Out of Scope
jtistate-tracking — optional and stateful per RFC 9449 §11.1; not aclient behaviour.
separate server and authorization-server conformance issues.
Notes
Prepared with the aid of Claude (Opus 4.8)