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 when present and returns the session identifier string or None if unavailable.

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 'Expose get_session_id callback' accurately and concisely describes the primary change of exposing the session ID callback as a public method.
Description check ✅ Passed The PR description covers the main change, references the linked issue (#2485), and includes all checklist items marked as complete. However, it lacks technical details about the implementation.
Linked Issues check ✅ Passed The implementation fully addresses issue #2485 by capturing the get_session_id callback from streamablehttp_client and exposing it via a public get_session_id() method on StreamableHTTPTransport.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objective of exposing the session ID callback. No extraneous modifications are present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

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

⛔ 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 (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/fastmcp/client/transports.py
⏰ 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

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

Comment on lines +297 to +301
def get_session_id(self) -> str | None:
if self.get_session_id_cb:
return self.get_session_id_cb()
return None

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against missing get_session_id_cb to avoid AttributeError before connection

get_session_id assumes self.get_session_id_cb always exists. If a caller invokes get_session_id() before any connect_session() has run, this will raise AttributeError instead of cleanly returning None, which contradicts the intended “id or None” API.

Initialize the callback on construction and check it explicitly:

@@ class StreamableHttpTransport(ClientTransport):
     def __init__(
         self,
         url: str | AnyUrl,
         headers: dict[str, str] | None = None,
         auth: httpx.Auth | Literal["oauth"] | str | None = None,
         sse_read_timeout: datetime.timedelta | float | int | None = None,
         httpx_client_factory: McpHttpClientFactory | None = None,
     ):
@@
         if isinstance(sse_read_timeout, int | float):
             sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout))
         self.sse_read_timeout = sse_read_timeout
+        # Session id callback provided by underlying streamablehttp_client transport
+        self.get_session_id_cb = None
@@
     def get_session_id(self) -> str | None:
-        if self.get_session_id_cb:
-            return self.get_session_id_cb()
-        return None
+        """
+        Return the current MCP session id, or None if no session has been established yet.
+        """
+        cb = self.get_session_id_cb
+        if cb is None:
+            return None
+        return cb()

If you want stricter typing, you can additionally annotate the attribute in __init__:

from typing import Callable  # at top of file

self.get_session_id_cb: Callable[[], str | None] | None = None

This keeps the method safe to call at any time while preserving the desired str | None return contract.

🤖 Prompt for AI Agents
In src/fastmcp/client/transports.py around lines 297 to 301, get_session_id()
assumes self.get_session_id_cb exists and can raise AttributeError if called
before connect_session(); initialize self.get_session_id_cb = None in __init__
(and optionally annotate it as Callable[[], str | None] | None after importing
Callable) and change get_session_id() to explicitly check "if
self.get_session_id_cb is not None:" before calling it, returning None otherwise
so the method always returns str | None without raising.

@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

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