Skip to content

Commit 99cb442

Browse files
committed
fix(mcp): pass timedelta to streamablehttp_client for old-SDK compat (codex P2)
Codex P2 review on #72: streamablehttp_client's timeout signature drifted between mcp SDK releases. 1.8 / 1.9-era releases required ``datetime.timedelta`` and dereferenced ``.total_seconds()`` on the value, so passing the loader's raw ``float`` raises ``AttributeError`` before discovery initializes. ~1.10+ widened the type to ``float | timedelta``, and the modern signature still happily accepts ``timedelta``. Since cubepi's mcp floor is ``mcp>=1.0``, supporting only the modern shape would silently break legitimate installs that pin an older 1.x SDK. Convert the timeout to a ``timedelta`` for streamable_http so the loader works on every advertised mcp version. ``sse_client``'s signature is unchanged across versions (always ``float``), so the SSE branch is left alone. Test: updated the streamable_transport regression test to assert the recorded sh_calls receive ``timedelta`` values rather than raw floats.
1 parent 9eeee29 commit 99cb442

2 files changed

Lines changed: 17 additions & 4 deletions

File tree

cubepi/mcp/http_loader.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import asyncio
66
from collections.abc import AsyncIterator
77
from contextlib import asynccontextmanager
8+
from datetime import timedelta
89
from typing import Any, Literal
910

1011
from cubepi.agent.types import AgentTool
@@ -34,11 +35,17 @@ async def _open_session(
3435
if transport == "streamable_http":
3536
from mcp.client.streamable_http import streamablehttp_client
3637

38+
# streamablehttp_client's timeout signature drifted across mcp SDK
39+
# versions: 1.8/1.9-era releases required ``timedelta`` and called
40+
# ``.total_seconds()`` internally, while ~1.10+ accepts ``float |
41+
# timedelta``. Passing a ``timedelta`` works on every version we
42+
# declare in our ``mcp>=1.0`` floor, so we always convert here.
43+
timeout_td = timedelta(seconds=timeout)
3744
async with streamablehttp_client(
3845
server_url,
3946
headers=headers,
40-
timeout=timeout,
41-
sse_read_timeout=timeout,
47+
timeout=timeout_td,
48+
sse_read_timeout=timeout_td,
4249
) as (read_stream, write_stream, _get_session_id):
4350
async with ClientSession(read_stream, write_stream) as session:
4451
yield session

tests/mcp/test_http_loader.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,17 @@ async def test_load_mcp_tools_http_streamable_transport(monkeypatch) -> None:
280280

281281
# Discovery used streamable_http (sse must not be touched).
282282
assert sse_calls == []
283+
# streamablehttp_client expects ``timedelta`` on older mcp SDKs (1.8/1.9
284+
# era) and ``float | timedelta`` on newer ones; we always pass a
285+
# ``timedelta`` so the loader works on every release in our ``mcp>=1.0``
286+
# dependency range.
287+
from datetime import timedelta
288+
283289
assert sh_calls[0] == {
284290
"url": "https://mcp.example/mcp",
285291
"headers": {"authorization": "Bearer x"},
286-
"timeout": 7.5,
287-
"sse_read_timeout": 7.5,
292+
"timeout": timedelta(seconds=7.5),
293+
"sse_read_timeout": timedelta(seconds=7.5),
288294
}
289295
assert len(sessions) == 1
290296

0 commit comments

Comments
 (0)