@@ -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