diff --git a/lib/ruby_llm/mcp/native/transports/streamable_http.rb b/lib/ruby_llm/mcp/native/transports/streamable_http.rb index d864090..7c85309 100644 --- a/lib/ruby_llm/mcp/native/transports/streamable_http.rb +++ b/lib/ruby_llm/mcp/native/transports/streamable_http.rb @@ -406,6 +406,7 @@ def handle_response(response, request_id, original_message) handle_authorization_challenge(response, request_id, original_message) when 405 # Method not allowed - acceptable for some endpoints + fail_pending_request(request_id, response) if request_id nil when 400...500 handle_client_error(response) @@ -418,6 +419,19 @@ def handle_response(response, request_id, original_message) end end + def fail_pending_request(request_id, response) + response_body = response.respond_to?(:body) ? response.body.to_s : "Unknown error" + error = Errors::TransportError.new( + code: response.status, + message: "HTTP request failed: #{response.status} - #{response_body}" + ) + + @pending_mutex.synchronize do + queue = @pending_requests.delete(request_id.to_s) + queue&.push(error) + end + end + def handle_success_response(response, request_id, _original_message) content_type = response.respond_to?(:headers) ? response.headers["content-type"] : nil diff --git a/spec/ruby_llm/mcp/native/transports/streamable_http_spec.rb b/spec/ruby_llm/mcp/native/transports/streamable_http_spec.rb index 8970f0a..789b78a 100644 --- a/spec/ruby_llm/mcp/native/transports/streamable_http_spec.rb +++ b/spec/ruby_llm/mcp/native/transports/streamable_http_spec.rb @@ -2207,6 +2207,28 @@ /no OAuth provider configured/ ) end + + it "delivers a POST 405 response to the pending request instead of timing out" do + short_timeout_transport = described_class.new( + url: TestServerManager::HTTP_SERVER_URL, + request_timeout: 100, + coordinator: mock_coordinator, + options: { headers: {} } + ) + + stub_request(:post, TestServerManager::HTTP_SERVER_URL) + .to_return(status: 405, body: "Method Not Allowed") + + expect do + short_timeout_transport.request({ "method" => "tools/list", "id" => "post-405" }) + end.to raise_error(RubyLLM::MCP::Errors::TransportError) { |error| + expect(error.code).to eq(405) + expect(error.message).to match(/405/) + } + + pending_requests = short_timeout_transport.instance_variable_get(:@pending_requests) + expect(pending_requests).not_to have_key("post-405") + end end end