Skip to content

Conversation

@mathewjhan
Copy link

Description

This PR exposes the session id callback from MCP's streamablehttp_client as a method in StreamableHTTPTransport. It let's users have easier access to mcp-session-id.

Contributors Checklist

Review Checklist

  • I have self-reviewed my changes
  • My Pull Request is ready for review

@marvin-context-protocol marvin-context-protocol bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. client Related to the FastMCP client SDK or client-side functionality. http Related to HTTP transport, networking, or web server functionality. labels Nov 25, 2025
@mathewjhan mathewjhan changed the title [feat] expose get_session_id callback Expose get_session_id callback Nov 25, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

Walkthrough

The StreamableHttpTransport class in src/fastmcp/client/transports.py now captures a third value (get_session_id) from the underlying streamablehttp_client transport during connect_session and stores it as self.get_session_id_cb. A new public method get_session_id() invokes this callback and returns the session identifier string or None if unavailable. A new async close() method clears the stored callback (self.get_session_id_cb = None) to avoid stale references after the transport is closed.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: exposing the get_session_id callback from the transport layer.
Description check ✅ Passed The description includes all required sections with completed checklists and references the linked issue #2485, though it could be more detailed about implementation specifics.
Linked Issues check ✅ Passed The code changes fully implement the requirements from issue #2485: capturing the get_session_id callback, storing it, exposing it via a public method, and providing safe return of session id or None.
Out of Scope Changes check ✅ Passed All changes are directly within scope of issue #2485; only the StreamableHTTPTransport class was modified to expose session id functionality with no extraneous modifications.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd02970 and ec5546d.

⛔ Files ignored due to path filters (1)
  • tests/client/test_streamable_http.py is excluded by none and included by none
📒 Files selected for processing (1)
  • src/fastmcp/client/transports.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/fastmcp/client/transports.py (2)
src/fastmcp/server/context.py (1)
  • session (383-393)
src/fastmcp/client/client.py (1)
  • session (291-298)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (1)
src/fastmcp/client/transports.py (1)

285-292: Tuple unpack and callback capture look correct

Unpacking the third element from streamablehttp_client and storing it on the instance gives callers a straightforward way to reach the session-id callback and matches the intended API surface for this transport.

@marvin-context-protocol
Copy link
Contributor

Test Failure Analysis

Summary: The test failure in the Windows CI run is not related to the PR changes. This is a known Windows-specific flaky test issue with pytest-xdist parallel execution.

Root Cause: The failing test test_unregistered_client_returns_html_for_browser in tests/server/auth/test_enhanced_error_responses.py tests OAuth authorization error handling, which is completely unrelated to the PR changes. The PR only modifies StreamableHttpTransport to expose the get_session_id() callback.

The error shows a pytest-xdist worker crash:

worker 'gw0' crashed while running 'tests/server/auth/test_enhanced_error_responses.py::TestEnhancedAuthorizationHandler::test_unregistered_client_returns_html_for_browser'

This is accompanied by Windows-specific resource warnings about closed pipes and event loops (ConnectionResetError: [WinError 10054], RuntimeError: Event loop is closed), which are common issues with asyncio on Windows when running parallel tests.

Verification: I tested both the failing test and the new feature:

  • ✅ The failing test passes on Linux (both on main and PR branch)
  • ✅ The new feature test test_session_id_callback passes successfully
  • ✅ The PR changes are correct and functional

Suggested Solution:

This is a flaky test infrastructure issue, not a code problem. The CI should be re-run. If the Windows test continues to flake, consider one of these approaches:

  1. Re-run the workflow - Most likely to pass on retry
  2. Mark the test as flaky on Windows using pytest markers
  3. Disable parallel execution for this specific test file on Windows
  4. Skip the test on Windows if it continues to be problematic

The PR code itself is solid and ready for merge once the CI passes.

Detailed Analysis

What the PR Changes

  • Adds self.get_session_id_cb = None initialization in StreamableHttpTransport.__init__
  • Captures the third return value (get_session_id) from streamablehttp_client transport
  • Stores the callback as self.get_session_id_cb
  • Adds a public get_session_id() method to retrieve the session ID

What Failed

  • A completely unrelated test about OAuth authorization error HTML responses
  • Test file: tests/server/auth/test_enhanced_error_responses.py
  • Failure mode: Worker crash (not assertion failure)
  • Platform: Windows only

Log Evidence

ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
RuntimeError: Event loop is closed
ValueError: I/O operation on closed pipe

These are classic Windows asyncio cleanup issues when tests run in parallel with pytest-xdist.

Related Files

Modified by PR:

  • src/fastmcp/client/transports.py - Correctly implements session ID callback exposure
  • tests/client/test_streamable_http.py - Adds passing test for the new feature

Unrelated failing test:

  • tests/server/auth/test_enhanced_error_responses.py - OAuth error handling (no connection to PR changes)

Copy link
Owner

@jlowin jlowin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

  • get_session_id_cb should be a private variable (_get_session_id_cb) since it is only used to persist state for the duration of a connection
  • relatedly, _get_session_id_cb needs to be reset when the connection is closed. I'm not sure what would happen if the user tried to call get_session_id() after closing a session but I imagine it would raise a low-level error. Better to return None in that case
  • the private variable should get typing in init of _get_session_id_cb: Callable[[], str | None] | None I believe

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/fastmcp/client/transports.py (2)

299-305: Avoid catching broad Exception in get_session_id

Catching all Exception and returning None will silently hide programming or transport errors and conflicts with the guideline to avoid blind exception handling (also flagged by Ruff BLE001). Unless the underlying callback is documented to raise a specific “no session” error, it’s safer to either:

  • Rely on the callback returning str | None, or
  • Catch only the specific, expected exception type(s) once known.

A simple, clearer version that still guards against “not connected yet” would be:

-    def get_session_id(self) -> str | None:
-        if self._get_session_id_cb:
-            try:
-                return self._get_session_id_cb()
-            except Exception:
-                return None
-        return None
+    def get_session_id(self) -> str | None:
+        cb = self._get_session_id_cb
+        if cb is None:
+            return None
+        return cb()

If the callback can raise a specific “no session available” exception, narrow the except to just that type instead of Exception.


307-309: close() behavior is fine; consider adding a return annotation

Resetting _get_session_id_cb in close() avoids stale callbacks after the client is torn down and integrates cleanly with Client.close(). For consistency with the “full type annotations” guideline, you could optionally annotate the return type:

-    async def close(self):
+    async def close(self) -> None:
         # Reset the session id callback
         self._get_session_id_cb = None

Not critical, but it would bring this in line with stronger typing elsewhere.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 407614f and 29d0edf.

📒 Files selected for processing (1)
  • src/fastmcp/client/transports.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style

Files:

  • src/fastmcp/client/transports.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
🧬 Code graph analysis (1)
src/fastmcp/client/transports.py (2)
src/fastmcp/server/context.py (1)
  • session (383-393)
src/fastmcp/client/client.py (2)
  • session (291-298)
  • close (490-492)
🪛 Ruff (0.14.7)
src/fastmcp/client/transports.py

303-303: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (3)
src/fastmcp/client/transports.py (3)

9-9: Importing Callable from collections.abc is appropriate

Using collections.abc.Callable for type annotations is consistent with modern Python (3.10+) style and fits the existing import pattern in this file.


257-257: Good: initialize _get_session_id_cb in __init__ to avoid attribute errors

Storing the session-id callback as self._get_session_id_cb: Callable[[], str | None] | None = None ensures get_session_id() can be safely called before any connection is established and keeps typing explicit.


287-295: Callback wiring from streamablehttp_client looks correct

Unpacking read_stream, write_stream, get_session_id = transport and assigning self._get_session_id_cb = get_session_id cleanly exposes the underlying session-id callback at the transport level, matching the PR’s goal without altering existing session setup.

@mathewjhan
Copy link
Author

@jlowin thanks for the feedback!

I've updated the code accordingly, let me know if there is anything else you'd like me to change 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client Related to the FastMCP client SDK or client-side functionality. enhancement Improvement to existing functionality. For issues and smaller PR improvements. http Related to HTTP transport, networking, or web server functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose streamablehttp_client session id callback to users

2 participants