11-25-2025 compliant Auth (#651)

* fix: correct discovery for AS metadata

* fix: add commitlint to dev container

* feat: add RFC 8707 support for resource parameter

* feat: pkce method verification

* feat(auth): implement SEP-835 scope handling and 403 upgrade flow

- add WWWAuthenticateParams for parsing scope and resource_metadata from headers
- add ScopeUpgradeConfig and scope tracking in AuthorizationManager
- add InsufficientScopeError and 403 handling in streamable HTTP client
- add scope union computation for progressive authorization
- export new public types: AuthClient, ScopeUpgradeConfig, WWWAuthenticateParams

Co-authored-by: fizy069 <fizy069@users.noreply.github.com>

* fix: reorg auth tests

* feat: add error to www-authenticate header parsing

* feat: consider protected resource metadata in scope selection

* fix: reorganize auth tests

* feat: add examples and docs for updated auth

---------

Co-authored-by: fizy069 <fizy069@users.noreply.github.com>
This commit is contained in:
Wils Dawson 2026-02-12 08:30:13 -08:00 • committed by GitHub
commit 61845d61c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 996 additions and 256 deletions

View file

@ -114,14 +114,16 @@ async fn main() -> Result<()> {
client_metadata_url
);
// Initialize oauth state machine
// initialize oauth state machine
let mut oauth_state = OAuthState::new(&server_url, None)
.await
.context("Failed to initialize oauth state machine")?;
// Use CIMD (SEP-991) with client metadata URL
// use CIMD (SEP-991) with client metadata URL.
// passing empty scopes lets the SDK auto-select from the server's
// WWW-Authenticate header, Protected Resource Metadata, or AS metadata.
oauth_state
.start_authorization_with_metadata_url(
&["mcp", "profile", "email"],
&[],
MCP_REDIRECT_URI,
Some("Test MCP Client"),
Some(&client_metadata_url),

View file

@ -520,16 +520,13 @@ async fn oauth_authorization_server() -> impl IntoResponse {
"response_types_supported".into(),
Value::Array(vec![Value::String("code".into())]),
);
additional_fields.insert(
"code_challenge_methods_supported".into(),
Value::Array(vec![Value::String("S256".into())]),
);
let metadata = AuthorizationMetadata {
authorization_endpoint: format!("http://{}/oauth/authorize", BIND_ADDRESS),
token_endpoint: format!("http://{}/oauth/token", BIND_ADDRESS),
scopes_supported: Some(vec!["profile".to_string(), "email".to_string()]),
registration_endpoint: Some(format!("http://{}/oauth/register", BIND_ADDRESS)),
response_types_supported: Some(vec!["code".to_string()]),
code_challenge_methods_supported: Some(vec!["S256".to_string()]),
issuer: Some(BIND_ADDRESS.to_string()),
jwks_uri: Some(format!("http://{}/oauth/jwks", BIND_ADDRESS)),
additional_fields,