Commit graph rust-sdk/crates/rmcp/src/transport/auth.rs
Author SHA1 Message Date
Jack Amadeo
7d811c49d1
fix(auth): add an SDK path for pre-registered OAuth clients (#994)
... and exercise it in conformance tests
2026-07-17 21:20:30 -04:00
stevenlee-oai
9df629e631
fix(auth): distinguish rejected refresh tokens from transient failures (#963)
* fix(auth): distinguish rejected refresh tokens

* docs(auth): clarify refresh failure handling
2026-07-16 22:15:19 -04:00
Jack Amadeo
81745f1375
fix(auth): validate discovered metadata issuer (#996)
* fix(auth): validate discovered metadata issuer

Validate authorization server metadata issuers against the issuer implied by
standard discovery URLs before trusting advertised endpoints.

fixes #983

* test(auth): add required issuer to authorization-server mock metadata

---------

Co-authored-by: Alex Hancock <alexhancock@block.xyz>
2026-07-16 16:55:11 -04:00
Stefano Amorelli
7824bfdada
feat(auth): accumulate client-side scopes during step-up authorization (#888)
SEP-2350 [1] clarifies that scope accumulation is a client-side
responsibility: during re-authorization the client requests the union of
its previously requested scopes and the newly challenged scopes, because
servers report only the scopes needed for the current operation in
403 / insufficient_scope challenges (RFC 6750 §3.1), not the union of
everything granted so far.

select_base_scopes returned a single source, so a 403 challenge replaced
the previously requested scopes instead of widening them, dropping prior
permissions across step-up rounds. I make it union the previously
requested scopes, the WWW-Authenticate challenge, and the protected
resource metadata scopes (RFC 9728), treating each server-reported set as
an operational requirement for the current operation rather than an
exclusive directive; AS metadata and caller defaults only seed the
request when nothing has been requested or challenged yet.

exchange_code_for_token treats an explicit scope list as authoritative,
so a server may still narrow the grant. When the server omits scope it
has granted exactly what the client requested (RFC 6749 §5.1), so the
grant has to fall back to the scopes requested in this round, not the
previously granted set; otherwise a step-up that the server confirms by
omitting scope would silently drop the just-added permission and the
client would loop on the same 403. The widened request was not persisted
anywhere the exchange could read it, so I record it on
StoredAuthorizationState per authorization (defaulting empty for states
stored before this field existed) and resolve the grant from there. This
addresses review feedback [2] that the earlier fallback returned the
previous grant rather than the request. Deduplication preserves
first-seen order for stable, testable output. Tests cover multi-round
accumulation, dedup, resource-metadata unioning, and grant resolution
when the response omits scope. Implements [3].

[1]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/basic/authorization.mdx#L682
[2]: https://github.com/modelcontextprotocol/rust-sdk/pull/888#pullrequestreview-4454854582
[3]: https://github.com/modelcontextprotocol/rust-sdk/issues/877

Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>
2026-07-16 15:47:52 -04:00
Dale Seo
a03793530d
fix: pass client conformance suite (#960)
* fix(auth): support oauth metadata fallbacks

* ci: run client conformance scenarios

* fix: pass full client conformance suite

* ci: run full client conformance suite

* fix: update SSE stream constructor
2026-07-08 11:25:12 -04:00
Dale Seo
95490facd6
feat: reject auth servers lacking S256 PKCE support (#955) 2026-07-05 09:05:09 -04:00
Dale Seo
bdf0c32e8c
fix: address 2025-11-25 conformance audit findings (#951)
* fix: interpret task ttl as milliseconds

* fix: use text/plain for default text mime type

* fix: include resource param in token refresh

* test: align conformance prompt args with runner

* ci: run server conformance suite on PRs

* ci: build client bin and gate pending scenarios
2026-07-02 22:42:35 -04:00
Sarthak Bhardwaj
5837e225d3
fix(auth): preserve refresh_token when refresh response omits it (#949)
Per RFC 6749 section 6 and OAuth 2.1: when the server does not issue a new
refresh_token on a refresh response, the client MUST keep the existing one.
AuthorizationManager::refresh_token() was persisting the response verbatim,
dropping the previous refresh_token and forcing full re-authorization on
the next refresh.

Match the fix from python-sdk#2270: preserve the existing refresh_token
when the response omits it, replace when the server rotates.

Fixes #921

Signed-off-by: SarthakB11 <sarthak.bhardwaj21b@iiitg.ac.in>
2026-07-02 08:44:59 -04:00
Dale Seo
c1a8b29ff2
fix: prevent OAuth resource spoofing (#937) 2026-06-27 19:43:46 -04:00
Dale Seo
eb435c6f51
fix: block oauth metadata ssrf (#935)
* fix: block oauth metadata ssrf

* fix: warn on oauth metadata blocks
2026-06-26 23:37:12 -04:00
Dale Seo
e1af378949
chore: consolidate repeated rmcp tests (#931) 2026-06-26 16:15:20 -04:00
Dale Seo
6d020c9684
fix(auth): preserve configured reqwest client (#917) 2026-06-22 17:34:16 -04:00
jif
de898dd842
Allow custom HTTP clients for OAuth (#908)
* feat: allow custom HTTP clients for OAuth

* fix(auth): preserve configured client for refresh

* fix(auth): harden OAuth HTTP adapter

* refactor(auth): simplify OAuth HTTP plumbing

* fix(auth): stop refresh token redirects by default

* fix(auth): re-export OAuth HTTP client types
2026-06-22 13:24:32 -04:00
King Star
3c5ce2b0d7
fix(auth): align OAuth metadata discovery ordering (#887) 2026-06-22 11:34:18 -04:00
Michael Neale
53c6daadd9
fix(auth): apply offline_access to reauth paths (#897)
* fix(auth): apply offline_access to reauth paths

* Update crates/rmcp/src/transport/auth.rs

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>

---------

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-06-11 06:43:49 -04:00
Michael Neale
2d3d1879ad
feat: validate OAuth authorization response issuer (#896)
* feat: validate OAuth authorization response issuer

* fix: tighten issuer validation callbacks
2026-06-10 13:08:31 +10:00
Stefano Amorelli
f1ef2ec86c
feat: specify OIDC application_type during dynamic client registration (SEP-837) (#883)
* feat(auth): specify OIDC application_type during client registration

SEP-837 [1] requires an MCP client to specify an application_type during
OIDC Dynamic Client Registration. When it is omitted, OIDC servers
default the client to "web", which conflicts with the loopback redirect
URIs that CLI and desktop clients use, so the registration can be
rejected.

I make register_client always send an application_type. It defaults to
"native" to match the loopback redirect this SDK uses, and I added
OAuthClientConfig::with_application_type so web clients can opt in. Tests
cover the serialized request body and the config default. Implements [2].

[1]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/basic/authorization.mdx#L395
[2]: https://github.com/modelcontextprotocol/rust-sdk/issues/880

Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>

* chore(auth): declare application_type in client metadata document

I set application_type to "native" in the hosted client metadata
document so the URL-based client id flow and dynamic registration agree
on the client type that SEP-837 [1] expects.

[1]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/basic/authorization.mdx#L395

Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>

---------

Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>
2026-06-04 10:53:18 -04:00
jh-block
01a6666429
fix: treat resource metadata JSON parse failure as soft error (#810)
In fetch_resource_metadata_from_url, a JSON parse failure on the
response body caused a fatal AuthError::MetadataError, preventing
discover_metadata() from falling through to direct
.well-known/oauth-authorization-server discovery (Strategy B).

MCP servers that return HTTP 200 with non-JSON content (e.g. HTML)
at their base URL caused the OAuth flow to abort entirely, even
when the server had a valid .well-known/oauth-authorization-server
endpoint.

Return Ok(None) on parse failure, consistent with how HTTP errors
are already handled in the same function.
2026-04-16 12:16:19 -04:00
Dale Seo
8a8c036ccb
chore: update Rust toolchain to 1.92 (#797) 2026-04-09 14:09:59 -04:00
Will Pfleger
b74f5ca35b
feat(auth): add StoredCredentials::new() constructor (#778)
StoredCredentials is #[non_exhaustive] but has no constructor, making
it impossible for external crates implementing CredentialStore to
construct instances without a serde roundtrip workaround. Add a new()
constructor matching the pattern used for other #[non_exhaustive]
types in this crate.

Fixes #777
2026-03-27 14:47:41 -04:00
Dale Seo
6a3b32d3ab
chore: add #[non_exhaustive] to remaining public structs (#768)
* chore: add #[non_exhaustive] to remaining public structs

* chore: add #[non_exhaustive] to remaining public types

* chore: enable exhaustive_structs/enums clippy lints

* test: add untagged ServerResult deserialization regression tests
2026-03-26 10:23:58 -04:00
Wils Dawson
a32a9c83a1
feat(auth): implement SEP-2207 OIDC-flavored refresh token guidance (#676)
* feat: implement sep-2207 refresh token guidance

* fix: update client-metadata.json to allow refresh tokens
2026-03-23 20:15:23 -04:00
Warwick
66712db808
fix(auth): redact secrets in Debug output for StoredCredentials and StoredAuthorizationState (#744)
* fix(auth): redact secrets in Debug output for StoredCredentials and StoredAuthorizationState

Removes `Debug` from the derive macros on `StoredCredentials` and
`StoredAuthorizationState` and replaces them with manual `Debug` impls
that print `[REDACTED]` for sensitive fields (access/refresh tokens,
PKCE verifiers, and CSRF tokens), preventing accidental credential
leakage via `{:?}` formatters, log calls, and error chains.

Fixes #741

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(auth): assert Debug output redacts secrets for credential types

Adds regression tests for the fix in the previous commit, verifying
that `{:?}` formatting of `StoredAuthorizationState` and
`StoredCredentials` does not emit plaintext secrets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(auth): address review feedback on debug redaction tests

- Remove redundant VendorExtraTokenFields from use super:: in
  test_stored_credentials_debug_redacts_token_response (already
  imported at module scope)
- Add assert!(debug_output.contains("created_at")) to
  test_stored_authorization_state_debug_redacts_secrets to verify
  non-secret fields remain visible in Debug output
- Run cargo fmt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update crates/rmcp/src/transport/auth.rs

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>

* fix: remaining formatting issue

* fix: formatting

* fix: formatting

* fix: please

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-03-13 15:51:02 -04:00
Dale Seo
54bb522e7f
feat: include granted scopes in OAuth refresh token request (#731)
* fix: include granted scopes in OAuth refresh token request

* docs: document scope forwarding in token refresh flow
2026-03-09 16:30:19 -04:00
Dale Seo
bb6c8043bf
feat: implement OAuth 2.0 Client Credentials flow (#707)
* feat: implement OAuth 2.0 Client Credentials flow

* fix: address SEP-1046 review findings

* fix: validate HTTPS on JWT token endpoint
2026-03-03 20:53:51 -05:00
Peter Siska
434ccb7812
fix(auth): pass WWW-Authenticate scopes to DCR registration request (#705)
* fix(auth): pass WWW-Authenticate scopes to DCR registration request

When an MCP server returns a 401 with `WWW-Authenticate: Bearer scope="..."`,
the scopes are parsed but never included in the Dynamic Client Registration
(DCR) request. Per RFC 7591, the DCR request should include a `scope` field
so the authorization server knows what scopes the client intends to use.
Servers that enforce scope-matching between registration and authorization
will reject the flow without this.

Changes:
- Add optional `scope` field to `ClientRegistrationRequest` with
  `skip_serializing_if` for backward compatibility
- Update `register_client()` to accept scopes parameter and include
  them in the DCR request body and returned `OAuthClientConfig`
- Thread scopes from `AuthorizationSession::new()` into both
  `register_client()` call sites
- Re-export `oauth2::TokenResponse` trait so consumers can extract
  scopes from token responses
- Add serialization tests for the new `scope` field

* refactor(auth): change register_client to accept &[&str] instead of &[String]

Avoids unnecessary Vec<String> allocation in callers that already have &[&str].

* fix(auth): make ClientRegistrationRequest crate-private

* refactor(auth): stop re-exporting oauth2 TokenResponse trait

* style(auth): merge TokenResponse into grouped oauth2 import

Fix nightly rustfmt check by consolidating the separate
`use oauth2::TokenResponse` into the existing `use oauth2::{...}` block.
2026-03-03 12:43:31 -05:00
Jack Amadeo
f63718d202
chore: add #[non_exhaustive] and mutation methods to improve compatibility (#715)
* chore: add #[non_exhaustive] to reduce backwards-incompatible changes going forward

* fix: remove ProtocolVersion import

* fix: add a few more with_ mutator methods

---------

Co-authored-by: Alex Hancock <alexhancock@block.xyz>
2026-03-03 10:38:01 -05:00
Guy Lichtman
78d959fcd4
feat(auth): support returning extra fields from token exchange (#700)
* feat(auth): support returning extra fields that may be returned from token generation

exchange_code_for_token and refresh_token now return a StandardTokenResponse which includes
any additionalfields which might have been sent by the vendor

BREAKING CHANGE: Return type of exchange_code_for_token and refresh_token has changed
and may require code changes.

* fix: doc links
2026-03-02 10:38:15 -05:00
Dale Seo
b967c132ae
fix: improve error logging and remove token secret from logs (#685) 2026-02-26 10:05:43 -05:00
Wils Dawson
83808d3114
fix: refresh token expiry (#680) 2026-02-24 12:02:50 -05:00
Dale Seo
021a431bef
chore: upgrade reqwest to 0.13.2 (#669) 2026-02-17 13:41:12 -05:00
Arc
016b7d3bfa
feat: add support for custom HTTP headers in StreamableHttpClient (#655)
* feat: add support for custom HTTP headers in StreamableHttpClient

* feat: implement reserved header checks for custom HTTP headers in StreamableHttpClient
2026-02-13 12:28:55 -05:00
Anar Azadaliyev
d9a5560953
feat(auth): add token_endpoint_auth_method to OAuthClientConfig (#648)
* feat(auth): add token_endpoint_auth_method to OAuthClientConfig

Some OAuth providers (e.g. HubSpot) require client credentials to be
sent as POST body parameters (client_secret_post) instead of via HTTP
Basic Auth header. The oauth2 crate defaults to BasicAuth, and rmcp
had no way to override this, causing TokenExchangeFailed errors.

Add an optional `token_endpoint_auth_method` field to OAuthClientConfig
that accepts "client_secret_post" (RequestBody) and "client_secret_basic"
(BasicAuth). Unknown values are silently ignored, preserving the default.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(auth): derive token_endpoint_auth_method from server metadata

Move auth method selection from per-client config to server's
AuthorizationMetadata, which is the correct OAuth 2.0 approach.
Servers like HubSpot advertise token_endpoint_auth_methods_supported
in their metadata; reading it from there avoids manual configuration
and prevents TokenExchangeFailed errors with non-BasicAuth providers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(auth): read token_endpoint_auth_methods_supported from additional_fields

Move token_endpoint_auth_methods_supported out of AuthorizationMetadata
as an explicit field and read it from the serde(flatten) additional_fields
HashMap instead. This avoids serializing `null` when the field is absent,
which broke Zod validation in downstream consumers like MCP Inspector.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(auth): prefer basic auth when both methods supported and improve test assertions

When token_endpoint_auth_methods_supported contains both client_secret_post
and client_secret_basic, default to basic auth per RFC 6749 §2.3.1.
Update configure_client tests to assert actual AuthType instead of is_some().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(auth): apply cargo fmt formatting

* style(auth): apply nightly cargo fmt import grouping

* revert: undo .gitignore change

---------

Co-authored-by: Anar Azadaliyev <anar.azadaliye@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 09:56:07 -05:00
Wils Dawson
61845d61c4
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>
2026-02-12 11:30:13 -05:00
Guy Lichtman
f6ebc7af13
fix(auth): oauth metadata discovery (#641)
* fix(auth): oauth metadata discovery

* fix: format auth.rs
2026-02-04 17:57:52 +01:00
centdix
ce559f28e0
feat(auth): add StateStore trait for pluggable OAuth state storage (#614)
* feat(auth): add StateStore trait for pluggable OAuth state storage

* fix(examples): use CLI server_url for transport connection
2026-01-13 15:33:25 -05:00
Tanish Desai
61f7b7b99e
fix: add OpenID Connect discovery support per spec-2025-11-25 4.3 (#598)
* fix: add OpenID Connect discovery support per spec-2025-11-25 4.3

Previously only tried OAuth 2.0 endpoints. Now tries OAuth first, then
OpenID Connect Discovery 1.0 in the spec-mandated priority order.

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

* fix: format auth.rs test assertions

Reformat assert_eq! statements to satisfy rustfmt checks in CI.

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

---------

Signed-off-by: tanish111 <tanishdesai37@gmail.com>
2026-01-09 12:20:32 -05:00
David Stern
63d89b1a4e
fix: only try to refresh access tokens if we have a refresh token or an expiry time (#594) 2026-01-06 09:34:42 +08:00
Tanish Desai
f20ed202af
Add SEP-991 (CIMD) support for URL-based client IDs (#570)
* feat(auth): add cimd support for SEP-991

add cimd support for url-based client ids

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

* test(auth): add unit tests for is_https_url helper

Add test coverage for is_https_url helper to validate HTTPS scheme, non-root paths,
and reject http, javascript, data schemes, and invalid inputs per SEP-991 requirements.

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

* feat(example): add CIMD OAuth server for SEP-991 testing

Implements a new server example (servers_cimd_auth_streamhttp) that
demonstrates CIMD (Client ID Metadata Document) support for URL-based
client IDs. The server validates client_id URLs, fetches and validates
client metadata documents, and provides OAuth 2.0 authorization endpoints
with MCP integration for end-to-end testing.

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

* fix(oauth): add CORS headers to token endpoint

Add CORS headers to token endpoint to allow cross-origin requests from browsers
during OAuth authorization code exchange flow.

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

* refactor: improve is_https_url function and consolidate tests

- Improve is_https_url function formatting and readability
- Merge all test cases into single test_is_https_url_scenarios function
- Add missing test case for "https://" URL

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

* refactor: use map_err instead of match for error handling in auth.rs

Replace the verbose match statement with
map_err for more idiomatic

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

* feat: add client-metadata.json

Add client metadata file for SEP-991 CIMD
authentication support

Signed-off-by: tanish111 <tanishdesai37@gmail.com>

---------

Signed-off-by: tanish111 <tanishdesai37@gmail.com>
2025-12-10 08:55:38 -05:00
jokemanfire
bce0555068
fix(oauth): rfc8414 should judement the response_types (#485)
response_types_supported should be judement while try to do 'Authorization Code Flow'

Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
2025-12-09 11:18:18 -05:00
adam jones
eb5a7f7408
feat!: remove SSE transport support (#562)
SSE transport has been removed from the MCP specification in favor of
streamable HTTP. This removes all SSE-specific transport code:

- Remove `transport-sse-client` and `transport-sse-server` features
- Remove `SseClientTransport` and `SseServer` types
- Remove SSE-specific examples (`counter_sse`, `counter_sse_directly`)
- Migrate auth examples from SSE to streamable HTTP
- Update tests to remove SSE transport usage
- Update documentation

BREAKING CHANGE: The following have been removed:
- `transport-sse-client` feature
- `transport-sse-client-reqwest` feature
- `transport-sse-server` feature
- `SseClientTransport` type
- `SseServer` type
- `sse_client` and `sse_server` modules

Users should migrate to streamable HTTP transport which provides
equivalent functionality. See `StreamableHttpClientTransport` and
`StreamableHttpService` for the replacement APIs.

Ref: https://github.com/modelcontextprotocol/rust-sdk/pull/561#issuecomment-3576551699
2025-12-01 20:40:13 -05:00
Måns
b6cbd39503
fix(oauth): let OAuth discovery skip to next well-known URL candidate on JSON parse error. (#545) 2025-11-18 09:21:25 +08:00
Alex Hancock
31be9b25b5
feat(auth): implement CredentialStore trait (#542) 2025-11-17 10:17:08 -05:00
Gabriel Peal
82f9b0976a
fix(oauth): respect oauth-protected-resource discovery (#511) 2025-11-05 09:19:13 -05:00
Gabriel Peal
dcbeb9b9bc
fix(oauth): fix oauth credential refresh (#509)
auth.rs was using an in-memory expires-at which is only set on initial token exchange.
Instead, this PR switches it to use the expires-at set in the credentials that are passed in.
2025-11-04 16:17:11 -05:00
Alex Hancock
d3ddc09e52
fix: do not manually construct fallback authorization metadata (#507) 2025-11-03 12:08:32 -05:00
Gabriel Peal
e98e0d05df
fix(oauth): three oauth discovery and registration issues (#489) 2025-10-20 20:49:43 -04:00
jokemanfire
6cd779c5d7
fix(oauth): dynamic client registration should be optional (#463)
Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
2025-10-14 09:30:29 +08:00
Gabriel Peal
6011f34ddf
feat: allow clients to override client_name (#469)
Many MCP Servers use client_name for a variety of things including:
* Whitelisting
* Logos
* Copy shown directly on the page
* etc

As a result, it's important for MCP Clients to be able to override the client name.
2025-10-03 21:03:31 -04:00
Huabing (Robin) Zhao
0566d132ea
fix(oauth): support suffixed and preffixed well-knonw paths (#459)
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
2025-09-30 15:41:16 +08:00