Skip to content

Fix streamable http sampling #531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

andig
Copy link
Contributor

@andig andig commented Aug 6, 2025

Fix #530

Add e2e test and fix sampling

TODO

  • decide where/if e2e test should be added
  • decide getTextFromContent helper function
  • document sampling requirement for transport.WithContinuousListening()

Summary by CodeRabbit

  • New Features

    • Added comprehensive end-to-end tests for HTTP-based sampling functionality, improving test coverage and reliability.
  • Bug Fixes

    • Fixed content parsing for sampling requests to ensure correct handling of message content types.
  • Refactor

    • Improved session handling on the server by reusing persistent sessions for sampling support, enhancing reliability and efficiency.
    • Simplified connection timeout logic for HTTP streaming clients by relying on the original context for cancellation.

andig and others added 22 commits July 27, 2025 21:29
Implements sampling capability for HTTP transport, resolving issue mark3labs#419.
Enables servers to send sampling requests to HTTP clients via SSE and
receive LLM-generated responses.

## Key Changes

### Core Implementation
- Add `BidirectionalInterface` support to `StreamableHTTP`
- Implement `SetRequestHandler` for server-to-client requests
- Enhance SSE parsing to handle requests alongside responses/notifications
- Add `handleIncomingRequest` and `sendResponseToServer` methods

### HTTP-Specific Features
- Leverage existing MCP headers (`Mcp-Session-Id`, `Mcp-Protocol-Version`)
- Bidirectional communication via HTTP POST for responses
- Proper JSON-RPC request/response handling over HTTP

### Error Handling
- Add specific JSON-RPC error codes for different failure scenarios:
  - `-32601` (Method not found) when no handler configured
  - `-32603` (Internal error) for sampling failures
  - `-32800` (Request cancelled/timeout) for context errors
- Enhanced error messages with sampling-specific context

### Testing & Examples
- Comprehensive test suite in `streamable_http_sampling_test.go`
- Complete working example in `examples/sampling_http_client/`
- Tests cover success flows, error scenarios, and interface compliance

## Technical Details

The implementation maintains full backward compatibility while adding
bidirectional communication support. Server requests are processed
asynchronously to avoid blocking the SSE stream reader.

HTTP transport now supports the complete sampling flow that was
previously only available in stdio and inprocess transports.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
This completes the server-side implementation of sampling support for
HTTP transport, addressing the remaining requirements from issue mark3labs#419.

Changes:
- Enhanced streamableHttpSession to implement SessionWithSampling interface
- Added bidirectional SSE communication for server-to-client requests
- Implemented session registry for proper response correlation
- Added comprehensive error handling with JSON-RPC error codes
- Created extensive test suite covering all scenarios
- Added working example server with sampling tools

Key Features:
- Server can send sampling requests to HTTP clients via SSE
- Clients respond via HTTP POST with proper session correlation
- Queue overflow protection and timeout handling
- Compatible with existing HTTP transport architecture

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Replace flaky time.Sleep calls with proper synchronization using channels
and sync.WaitGroup to make tests deterministic and avoid race conditions.

Also improves error handling robustness in test servers with proper JSON
decoding error checks.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Make request vs response detection more robust by checking for presence
  of "method" field instead of relying on nil Result/Error fields
- Add nil pointer check in sendResponseToServer function to prevent panics

These changes improve reliability against malformed messages and edge cases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
The comment incorrectly stated that responses are broadcast to all sessions,
but the implementation actually delivers responses to the specific session
identified by sessionID using the activeSessions registry.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Previously, EnableSampling() was a no-op that didn't actually enable the
sampling capability in the server's declared capabilities.

Changes:
- Add Sampling field to mcp.ServerCapabilities struct
- Add sampling field to internal serverCapabilities struct
- Update EnableSampling() to set the sampling capability flag
- Update handleInitialize() to include sampling in capability response
- Add test to verify sampling capability is properly declared

Now when EnableSampling() is called, the server will properly declare
sampling capability during initialization, allowing clients to know
that the server supports sending sampling requests.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Replace unsafe type assertion result.Content.(mcp.TextContent).Text
with safe type checking to handle cases where Content might not
be a TextContent struct.

Now gracefully handles different content types without panicking.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
The SamplingInterface test was missing the EnableSampling() call,
which is necessary to activate sampling features for proper testing.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Replace single error test with comprehensive table-driven tests
- Add test cases for invalid request IDs and malformed results
- Replace t.Fatalf with t.Errorf to follow project conventions
- Use proper session ID format for valid test scenarios

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Remove recursive call in RequestSampling that could cause stack overflow
- Remove problematic response re-queuing to global channel
- Update deliverSamplingResponse to route responses directly to
  dedicated request channels via samplingRequests map lookup
- This prevents ordering issues and ensures responses reach
  the correct waiting request

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Modified deliverSamplingResponse to return error instead of just logging
- Added proper error handling for disconnected sessions
- Improved error messages for debugging
- Updated test expectations to match new error behavior

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Add signal handling for SIGINT and SIGTERM
- Move defer statement after error checking
- Improve shutdown error handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Add timeout context for SSE response processing (30s default)
- Add timeout for individual connection attempts in listenForever (10s)
- Use context-aware sleep in retry logic
- Ensure async goroutines properly respect context cancellation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Make error message more descriptive and actionable
- Provide clearer debugging information about why the channel is blocked

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Rename 'baseMessage' to 'jsonMessage' for more neutral naming
- Improves code readability and follows consistent naming conventions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Add test verifying that concurrent sampling requests are handled correctly
when the second request completes faster than the first. The test ensures:
- Responses are correctly associated with their request IDs
- Server processes requests concurrently without blocking
- Completion order follows actual processing time, not submission order

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Create new context with 30-second timeout for request handling
to prevent long-running handlers from blocking indefinitely.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Replace all occurrences of interface{} with the modern Go any type alias
for improved readability and consistency with current Go best practices.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Create timeout context from parent context instead of context.Background()
to ensure request handlers respect parent context cancellation.

Addresses review comment about context handling in async goroutine.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
The samplingResponseChan field was declared but never used in the
streamableHttpSession struct. Remove it and update tests accordingly.

Addresses review comment about unused fields in session struct.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Add signal handling for SIGINT and SIGTERM to allow graceful shutdown
of the sampling HTTP client example. This prevents indefinite blocking
and provides better production-ready behavior.

Addresses review comment about adding graceful shutdown handling.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Removes unused sync.RWMutex field that was flagged by golangci-lint.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

Walkthrough

The changes introduce fixes and enhancements to support persistent session reuse for streamable HTTP sampling, correct content parsing in the client, and add comprehensive end-to-end tests for HTTP-based sampling. Adjustments were made to both client and server logic to ensure proper request/response handling and session management for sampling scenarios.

Changes

Cohort / File(s) Change Summary
Client Content Parsing Fix
client/client.go
Adds logic in handleSamplingRequestTransport to properly parse message content unmarshaled as generic maps into typed mcp.TextContent structs for text messages.
Streamable HTTP Client Timeout Handling
client/transport/streamable_http.go
Removes per-connection 10s timeout in listenForever; now uses the original context for connection attempts, relying on caller's context for cancellation/timeout.
Streamable HTTP Server Session Reuse
server/streamable_http.go
Modifies handlePost and handleGet to reuse persistent sessions from activeSessions, registering new sessions only when necessary; includes minor formatting/whitespace cleanups.
E2E HTTP Sampling Tests
e2e/sampling_http_test.go
Introduces new end-to-end tests for HTTP-based sampling, including a custom sampling handler, server/client setup, sampling request/response flow validation, and basic HTTP tool invocation tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
Fix streamable HTTP sampling so that client/server correctly handle sampling requests and responses, ensuring the client does not hang waiting for a response and that SSE sampling events are properly processed (#530)

Suggested labels

area: mcp spec

Suggested reviewers

  • dugenkui03
  • pottekkat

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 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 6da5cd1 and e65ac96.

📒 Files selected for processing (4)
  • client/client.go (1 hunks)
  • client/transport/streamable_http.go (1 hunks)
  • e2e/sampling_http_test.go (1 hunks)
  • server/streamable_http.go (6 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: in the mcptest package, prefer returning errors from helper functions rather than calling t.fatalf()...
Learnt from: octo
PR: mark3labs/mcp-go#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:

  • e2e/sampling_http_test.go
📚 Learning: in go mcp server, servertool.tool field is only used for tool listing and indexing, not for tool exe...
Learnt from: floatingIce91
PR: mark3labs/mcp-go#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:

  • e2e/sampling_http_test.go
📚 Learning: in the mark3labs/mcp-go project, the mcpserver.capabilities field is a struct value (servercapabilit...
Learnt from: ezynda3
PR: mark3labs/mcp-go#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:

  • client/client.go
  • server/streamable_http.go
📚 Learning: in the mcp-go library, the getpromptparams.arguments field is of type map[string]string, not map[str...
Learnt from: lariel-fernandes
PR: mark3labs/mcp-go#428
File: www/docs/pages/servers/prompts.mdx:218-234
Timestamp: 2025-06-20T20:39:51.870Z
Learning: In the mcp-go library, the GetPromptParams.Arguments field is of type map[string]string, not map[string]interface{}, so direct string access without type assertions is safe and correct.

Applied to files:

  • client/client.go
📚 Learning: tool responses from the mcp server shouldn't contain rawinputschema, which is why the unmarshaljson ...
Learnt from: xinwo
PR: mark3labs/mcp-go#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.go
📚 Learning: the tool struct in the mark3labs/mcp-go project should handle both inputschema and rawinputschema co...
Learnt from: xinwo
PR: mark3labs/mcp-go#35
File: mcp/tools.go:0-0
Timestamp: 2025-03-04T07:00:57.111Z
Learning: The Tool struct in the mark3labs/mcp-go project should handle both InputSchema and RawInputSchema consistently between MarshalJSON and UnmarshalJSON methods, even though the tools response from MCP server typically doesn't contain rawInputSchema.

Applied to files:

  • server/streamable_http.go
🧬 Code Graph Analysis (2)
e2e/sampling_http_test.go (1)
mcp/types.go (1)
  • Content (884-886)
client/client.go (1)
mcp/types.go (3)
  • Content (884-886)
  • TextContent (890-897)
  • TextContent (899-899)
🪛 ast-grep (0.38.6)
e2e/sampling_http_test.go

[warning] 83-83: "Detected a network listener listening on 0.0.0.0 or an empty string.
This could unexpectedly expose the server publicly as it binds to all
available interfaces. Instead, specify another IP address that is not
0.0.0.0 nor the empty string."
Context: net.Listen("tcp", ":0")
Note: [CWE-200] Exposure of Sensitive Information to an Unauthorized Actor [REFERENCES]
- https://owasp.org/Top10/A01_2021-Broken_Access_Control

(avoid-bind-to-all-interfaces-go)

🔇 Additional comments (6)
client/transport/streamable_http.go (1)

604-605: LGTM! Essential fix for SSE sampling support.

Removing the per-connection timeout is correct and necessary. The 10-second timeout was prematurely closing SSE connections before sampling responses could be received, causing the hanging issue described in #530. Using the original context allows the connection to remain open for the duration needed for bidirectional sampling communication.

server/streamable_http.go (2)

276-287: Essential session reuse logic for sampling support.

The addition of persistent session checking before creating ephemeral sessions is correct and necessary. This allows sampling responses to be delivered to the appropriate session that initiated the sampling request.


398-417: Correct implementation of persistent session management.

The session reuse and registration logic properly maintains persistent sessions in activeSessions for sampling support. The deferred cleanup ensures proper resource management.

e2e/sampling_http_test.go (3)

83-90: Test utility correctly finds available port.

The :0 binding is appropriate here as it's only used temporarily to find an available port number for testing. The listener is immediately closed and the port is then used with a specific localhost address.


92-392: Comprehensive e2e test for HTTP sampling functionality.

Excellent test coverage that validates the complete sampling flow over HTTP:

  • Tests bidirectional communication between client and server
  • Verifies sampling request/response cycle
  • Tests multiple concurrent sampling requests
  • Includes proper setup/teardown and error handling

The test effectively validates the fixes for issue #530.


394-539: Well-structured basic HTTP test without sampling.

Good addition of a simpler test case that validates basic HTTP functionality without sampling. This helps isolate issues and ensures the HTTP transport works correctly even without the sampling feature.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@andig andig marked this pull request as draft August 6, 2025 08:47
@andig andig changed the title feat: enhance HTTP transport with comprehensive sampling support and improvements Fix streamable http sampling Aug 6, 2025
@@ -478,6 +478,22 @@ func (c *Client) handleSamplingRequestTransport(ctx context.Context, request tra
return nil, fmt.Errorf("failed to unmarshal params: %w", err)
}
}

// Fix content parsing - HTTP transport unmarshals TextContent as map[string]any
for i := range params.Messages {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually

func getTextFromContent(content any) string 

from the examples directory. Might be a good idea to make that a global helper function?

@andig andig marked this pull request as ready for review August 7, 2025 09:15
@andig
Copy link
Contributor Author

andig commented Aug 7, 2025

@ezynda3 how would you want to proceed? The fix here seems clear, not sure what to do about test, helper function and documentation?.

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: Streamable HTTP Sampling doesnt work
1 participant