{"record":{"id":"586d52324728fc6d","repo":"BerriAI/litellm","slug":"mcp-security-request-references-unregistered-mcp","errorCode":null,"errorMessage":"MCP Security: request references unregistered MCP server(s): {', '.join(sorted(unregistered))}. Only servers registered on this gateway are allowed.","messagePattern":"MCP Security: request references unregistered MCP server\\(s\\): (.+?)\\. Only servers registered on this gateway are allowed\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/mcp_security/mcp_security_guardrail.py","lineNumber":60,"sourceCode":"        cache: Any,\n        data: dict,\n        call_type: str,\n    ) -> Exception | str | dict | None:\n        if self.should_run_guardrail(data=data, event_type=GuardrailEventHooks.pre_call) is not True:\n            return data\n\n        unregistered: Final = self._find_unregistered_mcp_servers(data)\n        if not unregistered:\n            return data\n\n        message: Final = (\n            f\"MCP Security: request references unregistered MCP server(s): \"\n            f\"{', '.join(sorted(unregistered))}. \"\n            f\"Only servers registered on this gateway are allowed.\"\n        )\n\n        if self.on_violation == \"block\":\n            raise HTTPException(\n                status_code=400,\n                detail={\n                    \"error\": \"Violated guardrail policy\",\n                    \"guardrail\": \"mcp_security\",\n                    \"unregistered_servers\": sorted(unregistered),\n                    \"detection_message\": message,\n                },\n            )\n        else:\n            verbose_proxy_logger.warning(message)\n\n        return data\n\n    @staticmethod\n    def _extract_mcp_server_names_from_tools(tools: list[dict]) -> set[str]:\n        \"\"\"Extract MCP server names from tools with type=mcp and litellm_proxy server_url.\"\"\"\n        server_names: Final[set[str]] = set()\n        for tool in tools:","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/mcp_security/mcp_security_guardrail.py#L42-L78","documentation":"The MCP security guardrail inspects request tools of type mcp whose server_url uses the litellm_proxy:// prefix, extracts the server name, and compares it against the gateway's MCP server registry (global_mcp_server_manager, fed by the mcp_servers config). Unregistered names trigger HTTPException 400 with the sorted list when on_violation is 'block', or just a warning log when 'alert'.","triggerScenarios":"A request whose tools include {type: mcp, server_url: litellm_proxy://<name>} where <name> is not defined under mcp_servers in the proxy config - typo'd name, server removed but clients still send it, or requests crafted against servers this gateway never hosted.","commonSituations":"Renaming/removing an MCP server in config while client SDKs keep the old name; per-team gateways where a request assumes another gateway's servers; typo between config and client tool definitions; multi-environment drift (server exists in staging, not prod).","solutions":["Register the referenced server under mcp_servers in the gateway config with the exact name clients use","Fix the client's tool definition so server_url uses the registered name (check spelling/case)","If evaluation without blocking is wanted, set on_violation: 'alert' so violations only log","Remove stale tool references from clients after deleting a server from the registry"],"exampleFix":"# before - request references a server the gateway never registered\ntools = [{\"type\": \"mcp\", \"server_url\": \"litellm_proxy://filesystem\"}]\n\n# after - name matches the mcp_servers registry entry (config: mcp_servers: {fs: {...}})\ntools = [{\"type\": \"mcp\", \"server_url\": \"litellm_proxy://fs\"}]","handlingStrategy":"validation","validationCode":"def validate_mcp_tool_servers(tools: list[dict], registered: set[str]) -> None:  \n    prefix = \"litellm_proxy://\"  \n    referenced = {  \n        t[\"server_url\"][len(prefix):]  \n        for t in tools  \n        if isinstance(t, dict) and t.get(\"type\") == \"mcp\"  \n        and isinstance(t.get(\"server_url\"), str)  \n        and t[\"server_url\"].startswith(prefix)  \n    }  \n    unknown = referenced - registered  \n    assert not unknown, f\"tools reference unregistered MCP servers: {sorted(unknown)}; registered: {sorted(registered)}\"  \n  \n# registered = set of keys under mcp_servers in the gateway config  \nvalidate_mcp_tool_servers(request_tools, registered_server_names)","typeGuard":"def references_registered_servers_only(tools: object, registered: set[str]) -> bool:  \n    if not isinstance(tools, list):  \n        return True  \n    prefix = \"litellm_proxy://\"  \n    for t in tools:  \n        if isinstance(t, dict) and t.get(\"type\") == \"mcp\" and isinstance(t.get(\"server_url\"), str):  \n            url = t[\"server_url\"]  \n            if url.startswith(prefix) and url[len(prefix):] not in registered:  \n                return False  \n    return True","tryCatchPattern":null,"preventionTips":["Derive client tool definitions from the gateway's mcp_servers config (single source of truth) instead of hand-writing names","After renaming or removing a server, sweep clients for stale litellm_proxy:// references","Use on_violation: 'alert' while onboarding to observe violations in logs before enforcing blocks"],"tags":["mcp","guardrail","server-registry","http-400","security"],"backgroundTag":"unregistered-mcp-server","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}