Return 404 for invalid session ID in handle_regular_request

## Motivation and Context

The MCP specification requires that when a server receives a request containing
a session ID that is no longer valid, it MUST respond with HTTP 404 Not Found.
The `handle_get` path already returned 404 correctly, but `handle_regular_request`
(the POST path) was returning 400 "Invalid session ID" instead.

Ref: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management

> The server MAY terminate the session at any time, after which it MUST respond
> to requests containing that session ID with HTTP 404 Not Found.

## How Has This Been Tested?

Added a test for POST requests with an invalid session ID, verifying that the
response is 404 with "Session not found" error message. All existing tests pass.

## Breaking Change

POST requests with an invalid session ID now return HTTP 404 "Session not found"
instead of HTTP 400 "Invalid session ID". Clients that match on the 400 status
code or the old error message will need to be updated.

However, this change is considered a bug fix because it brings the behavior into compliance with
the MCP specification.
This commit is contained in:
Koichi ITO 2026-03-17 01:13:48 +09:00
commit cf1d578433
2 changed files with 20 additions and 1 deletions

View file

@ -261,7 +261,7 @@ module MCP
unless @stateless
# If session ID is provided, but not in the sessions hash, return an error
if session_id && !@sessions.key?(session_id)
return [400, { "Content-Type" => "application/json" }, [{ error: "Invalid session ID" }.to_json]]
return session_not_found_response
end
end

View file

@ -287,6 +287,25 @@ module MCP
assert_equal "Session not found", body["error"]
end
test "handles POST request with invalid session ID" do
request = create_rack_request(
"POST",
"/",
{
"CONTENT_TYPE" => "application/json",
"HTTP_MCP_SESSION_ID" => "invalid_id",
},
{ jsonrpc: "2.0", method: "ping", id: "456" }.to_json,
)
response = @transport.handle_request(request)
assert_equal 404, response[0]
assert_equal({ "Content-Type" => "application/json" }, response[1])
body = JSON.parse(response[2][0])
assert_equal "Session not found", body["error"]
end
test "handles DELETE request with valid session ID" do
# First create a session with initialize
init_request = create_rack_request(