{"record":{"id":"0ac89d6ec5d18149","repo":"BerriAI/litellm","slug":"mcp-server-is-already-rejected","errorCode":null,"errorMessage":"MCP server is already rejected.","messagePattern":"MCP server is already rejected\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"litellm/proxy/management_endpoints/mcp_management_endpoints.py","lineNumber":1399,"sourceCode":"        \"\"\"\n        Admin rejects a pending MCP server — sets approval_status=rejected with optional review_notes.\n        \"\"\"\n        if LitellmUserRoles.PROXY_ADMIN != user_api_key_dict.user_role:\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail={\"error\": \"Admin access required to reject MCP server submissions.\"},\n            )\n\n        prisma_client: Final = get_prisma_client_or_throw(\"Database not connected. Connect a database to your proxy\")\n\n        existing: Final = await get_mcp_server(prisma_client, server_id)\n        if existing is None:\n            raise HTTPException(\n                status_code=status.HTTP_404_NOT_FOUND,\n                detail={\"error\": f\"MCP server '{server_id}' not found.\"},\n            )\n        if existing.approval_status == MCPApprovalStatus.rejected:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail={\"error\": \"MCP server is already rejected.\"},\n            )\n\n        was_active: Final = existing.approval_status == MCPApprovalStatus.active\n        rejected: Final = await reject_mcp_server(\n            prisma_client,\n            server_id,\n            touched_by=user_api_key_dict.user_id or LITELLM_PROXY_ADMIN_NAME,\n            review_notes=payload.review_notes,\n        )\n        # Only evict from the runtime registry if the server was previously active\n        if was_active:\n            await global_mcp_server_manager.reload_servers_from_database()\n        return _redact_mcp_credentials(rejected)\n\n    @router.get(\n        \"/server/{server_id}\",","sourceCodeStart":1381,"sourceCodeEnd":1417,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/mcp_management_endpoints.py#L1381-L1417","documentation":"Raised by the LiteLLM proxy's MCP server rejection endpoint when an admin tries to reject an MCP server whose approval_status is already MCPApprovalStatus.rejected. The endpoint first enforces admin-only access, then a 404 for an unknown server_id, and only then this guard, which blocks a duplicate state transition. It exists so the submissions review workflow cannot double-process the same server or overwrite earlier review notes.","triggerScenarios":"Calling the MCP server rejection route (admin UI 'Reject' action, or the REST reject endpoint on the mcp management router) twice for the same server_id; calling reject on a submission that another admin already rejected; a script retrying a reject call whose first response was lost even though it committed.","commonSituations":"Two admins working the same submissions queue; double-click / double-submit in the UI; automation retrying on network timeout after the first reject already succeeded.","solutions":["If your goal is only 'server ends up rejected', treat this 400 as a successful no-op: check for status 400 and 'already rejected' in the response body.","Before rejecting, fetch the server (or the submissions list) and confirm approval_status is not already 'rejected'.","Refresh the submissions queue before acting and coordinate reviewers so only one admin rejects a given submission."],"exampleFix":"# before: naive reject that crashes on double-reject\nresp = requests.post(f\"{PROXY}/mcp/server/{server_id}/reject\", headers=AUTH, json={\"review_notes\": \"...\"})\nresp.raise_for_status()\n\n# after: idempotent reject\nresp = requests.post(f\"{PROXY}/mcp/server/{server_id}/reject\", headers=AUTH, json={\"review_notes\": \"...\"})\nif resp.status_code == 400 and \"already rejected\" in resp.text:\n    pass  # already in the desired state\nelse:\n    resp.raise_for_status()","handlingStrategy":"validation","validationCode":"server = requests.get(f\"{PROXY}/v1/mcp/server/{server_id}\", headers=AUTH).json()\nstatus = server.get(\"mcp_server\", server).get(\"approval_status\")\nif status != \"rejected\":\n    requests.post(f\"{PROXY}/mcp/server/{server_id}/reject\", headers=AUTH, json={\"review_notes\": notes})","typeGuard":null,"tryCatchPattern":"try:\n    reject(server_id)\nexcept HTTPError as e:\n    if e.response.status_code == 400 and \"already rejected\" in e.response.text:\n        return  # desired state already reached\n    raise","preventionTips":["Make reject operations idempotent by treating 'already rejected' as success.","Fetch current approval_status before issuing review actions.","Coordinate reviewers so only one admin acts per submission."],"tags":["litellm","mcp","approval-workflow","idempotency","admin"],"backgroundTag":"invalid-state-transition","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}