Files
letta-server/letta/services/mcp/sse_client.py
github-actions[bot] 5fbf8f93e2 fix: add explicit timeouts to httpx clients to prevent ReadTimeout errors (#8538)
This commit addresses the httpx.ReadTimeout error detected in production
by adding explicit timeout configurations to several httpx client usages:

1. MCP SSE client: Pass mcp_connect_to_server_timeout (30s) to sse_client()
2. MCP StreamableHTTP client: Pass mcp_connect_to_server_timeout (30s) to streamablehttp_client()
3. OpenAI model list API: Add 30s timeout with 10s connect timeout
4. Google AI model list/details API: Add 30s timeout with 10s connect timeout

Previously, these httpx clients were created without explicit timeouts,
which could cause ReadTimeout errors when remote servers are slow to respond.

Fixes #8073

🤖 Generated with [Letta Code](https://letta.com)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: datadog-official[bot] <datadog-official[bot]@users.noreply.github.com>
Co-authored-by: Kian Jones <11655409+kianjones9@users.noreply.github.com>
2026-01-19 15:54:38 -08:00

50 lines
2.0 KiB
Python

from typing import Optional
from mcp import ClientSession
from mcp.client.auth import OAuthClientProvider
from mcp.client.sse import sse_client
from letta.functions.mcp_client.types import SSEServerConfig
from letta.log import get_logger
from letta.services.mcp.base_client import AsyncBaseMCPClient
from letta.settings import tool_settings
# see: https://modelcontextprotocol.io/quickstart/user
MCP_CONFIG_TOPLEVEL_KEY = "mcpServers"
logger = get_logger(__name__)
# TODO: Get rid of Async prefix on this class name once we deprecate old sync code
class AsyncSSEMCPClient(AsyncBaseMCPClient):
def __init__(
self, server_config: SSEServerConfig, oauth_provider: Optional[OAuthClientProvider] = None, agent_id: Optional[str] = None
):
super().__init__(server_config, oauth_provider, agent_id)
async def _initialize_connection(self, server_config: SSEServerConfig) -> None:
headers = {}
if server_config.custom_headers:
headers.update(server_config.custom_headers)
if server_config.auth_header and server_config.auth_token:
headers[server_config.auth_header] = server_config.auth_token
if self.agent_id:
headers[self.AGENT_ID_HEADER] = self.agent_id
# Use OAuth provider if available, otherwise use regular headers
# Pass timeout to prevent httpx.ReadTimeout errors on slow connections
timeout = tool_settings.mcp_connect_to_server_timeout
if self.oauth_provider:
sse_cm = sse_client(url=server_config.server_url, headers=headers if headers else None, auth=self.oauth_provider, timeout=timeout)
else:
sse_cm = sse_client(url=server_config.server_url, headers=headers if headers else None, timeout=timeout)
sse_transport = await self.exit_stack.enter_async_context(sse_cm)
self.stdio, self.write = sse_transport
# Create and enter the ClientSession context manager
session_cm = ClientSession(self.stdio, self.write)
self.session = await self.exit_stack.enter_async_context(session_cm)