Skip to content

Commit 2aa1ad2

Browse files
authored
feat: standardize timeout values to floats in seconds (#1766)
1 parent 4807eb5 commit 2aa1ad2

File tree

12 files changed

+38
-49
lines changed

12 files changed

+38
-49
lines changed

examples/clients/conformance-auth-client/mcp_conformance_auth_client/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import logging
3030
import os
3131
import sys
32-
from datetime import timedelta
3332
from urllib.parse import ParseResult, parse_qs, urlparse
3433

3534
import httpx
@@ -263,8 +262,8 @@ async def _run_session(server_url: str, oauth_auth: OAuthClientProvider) -> None
263262
async with streamablehttp_client(
264263
url=server_url,
265264
auth=oauth_auth,
266-
timeout=timedelta(seconds=30),
267-
sse_read_timeout=timedelta(seconds=60),
265+
timeout=30.0,
266+
sse_read_timeout=60.0,
268267
) as (read_stream, write_stream, _):
269268
async with ClientSession(read_stream, write_stream) as session:
270269
# Initialize the session

examples/clients/simple-auth-client/mcp_simple_auth_client/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ async def _default_redirect_handler(authorization_url: str) -> None:
207207
async with sse_client(
208208
url=self.server_url,
209209
auth=oauth_auth,
210-
timeout=60,
210+
timeout=60.0,
211211
) as (read_stream, write_stream):
212212
await self._run_session(read_stream, write_stream, None)
213213
else:

src/mcp/client/session.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
from datetime import timedelta
32
from typing import Any, Protocol, overload
43

54
import anyio.lowlevel
@@ -113,7 +112,7 @@ def __init__(
113112
self,
114113
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
115114
write_stream: MemoryObjectSendStream[SessionMessage],
116-
read_timeout_seconds: timedelta | None = None,
115+
read_timeout_seconds: float | None = None,
117116
sampling_callback: SamplingFnT | None = None,
118117
elicitation_callback: ElicitationFnT | None = None,
119118
list_roots_callback: ListRootsFnT | None = None,
@@ -369,7 +368,7 @@ async def call_tool(
369368
self,
370369
name: str,
371370
arguments: dict[str, Any] | None = None,
372-
read_timeout_seconds: timedelta | None = None,
371+
read_timeout_seconds: float | None = None,
373372
progress_callback: ProgressFnT | None = None,
374373
*,
375374
meta: dict[str, Any] | None = None,

src/mcp/client/session_group.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import logging
1313
from collections.abc import Callable
1414
from dataclasses import dataclass
15-
from datetime import timedelta
1615
from types import TracebackType
1716
from typing import Any, TypeAlias, overload
1817

@@ -41,11 +40,11 @@ class SseServerParameters(BaseModel):
4140
# Optional headers to include in requests.
4241
headers: dict[str, Any] | None = None
4342

44-
# HTTP timeout for regular operations.
45-
timeout: float = 5
43+
# HTTP timeout for regular operations (in seconds).
44+
timeout: float = 5.0
4645

47-
# Timeout for SSE read operations.
48-
sse_read_timeout: float = 60 * 5
46+
# Timeout for SSE read operations (in seconds).
47+
sse_read_timeout: float = 300.0
4948

5049

5150
class StreamableHttpParameters(BaseModel):
@@ -57,11 +56,11 @@ class StreamableHttpParameters(BaseModel):
5756
# Optional headers to include in requests.
5857
headers: dict[str, Any] | None = None
5958

60-
# HTTP timeout for regular operations.
61-
timeout: timedelta = timedelta(seconds=30)
59+
# HTTP timeout for regular operations (in seconds).
60+
timeout: float = 30.0
6261

63-
# Timeout for SSE read operations.
64-
sse_read_timeout: timedelta = timedelta(seconds=60 * 5)
62+
# Timeout for SSE read operations (in seconds).
63+
sse_read_timeout: float = 300.0
6564

6665
# Close the client session when the transport closes.
6766
terminate_on_close: bool = True
@@ -76,7 +75,7 @@ class StreamableHttpParameters(BaseModel):
7675
class ClientSessionParameters:
7776
"""Parameters for establishing a client session to an MCP server."""
7877

79-
read_timeout_seconds: timedelta | None = None
78+
read_timeout_seconds: float | None = None
8079
sampling_callback: SamplingFnT | None = None
8180
elicitation_callback: ElicitationFnT | None = None
8281
list_roots_callback: ListRootsFnT | None = None
@@ -197,7 +196,7 @@ async def call_tool(
197196
self,
198197
name: str,
199198
arguments: dict[str, Any],
200-
read_timeout_seconds: timedelta | None = None,
199+
read_timeout_seconds: float | None = None,
201200
progress_callback: ProgressFnT | None = None,
202201
*,
203202
meta: dict[str, Any] | None = None,
@@ -210,7 +209,7 @@ async def call_tool(
210209
name: str,
211210
*,
212211
args: dict[str, Any],
213-
read_timeout_seconds: timedelta | None = None,
212+
read_timeout_seconds: float | None = None,
214213
progress_callback: ProgressFnT | None = None,
215214
meta: dict[str, Any] | None = None,
216215
) -> types.CallToolResult: ...
@@ -219,7 +218,7 @@ async def call_tool(
219218
self,
220219
name: str,
221220
arguments: dict[str, Any] | None = None,
222-
read_timeout_seconds: timedelta | None = None,
221+
read_timeout_seconds: float | None = None,
223222
progress_callback: ProgressFnT | None = None,
224223
*,
225224
meta: dict[str, Any] | None = None,
@@ -314,8 +313,8 @@ async def _establish_session(
314313
httpx_client = create_mcp_http_client(
315314
headers=server_params.headers,
316315
timeout=httpx.Timeout(
317-
server_params.timeout.total_seconds(),
318-
read=server_params.sse_read_timeout.total_seconds(),
316+
server_params.timeout,
317+
read=server_params.sse_read_timeout,
319318
),
320319
)
321320
await session_stack.enter_async_context(httpx_client)

src/mcp/client/sse.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ def _extract_session_id_from_endpoint(endpoint_url: str) -> str | None:
3131
async def sse_client(
3232
url: str,
3333
headers: dict[str, Any] | None = None,
34-
timeout: float = 5,
35-
sse_read_timeout: float = 60 * 5,
34+
timeout: float = 5.0,
35+
sse_read_timeout: float = 300.0,
3636
httpx_client_factory: McpHttpClientFactory = create_mcp_http_client,
3737
auth: httpx.Auth | None = None,
3838
on_session_created: Callable[[str], None] | None = None,
@@ -46,8 +46,8 @@ async def sse_client(
4646
Args:
4747
url: The SSE endpoint URL.
4848
headers: Optional headers to include in requests.
49-
timeout: HTTP timeout for regular operations.
50-
sse_read_timeout: Timeout for SSE read operations.
49+
timeout: HTTP timeout for regular operations (in seconds).
50+
sse_read_timeout: Timeout for SSE read operations (in seconds).
5151
auth: Optional HTTPX authentication handler.
5252
on_session_created: Optional callback invoked with the session ID when received.
5353
"""

src/mcp/client/streamable_http.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def __init__(
100100
self,
101101
url: str,
102102
headers: dict[str, str] | None = None,
103-
timeout: float | timedelta = 30,
104-
sse_read_timeout: float | timedelta = 60 * 5,
103+
timeout: float = 30.0,
104+
sse_read_timeout: float = 300.0,
105105
auth: httpx.Auth | None = None,
106106
) -> None: ...
107107

@@ -118,8 +118,8 @@ def __init__(
118118
Args:
119119
url: The endpoint URL.
120120
headers: Optional headers to include in requests.
121-
timeout: HTTP timeout for regular operations.
122-
sse_read_timeout: Timeout for SSE read operations.
121+
timeout: HTTP timeout for regular operations (in seconds).
122+
sse_read_timeout: Timeout for SSE read operations (in seconds).
123123
auth: Optional HTTPX authentication handler.
124124
"""
125125
# Check for deprecated parameters and issue runtime warning

src/mcp/shared/memory.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from collections.abc import AsyncGenerator
88
from contextlib import asynccontextmanager
9-
from datetime import timedelta
109
from typing import Any
1110

1211
import anyio
@@ -49,7 +48,7 @@ async def create_client_server_memory_streams() -> AsyncGenerator[tuple[MessageS
4948
@asynccontextmanager
5049
async def create_connected_server_and_client_session(
5150
server: Server[Any] | FastMCP,
52-
read_timeout_seconds: timedelta | None = None,
51+
read_timeout_seconds: float | None = None,
5352
sampling_callback: SamplingFnT | None = None,
5453
list_roots_callback: ListRootsFnT | None = None,
5554
logging_callback: LoggingFnT | None = None,

src/mcp/shared/session.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
from collections.abc import Callable
33
from contextlib import AsyncExitStack
4-
from datetime import timedelta
54
from types import TracebackType
65
from typing import Any, Generic, Protocol, TypeVar
76

@@ -189,7 +188,7 @@ def __init__(
189188
receive_request_type: type[ReceiveRequestT],
190189
receive_notification_type: type[ReceiveNotificationT],
191190
# If none, reading will never time out
192-
read_timeout_seconds: timedelta | None = None,
191+
read_timeout_seconds: float | None = None,
193192
) -> None:
194193
self._read_stream = read_stream
195194
self._write_stream = write_stream
@@ -241,7 +240,7 @@ async def send_request(
241240
self,
242241
request: SendRequestT,
243242
result_type: type[ReceiveResultT],
244-
request_read_timeout_seconds: timedelta | None = None,
243+
request_read_timeout_seconds: float | None = None,
245244
metadata: MessageMetadata = None,
246245
progress_callback: ProgressFnT | None = None,
247246
) -> ReceiveResultT:
@@ -283,9 +282,9 @@ async def send_request(
283282
# request read timeout takes precedence over session read timeout
284283
timeout = None
285284
if request_read_timeout_seconds is not None: # pragma: no cover
286-
timeout = request_read_timeout_seconds.total_seconds()
285+
timeout = request_read_timeout_seconds
287286
elif self._session_read_timeout_seconds is not None: # pragma: no cover
288-
timeout = self._session_read_timeout_seconds.total_seconds()
287+
timeout = self._session_read_timeout_seconds
289288

290289
try:
291290
with anyio.fail_after(timeout):

tests/client/test_session_group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ async def test_disconnect_non_existent_server(self):
273273
"mcp.client.session_group.mcp.stdio_client",
274274
),
275275
(
276-
SseServerParameters(url="http://test.com/sse", timeout=10),
276+
SseServerParameters(url="http://test.com/sse", timeout=10.0),
277277
"sse",
278278
"mcp.client.session_group.sse_client",
279279
), # url, headers, timeout, sse_read_timeout

tests/issues/test_88_random_error.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Test to reproduce issue #88: Random error thrown on response."""
22

33
from collections.abc import Sequence
4-
from datetime import timedelta
54
from pathlib import Path
65
from typing import Any
76

@@ -93,11 +92,9 @@ async def client(
9392
assert not slow_request_lock.is_set()
9493

9594
# Second call should timeout (slow operation with minimal timeout)
96-
# Use 10ms timeout to trigger quickly without waiting
95+
# Use very small timeout to trigger quickly without waiting
9796
with pytest.raises(McpError) as exc_info:
98-
await session.call_tool(
99-
"slow", read_timeout_seconds=timedelta(microseconds=1)
100-
) # artificial timeout that always fails
97+
await session.call_tool("slow", read_timeout_seconds=0.000001) # artificial timeout that always fails
10198
assert "Timed out while waiting" in str(exc_info.value)
10299

103100
# release the slow request not to have hanging process

0 commit comments

Comments
 (0)