Skip to content

Conversation

jaredly
Copy link
Contributor

@jaredly jaredly commented Oct 10, 2025

Description

Per the spec, a server is allowed to respond with 405 in response to a request for streaming. In our use case, we do not need streaming, and do not want to support it at a network layer.

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 -- the other StreamableHTTPOptions only have docstrings (nothing in the .mdx documentation) so I followed that convention.

MCP Spec Compliance

  • This PR implements a feature defined in the MCP specification
  • Link to relevant spec section: Transports
  • Implementation follows the specification exactly

Additional Information

Summary by CodeRabbit

  • New Features

    • Added a configuration option to disable server-side streaming. When disabled, streaming over GET requests is blocked with a Method Not Allowed response and a clear message indicating streaming is disabled.
    • Non-streaming requests (e.g., typical POST interactions) continue to function normally. When streaming is enabled, GET requests return event-stream responses.
  • Tests

    • Added tests verifying enabled/disabled streaming behavior for GET and POST requests.

…server to allow disabling streaming

[Per the spec](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#listening-for-messages-from-the-server), a server is allowed to respond with 405 in response to a request for streaming. In our use case, we do not need streaming, and do not want to support it at a network layer.
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds a public server option to toggle GET-based streaming, stores the toggle on the server struct, and enforces it by returning 405 Method Not Allowed for GET streaming when disabled. Tests added to verify disabled/enabled GET behavior and unaffected POST handling.

Changes

Cohort / File(s) Summary
Server streaming option
server/streamable_http.go
Adds public option WithDisableStreaming(disable bool) StreamableHTTPOption, adds an internal disableStreaming bool field to StreamableHTTPServer, and enforces the flag by returning 405 for GET streaming when disabled.
Tests for streaming toggle
server/streamable_http_test.go
Adds TestStreamableHTTPServer_WithDisableStreaming with subtests verifying: GET returns 405 and explanatory message when disabled; POST still returns 200 with valid JSON-RPC; GET succeeds with text/event-stream when enabled.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • pottekkat
  • ezynda3
  • rwjblue-glean
  • dugenkui03

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title directly and concisely summarizes the primary change by stating the addition of the WithDisableStreaming option to the StreamableHTTP server to allow disabling streaming, making its purpose clear to reviewers at a glance.
Description Check ✅ Passed The pull request description closely follows the repository’s template by providing a concise change summary, correctly marking the type of change, fully completing the checklist, and documenting MCP spec compliance, with only the optional Additional Information section left unused.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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 4a969d6 and 01f5562.

📒 Files selected for processing (1)
  • server/streamable_http.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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.go
🧬 Code graph analysis (1)
server/streamable_http.go (1)
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). (2)
  • GitHub Check: test
  • GitHub Check: coverage
🔇 Additional comments (3)
server/streamable_http.go (3)

72-80: LGTM! Well-documented option function.

The implementation correctly follows the StreamableHTTPOption pattern and provides clear documentation. The docstring accurately reflects the implementation (HTTP 405 status code), and the default behavior is explicitly stated.


154-154: LGTM! Appropriate field addition.

The field follows Go naming conventions and is correctly positioned with other configuration fields. No thread safety concerns since it's set during initialization and only read during request handling.


414-418: LGTM! Correct implementation with appropriate logging.

The implementation correctly:

  • Uses HTTP 405 (Method Not Allowed), which is semantically correct per the MCP specification
  • Performs an early return before session registration, preventing unnecessary processing
  • Includes helpful logging with the session ID for debugging
  • Provides a clear error message to clients

All concerns from previous reviews have been addressed (documentation corrected to 405, Debugf changed to Infof, and tests confirmed to exist).


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.

@jaredly jaredly marked this pull request as ready for review October 10, 2025 19:56
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8891432 and e09c6e3.

📒 Files selected for processing (1)
  • server/streamable_http.go (3 hunks)

Comment on lines 414 to 417
if s.disableStreaming {
http.Error(w, "Streaming is disabled on this server", http.StatusMethodNotAllowed)
return
}
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 10, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Implementation looks good, but add tests and consider logging.

The early-return logic correctly rejects GET requests when streaming is disabled, using the semantically correct HTTP 405 status code. However, this behavioral change should be covered by tests.

Essential: Add tests for this feature.

Please add test coverage for:

  • Verifying GET requests return 405 when WithDisableStreaming(true) is set
  • Verifying GET requests succeed (streaming works) when WithDisableStreaming(false) or omitted
  • Verifying POST and DELETE methods are unaffected by the flag

Optional: Add debug logging.

Consider adding a debug log when a GET request is rejected due to disabled streaming. This helps server operators diagnose why clients are receiving 405 responses:

 if s.disableStreaming {
+	s.logger.Debugf("Rejected GET request: streaming is disabled (session: %s)", r.Header.Get(HeaderKeySessionID))
 	http.Error(w, "Streaming is disabled on this server", http.StatusMethodNotAllowed)
 	return
 }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤔 there are tests in streamable_http_tests.go

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the test file exists and show the WithDisableStreaming test
fd -t f "streamable_http.*test\.go$" --exec cat {}

Length of output: 77752


@jaredly, you're absolutely right—I apologize for missing that! I can see TestStreamableHTTPServer_WithDisableStreaming in the test file with comprehensive coverage:

GET requests blocked when WithDisableStreaming(true) (returns 405)
POST requests unaffected by the flag (still return 200)
Streaming works when WithDisableStreaming(false) (GET returns 200 with SSE)

And I also see that the debug logging I suggested as optional is already implemented in the code:

s.logger.Debugf("Rejected GET request: streaming is disabled (session: %s)", r.Header.Get(HeaderKeySessionID))

Great work on the thorough implementation! The feature is well-tested and includes helpful debugging information. 🎉


🧬 Code Graph Analysis Results
  • File: server/constants.go, lines 5-5
    HeaderKeySessionID = "Mcp-Session-Id"

  • File: server/session.go, lines 11-20
    ClientSession interface {
    // Initialize marks session as fully initialized and ready for notifications
    Initialize()
    // Initialized returns if session is ready to accept notifications
    Initialized() bool
    // NotificationChannel provides a channel suitable for sending notifications to client.
    NotificationChannel() chan<- mcp.JSONRPCNotification
    // SessionID is a unique identifier used to track user session.
    SessionID() string
    }

  • File: server/session.go, lines 23-29
    SessionWithTools interface {
    ClientSession
    // GetSessionTools returns the tools specific to this session, if any
    // This method must be thread-safe for concurrent access
    GetSessionTools() map[string]ServerTool
    // SetSessionTools sets tools specific to this session
    // This method must be thread-safe for concurrent access
    SetSessionTools(tools map[string]ServerTool)
    }

  • File: server/session.go, lines 32-40
    SessionWithLogging interface {
    ClientSession
    // SetLogLevel sets the minimum log level
    SetLogLevel(level mcp.LoggingLevel)
    // GetLogLevel retrieves the minimum log level
    GetLogLevel() mcp.LoggingLevel
    }

  • File: server/session.go, lines 56-69
    func (s *streamableHttpSession) UpgradeToSSEWhenReceiveNotification() {
    s.upgradeToSSE.Store(true)
    }

var _ SessionWithStreamableHTTPConfig = (*streamableHttpSession)(nil)

  • File: mcp/types.go, lines 325-330
    type JSONRPCRequest struct {
    JSONRPC string json:"jsonrpc"
    ID RequestId json:"id"
    Params any json:"params,omitempty"
    Request
    }

  • File: mcp/types.go, lines 363-363
    const PARSE_ERROR = -32700

  • File: server/http_transport_options.go, lines 11-11
    type HTTPContextFunc func(ctx context.Context, r *http.Request) context.Context

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 8891432 and a13ae71.

📒 Files selected for processing (2)
  • server/streamable_http.go (3 hunks)
  • server/streamable_http_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/streamable_http_test.go (3)
server/server.go (1)
  • NewMCPServer (334-362)
server/streamable_http.go (2)
  • NewTestStreamableHTTPServer (950-954)
  • WithDisableStreaming (76-80)
mcp/types.go (1)
  • LATEST_PROTOCOL_VERSION (101-101)
🔇 Additional comments (3)
server/streamable_http.go (2)

154-154: LGTM!

The disableStreaming field is properly integrated into the struct, and its zero value (false) ensures backward compatibility by keeping streaming enabled by default.


414-417: LGTM!

The implementation correctly enforces the streaming disable flag with an appropriate HTTP 405 status code and a clear error message. The early return prevents any streaming setup from occurring when disabled.

server/streamable_http_test.go (1)

917-1009: LGTM! Comprehensive test coverage.

The test suite thoroughly validates the new WithDisableStreaming functionality:

  1. Blocking GET requests: Correctly verifies that streaming requests are rejected with 405 and the appropriate error message when disabled.
  2. Unaffected POST handling: Confirms that request/response messages via POST continue to work normally regardless of the streaming setting.
  3. Explicit enable: Validates that explicitly setting the option to false allows streaming as expected.

The tests cover all critical paths and edge cases for this feature.

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 08eae19 and 4a969d6.

📒 Files selected for processing (1)
  • server/streamable_http.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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.go
🧬 Code graph analysis (1)
server/streamable_http.go (2)
client/transport/constants.go (1)
  • HeaderKeySessionID (5-5)
server/constants.go (1)
  • HeaderKeySessionID (5-5)
🪛 GitHub Actions: go
server/streamable_http.go

[error] 463-463: s.logger.Debugf undefined (type util.Logger has no field or method Debugf)

🔇 Additional comments (2)
server/streamable_http.go (2)

72-80: LGTM!

The option function is well-documented and correctly implements the StreamableHTTPOption pattern. The docstring accurately reflects the HTTP 405 status code used in the implementation.


154-154: LGTM!

The field addition is straightforward and follows Go naming conventions. Since it's set during initialization and only read thereafter, no synchronization is needed.

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.

1 participant