Skip to content

Conversation

@ezynda3
Copy link
Contributor

@ezynda3 ezynda3 commented Nov 22, 2025

Description

Created StatelessGeneratingSessionIdManager to fix multi-instance deployments that were broken after commit da6f722. The change to InsecureStatefulSessionIdManager caused "Invalid session ID" errors in deployments without sticky sessions because it validated session existence locally.

The new default manager generates session IDs but only validates format (not existence locally), which fixes multi-instance deployments while maintaining backward compatibility.

Fixes #636

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

Additional Information

Changes Made:

  1. New Session Manager: Created StatelessGeneratingSessionIdManager that generates session IDs but only validates format
  2. Default Behavior: Updated default to use the new manager instead of InsecureStatefulSessionIdManager
  3. Backward Compatibility: Session IDs are still generated and returned in headers as expected
  4. Multi-Instance Support: Session IDs work across instances without requiring sticky sessions
  5. Updated Tests: Modified tests to reflect new behavior while maintaining test coverage
  6. Stateful Option: WithStateful() still available for local session tracking when needed

Impact:

  • Multi-instance deployments now work without requiring sticky sessions
  • Backward compatibility maintained - session IDs are still generated as expected
  • Production deployments will no longer experience "Invalid session ID" errors
  • Session termination now requires explicit stateful mode (appropriate for distributed systems)

Technical Details:

  • StatelessGeneratingSessionIdManager: Generates UUID-based session IDs with format validation only
  • Cross-instance operation: No local session storage, allowing requests to be routed to any instance
  • Format validation: Still validates session ID format to catch malformed requests
  • No-op termination: Termination is no-op in default mode (appropriate for stateless deployments)

Migration:

No migration required for most users. Only users who need local session termination should use WithStateful(true).

Summary by CodeRabbit

  • New Features

    • Added a toggle to enable stateful session management when persistent session state is required.
    • Introduced improved stateless session ID generation that supports cross-instance compatibility.
  • Changes

    • Default session handling is now stateless for better scalability; stateful mode remains opt-in.
    • Session validation and termination behavior updated to reflect stateless-by-default semantics.
  • Tests

    • Updated and expanded tests for stateful vs. stateless session flows and termination semantics.
  • Chores

    • CI coverage workflow now also runs on pushes to main and retains coverage artifacts for 30 days.

✏️ Tip: You can customize this high-level summary in your review settings.

…ce deployments

Fixes #636

In commit da6f722, the default session ID manager was changed from
StatelessSessionIdManager to InsecureStatefulSessionIdManager, which broke
multi-instance deployments without sticky sessions by causing Invalid
session ID errors when requests were routed to different server instances.

This change restores the previous default behavior:
- Default session manager is now StatelessSessionIdManager (no session validation)
- Multi-instance deployments work without requiring sticky sessions
- Added WithStateful() option for explicit stateful session management
- Updated all nil fallbacks to use stateless manager
- Updated tests to verify new default behavior
- Updated documentation to reflect the change

Backward compatibility is maintained while fixing the production deployment issue.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 22, 2025

Walkthrough

Defaults for session management were changed to stateless by default and a new public option WithStateful(bool) was added. A new exported type StatelessGeneratingSessionIdManager with Generate, Validate, and Terminate was introduced and wired as the default fallback; tests and CI workflow updates were adjusted accordingly.

Changes

Cohort / File(s) Summary
Server session manager & options
server/streamable_http.go
Added WithStateful(stateful bool) option; introduced StatelessGeneratingSessionIdManager and its methods (Generate, Validate, Terminate); changed default resolver/init wiring to prefer stateless managers (StatelessSessionIdManager / StatelessGeneratingSessionIdManager) instead of InsecureStatefulSessionIdManager; updated initialization paths to use stateless-generating manager by default.
Tests
server/streamable_http_test.go
Updated tests to opt into WithStateful(true) where stateful behavior is required; adjusted expectations to reflect stateless-by-default behavior and to exercise stateful termination/validation flows; renamed/rewrote subtests to match new semantics.
CI workflow
.github/workflows/ci.yml
CI triggers extended to also run on push to main branch; added retention-days: 30 for coverage artifact upload.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus attention on:
    • Consistent handling of the WithStateful option across all server/resolver initialization paths.
    • Correctness of StatelessGeneratingSessionIdManager.Generate format and Validate behavior to ensure cross-instance compatibility.
    • Test changes that flip default assumptions (ensure they exercise both stateless and stateful paths appropriately).

Possibly related PRs

Suggested labels

area: mcp spec

Suggested reviewers

  • rwjblue-glean
  • pottekkat
  • dugenkui03

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 accurately describes the main change: creating StatelessGeneratingSessionIdManager to fix multi-instance deployments, which is the core objective of this PR.
Description check ✅ Passed The description comprehensively covers the issue, solution, changes made, impact, and migration details. All key checklist items are marked, and the description follows the template structure with relevant sections filled out.
Linked Issues check ✅ Passed The PR directly addresses issue #636 by implementing the proposed solution: creating a stateless default session manager (StatelessGeneratingSessionIdManager) that validates format only, restoring cross-instance compatibility without local session storage.
Out of Scope Changes check ✅ Passed All code changes are scoped to the objectives: StatelessGeneratingSessionIdManager implementation, default behavior updates, WithStateful() option, and test updates. The CI workflow modification to extend coverage trigger and add retention is a minor supporting change.
✨ 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/session-manager-default-issue-636

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: 2

Caution

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

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

193-204: Default resolver change to StatelessSessionIdManager is aligned with the bugfix but impacts tests and existing behavior

Setting sessionIdManagerResolver to NewDefaultSessionIdManagerResolver(&StatelessSessionIdManager{}) makes the server stateless by default and fixes the multi-instance “Invalid session ID” issue, which is the stated goal of the PR.

However, several tests still rely on stateful behavior from the default server (no options), for example:

  • TestStreamableHTTP_POST_SendAndReceive (“initialize” and “Invalid session id” subtests) expect a non-empty session ID header and 400 for bogus IDs.
  • TestStreamableHTTP_SessionValidation expects 400/404 responses for invalid/terminated session IDs using a default NewTestStreamableHTTPServer(mcpServer).
  • TestStreamableHTTP_SendNotificationToSpecificClient expects a session ID header from an initialize POST on the default server.

With this new default, those scenarios will only hold when the server is created with WithStateful(true). Consider updating those tests (and any similar ones) to construct the server with WithStateful(true) when they are explicitly verifying stateful behavior, and leaving default, option-less servers to assert stateless semantics.

- server := NewTestStreamableHTTPServer(mcpServer)
+ server := NewTestStreamableHTTPServer(mcpServer, WithStateful(true))

(Apply this pattern in the tests that assert stateful semantics such as non-empty session IDs or “Invalid session ID” errors.)

🧹 Nitpick comments (1)
server/streamable_http.go (1)

82-90: WithStateful option API looks correct, but only acts when stateful == true

WithStateful(true) cleanly switches the resolver to an InsecureStatefulSessionIdManager, and the doc comment correctly warns about sticky sessions. Note that WithStateful(false) is a no-op, so callers cannot “turn off” stateful mode with a later false—they must rely on option order (or avoid passing it). If you want a strictly symmetric API later, you could consider documenting this explicitly or using an enum-like config.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecc6d8f and 169a2f2.

📒 Files selected for processing (2)
  • server/streamable_http.go (4 hunks)
  • server/streamable_http_test.go (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Order imports: standard library first, then third-party, then local packages (goimports enforces this)
Follow Go naming conventions: exported identifiers in PascalCase; unexported in camelCase; acronyms uppercase (HTTP, JSON, MCP)
Error handling: return sentinel errors, wrap with fmt.Errorf("context: %w", err), and check with errors.Is/As
Prefer explicit types and strongly-typed structs; avoid using any except where protocol flexibility is required (e.g., Arguments any)
All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary
Functions that are handlers or long-running must accept context.Context as the first parameter
Ensure thread safety for shared state using sync.Mutex and document thread-safety requirements in comments
For JSON: use json struct tags with omitempty for optional fields; use json.RawMessage for flexible/deferred parsing

Files:

  • server/streamable_http_test.go
  • server/streamable_http.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Testing: use testify/assert and testify/require
Write table-driven tests using a tests := []struct{ name, ... } pattern
Go test files must end with _test.go

Files:

  • server/streamable_http_test.go
🧬 Code graph analysis (1)
server/streamable_http_test.go (1)
server/streamable_http.go (2)
  • NewStreamableHTTPServer (194-211)
  • WithStateful (84-90)
⏰ 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). (4)
  • GitHub Check: coverage
  • GitHub Check: test
  • GitHub Check: coverage
  • GitHub Check: test
🔇 Additional comments (6)
server/streamable_http.go (2)

54-66: Nil manager now falls back to stateless (desired for multi-instance safety)

Using StatelessSessionIdManager as the fallback when WithSessionIdManager(nil) is passed aligns with the new stateless-by-default story and avoids reintroducing local state when callers misconfigure this option. The behavior and comments are consistent with the PR goal.


72-80: Nil resolver now falls back to stateless (consistent with other entry points)

Having WithSessionIdManagerResolver(nil) wrap a StatelessSessionIdManager via NewDefaultSessionIdManagerResolver keeps the server safe-by-default even when callers pass nil. This matches the new default and avoids surprising reversion to stateful mode.

server/streamable_http_test.go (4)

1863-1877: Stateless default preserved when WithStateLess(false) is used

The expectations here are correct: WithStateLess(false) should be a no-op, leaving the server’s default resolver as StatelessSessionIdManager, and asserting an empty session ID from Generate() verifies that.


1926-1943: Nil resolver path now explicitly asserts stateless fallback

This test correctly verifies that WithSessionIdManagerResolver(nil) results in a non-nil resolver that uses stateless semantics (empty session IDs). That matches the new implementation and protects against accidental reintroduction of stateful behavior via nil.


1945-1962: Nil manager path now asserts stateless fallback

The expectations that WithSessionIdManager(nil) yields a resolver whose Generate() returns "" are consistent with the code change and ensure the fallback is stateless rather than stateful.


1964-1984: Chained nil options correctly tested for stateless fallback

The “Multiple nil options fall back safely” test confirms that WithSessionIdManager(nil) followed by WithSessionIdManagerResolver(nil) still lands on a functioning, stateless manager. This is a good regression test for the interplay of these options.

… deployments

Fixes #636

The original fix was too aggressive - changing to completely stateless
broke existing functionality that expects session IDs to be generated.

New approach:
- Created StatelessGeneratingSessionIdManager that generates session IDs
  but only validates format (not existence locally)
- This fixes multi-instance deployments while maintaining compatibility
- Updated tests to reflect new behavior
- Session termination now requires explicit stateful mode

This solves the production issue where requests routed to different
instances failed with 'Invalid session ID' errors while preserving
the expected session ID generation behavior.
@ezynda3 ezynda3 changed the title fix: restore StatelessSessionIdManager as default to fix multi-instance deployments fix: create StatelessGeneratingSessionIdManager to fix multi-instance deployments Nov 22, 2025
…essionIdManager

As requested, updated NewDefaultSessionIdManagerResolver to fall back to
StatelessSessionIdManager instead of StatelessGeneratingSessionIdManager
when manager is nil.

This change affects:
- NewDefaultSessionIdManagerResolver(nil) fallback
- WithSessionIdManager(nil) fallback
- WithSessionIdManagerResolver(nil) fallback

Updated corresponding tests to expect stateless behavior:
- Generate() returns empty string ("")
- Validate() accepts any session ID without error

The server default still uses StatelessGeneratingSessionIdManager for
backward compatibility, but nil fallbacks now use truly stateless behavior.
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 (1)
server/streamable_http.go (1)

82-90: Consider clarifying WithStateful(false) behavior.

The function only acts when stateful is true; passing false is a no-op since the default is already stateless. While this is likely intentional, it could be clearer to readers.

Consider adding a comment to clarify:

 // WithStateful enables stateful session management using InsecureStatefulSessionIdManager.
-// This requires sticky sessions in multi-instance deployments.
+// When true, enables stateful session management (requires sticky sessions in multi-instance deployments).
+// When false, this option has no effect; the default remains stateless (StatelessGeneratingSessionIdManager).
 func WithStateful(stateful bool) StreamableHTTPOption {

Alternatively, you could make the no-op case explicit:

 func WithStateful(stateful bool) StreamableHTTPOption {
 	return func(s *StreamableHTTPServer) {
 		if stateful {
 			s.sessionIdManagerResolver = NewDefaultSessionIdManagerResolver(&InsecureStatefulSessionIdManager{})
+		} else {
+			// Explicitly retain default stateless behavior - no action needed
 		}
 	}
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 169a2f2 and 67b0cc0.

📒 Files selected for processing (2)
  • server/streamable_http.go (5 hunks)
  • server/streamable_http_test.go (8 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Order imports: standard library first, then third-party, then local packages (goimports enforces this)
Follow Go naming conventions: exported identifiers in PascalCase; unexported in camelCase; acronyms uppercase (HTTP, JSON, MCP)
Error handling: return sentinel errors, wrap with fmt.Errorf("context: %w", err), and check with errors.Is/As
Prefer explicit types and strongly-typed structs; avoid using any except where protocol flexibility is required (e.g., Arguments any)
All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary
Functions that are handlers or long-running must accept context.Context as the first parameter
Ensure thread safety for shared state using sync.Mutex and document thread-safety requirements in comments
For JSON: use json struct tags with omitempty for optional fields; use json.RawMessage for flexible/deferred parsing

Files:

  • server/streamable_http_test.go
  • server/streamable_http.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Testing: use testify/assert and testify/require
Write table-driven tests using a tests := []struct{ name, ... } pattern
Go test files must end with _test.go

Files:

  • server/streamable_http_test.go
🧠 Learnings (4)
📚 Learning: 2025-10-13T09:35:20.180Z
Learnt from: CR
Repo: mark3labs/mcp-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-13T09:35:20.180Z
Learning: Applies to **/*_test.go : Testing: use testify/assert and testify/require

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-06-23T11:10:42.948Z
Learnt from: floatingIce91
Repo: mark3labs/mcp-go PR: 401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-03-04T06:59:43.882Z
Learnt from: xinwo
Repo: mark3labs/mcp-go PR: 35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-04-21T21:26:32.945Z
Learnt from: octo
Repo: mark3labs/mcp-go PR: 149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.

Applied to files:

  • server/streamable_http_test.go
🧬 Code graph analysis (1)
server/streamable_http_test.go (2)
server/streamable_http.go (3)
  • NewTestStreamableHTTPServer (1352-1356)
  • WithStateful (84-90)
  • NewStreamableHTTPServer (194-211)
server/constants.go (1)
  • HeaderKeySessionID (5-5)
⏰ 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). (4)
  • GitHub Check: coverage
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: coverage
🔇 Additional comments (9)
server/streamable_http.go (5)

54-66: LGTM! Nil manager fallback is correct.

The nil guard correctly defaults to StatelessGeneratingSessionIdManager, and the updated comment clearly documents the new default behavior. This aligns with the PR's goal of restoring stateless-by-default behavior for multi-instance deployments.


68-80: LGTM! Consistent nil handling.

Follows the same pattern as WithSessionIdManager, correctly defaulting to StatelessGeneratingSessionIdManager when nil.


194-211: LGTM! Default correctly restored to stateless behavior.

The default initialization with StatelessGeneratingSessionIdManager directly addresses the multi-instance deployment issue described in #636. This generates unique session IDs while avoiding local state that breaks deployments without sticky sessions.


1254-1260: LGTM! Nil fallback uses generating manager (better than past suggestion).

The nil guard correctly defaults to StatelessGeneratingSessionIdManager. Note that this generates non-empty, prefixed session IDs (unlike StatelessSessionIdManager which returns empty strings). This is actually better than the past review comment's suggestion because it:

  1. Provides format validation (prefix + UUID check)
  2. Generates unique IDs useful for tracking/logging
  3. Maintains consistency with session ID format expectations

The test at lines 1822-1829 correctly expects this behavior.


1283-1305: LGTM! Excellent implementation for multi-instance deployments.

The StatelessGeneratingSessionIdManager strikes the right balance:

  • Generate: Creates unique session IDs with consistent format (mcp-session- + UUID), enabling tracking and logging while remaining stateless.
  • Validate: Only checks format (prefix + valid UUID), not existence—this is critical for multi-instance deployments where session state isn't shared.
  • Terminate: No-op is correct since there's no local state to clean up.

This design allows session IDs to work across multiple server instances without requiring sticky sessions or shared state, directly addressing the issue in #636.

Thread-safety: No mutable state, so no synchronization needed. ✓

server/streamable_http_test.go (4)

1407-1452: LGTM! Test correctly validates new stateless-by-default behavior.

The test rename and logic update accurately reflect the behavior change:

  • Old default (InsecureStatefulSessionIdManager): Rejected mcp-session-ffffffff-ffff-ffff-ffff-ffffffffffff because the session wasn't tracked locally → 400
  • New default (StatelessGeneratingSessionIdManager): Accepts properly-formatted session IDs regardless of local tracking → 200

The response parsing (lines 1432-1451) correctly verifies that the tool call executes and returns a valid result, confirming end-to-end functionality with the new manager.


1527-1603: LGTM! Correctly tests stateful session termination with explicit opt-in.

This test appropriately uses WithStateful(true) to test stateful-specific behavior (session termination tracking). The test flow is clear:

  1. Initialize session with stateful manager
  2. Terminate the session
  3. Verify subsequent requests return 404

This addresses the past review comment's recommendation to use WithStateful(true) for tests that explicitly require stateful behavior (tracking terminated sessions).


2067-2087: LGTM! Excellent test coverage for WithStateful option.

This test clearly validates the new WithStateful(true) behavior:

  1. ✓ Stateful manager generates non-empty session IDs with correct prefix
  2. ✓ Stateful manager rejects unknown session IDs (lines 2083-2086)

The second assertion is particularly important—it distinguishes stateful behavior (local tracking + rejection of unknown IDs) from the default stateless-generating behavior (format-only validation).


1807-1830: LGTM! Nil handling tests correctly updated for generating manager.

All nil fallback tests now correctly expect StatelessGeneratingSessionIdManager behavior:

  • ✓ Non-empty session IDs with mcp-session- prefix
  • ✓ Valid UUID format verification

These updates align with the design decision to use a generating manager (which provides format validation and unique IDs) rather than a purely stateless manager (empty IDs). The tests comprehensively cover nil handling across all configuration options: WithSessionIdManager(nil), WithSessionIdManagerResolver(nil), and chained nil options.

Note: This differs from the past review comment's suggestion (expecting empty IDs), but the generating approach is superior for production use.

Also applies to: 1902-1919, 1968-1988, 1990-2010, 2012-2035

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 (1)
server/streamable_http.go (1)

55-80: Nil fallback behavior is safe but potentially confusing.

The nil fallbacks use StatelessSessionIdManager (no ID generation), while the normal default uses StatelessGeneratingSessionIdManager (generates IDs). This creates distinct behaviors:

  • Default (no options): Generates session IDs
  • Explicit nil: Doesn't generate session IDs (fully stateless)

While this is tested and appears intentional as a "safe" defensive fallback, the distinction between the normal default and nil-fallback behavior could be clearer in documentation.

Consider adding a comment clarifying this distinction, for example:

// Note: When manager is nil, falls back to StatelessSessionIdManager (fully stateless, no ID generation)
// as a safe defensive default. This differs from the normal default which generates IDs.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67b0cc0 and 12e1846.

📒 Files selected for processing (2)
  • server/streamable_http.go (5 hunks)
  • server/streamable_http_test.go (10 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Order imports: standard library first, then third-party, then local packages (goimports enforces this)
Follow Go naming conventions: exported identifiers in PascalCase; unexported in camelCase; acronyms uppercase (HTTP, JSON, MCP)
Error handling: return sentinel errors, wrap with fmt.Errorf("context: %w", err), and check with errors.Is/As
Prefer explicit types and strongly-typed structs; avoid using any except where protocol flexibility is required (e.g., Arguments any)
All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary
Functions that are handlers or long-running must accept context.Context as the first parameter
Ensure thread safety for shared state using sync.Mutex and document thread-safety requirements in comments
For JSON: use json struct tags with omitempty for optional fields; use json.RawMessage for flexible/deferred parsing

Files:

  • server/streamable_http_test.go
  • server/streamable_http.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Testing: use testify/assert and testify/require
Write table-driven tests using a tests := []struct{ name, ... } pattern
Go test files must end with _test.go

Files:

  • server/streamable_http_test.go
🧠 Learnings (4)
📚 Learning: 2025-06-23T11:10:42.948Z
Learnt from: floatingIce91
Repo: mark3labs/mcp-go PR: 401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-03-04T06:59:43.882Z
Learnt from: xinwo
Repo: mark3labs/mcp-go PR: 35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-04-21T21:26:32.945Z
Learnt from: octo
Repo: mark3labs/mcp-go PR: 149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-06-30T07:13:17.052Z
Learnt from: ezynda3
Repo: mark3labs/mcp-go PR: 461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.

Applied to files:

  • server/streamable_http_test.go
🧬 Code graph analysis (1)
server/streamable_http_test.go (2)
server/streamable_http.go (3)
  • NewTestStreamableHTTPServer (1352-1356)
  • WithStateful (84-90)
  • NewStreamableHTTPServer (194-211)
server/constants.go (1)
  • HeaderKeySessionID (5-5)
⏰ 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: coverage
  • GitHub Check: test
  • GitHub Check: test
🔇 Additional comments (10)
server/streamable_http_test.go (6)

1407-1451: LGTM! Test correctly reflects stateless generating behavior.

The test rename and expectation changes accurately reflect the new default behavior where StatelessGeneratingSessionIdManager validates session ID format but not existence, allowing properly formatted session IDs to work across instances.


1527-1603: LGTM! Stateful termination test provides essential coverage.

This test correctly opts into stateful mode with WithStateful(true) and validates that termination semantics work as expected when local session tracking is enabled. This is important coverage for users who need explicit session termination.


1822-1835: LGTM! Nil fallback correctly expects fully stateless behavior.

The updated expectations correctly reflect that nil manager fallback uses StatelessSessionIdManager (returning empty strings), which is a safe defensive default distinct from the normal default StatelessGeneratingSessionIdManager.


1913-1924: LGTM! Test correctly validates default generating behavior.

The updated expectations properly reflect that WithStateLess(false) maintains the default StatelessGeneratingSessionIdManager, which generates prefixed UUID-based session IDs while validating only format (not existence).


1974-2032: LGTM! Nil handling tests are thorough and correct.

These tests comprehensively verify that nil inputs fall back to StatelessSessionIdManager (fully stateless, no ID generation) as a safe default. The behavior is consistent across all nil-handling scenarios.

Minor note: The comments alternate between "fallback" and "default" terminology when referring to the nil case, but the intent is clear from context.


2064-2084: LGTM! WithStateful test validates opt-in stateful behavior.

This test correctly exercises the new WithStateful(true) option and verifies that stateful mode generates session IDs and validates their existence (rejecting unknown IDs), which is the key distinction from the default stateless generating mode.

server/streamable_http.go (4)

82-90: LGTM! WithStateful option provides clear opt-in for stateful mode.

The new option cleanly enables stateful session management when needed, and the documentation correctly warns users about the sticky session requirement for multi-instance deployments.


193-210: LGTM! Default change fixes multi-instance session handling.

Changing the default from InsecureStatefulSessionIdManager to StatelessGeneratingSessionIdManager correctly addresses the multi-instance deployment issue. Session IDs are now generated and validated by format only (not local existence), enabling cross-instance compatibility without sticky sessions.


1255-1259: LGTM! Nil fallback provides safe defensive default.

The nil fallback to StatelessSessionIdManager is consistent with other nil-handling code and provides a safe fully-stateless default when no manager is explicitly configured.


1283-1305: Add GoDoc comment for the exported type.

The implementation of StatelessGeneratingSessionIdManager is correct and achieves the PR's goal of format-only validation for cross-instance compatibility. However, the exported type is missing a GoDoc comment.

As per coding guidelines, add a GoDoc comment starting with the type name:

+// StatelessGeneratingSessionIdManager generates UUID-based session IDs and validates
+// their format without tracking them locally. This enables cross-instance session handling
+// without requiring sticky sessions or shared state.
 type StatelessGeneratingSessionIdManager struct{}

Based on coding guidelines

⛔ Skipped due to learnings
Learnt from: CR
Repo: mark3labs/mcp-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-13T09:35:20.180Z
Learning: Applies to **/*.go : All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary
Learnt from: CR
Repo: mark3labs/mcp-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-13T09:35:20.180Z
Learning: Applies to **/*.go : Ensure thread safety for shared state using sync.Mutex and document thread-safety requirements in comments

As requested in the CodeRabbit review, updated tests that expect
stateful behavior to explicitly use WithStateful(true) instead of
relying on the old default behavior.

Updated tests:
- TestStreamableHTTP_POST_SendAndReceive: expects session IDs in headers and 400 for invalid IDs
- TestStreamableHttpResourceGet: expects session ID in header
- TestStreamableHTTP_SessionWithLogging: expects session ID in header
- TestStreamableHTTP_SendNotificationToSpecificClient: both subtests expect session IDs in headers

This ensures tests explicitly opt into stateful mode when needed,
while the default remains stateless for multi-instance deployments.
- Updated coverage job condition to run on push to main branch in addition to PRs
- Added 30-day artifact retention to ensure coverage artifacts are available for comparison
- This fixes the coverage triggers that were failing due to missing baseline artifacts

Now coverage will:
1. Run on main branch pushes to create baseline artifacts
2. Run on PRs to compare against baseline and ensure coverage doesn't decrease
3. Keep artifacts available for 30 days for proper comparison
@ezynda3 ezynda3 merged commit 2a23f4a into main Nov 22, 2025
5 of 8 checks passed
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 (1)
server/streamable_http_test.go (1)

1823-1836: Consider documenting nil-fallback vs default manager behavior

The tests reveal two different stateless managers in use:

  1. Normal default (line 1914): StatelessGeneratingSessionIdManager generates non-empty UUID-based session IDs (lines 1919-1925)
  2. Nil fallback (line 1810, 1823-1827): Falls back to StatelessSessionIdManager which returns empty session IDs

While this defensive nil-handling is reasonable, the distinction between "default stateless" and "nil-fallback stateless" could confuse users. The tests are correct, but consider adding comments explaining when each manager is used.

Also applies to: 1914-1925

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12e1846 and 3bc7315.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml (2 hunks)
  • server/streamable_http_test.go (15 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Order imports: standard library first, then third-party, then local packages (goimports enforces this)
Follow Go naming conventions: exported identifiers in PascalCase; unexported in camelCase; acronyms uppercase (HTTP, JSON, MCP)
Error handling: return sentinel errors, wrap with fmt.Errorf("context: %w", err), and check with errors.Is/As
Prefer explicit types and strongly-typed structs; avoid using any except where protocol flexibility is required (e.g., Arguments any)
All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary
Functions that are handlers or long-running must accept context.Context as the first parameter
Ensure thread safety for shared state using sync.Mutex and document thread-safety requirements in comments
For JSON: use json struct tags with omitempty for optional fields; use json.RawMessage for flexible/deferred parsing

Files:

  • server/streamable_http_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Testing: use testify/assert and testify/require
Write table-driven tests using a tests := []struct{ name, ... } pattern
Go test files must end with _test.go

Files:

  • server/streamable_http_test.go
🧠 Learnings (4)
📚 Learning: 2025-06-23T11:10:42.948Z
Learnt from: floatingIce91
Repo: mark3labs/mcp-go PR: 401
File: server/server.go:1082-1092
Timestamp: 2025-06-23T11:10:42.948Z
Learning: In Go MCP server, ServerTool.Tool field is only used for tool listing and indexing, not for tool execution or middleware. During handleToolCall, only the Handler field is used, so dynamic tools don't need the Tool field populated.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-03-04T06:59:43.882Z
Learnt from: xinwo
Repo: mark3labs/mcp-go PR: 35
File: mcp/tools.go:107-137
Timestamp: 2025-03-04T06:59:43.882Z
Learning: Tool responses from the MCP server shouldn't contain RawInputSchema, which is why the UnmarshalJSON method for the Tool struct is implemented to handle only the structured InputSchema format.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-04-21T21:26:32.945Z
Learnt from: octo
Repo: mark3labs/mcp-go PR: 149
File: mcptest/mcptest.go:0-0
Timestamp: 2025-04-21T21:26:32.945Z
Learning: In the mcptest package, prefer returning errors from helper functions rather than calling t.Fatalf() directly, giving callers flexibility in how to handle errors.

Applied to files:

  • server/streamable_http_test.go
📚 Learning: 2025-06-30T07:13:17.052Z
Learnt from: ezynda3
Repo: mark3labs/mcp-go PR: 461
File: server/sampling.go:22-26
Timestamp: 2025-06-30T07:13:17.052Z
Learning: In the mark3labs/mcp-go project, the MCPServer.capabilities field is a struct value (serverCapabilities), not a pointer, so it cannot be nil and doesn't require nil checking. Only pointer fields within the capabilities struct should be checked for nil.

Applied to files:

  • server/streamable_http_test.go
🧬 Code graph analysis (1)
server/streamable_http_test.go (2)
server/streamable_http.go (3)
  • NewTestStreamableHTTPServer (1352-1356)
  • WithStateful (84-90)
  • NewStreamableHTTPServer (194-211)
client/transport/constants.go (1)
  • HeaderKeySessionID (5-5)
⏰ 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). (4)
  • GitHub Check: coverage
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: coverage
🔇 Additional comments (11)
.github/workflows/ci.yml (2)

22-22: Good: Extend coverage tracking to main branch.

The conditional correctly triggers the coverage job on pull requests and on pushes to the main branch, ensuring consistent coverage metrics for the default branch.


41-41: Good: Set reasonable artifact retention.

The 30-day retention period for coverage artifacts provides sufficient history while managing storage costs.

server/streamable_http_test.go (9)

128-128: LGTM: Appropriate use of WithStateful(true)

This test validates session ID generation, header propagation, and invalid session rejection, all of which require stateful session tracking. The addition of WithStateful(true) correctly enables these behaviors.


598-598: LGTM: Stateful mode required for session-specific state

Both tests correctly use WithStateful(true):

  • Line 598: Session-specific resources require local session tracking
  • Line 1018: Session-specific log levels require local session tracking

Also applies to: 1018-1018


1408-1453: Good: Test updated to reflect stateless format-only validation

The test correctly validates the new stateless behavior where session IDs are validated by format only, not existence. A properly formatted session ID (line 1421) now returns HTTP 200 OK with valid response content, rather than being rejected. This aligns with the PR's goal of enabling cross-instance deployments without sticky sessions.


1528-1604: LGTM: Comprehensive test for stateful session termination

This test correctly validates stateful-specific behavior:

  1. Explicitly enables stateful mode (line 1531)
  2. Initializes and obtains a valid session ID
  3. Terminates the session via DELETE
  4. Verifies that subsequent requests with the terminated session ID return 404

This is essential coverage since the default (stateless) mode doesn't track termination.


1978-2032: LGTM: Comprehensive nil-handling coverage

These tests thoroughly validate defensive nil-handling across different option types:

  • Nil resolver (lines 1978-1991)
  • Nil manager (lines 1994-2010)
  • Chained nil options (lines 2013-2032)

All correctly expect fallback to StatelessSessionIdManager (empty session IDs). The test coverage is solid.


2065-2085: LGTM: Clear validation of WithStateful option

This test effectively validates the WithStateful(true) option:

  1. Generates non-empty session IDs with the correct prefix (lines 2072-2078)
  2. Validates session existence, not just format (lines 2080-2084)

The test clearly demonstrates the difference between stateful (existence checking) and default stateless (format checking) behavior.


2104-2104: LGTM: Stateful mode required for session-specific notifications

Both tests correctly use WithStateful(true) since SendNotificationToSpecificClient requires a local session registry to route notifications to specific session IDs.

Also applies to: 2175-2175


320-502: Excellent test coverage for stateful/stateless session management

The test suite comprehensively validates the new session management behavior:

Strengths:

  • Clear separation of stateful vs stateless test cases
  • Tests cover format-only validation (stateless) and existence validation (stateful)
  • Good coverage of nil-handling edge cases
  • Tests align with PR objectives (cross-instance compatibility)
  • Explicit WithStateful(true) usage where local session tracking is required

⚠️ One concern:

  • Line 322 uses WithStateLess(true) which may not exist (needs verification - see separate comment)

Also applies to: 598-598, 1018-1018, 1408-1453, 1528-1604, 2065-2085, 2104-2104, 2175-2175


320-502: No issues found

The WithStateLess option is properly defined in server/streamable_http.go:46. The test code on line 322 is valid and will compile correctly.

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: breaking change in commit da6f722

2 participants