Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/mcp/server/transports/streamable_http_transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def handle_post(request)
handle_regular_request(body_string, session_id)
end
rescue StandardError => e
ModelContextProtocol.configuration.exception_reporter.call(e, { request: body_string })
MCP.configuration.exception_reporter.call(e, { request: body_string })
[500, { "Content-Type" => "application/json" }, [{ error: "Internal server error" }.to_json]]
end

Expand Down Expand Up @@ -204,7 +204,7 @@ def send_response_to_stream(stream, response, session_id)
send_to_stream(stream, message)
[200, { "Content-Type" => "application/json" }, [{ accepted: true }.to_json]]
rescue IOError, Errno::EPIPE => e
ModelContextProtocol.configuration.exception_reporter.call(
MCP.configuration.exception_reporter.call(
e,
{ session_id: session_id, error: "Stream closed during response" },
)
Expand Down
100 changes: 100 additions & 0 deletions test/mcp/server/transports/streamable_http_transport_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,106 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
assert response[2].is_a?(Proc) # The body should be a Proc for streaming
end

test "handles POST request when IOError raised" do
# Create and initialize a session
init_request = create_rack_request(
"POST",
"/",
{ "CONTENT_TYPE" => "application/json" },
{ jsonrpc: "2.0", method: "initialize", id: "123" }.to_json,
)
init_response = @transport.handle_request(init_request)
session_id = init_response[1]["Mcp-Session-Id"]

# Connect with SSE
io = StringIO.new
get_request = create_rack_request(
"GET",
"/",
{ "HTTP_MCP_SESSION_ID" => session_id },
)
response = @transport.handle_request(get_request)
response[2].call(io) if response[2].is_a?(Proc)

# Give the stream time to set up
sleep(0.1)

# Close the stream
io.close

request = create_rack_request(
"POST",
"/",
{
"CONTENT_TYPE" => "application/json",
"HTTP_MCP_SESSION_ID" => session_id,
},
{ jsonrpc: "2.0", method: "ping", id: "456" }.to_json,
)

# This should handle IOError and return the original response
response = @transport.handle_request(request)
assert_equal 200, response[0]
assert_equal({ "Content-Type" => "application/json" }, response[1])

# Verify session was cleaned up
assert_not @transport.instance_variable_get(:@sessions).key?(session_id)
end

test "handles POST request when Errno::EPIPE raised" do
# Create and initialize a session
init_request = create_rack_request(
"POST",
"/",
{ "CONTENT_TYPE" => "application/json" },
{ jsonrpc: "2.0", method: "initialize", id: "123" }.to_json,
)
init_response = @transport.handle_request(init_request)
session_id = init_response[1]["Mcp-Session-Id"]

# Create a pipe to simulate EPIPE condition
reader, writer = IO.pipe

# Connect with SSE using the writer end of the pipe
get_request = create_rack_request(
"GET",
"/",
{ "HTTP_MCP_SESSION_ID" => session_id },
)
response = @transport.handle_request(get_request)
response[2].call(writer) if response[2].is_a?(Proc)

# Give the stream time to set up
sleep(0.1)

# Close the reader end to break the pipe - this will cause EPIPE on write
reader.close

request = create_rack_request(
"POST",
"/",
{
"CONTENT_TYPE" => "application/json",
"HTTP_MCP_SESSION_ID" => session_id,
},
{ jsonrpc: "2.0", method: "ping", id: "789" }.to_json,
)

# This should handle Errno::EPIPE and return the original response
response = @transport.handle_request(request)
assert_equal 200, response[0]
assert_equal({ "Content-Type" => "application/json" }, response[1])

# Verify session was cleaned up
assert_not @transport.instance_variable_get(:@sessions).key?(session_id)

begin
writer.close
rescue
nil
end
end

test "handles GET request with missing session ID" do
request = create_rack_request(
"GET",
Expand Down