Skip to content

Commit 27772c3

Browse files
author
Olivier Gintrand
committed
fix: include resource_metadata in WWW-Authenticate for strict auth mode
When mcp_require_auth=True, the 401 response was missing the RFC 9728 resource_metadata attribute in the WWW-Authenticate header. This prevented MCP clients (Open WebUI, VS Code) from discovering the OAuth authorization server and initiating the OAuth 2.1 flow. The fix moves the per-server OAuth enforcement check before the strict-mode and empty-bearer checks, so the resource_metadata URL is always included when the target virtual server has oauth_enabled=true.
1 parent c04f65e commit 27772c3

File tree

1 file changed

+10
-7
lines changed

1 file changed

+10
-7
lines changed

mcpgateway/transports/streamablehttp_transport.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3481,15 +3481,14 @@ async def _auth_no_token(self, *, path: str, bearer_header_supplied: bool) -> bo
34813481
Returns:
34823482
True if the request is allowed with public-only access, False if rejected.
34833483
"""
3484-
# If client supplied a Bearer header but with empty credentials, fail closed
3485-
if bearer_header_supplied:
3486-
return await self._send_error(detail="Invalid authentication credentials", headers={"WWW-Authenticate": "Bearer"})
3487-
3484+
# Build the WWW-Authenticate header, enriching it with RFC 9728
3485+
# resource_metadata when the target server has OAuth enabled.
34883486
# Per-server OAuth enforcement MUST run before the global auth check so that
34893487
# oauth_enabled servers always return 401 with resource_metadata URL (RFC 9728).
34903488
# Without this, strict mode (mcp_require_auth=True) returns a generic
34913489
# WWW-Authenticate: Bearer with no resource_metadata, and MCP clients cannot
3492-
# discover the OAuth server to authenticate. (Fixes #3752)
3490+
# discover the OAuth server to authenticate.
3491+
www_auth = "Bearer"
34933492
match = _SERVER_ID_RE.search(path)
34943493
if match:
34953494
per_server_id = match.group("server_id")
@@ -3503,9 +3502,13 @@ async def _auth_no_token(self, *, path: str, bearer_header_supplied: bool) -> bo
35033502
logger.exception("OAuth enforcement check failed for server %s", per_server_id)
35043503
return await self._send_error(detail="Service unavailable — unable to verify server authentication requirements", status_code=503)
35053504

3506-
# Strict mode: require authentication (non-OAuth servers get generic 401)
3505+
# If client supplied a Bearer header but with empty credentials, fail closed
3506+
if bearer_header_supplied:
3507+
return await self._send_error(detail="Invalid authentication credentials", headers={"WWW-Authenticate": www_auth})
3508+
3509+
# Strict mode: require authentication
35073510
if settings.mcp_require_auth:
3508-
return await self._send_error(detail="Authentication required for MCP endpoints", headers={"WWW-Authenticate": "Bearer"})
3511+
return await self._send_error(detail="Authentication required for MCP endpoints", headers={"WWW-Authenticate": www_auth})
35093512

35103513
# Permissive mode: allow unauthenticated access with public-only scope
35113514
# Set context indicating unauthenticated user with public-only access (teams=[])

0 commit comments

Comments
 (0)