Skip to content

Plain HTTP 403 without WWW-Authenticate starts OAuth discovery in the Inspector web client #2515

Closed

Description

@revetkn

Which version line?

v2 — current (@modelcontextprotocol/inspector@latest)

Which client?

Web

Inspector version

2.7.0 (also tested against 2.8.0 source code)

Node version

v26.5.0

Operating system (and browser, for the web client)

macOS 27.0, Chrome 153.0.8010.54

Transport

Streamable HTTP

MCP server under inspection

A disposable local Soklet MCP server using Streamable HTTP and the modern 2026-07-28 protocol. Soklet is a Java MCP server under active development. Inspector connects to http://127.0.0.1:/mcp with a valid static bearer credential. The server permits Apps catalog reads and tool calls, but intentionally denies subscriptions/listen with HTTP 403 and no WWW-Authenticate header. It has no OAuth authorization server. The port and credential are temporary.

Steps to reproduce

  1. Connect Inspector’s web client to the server above using Streamable HTTP, the modern protocol, and the valid bearer credential.
  2. Open the Apps catalog and select the app. Its catalog data renders, and a refresh succeeds.
  3. Inspector automatically requests subscriptions/listen. The server returns HTTP 403 without WWW-Authenticate.
  4. Observe Inspector attempt OAuth discovery and client registration despite the absence of an OAuth challenge.

Expected behavior

Treat this response as an ordinary denial of that operation. Preserve the 403 response for the MCP caller and do not start OAuth discovery. A genuine 401 challenge or a 403 with WWW-Authenticate: Bearer error="insufficient_scope" should still follow Inspector’s existing authorization flow.

Actual behavior

Inspector treats the plain 403 as an OAuth challenge and attempts protected-resource metadata discovery, authorization-server discovery, and client registration. The app itself rendered and refreshed, but this unsolicited OAuth recovery prevents the full Apps host check from passing.

Logs, errors, or screenshots

Sanitized observation from the Inspector 2.7.0 web run: a valid credential was accepted; subscriptions/listen received HTTP 403 with no WWW-Authenticate; Inspector then requested OAuth well-known endpoints and /register. No browser screenshot was captured. The credential and temporary port are omitted.
A focused check of Inspector 2.8.0 source also shows parseAuthChallengeFromResponse() classifying a 403 response without WWW-Authenticate as reason: "unauthorized".

Already prototyped a fix?

Yes. We tested a local change against clean Inspector 2.8.0 source so a 403 without WWW-Authenticate is left as a normal response. Three focused regression tests failed before the change and passed after it; the relevant test suite passed 65/65, as did the TypeScript build and targeted formatting and lint checks. This is an unpublished local prototype. The exact user message authorizing the approach was “Let's go with your recommended approach then.” No before/after UI screenshots were captured; the before/after evidence is the test results.

Before you submit

  • I searched existing issues and this is not a duplicate.
  • This is not a security vulnerability report (those go through the private advisory process).

Activity

  1. Ben-Home commented on Sep 29, 2026

    @Ben-Home

    This matches the RFC 6750 split: 401 means "missing or invalid credentials, here is a challenge", 403 means "credentials understood, this operation denied". A 403 without WWW-Authenticate is a plain authorization denial on that operation, not an invitation to authenticate. Only a 401 (or a 403 that carries a Bearer WWW-Authenticate with resource_metadata, the MCP auth spec's challenge form) should start OAuth discovery or DCR.

    Concretely, the response handler on subscriptions/listen should branch:

    • 401 -> begin auth flow
    • 403 with WWW-Authenticate Bearer resource_metadata -> begin auth flow (insufficient_scope style)
    • 403 without WWW-Authenticate -> surface the denial to the caller as-is, preserve status and body, no discovery, no registration

    Beyond the noise, the failure mode matters: DCR against a server with no authorization server wastes a round trip, and entering an auth flow on a scope denial can leave the client in a half-authenticated state when the user was simply not allowed to subscribe.

    A regression test would be cheap: a fixture server returns a bare 403 on listen, and the test asserts no /.well-known/oauth-authorization-server fetch happens and the 403 reaches the caller unchanged.

    Our gateway at https://corpusiq.io returns bare 403s for scope-denied operations on purpose, so clients that infer missing credentials from a bare 403 hit exactly this class of bug.

  2. nightcityblade commented on Oct 1, 2026

    @nightcityblade

    Hi, I'd like to work on this. I'll submit a PR shortly.

  3. nightcityblade commented on Oct 1, 2026

    @nightcityblade

    Apologies—I overlooked this repository's issues-only contribution policy before commenting. I won't submit a PR, so this issue remains available for the maintainers' prompt-driven workflow.

  4. cliffhall commented on Oct 12, 2026

    @cliffhall
    Member

    Triage: Priority Medium (total 6)

    • Severity 3 — a plain 403 without WWW-Authenticate is treated as an OAuth challenge and starts discovery/registration against a server with no AS
    • Urgency 2 — wanted eventually; the denied operation fails either way
    • Bonuses: +1 bug label (comment count is a withdrawn PR offer, not engagement)

    Board: #28, Status Incoming (awaiting maintainer review — no milestone yet).

  5. added this to the v2.11.0 milestone on Oct 12, 2026
  6. self-assigned this
    on Oct 12, 2026
  7. cliffhall commented on Oct 12, 2026

    @cliffhall
    Member

    Fixed by #2690 (merged into v2/main).

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingv2Issues and PRs for v2

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions