Skip to content

oauth client cannot exclude a problematic auto-discovered scope #5

Description

@bug-ops

Description

rmcp's OAuth authorization-code client flow (crates/rmcp/src/transport/auth.rs, auth feature) offers only all-or-nothing scope control:

  • Leave AuthorizationRequest::scopes / OAuthClientConfig::scopes empty and the SDK auto-selects and requests every scope discovered from the server (WWW-Authenticate challenge, protected-resource metadata, authorization-server metadata) via AuthorizationManager::select_scopes.
  • Supply with_scopes(...) and the caller must specify the full list themselves, losing the SDK's discovery/offline_access (SEP-2207)/step-up (SEP-2350)/dedup logic.

There is no way to exclude a single problematic scope the server advertises while still keeping the rest of auto-discovery.

This matters because it's not hypothetical: Google's Gmail MCP server advertises gmail.metadata in scopes_supported, but the Gmail API rejects the q search parameter on any token carrying gmail.metadata — even when gmail.readonly is also granted (a long-documented Gmail restriction, see googleapis/google-api-python-client#582). A client that lets rmcp auto-select scopes ends up with a token that silently breaks search, with no client-side remedy short of abandoning auto-discovery and hand-replicating its offline_access/step-up/dedup logic.

The Go SDK just added exactly this capability: modelcontextprotocol/go-sdk#1044 adds an optional ScopeFilter func(discovered []string) []string hook to AuthorizationCodeHandlerConfig, run after scope discovery but before the offline_access/step-up union logic, letting a caller narrow, extend, or reorder the discovered set.

There is also an open (not yet merged) upstream PR, modelcontextprotocol/rust-sdk#1102, that changes rmcp's scope handling to union configured scopes with server-required ones through a stable dedup policy — but it only adds scopes; it does not add any exclusion mechanism, so this gap persists regardless of whether/when modelcontextprotocol#1102 lands.

Reproduction Steps

  1. Configure an AuthorizationRequest / OAuthClientConfig against a server whose discovered scopes_supported (or WWW-Authenticate challenge) includes a scope that is mutually exclusive with another required scope (e.g. Gmail's gmail.metadata vs. gmail.readonly).
  2. Leave scopes empty (or use with_scopes only to add required scopes) to benefit from auto-discovery.
  3. Observe: the SDK requests the full discovered set, including the problematic scope, with no way to filter it out short of replacing auto-discovery entirely.

Expected Behavior

A caller can supply an optional callback/hook that receives the SDK's discovered scope set and returns the set to actually request, composing with (not bypassing) the existing offline_access/step-up union logic and whatever scope-merge policy modelcontextprotocol/rust-sdk#1102 eventually lands with. No hook set → unchanged (fully backward compatible) behavior.

Actual Behavior

No such hook exists (grep -n "ScopeFilter\|scope_filter" crates/rmcp/src/transport/auth.rs returns nothing). The only escape hatch, with_scopes(...), fully replaces auto-discovery rather than filtering it.

Environment

  • Version: rmcp 3.1.2 (commit 02c62ae)
  • Features: auth

Upstream

modelcontextprotocol/go-sdk#1044 — MERGED, adds the ScopeFilter hook this issue asks to mirror.
modelcontextprotocol/rust-sdk#1102 — OPEN, additive scope-union PR that this capability should compose with once merged; does not itself add an exclusion mechanism, so this finding is not a duplicate of it.
No existing upstream rust-sdk issue/PR found proposing a scope-filter/exclusion hook specifically (checked via gh issue list / gh pr list --search "scope filter" and related terms).

Logs / Evidence

N/A — competitive-parity research finding, not a live failure reproduction.

Spec

.local/specs/001-oauth-scope-filter-hook/spec.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Medium: suboptimal behavior, minor inconsistencyenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions