Skip to content

Commit dc38c9f

Browse files
committed
Rename to MCP when handling exceptions in Streamable HTTP transport
The reason for the change is that a NameError occurs when a POST request is made while the stream is closed because ModelContextProtocol is undefined. I have also added test cases to check the behaviour when IOError and Errno::EPIPE occurs.
1 parent 382ae13 commit dc38c9f

File tree

2 files changed

+102
-2
lines changed

2 files changed

+102
-2
lines changed

lib/mcp/server/transports/streamable_http_transport.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def handle_post(request)
106106
handle_regular_request(body_string, session_id)
107107
end
108108
rescue StandardError => e
109-
ModelContextProtocol.configuration.exception_reporter.call(e, { request: body_string })
109+
MCP.configuration.exception_reporter.call(e, { request: body_string })
110110
[500, { "Content-Type" => "application/json" }, [{ error: "Internal server error" }.to_json]]
111111
end
112112

@@ -204,7 +204,7 @@ def send_response_to_stream(stream, response, session_id)
204204
send_to_stream(stream, message)
205205
[200, { "Content-Type" => "application/json" }, [{ accepted: true }.to_json]]
206206
rescue IOError, Errno::EPIPE => e
207-
ModelContextProtocol.configuration.exception_reporter.call(
207+
MCP.configuration.exception_reporter.call(
208208
e,
209209
{ session_id: session_id, error: "Stream closed during response" },
210210
)

test/mcp/server/transports/streamable_http_transport_test.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,106 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
113113
assert response[2].is_a?(Proc) # The body should be a Proc for streaming
114114
end
115115

116+
test "handles POST request when IOError raised" do
117+
# Create and initialize a session
118+
init_request = create_rack_request(
119+
"POST",
120+
"/",
121+
{ "CONTENT_TYPE" => "application/json" },
122+
{ jsonrpc: "2.0", method: "initialize", id: "123" }.to_json,
123+
)
124+
init_response = @transport.handle_request(init_request)
125+
session_id = init_response[1]["Mcp-Session-Id"]
126+
127+
# Connect with SSE
128+
io = StringIO.new
129+
get_request = create_rack_request(
130+
"GET",
131+
"/",
132+
{ "HTTP_MCP_SESSION_ID" => session_id },
133+
)
134+
response = @transport.handle_request(get_request)
135+
response[2].call(io) if response[2].is_a?(Proc)
136+
137+
# Give the stream time to set up
138+
sleep(0.1)
139+
140+
# Close the stream
141+
io.close
142+
143+
request = create_rack_request(
144+
"POST",
145+
"/",
146+
{
147+
"CONTENT_TYPE" => "application/json",
148+
"HTTP_MCP_SESSION_ID" => session_id,
149+
},
150+
{ jsonrpc: "2.0", method: "ping", id: "456" }.to_json,
151+
)
152+
153+
# This should handle IOError and return the original response
154+
response = @transport.handle_request(request)
155+
assert_equal 200, response[0]
156+
assert_equal({ "Content-Type" => "application/json" }, response[1])
157+
158+
# Verify session was cleaned up
159+
assert_not @transport.instance_variable_get(:@sessions).key?(session_id)
160+
end
161+
162+
test "handles POST request when Errno::EPIPE raised" do
163+
# Create and initialize a session
164+
init_request = create_rack_request(
165+
"POST",
166+
"/",
167+
{ "CONTENT_TYPE" => "application/json" },
168+
{ jsonrpc: "2.0", method: "initialize", id: "123" }.to_json,
169+
)
170+
init_response = @transport.handle_request(init_request)
171+
session_id = init_response[1]["Mcp-Session-Id"]
172+
173+
# Create a pipe to simulate EPIPE condition
174+
reader, writer = IO.pipe
175+
176+
# Connect with SSE using the writer end of the pipe
177+
get_request = create_rack_request(
178+
"GET",
179+
"/",
180+
{ "HTTP_MCP_SESSION_ID" => session_id },
181+
)
182+
response = @transport.handle_request(get_request)
183+
response[2].call(writer) if response[2].is_a?(Proc)
184+
185+
# Give the stream time to set up
186+
sleep(0.1)
187+
188+
# Close the reader end to break the pipe - this will cause EPIPE on write
189+
reader.close
190+
191+
request = create_rack_request(
192+
"POST",
193+
"/",
194+
{
195+
"CONTENT_TYPE" => "application/json",
196+
"HTTP_MCP_SESSION_ID" => session_id,
197+
},
198+
{ jsonrpc: "2.0", method: "ping", id: "789" }.to_json,
199+
)
200+
201+
# This should handle Errno::EPIPE and return the original response
202+
response = @transport.handle_request(request)
203+
assert_equal 200, response[0]
204+
assert_equal({ "Content-Type" => "application/json" }, response[1])
205+
206+
# Verify session was cleaned up
207+
assert_not @transport.instance_variable_get(:@sessions).key?(session_id)
208+
209+
begin
210+
writer.close
211+
rescue
212+
nil
213+
end
214+
end
215+
116216
test "handles GET request with missing session ID" do
117217
request = create_rack_request(
118218
"GET",

0 commit comments

Comments
 (0)