Skip to content

Conversation

@ezynda3
Copy link
Contributor

@ezynda3 ezynda3 commented Oct 13, 2025

Description

Fixes session management in the streamable HTTP transport to properly reuse registered sessions for POST requests instead of always creating ephemeral sessions. This enables SendNotificationToSpecificClient and other session-aware features to work correctly with POST-based interactions.

Changes:

  • Added session reuse logic for non-initialize POST requests by checking s.server.sessions before creating ephemeral sessions
  • Implemented session registration after successful initialization from POST requests
  • Sessions are now stored in both s.server.sessions (via RegisterSession) and s.activeSessions for consistency
  • Added comprehensive tests to verify SendNotificationToSpecificClient works with streamable HTTP sessions
  • Implemented defensive checks to prevent duplicate session registration when both POST and GET connections exist

Fixes #614

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • MCP spec compatibility implementation
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Tests only (no functional changes)
  • Other (please describe):

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly

MCP Spec Compliance

N/A - This is a bug fix for existing functionality, not a new spec implementation.

Additional Information

Testing:

  • ✅ Added new test TestStreamableHTTP_SendNotificationToSpecificClient with two subtests
  • ✅ All existing StreamableHTTP tests pass
  • ✅ Full test suite passes with race detection (go test ./... -race)
  • ✅ E2E tests pass (including sampling tests)

Backward Compatibility:
This fix restores intended behavior and maintains backward compatibility by preserving the ephemeral session fallback. No breaking changes.

Summary by CodeRabbit

  • New Features

    • Reuse existing sessions for non-initialize requests.
    • Enable per-client notifications after session registration via HTTP initialize.
  • Bug Fixes

    • Prevent duplicate session registrations from concurrent connections.
    • Defer registration until successful initialization to improve reliability.
    • Avoid failing POST requests when background registration encounters errors.
  • Tests

    • Added tests for session registration via POST, per-client notifications, and session reuse during tool invocation, including SSE-formatted responses.

Fixes session management in the streamable HTTP server to properly reuse
registered sessions for POST requests instead of always creating ephemeral
sessions. This enables SendNotificationToSpecificClient and session-aware
features to work correctly with POST-based interactions.

Changes:
- Check s.server.sessions for existing sessions before creating ephemeral ones
- Register sessions after successful initialization from POST requests
- Store sessions in both s.server.sessions and s.activeSessions for consistency
- Add comprehensive tests for session reuse and notification delivery

Fixes #614
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 13, 2025

Walkthrough

Updates handlePost in streamable_http to reuse existing sessions for non-initialize requests, defer session registration until after a successful initialize response, and manage activeSessions with rollback on registration failure. Adds tests validating per-client notifications and session reuse during tool invocations.

Changes

Cohort / File(s) Summary of changes
Server session handling
server/streamable_http.go
Reuse existing registered session for non-initialize requests; conditionally retrieve persistent session; after successful initialize, temporarily register in activeSessions and call RegisterSession; on failure, log and remove temporary registration without failing the request.
Tests for notifications and session reuse
server/streamable_http_test.go
Added TestStreamableHTTP_SendNotificationToSpecificClient with subtests covering session registration enabling per-client notifications and session reuse for non-initialize tool calls, including SSE-aware assertions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

type: bug, area: mcp spec

Suggested reviewers

  • digenkui03
  • rwjblue-glean

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 succinctly summarizes the primary change by indicating a fix and specifically that sessions are now reused correctly in the streamable HTTP transport without unnecessary noise or ambiguity.
Linked Issues Check ✅ Passed The submitted changes fully implement the objectives of issue [#614] by adding non-initialize POST session reuse, registering sessions after successful initialization, synchronizing session storage between server and activeSessions, preventing duplicate registrations, and preserving the ephemeral fallback as specified.
Out of Scope Changes Check ✅ Passed All code modifications are directly related to the session reuse logic and test coverage for streamable HTTP transport and no unrelated features or files were altered outside the scope of issue [#614].
Description Check ✅ Passed The pull request description closely follows the repository template by providing a concise overview, referencing Fixes #614, specifying the type of change, completing the checklist, addressing MCP spec compliance, and including additional testing and compatibility details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/streamable-http-session-reuse

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.

@github-actions
Copy link

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/mark3labs/mcp-go/server 72.23% (+0.40%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/mark3labs/mcp-go/server/streamable_http.go 65.52% (+1.72%) 406 (+11) 266 (+14) 140 (-3) 👍

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/mark3labs/mcp-go/server/streamable_http_test.go

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/streamable_http.go (2)

125-127: Update outdated documentation.

The comment states that POST handlers will not trigger session registration, but the new code on lines 432-445 now registers sessions from POST initialize requests. This documentation is now incorrect.

Apply this diff to update the comment:

-// Notice:
-// Except for the GET handlers(listening), the POST handlers(request/notification) will
-// not trigger the session registration. So the methods like `SendNotificationToSpecificClient`
-// or `hooks.onRegisterSession` will not be triggered for POST messages.
+// Notice:
+// Session registration is triggered by GET handlers (listening) and POST initialize requests.
+// For non-initialize POST requests, sessions are reused if already registered.
+// Methods like `SendNotificationToSpecificClient` and `hooks.onRegisterSession` will be
+// triggered for both GET connections and POST initialize requests.

291-293: Update misleading comment about ephemeral sessions.

The comment states "The session is ephemeral. Its life is the same as the request." However, for initialize requests, the session is now registered (lines 432-445) and persists across requests, so it's no longer ephemeral.

Apply this diff to clarify the comment:

 // Prepare the session for the mcp server
-// The session is ephemeral. Its life is the same as the request. It's only created
-// for interaction with the mcp server.
+// For initialize requests, the session will be registered and persist across requests.
+// For non-initialize requests, the session is reused if registered, or an ephemeral
+// session is created with a lifetime equal to the request.
🧹 Nitpick comments (1)
server/streamable_http_test.go (1)

1383-1482: Replace sleep with robust synchronization.

The test uses time.Sleep(100 * time.Millisecond) on line 1421 to wait for registration to complete. This can lead to flaky tests on slow systems or under load.

Consider using a WaitGroup similar to the first subtest:

+	hooks := &Hooks{}
+	var sessionRegistered sync.WaitGroup
+	sessionRegistered.Add(1)
+	
+	hooks.AddOnRegisterSession(func(ctx context.Context, session ClientSession) {
+		sessionRegistered.Done()
+	})
+	
-	mcpServer := NewMCPServer("test", "1.0.0")
+	mcpServer := NewMCPServer("test", "1.0.0", WithHooks(hooks))

 	// ... initialize session code ...

-	// Give time for registration to complete
-	time.Sleep(100 * time.Millisecond)
+	// Wait for registration to complete
+	done := make(chan struct{})
+	go func() {
+		sessionRegistered.Wait()
+		close(done)
+	}()
+	
+	select {
+	case <-done:
+		// Registration complete
+	case <-time.After(2 * time.Second):
+		t.Fatal("Timeout waiting for session registration")
+	}

 	// Call tool with the session ID
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74a600b and c977042.

📒 Files selected for processing (2)
  • server/streamable_http.go (2 hunks)
  • server/streamable_http_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/streamable_http_test.go (7)
server/hooks.go (1)
  • Hooks (94-121)
server/session.go (2)
  • ClientSession (11-20)
  • ClientSessionFromContext (82-87)
server/server.go (3)
  • NewMCPServer (335-363)
  • WithHooks (287-291)
  • ServerFromContext (80-85)
server/streamable_http.go (1)
  • NewTestStreamableHTTPServer (1089-1093)
server/constants.go (1)
  • HeaderKeySessionID (5-5)
mcp/tools.go (3)
  • NewTool (684-706)
  • CallToolRequest (54-58)
  • CallToolResult (40-51)
mcp/utils.go (2)
  • NewToolResultError (393-403)
  • NewToolResultText (271-280)
⏰ 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). (2)
  • GitHub Check: test
  • GitHub Check: coverage
🔇 Additional comments (5)
server/streamable_http.go (3)

312-320: LGTM! Session reuse logic correctly implements the fix.

The code correctly checks s.server.sessions for existing registered sessions before creating ephemeral ones, enabling session-aware features like SendNotificationToSpecificClient for POST requests. The type assertion provides safety against incorrect session types.


324-329: LGTM! Correct fallback to activeSessions for sampling support.

The modified logic correctly checks activeSessions only when no registered session was found in the previous step. This maintains support for persistent sessions created by GET connections while respecting the new session reuse logic.


432-445: LGTM! Registration logic correctly handles race conditions.

The code properly:

  1. Checks for existing registration to prevent duplicates (e.g., from concurrent GET connections)
  2. Stores in activeSessions before calling RegisterSession to coordinate with GET handler
  3. Rolls back activeSessions on registration failure without failing the request
  4. Uses appropriate error logging

The defensive check against s.server.sessions combined with the atomic LoadOrStore in the GET handler (line 465) effectively prevents duplicate registration races.

server/streamable_http_test.go (2)

1318-1381: LGTM! Comprehensive test with proper synchronization.

The test correctly:

  1. Uses a WaitGroup with timeout to wait for async registration
  2. Verifies the registered session ID matches the initialize response
  3. Tests SendNotificationToSpecificClient functionality

The synchronization approach is robust and avoids flaky timing issues.


1447-1464: LGTM! Test correctly handles both response formats.

The test properly handles both SSE format (when notifications are sent) and JSON format (when no notifications occur), making it resilient to the conditional response format logic in the production code.

@ezynda3 ezynda3 merged commit 6d52180 into main Oct 13, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Sessions are not reused correctly in the streamable_http transport

1 participant