Skip to content

Commit 3a604ee

Browse files
committed
fix: clean up failed HTTP connect handshakes
1 parent 31892a5 commit 3a604ee

2 files changed

Lines changed: 64 additions & 6 deletions

File tree

lib/mcp/client/http.rb

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def connect(client_info: nil, protocol_version: nil, capabilities: {})
6565
})
6666

6767
if response.is_a?(Hash) && response.key?("error")
68+
clear_session
6869
error = response["error"]
6970
raise RequestHandlerError.new(
7071
"Server initialization failed: #{error["message"]}",
@@ -74,6 +75,7 @@ def connect(client_info: nil, protocol_version: nil, capabilities: {})
7475
end
7576

7677
unless response.is_a?(Hash) && response["result"].is_a?(Hash)
78+
clear_session
7779
raise RequestHandlerError.new(
7880
"Server initialization failed: missing result in response",
7981
{ method: MCP::Methods::INITIALIZE },
@@ -82,11 +84,25 @@ def connect(client_info: nil, protocol_version: nil, capabilities: {})
8284
end
8385

8486
@server_info = response["result"]
87+
negotiated_protocol_version = @server_info["protocolVersion"]
88+
unless MCP::Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS.include?(negotiated_protocol_version)
89+
clear_session
90+
raise RequestHandlerError.new(
91+
"Server initialization failed: unsupported protocol version #{negotiated_protocol_version.inspect}",
92+
{ method: MCP::Methods::INITIALIZE },
93+
error_type: :internal_error,
94+
)
95+
end
8596

86-
send_request(request: {
87-
jsonrpc: JsonRpcHandler::Version::V2_0,
88-
method: MCP::Methods::NOTIFICATIONS_INITIALIZED,
89-
})
97+
begin
98+
send_request(request: {
99+
jsonrpc: JsonRpcHandler::Version::V2_0,
100+
method: MCP::Methods::NOTIFICATIONS_INITIALIZED,
101+
})
102+
rescue StandardError
103+
clear_session
104+
raise
105+
end
90106

91107
@connected = true
92108
@server_info

test/mcp/client/http_test.rb

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ def test_connect_is_idempotent
797797
def test_connect_raises_on_jsonrpc_error_response
798798
stub_request(:post, url).to_return(
799799
status: 200,
800-
headers: { "Content-Type" => "application/json" },
800+
headers: { "Content-Type" => "application/json", "Mcp-Session-Id" => "session-abc" },
801801
body: { error: { code: -32602, message: "Unsupported protocol version" } }.to_json,
802802
)
803803

@@ -807,12 +807,15 @@ def test_connect_raises_on_jsonrpc_error_response
807807

808808
assert_includes(error.message, "Unsupported protocol version")
809809
refute_predicate(client, :connected?)
810+
assert_nil(client.session_id)
811+
assert_nil(client.protocol_version)
812+
assert_nil(client.server_info)
810813
end
811814

812815
def test_connect_raises_on_missing_result
813816
stub_request(:post, url).to_return(
814817
status: 200,
815-
headers: { "Content-Type" => "application/json" },
818+
headers: { "Content-Type" => "application/json", "Mcp-Session-Id" => "session-abc" },
816819
body: { jsonrpc: "2.0", id: "x" }.to_json,
817820
)
818821

@@ -822,6 +825,45 @@ def test_connect_raises_on_missing_result
822825

823826
assert_includes(error.message, "missing result in response")
824827
refute_predicate(client, :connected?)
828+
assert_nil(client.session_id)
829+
assert_nil(client.protocol_version)
830+
assert_nil(client.server_info)
831+
end
832+
833+
def test_connect_raises_on_unsupported_negotiated_protocol_version
834+
stub_request(:post, url)
835+
.with { |req| JSON.parse(req.body)["method"] == "initialize" }
836+
.to_return(
837+
status: 200,
838+
headers: { "Content-Type" => "application/json", "Mcp-Session-Id" => "session-abc" },
839+
body: { result: { protocolVersion: "2099-01-01" } }.to_json,
840+
)
841+
842+
error = assert_raises(RequestHandlerError) do
843+
client.connect
844+
end
845+
846+
assert_includes(error.message, 'unsupported protocol version "2099-01-01"')
847+
refute_predicate(client, :connected?)
848+
assert_nil(client.session_id)
849+
assert_nil(client.protocol_version)
850+
assert_nil(client.server_info)
851+
end
852+
853+
def test_connect_clears_session_when_initialized_notification_fails
854+
stub_initialize
855+
stub_request(:post, url)
856+
.with { |req| JSON.parse(req.body)["method"] == "notifications/initialized" }
857+
.to_return(status: 500)
858+
859+
assert_raises(RequestHandlerError) do
860+
client.connect
861+
end
862+
863+
refute_predicate(client, :connected?)
864+
assert_nil(client.session_id)
865+
assert_nil(client.protocol_version)
866+
assert_nil(client.server_info)
825867
end
826868

827869
def test_connected_lifecycle

0 commit comments

Comments
 (0)