Skip to content

Scope challenge raised from inside a running tool handler #2957

Open

Description

@felixweinberger

Follow-up to #1151. The first half shipped in 2.1.0 (#1624): scopeChallenge on a tool registration answers with a 403 scope challenge before the handler runs.

Still open is the second half: a handler that only learns mid-execution that more scopes are needed (for example because the backend says so) and wants to turn that into a scope challenge instead of a failed tool call, something like throw new ScopeChallengeError(['repo:read']).

Open question from the original thread: whether this is possible at all once the response has started.

Activity

  1. added
    enhancementRequest for a new feature that's not currently supported
    authIssues and PRs related to Authentication / OAuth
    needs decisionIssue is actionable, needs maintainer decision on whether to implement
    on Oct 5, 2026
  2. Ben-Home commented on Oct 5, 2026

    @Ben-Home
  3. buptliuhs commented on Nov 11, 2026

    @buptliuhs

    A related case that this design could cover with the same mechanism: 401 invalid_token instead of 403 insufficient_scope.

    Servers that proxy an upstream API with delegated OAuth (the MCP access token maps to an upstream refresh token stored server-side) can learn mid-call that the user's upstream authorization is gone — the upstream provider revoked the refresh token (for example, a per-user connection cap evicting the oldest connection). The MCP access token is still valid, but the connection is unusable until the user re-authorizes. It's the same shape as this issue: the handler only finds out from its backend, and a failed tool call (200 + isError) is the wrong answer, because the client doesn't start its re-auth flow until a later request.

    So a request: whatever ships here (ScopeChallengeError or similar), allow it to carry a 401 invalid_token challenge as well as a 403 insufficient_scope one — e.g. a general AuthChallengeError with { status: 401 | 403, error, errorDescription?, scopes? } — answered through the same WWW-Authenticate + resource_metadata path as requireBearerAuth.

    Data point on the "after the response has started" question: we handle this today with a wrapper around the handler's fetch face (FetchLikeMcpHandler) that holds the complete Response for tools/call and swaps in the 401 when the tool recorded a lost upstream link. It works on both protocol eras because our server is stateless and never sends anything mid-request, so the response is never committed early. It also confirms the retry-safety point above: we only swap when the upstream call was rejected before processing (an upstream 401), so no side effects happened and the client can safely retry after re-authorizing.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    authIssues and PRs related to Authentication / OAuthenhancementRequest for a new feature that's not currently supportedneeds decisionIssue is actionable, needs maintainer decision on whether to implement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions