Skip to content

Mark DPoP as experimental extension - #38

Open
pcarleton wants to merge 23 commits into
mainfrom
paulc/sep3392-dpop-experimental
Open

pcarleton wants to merge 23 commits into
mainfrom
paulc/sep3392-dpop-experimental

Conversation

@pcarleton

Copy link
Copy Markdown
Member

Brings @PieterKas's DPoP profile to specification/experimental/dpop.mdx, as an Experimental extension under SEP-3392. The proposal is SEP-1932.

@PieterKas feel free to push final tweaks here before it merges. Experimental carries no stability promise, so the rest can be settled on main. The more serious review milestone comes before promotion to beta.

PieterKas and others added 23 commits December 5, 2025 18:15
Revised the DPoP extension documentation to clarify the purpose, scope, and requirements for sender-constrained access tokens in the Model Context Protocol. Enhanced explanations of DPoP proof requirements, content digest calculations, and replay protection mechanisms.
Added section on Bearer Scheme Downgrade Protection, specifying that DPoP-bound access tokens must not be used as bearer tokens and detailing the server's response requirements.
Clarified error codes for DPoP proof validation failures and access token issues. See modelcontextprotocol/conformance#528
Added DPoP advertisement section for MCP servers, including requirements for `dpop_signing_alg_values_supported` and `dpop_bound_access_tokens_required` fields in Protected Resource Metadata.
Added details on MCP server DPoP advertisement and requirements for DPoP-bound access tokens.
Removed extra line break in the DPoP challenge section.
Clarify requirements for DPoP challenge in WWW-Authenticate header.
Adds a SHOULD for MCP clients to bind the authorization code to their DPoP key per RFC 9449 Section 10. RFC 9449 makes this OPTIONAL; this profile recommends it as defense in depth alongside MCP’s mandatory PKCE. Surfaced by the conformance work in modelcontextprotocol/conformance#523; kept separate from #33 so the two can be discussed and merged independently.
Clarified DPoP proof validation requirements and replay protection mechanisms for MCP servers, emphasizing the rejection of invalid 'iat' claims and the use of nonces for enhanced security.
Added section on server-provided nonces in DPoP proofs, detailing requirements for authorization and MCP servers regarding nonce handling and client behavior.
Added section on obtaining DPoP-bound access tokens, detailing requirements for MCP clients to include DPoP proof in token requests.
Clarified nonce usage and replay protection mechanisms for DPoP Proofs, including guidance on client binding and stateless server requirements.
Add example challenge for server supporting dual authentication schemes with scope and resource metadata.
Move Pieter Kasselman's DPoP profile from specification/draft/ to
specification/experimental/dpop.mdx and list it in README.md. The only
changes to the text are the ones Prettier makes, so the format check
passes.
@pcarleton
pcarleton added this pull request to stack #39 October 23, 2026 16:21
Base automatically changed from paulc/sep3392-alignment to main October 23, 2026 16:24
@pcarleton
pcarleton marked this pull request as ready for review October 23, 2026 16:27
@pcarleton

Copy link
Copy Markdown
Member Author

I'm implicitly approving this as experimental given the amount of work and conversation we've had about it. Note that in SEP-3392 terms, experimental is for prototypes, and the move to beta is what requires Core Maintainer approval and...:

A Beta extension is one the Core Maintainers are willing to have implementers try. The goal is real usage: implementers learn what needs to change, and the design iterates.

"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/token",
"dpop_signing_alg_values_supported": ["ES256", "PS256"]
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a recent discussion with @ThisIsMissEm we noticed that there is no AS Metadata parameter to indicate that DPoP is required by an AS. There's a DCR Metadata param, a RS Metadata param, but no AS Metadata param. Should we propose one to IANA? (very much outside the scope of this PR)

WWW-Authenticate: Bearer scope="files:read",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp",
DPoP algs="ES256 PS256"
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading https://www.rfc-editor.org/info/rfc9110/#section-11.6.1 - it looks like WWW-Auth can operate in two ways:

  • a challenge + list of params per challenge, one per header
  • A list of (challenge, param list), all under one header

The params are unique to the challenge, and are not shared among challenges. The §11.6.1 example shows multiple challenges describing the realm param.

So I think that we need to restate scope and resource_metadata on the DPoP challenge as well

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer scope="files:read",
  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp",
  DPoP algs="ES256 PS256", scope="files:read",
  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"

We could also use an example showing WWW-Auth split over multiple headers, which is also valid (ref)

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer scope="files:read", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"
WWW-Authenticate: DPoP scope="files:read", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", algs="ES256 PS256"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, agreed parameters should belong to a specific challenge. Our server already repeats resource_metadata on the DPoP challenge. I folded this into my suggestion below too.


### DPoP Limitations

DPoP does not prevent token theft, but provides a mechanism that allows detection of replay attacks that utilise a stolen token. In addition, DPoP does not address confused deputy vulnerabilities or other authorization-level attacks. Consequently. implementations must maintain defense in depth by implementing all required security controls.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DPoP does not prevent token theft, but provides a mechanism that allows detection of replay attacks that utilise a stolen token. In addition, DPoP does not address confused deputy vulnerabilities or other authorization-level attacks. Consequently. implementations must maintain defense in depth by implementing all required security controls.
DPoP does not prevent token theft, but provides a mechanism that allows detection of replay attacks that utilise a stolen token. In addition, DPoP does not address confused deputy vulnerabilities or other authorization-level attacks. Consequently, implementations must maintain defense in depth by implementing all required security controls.


### Error Response Example

When validation fails, the MCP server returns an error response:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also <3 an error response showing the nonce

HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="use_dpop_nonce",
  error_description="Fresh nonce required in DPoP proof",
  algs="ES256 PS256"
DPoP-Nonce: eyJ7S_zG.eyJH0-Z.HX4w-7v
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32001,
    "message": "Unauthorized"
  },
  "id": 1
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this error include resource_metadata and/or scope args?


```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="invalid_dpop_proof",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this error include resource_metadata and/or scope args?

- Deployments where token theft risk is elevated.
- Compliance requirements mandating proof of possession or sender constrained tokens.

This extension uses DPoP as defined in [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449), without introducing MCP-specific claims, request body digests, or additional proof material. This design choice reflects a deliberate trade-off in favor of interoperability, deployability, and alignment with existing OAuth 2.0 security models. It does not preclude the use of the extension mechanisms defined in [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be more specific about the trade-off somewhere in the spec? Namely htm and htu are always the same for MCP servers, which makes it possible that a DPoP proof can be replayed if the RS doesn't check jti and the proof is still valid.

When making requests to protected MCP server resources, clients MUST:

1. Include the access token using the `Authorization` header with the `DPoP` authentication scheme as specified in [RFC 9449 Section 7.1](https://datatracker.ietf.org/doc/html/rfc9449#section-7.1)
2. Include a fresh DPoP proof in the `DPoP` header

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fresh is hard to pin down. Does fresh mean one per request? One non-expired proof?

Suggested change
2. Include a fresh DPoP proof in the `DPoP` header
2. Include a valid DPoP proof in the `DPoP` header


[RFC 9449 Section 11.1](https://www.rfc-editor.org/rfc/rfc9449.html#name-dpop-proof-replay) provides specific guidance on replay protection mechanisms that adress the risks of a DPoP Proofs being replayed.

MCP servers are NOT required to track `jti` values, however, MCP servers MAY choose to implement `jti` tracking for additional security, following the guidance in [RFC 9449 Section 11.1](https://datatracker.ietf.org/doc/html/rfc9449#section-11.1).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like the riskiest aspect of the extension. In vanilla DPoP, htm and htu add some level of binding to the specific request. An attacker could replay a DPoP proof, but only to the specific endpoint it was originally intended for. That works pretty sorta kinda well for REST, but breaks for RPC style APIs like MCP. htm and htu will always be the same on every request.

Would prefer stronger language here - a RECOMMENDED would go a long way if we don't feel comfortable with the performance implications of SHOULD

@figitaki figitaki Oct 30, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with RECOMMENDED being the stronger language. Our experimental RS tracks jti by default. Entries are keyed by proof tumbprint + jti, so one client cannot block another by guessing a jti and each entry lives only for the iat window. The cost is small next to the replay gap that constant htm/htu leaves open.

@max-stytch

Copy link
Copy Markdown
Contributor

I snuck in a review! Don't consider any of my comments blocking - happy to iterate on main.

@figitaki figitaki left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to landing this as experimental. Our TypeScript resource server passes all 28 checks of auth/dpop-server-validation (modelcontextprotocol/conformance#529). These notes come from building against this text.

  • Before merge: the file doesn't compile as MDX, and two examples contradict the text. Suggestions are inline.
  • Non-blocking: my two #33 points (403 step-up, and algs vs PRM) didn't carry over. I re-linked them inline so they survive #33 closing. Settling them on main is fine.

Drafted with AI assistance. I checked each point against the file and our implementation.


### MCP Server Validation

MCP servers MUST validate DPoP proofs according to [RFC 9449 Section 4.3](https://datatracker.ietf.org/doc/html/rfc9449#section-4.3). Implementations conforming to this specification MUST reject DPoP proofs whose `iat` claim falls outside ±5 minutes of the server's current time. Servers MAY enforce a narrower acceptance window (see {{proof-validity-window}}).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file doesn't compile as MDX. MDX parses {...} as a JavaScript expression, so {{proof-validity-window}} fails (@mdx-js/mdx 3: "Could not parse expression with acorn" at 83:342). The same break is on lines 93, 250, 252, 256 and 272. CI misses it because it runs only Prettier and the link check. Plain anchor links fix it:

Suggested change
MCP servers MUST validate DPoP proofs according to [RFC 9449 Section 4.3](https://datatracker.ietf.org/doc/html/rfc9449#section-4.3). Implementations conforming to this specification MUST reject DPoP proofs whose `iat` claim falls outside ±5 minutes of the server's current time. Servers MAY enforce a narrower acceptance window (see {{proof-validity-window}}).
MCP servers MUST validate DPoP proofs according to [RFC 9449 Section 4.3](https://datatracker.ietf.org/doc/html/rfc9449#section-4.3). Implementations conforming to this specification MUST reject DPoP proofs whose `iat` claim falls outside ±5 minutes of the server's current time. Servers MAY enforce a narrower acceptance window (see [Proof Validity Window](#proof-validity-window)).


A DPoP-bound access token is sender-constrained and MUST NOT be usable as a bearer token. MCP servers MUST NOT accept a DPoP-bound access token presented using the `Bearer` authentication scheme (see [RFC 9449 Section 7.2](https://datatracker.ietf.org/doc/html/rfc9449#section-7.2)). The MCP server MUST reject such a request with an HTTP 401 response that includes a `WWW-Authenticate` challenge using the `Bearer` ([RFC 6750 Section 3](https://datatracker.ietf.org/doc/html/rfc6750#section-3)) or `DPoP` ([RFC 9449 Section 7.1](https://datatracker.ietf.org/doc/html/rfc9449#section-7.1)) scheme, and SHOULD indicate the `invalid_token` error code ([RFC 6750 Section 3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1)).

### Stateless Operation and DPoP Proof Replay Protection {#stateless-replay-protection}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same MDX fix.

Suggested change
### Stateless Operation and DPoP Proof Replay Protection {#stateless-replay-protection}
### Stateless Operation and DPoP Proof Replay Protection

- The baseline MCP Authorization specification security requirements
- OAuth 2.1 security best practices

In addition, MCP servers MUST provide DPoP proof replay protection as described in {{stateless-replay-protection}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same MDX fix.

Suggested change
In addition, MCP servers MUST provide DPoP proof replay protection as described in {{stateless-replay-protection}}
In addition, MCP servers MUST provide DPoP proof replay protection as described in [Stateless Operation and DPoP Proof Replay Protection](#stateless-operation-and-dpop-proof-replay-protection).


In addition, MCP servers MUST provide DPoP proof replay protection as described in {{stateless-replay-protection}}

### Proof Validity Window {#proof-validity-window}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same MDX fix.

Suggested change
### Proof Validity Window {#proof-validity-window}
### Proof Validity Window


The ±5 minute bound balances security and usability. MCP servers MAY enforce a narrower window within it, but should be aware that overly restrictive windows may cause operational issues due to clock skew in distributed systems.

For high-security environments, MCP servers SHOULD implement server-provided nonces as described in [RFC 9449 Section 9](https://datatracker.ietf.org/doc/html/rfc9449#section-9) to further limit proof lifetime, even if it is stateless (see {{stateless-replay-protection}}).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same MDX fix.

Suggested change
For high-security environments, MCP servers SHOULD implement server-provided nonces as described in [RFC 9449 Section 9](https://datatracker.ietf.org/doc/html/rfc9449#section-9) to further limit proof lifetime, even if it is stateless (see {{stateless-replay-protection}}).
For high-security environments, MCP servers SHOULD implement server-provided nonces as described in [RFC 9449 Section 9](https://datatracker.ietf.org/doc/html/rfc9449#section-9) to further limit proof lifetime, even if it is stateless (see [Stateless Operation and DPoP Proof Replay Protection](#stateless-operation-and-dpop-proof-replay-protection)).


### Replay Detection Without State

Replay protection options for stateless MCP servers are described in {{stateless-replay-protection}}. MCP servers requiring maximum replay protection SHOULD implement `jti` tracking despite the operational complexity. The trade-offs are discussed in detail in [RFC 9449 Section 11.1](https://datatracker.ietf.org/doc/html/rfc9449#section-11.1).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same MDX fix.

Suggested change
Replay protection options for stateless MCP servers are described in {{stateless-replay-protection}}. MCP servers requiring maximum replay protection SHOULD implement `jti` tracking despite the operational complexity. The trade-offs are discussed in detail in [RFC 9449 Section 11.1](https://datatracker.ietf.org/doc/html/rfc9449#section-11.1).
Replay protection options for stateless MCP servers are described in [Stateless Operation and DPoP Proof Replay Protection](#stateless-operation-and-dpop-proof-replay-protection). MCP servers requiring maximum replay protection SHOULD implement `jti` tracking despite the operational complexity. The trade-offs are discussed in detail in [RFC 9449 Section 11.1](https://datatracker.ietf.org/doc/html/rfc9449#section-11.1).

Comment on lines +213 to +215
Content-Length: 52
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6IkJlQUxrYiJ9.eyJzdWIiOiJzb21lb25lQGV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwiYXVkIjoiaHR0cHM6Ly9tY3AuZXhhbXBsZS5jb20iLCJuYmYiOjE2OTk1NjQyMTEsImV4cCI6MTY5OTU2NzgxMSwiY25mIjp7ImprdCI6IjBaY09DT1JaTll5LURXcHFxMzBqWnlKR0hUTjBkMkhnbEJWM3VpZ3VBNEkifX0.fT0eG8Qa0nKW8P0qT6YZqVhBnb2r5Q0yX7VqA8Zr1Yk
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCRnMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5REFLVXJVMENZR1JEQSIsImNydiI6IlAtMjU2In19LnsianlpIjoiZTFqM1ZfYktpYzgtTEFFQiIsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL21jcC5leGFtcGxlLmNvbS9tY3AiLCJpYXQiOjE2OTk1NjQyMTgsImF0aCI6ImZVSHlPMnIyWjNEWjUzRXNOcldCYjB4V1hvYU55NTlJaUtDQXFrc21RRW8ifQ==.mHN0qXKq_EXAMPLE_SIGNATURE_DATA_HERE

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementers will try to verify this example, and it fails:

  • The DPoP proof has two segments, not three. Header and payload are encoded together with the . inside.
  • The decoded payload has jyi, not jti. The header's y differs from line 189.
  • ath is the RFC 9449 example value, not the hash of the token on line 214.
  • The token's aud is https://mcp.example.com, but the PRM resource is https://mcp.example.com/mcp. The audience check on line 280 rejects it.
  • The body is 46 bytes, not 52.

Cheapest fix: truncate both tokens, as the first example (lines 60-61) does, and drop Content-Length. A consistent test vector can come later on main.

Suggested change
Content-Length: 52
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6IkJlQUxrYiJ9.eyJzdWIiOiJzb21lb25lQGV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwiYXVkIjoiaHR0cHM6Ly9tY3AuZXhhbXBsZS5jb20iLCJuYmYiOjE2OTk1NjQyMTEsImV4cCI6MTY5OTU2NzgxMSwiY25mIjp7ImprdCI6IjBaY09DT1JaTll5LURXcHFxMzBqWnlKR0hUTjBkMkhnbEJWM3VpZ3VBNEkifX0.fT0eG8Qa0nKW8P0qT6YZqVhBnb2r5Q0yX7VqA8Zr1Yk
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCRnMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5REFLVXJVMENZR1JEQSIsImNydiI6IlAtMjU2In19LnsianlpIjoiZTFqM1ZfYktpYzgtTEFFQiIsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL21jcC5leGFtcGxlLmNvbS9tY3AiLCJpYXQiOjE2OTk1NjQyMTgsImF0aCI6ImZVSHlPMnIyWjNEWjUzRXNOcldCYjB4V1hvYU55NTlJaUtDQXFrc21RRW8ifQ==.mHN0qXKq_EXAMPLE_SIGNATURE_DATA_HERE
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6IkJlQUxrYiJ9...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6...

Comment on lines +227 to +228
WWW-Authenticate: DPoP error="invalid_dpop_proof",
error_description="Content digest mismatch"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 41 says this profile adds no request body digests, so this error can't happen. Line 151 also says the 401 challenge SHOULD carry algs. This uses an RFC 9449 §4.3 failure and adds algs:

Suggested change
WWW-Authenticate: DPoP error="invalid_dpop_proof",
error_description="Content digest mismatch"
WWW-Authenticate: DPoP error="invalid_dpop_proof",
error_description="DPoP proof htu does not match the request URI",
algs="ES256 PS256"


When responding to an unauthenticated or failed request with HTTP 401, an MCP server supporting this extension SHOULD include a `DPoP` challenge in the `WWW-Authenticate` header, and that challenge SHOULD include the `algs` parameter signaling the JWS algorithms acceptable for DPoP proof JWTs, as described in [RFC 9449 Section 7.1](https://datatracker.ietf.org/doc/html/rfc9449#section-7.1). The `DPoP` challenge MAY appear alongside other challenges, such as a `Bearer` challenge carrying the `resource_metadata` parameter defined in [RFC 9728 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9728#section-5.1).

An MCP server that sets `dpop_bound_access_tokens_required` to `true` in its Protected Resource Metadata MUST include a `DPoP` challenge in every `WWW-Authenticate` response it sends. Other challenges MAY appear alongside it, for example a `Bearer` challenge used to deliver error information to a client that attempted bearer authentication, as described in [RFC 9449 Section 7.2](https://datatracker.ietf.org/doc/html/rfc9449#section-7.2).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, carried over from my #33 review (#33 (comment)). Does "every WWW-Authenticate response" include a 403 insufficient_scope step-up? Scope checks often run outside the auth gate. In the TypeScript SDK, a per-tool requireScopes check answers 403 with a Bearer-only challenge, even after DPoP authentication. One sentence settles it:

Suggested change
An MCP server that sets `dpop_bound_access_tokens_required` to `true` in its Protected Resource Metadata MUST include a `DPoP` challenge in every `WWW-Authenticate` response it sends. Other challenges MAY appear alongside it, for example a `Bearer` challenge used to deliver error information to a client that attempted bearer authentication, as described in [RFC 9449 Section 7.2](https://datatracker.ietf.org/doc/html/rfc9449#section-7.2).
An MCP server that sets `dpop_bound_access_tokens_required` to `true` in its Protected Resource Metadata MUST include a `DPoP` challenge in every `WWW-Authenticate` response it sends. This includes HTTP 403 responses with the `insufficient_scope` error code, such as responses to per-operation scope checks. Other challenges MAY appear alongside it, for example a `Bearer` challenge used to deliver error information to a client that attempted bearer authentication, as described in [RFC 9449 Section 7.2](https://datatracker.ietf.org/doc/html/rfc9449#section-7.2).


### WWW-Authenticate Challenge

When responding to an unauthenticated or failed request with HTTP 401, an MCP server supporting this extension SHOULD include a `DPoP` challenge in the `WWW-Authenticate` header, and that challenge SHOULD include the `algs` parameter signaling the JWS algorithms acceptable for DPoP proof JWTs, as described in [RFC 9449 Section 7.1](https://datatracker.ietf.org/doc/html/rfc9449#section-7.1). The `DPoP` challenge MAY appear alongside other challenges, such as a `Bearer` challenge carrying the `resource_metadata` parameter defined in [RFC 9728 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9728#section-5.1).

@figitaki figitaki Oct 30, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, carried over from #33 (#33 (comment)). Nothing ties algs to dpop_signing_alg_values_supported, which line 172 calls authoritative. They differ in practice: the TypeScript SDK challenge lists 10 algorithms, while a deployment's PRM can list only ES256. Both conform today.

Suggested change
When responding to an unauthenticated or failed request with HTTP 401, an MCP server supporting this extension SHOULD include a `DPoP` challenge in the `WWW-Authenticate` header, and that challenge SHOULD include the `algs` parameter signaling the JWS algorithms acceptable for DPoP proof JWTs, as described in [RFC 9449 Section 7.1](https://datatracker.ietf.org/doc/html/rfc9449#section-7.1). The `DPoP` challenge MAY appear alongside other challenges, such as a `Bearer` challenge carrying the `resource_metadata` parameter defined in [RFC 9728 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9728#section-5.1).
When responding to an unauthenticated or failed request with HTTP 401, an MCP server supporting this extension SHOULD include a `DPoP` challenge in the `WWW-Authenticate` header, and that challenge SHOULD include the `algs` parameter signaling the JWS algorithms acceptable for DPoP proof JWTs, as described in [RFC 9449 Section 7.1](https://datatracker.ietf.org/doc/html/rfc9449#section-7.1). If the `algs` parameter is present, it MUST contain the same algorithms as the `dpop_signing_alg_values_supported` field in the server's Protected Resource Metadata. The DPoP challenge MAY appear alongside other challenges, such as a Bearer challenge. Parameters belong to the challenge that carries them (RFC 9110 Section 11.6.1 (https://www.rfc-editor.org/rfc/rfc9110#section-11.6.1)), so a DPoP challenge repeats any resource_metadata or scope parameter that another challenge in the response carries.

If the PRM is meant to be able to list a subset, one sentence saying the PRM wins when they differ also works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants