Skip to content

Commit 6b08edd

Browse files
committed
Ignore empty members of the Accept header
## Motivation and Context An `Accept` header with an empty list member, such as `application/json,,text/event-stream` or one with a leading comma, makes the Streamable HTTP transport answer a POST with 500 and report a `NoMethodError` to the exception reporter, and raise out of a GET, although the required media types are listed (issue #609): `parse_accept_header` splits each member on `;` and an empty member yields nothing to strip. RFC 9110 section 5.6.1.2 has a recipient parse and ignore a reasonable number of empty list elements, and the TypeScript and Python servers accept such a header, the one by substring checks and the other by stripping each member. `parse_accept_header` now skips empty members, so the POST and GET checks see the media types that are there. Fixes #609. ## How Has This Been Tested? Two new tests in `test/mcp/server/transports/streamable_http_transport_test.rb`, a POST with a doubled and with a leading comma and a GET with a leading comma. Against the previous library the POST test fails with a 500 and the GET test raises. ## Breaking Changes None.
1 parent 0019b4d commit 6b08edd

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

‎lib/mcp/server/transports/streamable_http_transport.rb‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,10 +1883,10 @@ def validate_accept_header(request, required_types)
18831883
nil
18841884
end
18851885

1886+
# Empty list members (`application/json,,text/event-stream`), which RFC 9110 section 5.6.1.2 has
1887+
# a recipient parse and ignore, are skipped.
18861888
def parse_accept_header(header)
1887-
header.split(",").map do |part|
1888-
part.split(";").first.strip.downcase
1889-
end
1889+
header.split(",").map { |part| part.split(";").first.to_s.strip.downcase }.reject(&:empty?)
18901890
end
18911891

18921892
def validate_content_type(request)

‎test/mcp/server/transports/streamable_http_transport_test.rb‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,23 @@ def string
17561756
assert_equal 200, response[0]
17571757
end
17581758

1759+
test "POST request with empty members in the Accept header succeeds" do
1760+
["application/json,,text/event-stream", ", application/json, text/event-stream"].each do |accept|
1761+
request = create_rack_request_without_accept(
1762+
"POST",
1763+
"/",
1764+
{
1765+
"CONTENT_TYPE" => "application/json",
1766+
"HTTP_ACCEPT" => accept,
1767+
},
1768+
{ jsonrpc: "2.0", method: "initialize", id: "123", params: initialize_params }.to_json,
1769+
)
1770+
1771+
response = @transport.handle_request(request)
1772+
assert_equal 200, response[0], accept.inspect
1773+
end
1774+
end
1775+
17591776
test "POST request with Accept: */* succeeds" do
17601777
request = create_rack_request_without_accept(
17611778
"POST",
@@ -1886,6 +1903,30 @@ def string
18861903
assert_equal "text/event-stream", response[1]["content-type"]
18871904
end
18881905

1906+
test "GET request with an empty member in the Accept header succeeds" do
1907+
init_request = create_rack_request(
1908+
"POST",
1909+
"/",
1910+
{ "CONTENT_TYPE" => "application/json" },
1911+
{ jsonrpc: "2.0", method: "initialize", id: "123", params: initialize_params }.to_json,
1912+
)
1913+
init_response = @transport.handle_request(init_request)
1914+
session_id = init_response[1]["mcp-session-id"]
1915+
1916+
request = create_rack_request(
1917+
"GET",
1918+
"/",
1919+
{
1920+
"HTTP_MCP_SESSION_ID" => session_id,
1921+
"HTTP_ACCEPT" => ",text/event-stream",
1922+
},
1923+
)
1924+
1925+
response = @transport.handle_request(request)
1926+
assert_equal 200, response[0]
1927+
assert_equal "text/event-stream", response[1]["content-type"]
1928+
end
1929+
18891930
test "GET request with Accept: */* succeeds" do
18901931
init_request = create_rack_request(
18911932
"POST",

0 commit comments

Comments
 (0)