Skip to content
Open
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
14 changes: 14 additions & 0 deletions lib/ruby_llm/mcp/native/transports/streamable_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
22 changes: 22 additions & 0 deletions spec/ruby_llm/mcp/native/transports/streamable_http_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading