Skip to content

Commit 0a104d4

Browse files
committed
Return 404 for invalid session ID in handle_delete
## 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. `handle_delete` was unconditionally calling `cleanup_session` and returning 200 even for nonexistent session IDs. 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 tests for DELETE with invalid session ID, and lifecycle tests for delete-then-POST and delete-then-DELETE scenarios. ## Breaking Change DELETE requests with an invalid session ID now return HTTP 404 "Session not found" instead of HTTP 200. However, this change is considered a bug fix because it brings the behavior into compliance with the MCP specification.
1 parent f4aab5c commit 0a104d4

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

lib/mcp/server/transports/streamable_http_transport.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,10 @@ def handle_delete(request)
162162
[{ error: "Missing session ID" }.to_json],
163163
] unless session_id
164164

165+
return session_not_found_response unless session_exists?(session_id)
166+
165167
cleanup_session(session_id)
168+
166169
success_response
167170
end
168171

test/mcp/server/transports/streamable_http_transport_test.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,84 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
332332
assert body["success"]
333333
end
334334

335+
test "handles DELETE request with invalid session ID" do
336+
request = create_rack_request(
337+
"DELETE",
338+
"/",
339+
{ "HTTP_MCP_SESSION_ID" => "invalid_id" },
340+
)
341+
342+
response = @transport.handle_request(request)
343+
assert_equal 404, response[0]
344+
assert_equal({ "Content-Type" => "application/json" }, response[1])
345+
346+
body = JSON.parse(response[2][0])
347+
assert_equal "Session not found", body["error"]
348+
end
349+
350+
test "POST returns 404 after session is deleted" do
351+
init_request = create_rack_request(
352+
"POST",
353+
"/",
354+
{ "CONTENT_TYPE" => "application/json" },
355+
{ jsonrpc: "2.0", method: "initialize", id: "init" }.to_json,
356+
)
357+
init_response = @transport.handle_request(init_request)
358+
session_id = init_response[1]["Mcp-Session-Id"]
359+
360+
delete_request = create_rack_request(
361+
"DELETE",
362+
"/",
363+
{ "HTTP_MCP_SESSION_ID" => session_id },
364+
)
365+
@transport.handle_request(delete_request)
366+
367+
post_request = create_rack_request(
368+
"POST",
369+
"/",
370+
{
371+
"CONTENT_TYPE" => "application/json",
372+
"HTTP_MCP_SESSION_ID" => session_id,
373+
},
374+
{ jsonrpc: "2.0", method: "ping", id: "456" }.to_json,
375+
)
376+
response = @transport.handle_request(post_request)
377+
assert_equal 404, response[0]
378+
379+
body = JSON.parse(response[2][0])
380+
assert_equal "Session not found", body["error"]
381+
end
382+
383+
test "DELETE returns 404 after session is already deleted" do
384+
init_request = create_rack_request(
385+
"POST",
386+
"/",
387+
{ "CONTENT_TYPE" => "application/json" },
388+
{ jsonrpc: "2.0", method: "initialize", id: "init" }.to_json,
389+
)
390+
init_response = @transport.handle_request(init_request)
391+
session_id = init_response[1]["Mcp-Session-Id"]
392+
393+
first_delete = create_rack_request(
394+
"DELETE",
395+
"/",
396+
{ "HTTP_MCP_SESSION_ID" => session_id },
397+
)
398+
response = @transport.handle_request(first_delete)
399+
assert_equal 200, response[0]
400+
401+
second_delete = create_rack_request(
402+
"DELETE",
403+
"/",
404+
{ "HTTP_MCP_SESSION_ID" => session_id },
405+
)
406+
response = @transport.handle_request(second_delete)
407+
assert_equal 404, response[0]
408+
409+
body = JSON.parse(response[2][0])
410+
assert_equal "Session not found", body["error"]
411+
end
412+
335413
test "handles DELETE request with missing session ID" do
336414
request = create_rack_request(
337415
"DELETE",

0 commit comments

Comments
 (0)