From 283c6949f463d980288d496c0ae8817555a8f937 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 23 Jul 2026 18:20:30 +0800 Subject: [PATCH] fix(mcp): validate tool call timeout config --- src/langbot/pkg/provider/tools/loaders/mcp.py | 12 +++++++++--- tests/unit_tests/provider/test_mcp_resources.py | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/langbot/pkg/provider/tools/loaders/mcp.py b/src/langbot/pkg/provider/tools/loaders/mcp.py index cdb6a0a2e..1a594260b 100644 --- a/src/langbot/pkg/provider/tools/loaders/mcp.py +++ b/src/langbot/pkg/provider/tools/loaders/mcp.py @@ -315,8 +315,12 @@ class RuntimeMCPSession: def _parse_tool_call_timeout(self, value: typing.Any) -> float: """Return a safe tool-call timeout; zero explicitly disables it.""" try: - timeout = float(value) - except (TypeError, ValueError): + timeout = -1 if isinstance(value, bool) else float(value) + if timeout > 0: + # Validate the exact conversion used for each call here, so a + # finite-but-enormous manual config cannot fail at invocation. + timedelta(seconds=timeout) + except (TypeError, ValueError, OverflowError): timeout = -1 if not math.isfinite(timeout) or timeout < 0: @@ -1035,7 +1039,9 @@ class RuntimeMCPSession: def _is_tool_call_timeout(exc: BaseException) -> bool: """Recognize the MCP SDK's per-request timeout without retrying it.""" return any( - isinstance(leaf, McpError) and leaf.error.code == httpx.codes.REQUEST_TIMEOUT + isinstance(leaf, McpError) + and leaf.error.code == httpx.codes.REQUEST_TIMEOUT + and leaf.error.message.startswith('Timed out while waiting for response') for leaf in RuntimeMCPSession._iter_exception_leaves(exc) ) diff --git a/tests/unit_tests/provider/test_mcp_resources.py b/tests/unit_tests/provider/test_mcp_resources.py index a0ae37a16..cee241b75 100644 --- a/tests/unit_tests/provider/test_mcp_resources.py +++ b/tests/unit_tests/provider/test_mcp_resources.py @@ -153,7 +153,7 @@ async def test_invoke_mcp_tool_timeout_is_not_retried_and_session_remains_usable assert call_tool.await_count == 2 -@pytest.mark.parametrize('invalid_timeout', [-1, float('inf'), 'not-a-number']) +@pytest.mark.parametrize('invalid_timeout', [-1, float('inf'), 1e300, True, 'not-a-number']) def test_invalid_tool_call_timeout_falls_back_to_default(invalid_timeout): ap = _app() session = RuntimeMCPSession(