feat: add idle timeout for StreamableHTTP sessions by felixweinberger · Pull Request #1994 · modelcontextprotocol/python-sdk

@felixweinberger

Add a session_idle_timeout parameter to StreamableHTTPSessionManager that
enables automatic cleanup of idle sessions, fixing the memory leak
described in #1283.

Uses a CancelScope with a deadline inside each session's run_server task.
When the deadline passes, app.run() is cancelled and the session cleans
up. Incoming requests push the deadline forward. No background tasks
needed — each session manages its own lifecycle.

- terminate() on the transport is now idempotent
- Effective timeout accounts for retry_interval
- Default is None (no timeout, fully backwards compatible)

Github-Issue: #1283

@felixweinberger

- Remove unrelated blank line between logger.info and try block
- Only create CancelScope when session_idle_timeout is set, keeping
  the no-timeout path identical to the original code
- Add docstring to _effective_idle_timeout explaining the 3x
  retry_interval multiplier rationale

@felixweinberger

The helper silently clamped the user's configured timeout to
retry_interval * 3, which is surprising. Users should set a timeout
that suits their deployment. Updated the docstring to note that the
timeout should comfortably exceed retry_interval when both are set.

This was referenced

Feb 9, 2026

Kludex

Kludex

Kludex

Kludex

Kludex

Kludex

@felixweinberger

- Reformat session_idle_timeout docstring to use 4-space continuation
  indent and fill to 120 chars
- Move idle timeout tests from tests/issues/ into
  tests/server/test_streamable_http_manager.py alongside existing
  session manager tests
- Remove redundant/unnecessary tests (6 dropped, 5 kept)
- Replace sleep-based test assertions with event-based polling
  using anyio.fail_after for deterministic behavior

Github-Issue: #1283

Kludex

Kludex

Kludex

felixweinberger

@felixweinberger

- Fix docstring indentation: use 4-space continuation indent filled to
  120 cols consistently across all Args parameters
- Change stateless+idle_timeout error from ValueError to RuntimeError
- Use logger.exception() instead of logger.error() with exc_info
- Remove unnecessary None guard on session_id in idle timeout cleanup
- Replace while+sleep(0) polling with anyio.Event in test

Github-Issue: #1283