RFC 7235 requires a 401 to carry WWW-Authenticate. Today it doesn't, for servers with no oauth config.
Why: an authenticate() returning { authenticated: false } (or nullish) is intercepted by mcp-proxy's per-request gate, which only builds a challenge when oauth is configured. FastMCP's own #createUnauthorizedResponse always emits one, but on httpStream it never gets the chance.
Verified on main, no oauth configured, { authenticated: false, error: "Access denied" }:
status=401 WWW-Authenticate=<absent> body={"error":{"code":-32000,"message":"Access denied"},"id":1,...}
Constraint for any fix: preserve the JSON-RPC id. mcp-proxy echoes body?.id ?? null; #createUnauthorizedResponse hardcodes id: null. Moving the rejection to FastMCP's side without threading the request id through swaps one spec violation for another — see #353, which was closed for exactly this.
Pre-existing, not a regression. Surfaced by @aaronik in #353.
RFC 7235 requires a 401 to carry
WWW-Authenticate. Today it doesn't, for servers with nooauthconfig.Why: an
authenticate()returning{ authenticated: false }(or nullish) is intercepted by mcp-proxy's per-request gate, which only builds a challenge whenoauthis configured. FastMCP's own#createUnauthorizedResponsealways emits one, but on httpStream it never gets the chance.Verified on main, no oauth configured,
{ authenticated: false, error: "Access denied" }:Constraint for any fix: preserve the JSON-RPC
id. mcp-proxy echoesbody?.id ?? null;#createUnauthorizedResponsehardcodesid: null. Moving the rejection to FastMCP's side without threading the request id through swaps one spec violation for another — see #353, which was closed for exactly this.Pre-existing, not a regression. Surfaced by @aaronik in #353.