{"record":{"id":"105aa3356d4b6232","repo":"BerriAI/litellm","slug":"invalid-redirect-uri","errorCode":null,"errorMessage":"Invalid redirect URI","messagePattern":"Invalid redirect URI","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py","lineNumber":429,"sourceCode":"    if cookie_name not in request.cookies:\n        return\n    path, secure = _oauth_state_cookie_path_and_secure(request)\n    response.delete_cookie(\n        key=cookie_name,\n        path=path,\n        secure=secure,\n        httponly=True,\n        samesite=\"lax\",\n    )\n\n\ndef _get_validated_client_redirect_uri(request: Request, state_data: dict[str, Any]) -> str:\n    \"\"\"Return a trusted (same-origin, loopback, or ops-allowlisted)\n    client redirect URI from OAuth state.\n    \"\"\"\n    redirect_uri: Final = state_data.get(\"client_redirect_uri\") or state_data.get(\"base_url\")\n    if not redirect_uri or not isinstance(redirect_uri, str):\n        raise HTTPException(status_code=400, detail=\"Invalid redirect URI\")\n    validate_trusted_redirect_uri(request, redirect_uri)\n    return redirect_uri\n\n\ndef _append_query_params(url: str, params: dict[str, str]) -> str:\n    parsed: Final = urlparse(url)\n    query_params: Final = parse_qsl(parsed.query, keep_blank_values=True)\n    query_params.extend(params.items())\n    return urlunparse(parsed._replace(query=urlencode(query_params)))\n\n\ndef _resolve_oauth2_server_for_root_endpoints(\n    client_ip: str | None = None,\n) -> MCPServer | None:\n    \"\"\"\n    Resolve the MCP server for root-level OAuth endpoints (no server name in path).\n\n    When the MCP SDK hits root-level endpoints like /register, /authorize, /token","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py#L411-L447","documentation":"Raised when the OAuth state's client_redirect_uri (or base_url fallback) is not same-origin, loopback, or on the ops allowlist: the post-login redirect target is untrusted, so redirect is refused to prevent open-redirect abuse.","triggerScenarios":"Thrown at litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py:429 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Send a redirect_uri that exactly matches a registered redirect URI."],"exampleFix":"redirect_uri must equal the value registered on the server.","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}