You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
session_idle_timeout is not exposed via streamable_http_app() and can cancel active requests #2455
Problem 2: active requests can be cancelled by the idle timeout
With a short idle timeout and a handler that takes longer than that timeout, the session can be reaped mid-request and the client waits until timeout / session termination rather than receiving the tool result.
thanks for the independent confirmation of both bugs. the suggested fix matches PR #2457 (suspend-then-reset the idle deadline around handle_request + forward session_idle_timeout from both Server.streamable_http_app() and MCPServer.streamable_http_app()). PR is up with a regression test that uses a 50ms idle timeout against a 150ms handler — feedback welcome.
Picking this up. Approach matches the validation outline in the issue:
Plumb session_idle_timeout through both streamable_http_app() wrappers (low-level Server and MCPServer).
Suspend the idle deadline while at least one request is in flight. Using a per-transport idle_active_requests counter; on first increment set idle_scope.deadline = math.inf, on drop to 0 reset to now + session_idle_timeout. This lets long-running tool calls finish even when they exceed the timeout.
Tests in tests/server/test_streamable_http_manager.py covering: (a) passthrough from streamable_http_app(session_idle_timeout=...) on both wrappers, (b) handler running longer than the timeout still completes, (c) existing idle reaping behavior intact once requests are finished, (d) deadline only resumes after the last concurrent request completes.
Production data point in support of this issue, from a FastMCP streamable-HTTP server
(mcp 1.27.1, uvicorn, loopback only, many short-lived MCP clients).
Over a single 18.2 hour process lifetime the server logged:
Created new transport with session ID: ... 224
Session ... idle timeout 0
Cleaning up crashed session ... 0
DELETE /mcp 0
POST /mcp HTTP/1.1" 200 461
POST /mcp HTTP/1.1" 202 115
POST /mcp HTTP/1.1" 400 109
224 sessions created, zero ever removed. Only 57 client sockets were still established at
the end, so roughly 167 StreamableHTTPServerTransport objects and their run_server
tasks were retained with no client behind them, for the lifetime of the process. Growth
was steady at about 12 leaked sessions per hour and is bounded only by process restarts.
Cause, on 1.27.1:
mcp/server/streamable_http_manager.py:268-269 only arms the idle cancel scope when self.session_idle_timeout is not None.
mcp/server/fastmcp/server.py:956-962 builds the manager as StreamableHTTPSessionManager(app=..., event_store=..., retry_interval=..., json_response=..., stateless=..., security_settings=...) with no session_idle_timeout,
and FastMCP.settings has no field for it, so for every FastMCP streamable-HTTP server
the value is unconditionally None.
The class docstring at streamable_http_manager.py:58-62 recommends 1800 for most
deployments, which no FastMCP user can currently set.
A client that goes away without sending DELETE /mcp (process killed, laptop closed,
container reaped) therefore leaks its transport, its task and its SSE stream permanently.
Suggested minimal fix for the FastMCP side: add session_idle_timeout: float | None = None
to Settings and pass session_idle_timeout=self.settings.session_idle_timeout in the StreamableHTTPSessionManager(...) call at fastmcp/server.py:956. The same argument
applies to mcp/server/lowlevel/server.py:746 in 2.0.0, where streamable_http_app()
takes json_response, stateless_http, event_store, retry_interval, max_request_body_size, transport_security and host but still not session_idle_timeout, so 2.0.0 inherits the same defect.
Workaround currently in use, for anyone hitting this on 1.x, using only public surface:
mcp.streamable_http_app() # create the manager eagerlymcp.session_manager.session_idle_timeout=1800.0# public attributemcp.run(transport="streamable-http") # reuses the same manager
Verified: with the timeout set to 3 seconds, an initialized session logs Session <id> idle timeout three seconds later and is removed from _server_instances.
Without it, nothing is ever logged.
Activity
shaun0927 commentedon Apr 17, 2026
thanks for the independent confirmation of both bugs. the suggested fix matches PR #2457 (suspend-then-reset the idle deadline around
handle_request+ forwardsession_idle_timeoutfrom bothServer.streamable_http_app()andMCPServer.streamable_http_app()). PR is up with a regression test that uses a 50ms idle timeout against a 150ms handler — feedback welcome.buildwithabid commentedon Apr 27, 2026
Picking this up. Approach matches the validation outline in the issue:
session_idle_timeoutthrough bothstreamable_http_app()wrappers (low-levelServerandMCPServer).idle_active_requestscounter; on first increment setidle_scope.deadline = math.inf, on drop to 0 reset tonow + session_idle_timeout. This lets long-running tool calls finish even when they exceed the timeout.tests/server/test_streamable_http_manager.pycovering: (a) passthrough fromstreamable_http_app(session_idle_timeout=...)on both wrappers, (b) handler running longer than the timeout still completes, (c) existing idle reaping behavior intact once requests are finished, (d) deadline only resumes after the last concurrent request completes.PR incoming.
finedesignz commentedon Aug 19, 2026
Production data point in support of this issue, from a FastMCP streamable-HTTP server
(
mcp1.27.1, uvicorn, loopback only, many short-lived MCP clients).Over a single 18.2 hour process lifetime the server logged:
224 sessions created, zero ever removed. Only 57 client sockets were still established at
the end, so roughly 167
StreamableHTTPServerTransportobjects and theirrun_servertasks were retained with no client behind them, for the lifetime of the process. Growth
was steady at about 12 leaked sessions per hour and is bounded only by process restarts.
Cause, on 1.27.1:
mcp/server/streamable_http_manager.py:268-269only arms the idle cancel scope whenself.session_idle_timeout is not None.mcp/server/fastmcp/server.py:956-962builds the manager asStreamableHTTPSessionManager(app=..., event_store=..., retry_interval=..., json_response=..., stateless=..., security_settings=...)with nosession_idle_timeout,and
FastMCP.settingshas no field for it, so for every FastMCP streamable-HTTP serverthe value is unconditionally
None.streamable_http_manager.py:58-62recommends 1800 for mostdeployments, which no FastMCP user can currently set.
A client that goes away without sending
DELETE /mcp(process killed, laptop closed,container reaped) therefore leaks its transport, its task and its SSE stream permanently.
Suggested minimal fix for the FastMCP side: add
session_idle_timeout: float | None = Noneto
Settingsand passsession_idle_timeout=self.settings.session_idle_timeoutin theStreamableHTTPSessionManager(...)call atfastmcp/server.py:956. The same argumentapplies to
mcp/server/lowlevel/server.py:746in 2.0.0, wherestreamable_http_app()takes
json_response,stateless_http,event_store,retry_interval,max_request_body_size,transport_securityandhostbut still notsession_idle_timeout, so 2.0.0 inherits the same defect.Workaround currently in use, for anyone hitting this on 1.x, using only public surface:
Verified: with the timeout set to 3 seconds, an initialized session logs
Session <id> idle timeoutthree seconds later and is removed from_server_instances.Without it, nothing is ever logged.
fix(server): discard a session whose establishing request was refused