fix: support cloud OAuth clients and graceful DCR fallback
Claude AI (web) sends a Cognito-issued client_id with an HTTPS redirect URI, but the client registry only supported localhost redirect URIs via ALLOWED_MCP_CLIENTS. Add ALLOWED_MCP_CLOUD_CLIENTS env var for web-based clients with format "client_id|redirect_uri". Also fix the DCR proxy to return a clear error when the upstream IdP (e.g. Cognito) doesn't support dynamic client registration, instead of silently falling back to a Nextcloud-specific endpoint that fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
15016ba2f1
commit
2a34015443
@@ -70,6 +70,24 @@ class ClientRegistry:
|
|||||||
)
|
)
|
||||||
logger.info(f"Registered static client: {client_id}")
|
logger.info(f"Registered static client: {client_id}")
|
||||||
|
|
||||||
|
# Load cloud clients (web-based, HTTPS redirect URIs)
|
||||||
|
# Format: "client_id|redirect_uri,client_id2|redirect_uri2"
|
||||||
|
cloud_clients = os.getenv("ALLOWED_MCP_CLOUD_CLIENTS", "").strip()
|
||||||
|
if cloud_clients:
|
||||||
|
for entry in cloud_clients.split(","):
|
||||||
|
entry = entry.strip()
|
||||||
|
if "|" in entry:
|
||||||
|
cid, redirect = entry.split("|", 1)
|
||||||
|
cid, redirect = cid.strip(), redirect.strip()
|
||||||
|
self._clients[cid] = MCPClientInfo(
|
||||||
|
client_id=cid,
|
||||||
|
name=self._get_client_name(cid),
|
||||||
|
redirect_uris=[redirect],
|
||||||
|
allowed_scopes=["*"],
|
||||||
|
is_public=True,
|
||||||
|
)
|
||||||
|
logger.info(f"Registered cloud client: {cid}")
|
||||||
|
|
||||||
# Add well-known clients if not explicitly configured
|
# Add well-known clients if not explicitly configured
|
||||||
if not self._clients:
|
if not self._clients:
|
||||||
self._add_well_known_clients()
|
self._add_well_known_clients()
|
||||||
@@ -78,6 +96,7 @@ class ClientRegistry:
|
|||||||
"""Get human-readable name for client_id."""
|
"""Get human-readable name for client_id."""
|
||||||
known_names = {
|
known_names = {
|
||||||
"claude-desktop": "Claude Desktop",
|
"claude-desktop": "Claude Desktop",
|
||||||
|
"claude-ai": "Claude AI",
|
||||||
"continue-dev": "Continue IDE Extension",
|
"continue-dev": "Continue IDE Extension",
|
||||||
"zed-editor": "Zed Editor",
|
"zed-editor": "Zed Editor",
|
||||||
"vscode-mcp": "VS Code MCP Extension",
|
"vscode-mcp": "VS Code MCP Extension",
|
||||||
|
|||||||
@@ -1228,7 +1228,6 @@ async def oauth_register_proxy(request: Request) -> JSONResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
oauth_config = oauth_ctx["config"]
|
oauth_config = oauth_ctx["config"]
|
||||||
nextcloud_host = oauth_config["nextcloud_host"]
|
|
||||||
|
|
||||||
# Rate limit DCR requests per client IP
|
# Rate limit DCR requests per client IP
|
||||||
client_ip = request.client.host if request.client else "unknown"
|
client_ip = request.client.host if request.client else "unknown"
|
||||||
@@ -1249,21 +1248,29 @@ async def oauth_register_proxy(request: Request) -> JSONResponse:
|
|||||||
timestamps.append(now)
|
timestamps.append(now)
|
||||||
_dcr_rate_limit[client_ip] = timestamps
|
_dcr_rate_limit[client_ip] = timestamps
|
||||||
|
|
||||||
# Discover registration endpoint from OIDC discovery (prefer over hardcoded path)
|
# Discover registration endpoint from OIDC discovery
|
||||||
discovery_url = oauth_config.get("discovery_url")
|
discovery_url = oauth_config.get("discovery_url")
|
||||||
|
registration_endpoint = None
|
||||||
if discovery_url:
|
if discovery_url:
|
||||||
try:
|
try:
|
||||||
discovery = await _get_cached_discovery(discovery_url)
|
discovery = await _get_cached_discovery(discovery_url)
|
||||||
registration_endpoint = discovery.get(
|
registration_endpoint = discovery.get("registration_endpoint")
|
||||||
"registration_endpoint", f"{nextcloud_host}/apps/oidc/register"
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(
|
logger.warning("Failed to fetch OIDC discovery for DCR endpoint")
|
||||||
"Failed to fetch OIDC discovery for DCR endpoint, using fallback"
|
|
||||||
)
|
if not registration_endpoint:
|
||||||
registration_endpoint = f"{nextcloud_host}/apps/oidc/register"
|
logger.warning(
|
||||||
else:
|
"DCR proxy: Upstream IdP does not support dynamic client registration"
|
||||||
registration_endpoint = f"{nextcloud_host}/apps/oidc/register"
|
)
|
||||||
|
return JSONResponse(
|
||||||
|
{
|
||||||
|
"error": "registration_not_supported",
|
||||||
|
"error_description": "The upstream identity provider does not support "
|
||||||
|
"dynamic client registration. Configure the client statically using "
|
||||||
|
"ALLOWED_MCP_CLIENTS or ALLOWED_MCP_CLOUD_CLIENTS.",
|
||||||
|
},
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"DCR proxy: Forwarding registration to {registration_endpoint}")
|
logger.info(f"DCR proxy: Forwarding registration to {registration_endpoint}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user