mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-02 08:21:53 +00:00
Return Clear error message why no tools are available / IP Filtering occured
This commit is contained in:
@@ -756,14 +756,30 @@ class MCPServerManager:
|
||||
|
||||
Returns server_ids unchanged when client_ip is None (no filtering).
|
||||
"""
|
||||
filtered, _ = self.filter_server_ids_by_ip_with_info(server_ids, client_ip)
|
||||
return filtered
|
||||
|
||||
def filter_server_ids_by_ip_with_info(
|
||||
self, server_ids: List[str], client_ip: Optional[str]
|
||||
) -> Tuple[List[str], int]:
|
||||
"""
|
||||
Filter server IDs by client IP — external callers only see public servers.
|
||||
|
||||
Returns (filtered_ids, ip_blocked_count) where ip_blocked_count is the number
|
||||
of servers that were blocked because the client IP is not allowed to access them.
|
||||
Returns server_ids unchanged (with 0 blocked) when client_ip is None.
|
||||
"""
|
||||
if client_ip is None:
|
||||
return server_ids
|
||||
return [
|
||||
sid
|
||||
for sid in server_ids
|
||||
if (s := self.get_mcp_server_by_id(sid)) is not None
|
||||
and self._is_server_accessible_from_ip(s, client_ip)
|
||||
]
|
||||
return server_ids, 0
|
||||
allowed = []
|
||||
blocked = 0
|
||||
for sid in server_ids:
|
||||
s = self.get_mcp_server_by_id(sid)
|
||||
if s is not None and self._is_server_accessible_from_ip(s, client_ip):
|
||||
allowed.append(sid)
|
||||
elif s is not None:
|
||||
blocked += 1
|
||||
return allowed, blocked
|
||||
|
||||
async def get_tools_for_server(self, server_id: str) -> List[MCPTool]:
|
||||
"""
|
||||
|
||||
@@ -10,9 +10,9 @@ from litellm.proxy._experimental.mcp_server.ui_session_utils import (
|
||||
)
|
||||
from litellm.proxy._experimental.mcp_server.utils import merge_mcp_headers
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.common_utils.http_parsing_utils import _safe_get_request_headers
|
||||
from litellm.proxy.auth.ip_address_utils import IPAddressUtils
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.common_utils.http_parsing_utils import _safe_get_request_headers
|
||||
from litellm.types.mcp import MCPAuth
|
||||
from litellm.types.utils import CallTypes
|
||||
|
||||
@@ -283,8 +283,10 @@ if MCP_AVAILABLE:
|
||||
)
|
||||
allowed_server_ids_set.update(servers)
|
||||
|
||||
allowed_server_ids = global_mcp_server_manager.filter_server_ids_by_ip(
|
||||
list(allowed_server_ids_set), _rest_client_ip
|
||||
allowed_server_ids, _ip_blocked_count = (
|
||||
global_mcp_server_manager.filter_server_ids_by_ip_with_info(
|
||||
list(allowed_server_ids_set), _rest_client_ip
|
||||
)
|
||||
)
|
||||
|
||||
list_tools_result = []
|
||||
@@ -293,6 +295,26 @@ if MCP_AVAILABLE:
|
||||
# If server_id is specified, only query that specific server
|
||||
if server_id:
|
||||
if server_id not in allowed_server_ids:
|
||||
_server = global_mcp_server_manager.get_mcp_server_by_id(server_id)
|
||||
if (
|
||||
_server is not None
|
||||
and _rest_client_ip is not None
|
||||
and not global_mcp_server_manager._is_server_accessible_from_ip(
|
||||
_server, _rest_client_ip
|
||||
)
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
"error": "ip_filtering",
|
||||
"message": (
|
||||
f"MCP server '{server_id}' is not accessible from your IP address "
|
||||
f"({_rest_client_ip}). This server is restricted to internal "
|
||||
"networks only. To make it externally accessible, set "
|
||||
"'available_on_public_internet: true' in the server configuration."
|
||||
),
|
||||
},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
@@ -330,6 +352,19 @@ if MCP_AVAILABLE:
|
||||
}
|
||||
else:
|
||||
if not allowed_server_ids:
|
||||
if _ip_blocked_count > 0:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
"error": "ip_filtering",
|
||||
"message": (
|
||||
f"No MCP tools are available for your IP address ({_rest_client_ip}). "
|
||||
f"{_ip_blocked_count} server(s) are restricted to internal networks only. "
|
||||
"To make servers externally accessible, set "
|
||||
"'available_on_public_internet: true' in the server configuration."
|
||||
),
|
||||
},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
|
||||
@@ -771,8 +771,8 @@ if MCP_AVAILABLE:
|
||||
user_api_key_auth
|
||||
)
|
||||
)
|
||||
allowed_mcp_server_ids = (
|
||||
global_mcp_server_manager.filter_server_ids_by_ip(
|
||||
allowed_mcp_server_ids, _ip_blocked = (
|
||||
global_mcp_server_manager.filter_server_ids_by_ip_with_info(
|
||||
allowed_mcp_server_ids, client_ip
|
||||
)
|
||||
)
|
||||
@@ -780,6 +780,16 @@ if MCP_AVAILABLE:
|
||||
"MCP IP filter: client_ip=%s, allowed_server_ids=%s",
|
||||
client_ip, allowed_mcp_server_ids,
|
||||
)
|
||||
if _ip_blocked > 0:
|
||||
verbose_logger.debug(
|
||||
"MCP IP filtering: %d server(s) are not accessible from client IP %s "
|
||||
"because they are restricted to internal networks. "
|
||||
"No tools from those servers will be returned. "
|
||||
"To expose a server externally, set 'available_on_public_internet: true' "
|
||||
"in its configuration.",
|
||||
_ip_blocked,
|
||||
client_ip,
|
||||
)
|
||||
allowed_mcp_servers: List[MCPServer] = []
|
||||
for allowed_mcp_server_id in allowed_mcp_server_ids:
|
||||
mcp_server = global_mcp_server_manager.get_mcp_server_by_id(
|
||||
|
||||
@@ -91,3 +91,58 @@ class TestMCPServerIPFiltering:
|
||||
|
||||
result = manager.filter_server_ids_by_ip(["priv"], client_ip=None)
|
||||
assert result == ["priv"]
|
||||
|
||||
|
||||
class TestFilterServerIdsByIpWithInfo:
|
||||
"""Tests that filter_server_ids_by_ip_with_info returns accurate block counts."""
|
||||
|
||||
@patch("litellm.public_mcp_servers", [])
|
||||
@patch("litellm.proxy.proxy_server.general_settings", {})
|
||||
def test_external_ip_reports_blocked_count(self):
|
||||
pub = _make_server("pub", available_on_public_internet=True)
|
||||
priv = _make_server("priv", available_on_public_internet=False)
|
||||
manager = _make_manager([pub, priv])
|
||||
|
||||
allowed, blocked = manager.filter_server_ids_by_ip_with_info(
|
||||
["pub", "priv"], client_ip="8.8.8.8"
|
||||
)
|
||||
assert allowed == ["pub"]
|
||||
assert blocked == 1
|
||||
|
||||
@patch("litellm.public_mcp_servers", [])
|
||||
@patch("litellm.proxy.proxy_server.general_settings", {})
|
||||
def test_internal_ip_reports_zero_blocked(self):
|
||||
pub = _make_server("pub", available_on_public_internet=True)
|
||||
priv = _make_server("priv", available_on_public_internet=False)
|
||||
manager = _make_manager([pub, priv])
|
||||
|
||||
allowed, blocked = manager.filter_server_ids_by_ip_with_info(
|
||||
["pub", "priv"], client_ip="192.168.1.1"
|
||||
)
|
||||
assert allowed == ["pub", "priv"]
|
||||
assert blocked == 0
|
||||
|
||||
@patch("litellm.public_mcp_servers", [])
|
||||
@patch("litellm.proxy.proxy_server.general_settings", {})
|
||||
def test_no_ip_returns_all_with_zero_blocked(self):
|
||||
priv = _make_server("priv", available_on_public_internet=False)
|
||||
manager = _make_manager([priv])
|
||||
|
||||
allowed, blocked = manager.filter_server_ids_by_ip_with_info(
|
||||
["priv"], client_ip=None
|
||||
)
|
||||
assert allowed == ["priv"]
|
||||
assert blocked == 0
|
||||
|
||||
@patch("litellm.public_mcp_servers", [])
|
||||
@patch("litellm.proxy.proxy_server.general_settings", {})
|
||||
def test_all_private_external_ip_reports_all_blocked(self):
|
||||
priv1 = _make_server("priv1", available_on_public_internet=False)
|
||||
priv2 = _make_server("priv2", available_on_public_internet=False)
|
||||
manager = _make_manager([priv1, priv2])
|
||||
|
||||
allowed, blocked = manager.filter_server_ids_by_ip_with_info(
|
||||
["priv1", "priv2"], client_ip="1.2.3.4"
|
||||
)
|
||||
assert allowed == []
|
||||
assert blocked == 2
|
||||
|
||||
Reference in New Issue
Block a user