added configurable env for mcp timeouts (#22287)

This commit is contained in:
Shivam Rawat
2026-03-02 13:13:41 -08:00
committed by GitHub
parent ea8d22753d
commit d5355602d5
6 changed files with 47 additions and 9 deletions
@@ -557,6 +557,10 @@ router_settings:
| DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD | Default similarity threshold for MCP semantic tool filtering. Default is 0.3
| DEFAULT_MCP_SEMANTIC_FILTER_TOP_K | Default number of top results to return for MCP semantic tool filtering. Default is 10
| MCP_NPM_CACHE_DIR | Directory for npm cache used by STDIO MCP servers. In containers the default (~/.npm) may not exist or be read-only. Default is `/tmp/.npm_mcp_cache`
| LITELLM_MCP_CLIENT_TIMEOUT | MCP client connection timeout in seconds (stdio and HTTP/SSE transports). Default is 60
| LITELLM_MCP_TOOL_LISTING_TIMEOUT | Timeout in seconds for listing tools from an MCP server. Default is 30
| LITELLM_MCP_METADATA_TIMEOUT | HTTP client timeout in seconds for OAuth metadata fetching. Default is 10
| LITELLM_MCP_HEALTH_CHECK_TIMEOUT | Health check timeout in seconds for MCP servers. Default is 10
| MCP_OAUTH2_TOKEN_CACHE_DEFAULT_TTL | Default TTL in seconds for MCP OAuth2 token cache. Default is 3600
| MCP_OAUTH2_TOKEN_CACHE_MAX_SIZE | Maximum number of entries in MCP OAuth2 token cache. Default is 200
| MCP_OAUTH2_TOKEN_CACHE_MIN_TTL | Minimum TTL in seconds for MCP OAuth2 token cache. Default is 10
+6
View File
@@ -137,6 +137,12 @@ MCP_OAUTH2_TOKEN_CACHE_DEFAULT_TTL = int(
MCP_NPM_CACHE_DIR = os.getenv("MCP_NPM_CACHE_DIR", "/tmp/.npm_mcp_cache")
MCP_OAUTH2_TOKEN_CACHE_MIN_TTL = int(os.getenv("MCP_OAUTH2_TOKEN_CACHE_MIN_TTL", "10"))
# MCP timeout defaults (seconds). Override via env vars for slow/custom MCP servers.
MCP_CLIENT_TIMEOUT = float(os.getenv("LITELLM_MCP_CLIENT_TIMEOUT", "60.0"))
MCP_TOOL_LISTING_TIMEOUT = float(os.getenv("LITELLM_MCP_TOOL_LISTING_TIMEOUT", "30.0"))
MCP_METADATA_TIMEOUT = float(os.getenv("LITELLM_MCP_METADATA_TIMEOUT", "10.0"))
MCP_HEALTH_CHECK_TIMEOUT = float(os.getenv("LITELLM_MCP_HEALTH_CHECK_TIMEOUT", "10.0"))
LITELLM_UI_ALLOW_HEADERS = [
"x-litellm-semantic-filter",
"x-litellm-semantic-filter-tools",
+3 -2
View File
@@ -30,6 +30,7 @@ from mcp.types import Tool as MCPTool
from pydantic import AnyUrl
from litellm._logging import verbose_logger
from litellm.constants import MCP_CLIENT_TIMEOUT
from litellm.llms.custom_httpx.http_handler import get_ssl_configuration
from litellm.types.llms.custom_http import VerifyTypes
from litellm.types.mcp import (
@@ -63,7 +64,7 @@ class MCPClient:
transport_type: MCPTransportType = MCPTransport.http,
auth_type: MCPAuthType = None,
auth_value: Optional[Union[str, Dict[str, str]]] = None,
timeout: float = 60.0,
timeout: Optional[float] = None,
stdio_config: Optional[MCPStdioConfig] = None,
extra_headers: Optional[Dict[str, str]] = None,
ssl_verify: Optional[VerifyTypes] = None,
@@ -71,7 +72,7 @@ class MCPClient:
self.server_url: str = server_url
self.transport_type: MCPTransport = transport_type
self.auth_type: MCPAuthType = auth_type
self.timeout: float = timeout
self.timeout: float = timeout if timeout is not None else MCP_CLIENT_TIMEOUT
self._mcp_auth_value: Optional[Union[str, Dict[str, str]]] = None
self.stdio_config: Optional[MCPStdioConfig] = stdio_config
self.extra_headers: Optional[Dict[str, str]] = extra_headers
@@ -31,6 +31,12 @@ from pydantic import AnyUrl
import litellm
from litellm._logging import verbose_logger
from litellm.constants import (
MCP_CLIENT_TIMEOUT,
MCP_HEALTH_CHECK_TIMEOUT,
MCP_METADATA_TIMEOUT,
MCP_TOOL_LISTING_TIMEOUT,
)
from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException
from litellm.experimental_mcp_client.client import MCPClient
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
@@ -943,7 +949,7 @@ class MCPServerManager:
transport_type=transport,
auth_type=server.auth_type,
auth_value=auth_value,
timeout=60.0,
timeout=MCP_CLIENT_TIMEOUT,
stdio_config=stdio_config,
extra_headers=extra_headers,
)
@@ -955,7 +961,7 @@ class MCPServerManager:
transport_type=transport,
auth_type=server.auth_type,
auth_value=auth_value,
timeout=60.0,
timeout=MCP_CLIENT_TIMEOUT,
extra_headers=extra_headers,
)
@@ -1334,7 +1340,7 @@ class MCPServerManager:
try:
client = get_async_httpx_client(
llm_provider=httpxSpecialProvider.MCP,
params={"timeout": 10.0},
params={"timeout": MCP_METADATA_TIMEOUT},
)
response = await client.get(resource_metadata_url)
response.raise_for_status()
@@ -1430,7 +1436,7 @@ class MCPServerManager:
try:
client = get_async_httpx_client(
llm_provider=httpxSpecialProvider.MCP,
params={"timeout": 10.0},
params={"timeout": MCP_METADATA_TIMEOUT},
)
response = await client.get(url)
response.raise_for_status()
@@ -1489,7 +1495,7 @@ class MCPServerManager:
List of tools from the server
"""
try:
with anyio.fail_after(30.0):
with anyio.fail_after(MCP_TOOL_LISTING_TIMEOUT):
tools = await client.list_tools()
verbose_logger.debug(f"Tools from {server_name}: {tools}")
return tools
@@ -2508,10 +2514,14 @@ class MCPServerManager:
return "ok"
# Add timeout wrapper to prevent hanging
await asyncio.wait_for(client.run_with_session(_noop), timeout=10.0)
await asyncio.wait_for(
client.run_with_session(_noop), timeout=MCP_HEALTH_CHECK_TIMEOUT
)
status = "healthy"
except asyncio.TimeoutError:
health_check_error = "Health check timed out after 10 seconds"
health_check_error = (
f"Health check timed out after {MCP_HEALTH_CHECK_TIMEOUT} seconds"
)
status = "unhealthy"
except asyncio.CancelledError:
health_check_error = "Health check was cancelled"
+13
View File
@@ -16,6 +16,19 @@ from litellm.types.mcp import MCPAuth, MCPTransport
from mcp.types import Tool as MCPTool, CallToolResult as MCPCallToolResult
def test_mcp_client_uses_configurable_default_timeout():
"""MCPClient should use MCP_CLIENT_TIMEOUT constant when no timeout is passed."""
with patch(
"litellm.experimental_mcp_client.client.MCP_CLIENT_TIMEOUT", 120.0
):
# Client reads constant at runtime when timeout is None
client = MCPClient(
server_url="http://example.com",
transport_type=MCPTransport.sse,
)
assert client.timeout == 120.0
class TestMCPClientUnitTests:
"""Unit tests for MCPClient functionality."""
+4
View File
@@ -41,6 +41,10 @@ def test_all_numeric_constants_can_be_overridden():
# Constants that use a different env var name than the constant name
constant_to_env_var = {
"MAX_CALLBACKS": "LITELLM_MAX_CALLBACKS",
"MCP_CLIENT_TIMEOUT": "LITELLM_MCP_CLIENT_TIMEOUT",
"MCP_TOOL_LISTING_TIMEOUT": "LITELLM_MCP_TOOL_LISTING_TIMEOUT",
"MCP_METADATA_TIMEOUT": "LITELLM_MCP_METADATA_TIMEOUT",
"MCP_HEALTH_CHECK_TIMEOUT": "LITELLM_MCP_HEALTH_CHECK_TIMEOUT",
}
# Verify all numeric constants have environment variable support