Derive Mcp-Name from the body in the modern transport test helper

## Motivation and Context

#490 and #492 merged independently green but broke each other on main: #492 made the modern path reject
a name-bearing POST whose body carries a target name without `Mcp-Name` (-32020, HTTP 400)
and taught the `modern_rack_request` helper to derive `Mcp-Method` from the body, while #490,
merged in between, added five modern `tools/call` tests that build their requests through that helper
and therefore send no `Mcp-Name`. Since the #492 merge every CI run on main fails those five tests
with 400 where 200 is expected; the library behavior itself is correct.

The helper now derives `Mcp-Name` the same way it derives `Mcp-Method`: from `params.name` or
`params.uri` when the body's method is one of the name-bearing three. That is what a conforming client sends per
the 2026-07-28 specification, and what the TypeScript SDK's client, the bundled `MCP::Client::HTTP`,
and the conformance harness's standard headers all do, so tests built through
the helper model a compliant client by default. `name_header: nil` omits the header for tests of
the requirement itself, symmetric with `method_header: nil`, and an explicit `headers:` entry still overrides
the derived value, which keeps the base64 mismatch tests exercising their divergent names.

## How Has This Been Tested?

The five failing tests pass again without being touched, and the #492 requirement tests
(missing `Mcp-Method`, missing `Mcp-Name`, header mismatches, base64 decoding) still pass:
the transport test file reports 217 runs with zero failures. `bundle exec rake` is green,
including RuboCop and the `--requirements 2025-11-25` conformance baseline.

## Breaking Changes

None. The change is confined to a test helper; the shipped gem is untouched.
This commit is contained in:
Koichi ITO 2026-08-08 15:43:16 +09:00
commit 90b8fd49c8

View file

@ -5357,6 +5357,7 @@ module MCP
test "modern POST without the Mcp-Name header on a name-bearing method returns 400 with -32020" do
response = @transport.handle_request(modern_rack_request(
modern_body("tools/call", { name: "some_tool", arguments: {} }),
name_header: nil,
))
assert_equal 400, response[0]
@ -5788,23 +5789,31 @@ module MCP
end
# Builds a POST request routed to the modern path via the `MCP-Protocol-Version` header.
# Mirrors what a conforming modern client sends: `Mcp-Method` is required on every modern POST,
# so it defaults to the body's method. Pass `method_header: nil` to omit it
# Mirrors what a conforming modern client sends: `Mcp-Method` is required on every modern POST
# and `Mcp-Name` on a name-bearing method whose body carries a target name, so both default to
# the body's values. Pass `method_header: nil` / `name_header: nil` to omit one
# (for tests of the requirement itself) or a String to send a specific value.
def modern_rack_request(body, version: "2026-07-28", headers: {}, method_header: :from_body)
derived_method = if method_header == :from_body
begin
parsed = JSON.parse(body)
parsed["method"] if parsed.is_a?(Hash)
rescue JSON::ParserError
nil
def modern_rack_request(body, version: "2026-07-28", headers: {}, method_header: :from_body,
name_header: :from_body)
parsed = begin
JSON.parse(body)
rescue JSON::ParserError
nil
end
parsed = nil unless parsed.is_a?(Hash)
derived_method = method_header == :from_body ? parsed&.dig("method") : method_header
derived_name = if name_header == :from_body
if StreamableHTTPTransport::NAME_BEARING_METHODS.include?(parsed&.dig("method"))
parsed.dig("params", "name") || parsed.dig("params", "uri")
end
else
method_header
name_header
end
base_headers = { "CONTENT_TYPE" => "application/json", "HTTP_MCP_PROTOCOL_VERSION" => version }
base_headers["HTTP_MCP_METHOD"] = derived_method if derived_method
base_headers["HTTP_MCP_NAME"] = derived_name if derived_name
create_rack_request("POST", "/", base_headers.merge(headers), body)
end