From 3871b114637e26231fd32288fdfa853b6b7b3314 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Tue, 22 Sep 2026 06:21:43 -0600 Subject: [PATCH] Revert "Validate client response IDs before accepting session state" This reverts commit 941d1b593ca6d526659d93c8031698deb6477af0. --- ROADMAP.md | 2 -- docs/src/index.md | 4 --- src/jsonrpc.jl | 14 ++++---- test/response_ids.jl | 81 -------------------------------------------- test/runtests.jl | 2 -- 5 files changed, 6 insertions(+), 97 deletions(-) delete mode 100644 test/response_ids.jl diff --git a/ROADMAP.md b/ROADMAP.md index e69c147..cdc9a27 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -32,8 +32,6 @@ The repository tests these areas: - OAuth 2 and OAuth 3 compatibility. - Stateful and stateless HTTP client/server integration. - Strict JSON-RPC parsing and notification side-effect rules. -- Client result response IDs must match the request before results or session - state are accepted. - MCP Apps resource and tool metadata. - Official MCP conformance scenarios for stateless metadata, capability checks, standard request headers, and `x-mcp-header` behavior. diff --git a/docs/src/index.md b/docs/src/index.md index 8221187..3795176 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -106,10 +106,6 @@ For a stateless client, set The same `initialize_client!` call then performs `server/discover` instead of the legacy initialization handshake. -The client rejects result responses whose JSON-RPC ID does not match the -request. A rejected initialization response does not establish a session or -send an initialized notification. - ## OAuth For OAuth-protected MCP servers, use `OAuth.jl` to acquire a token and attach it diff --git a/src/jsonrpc.jl b/src/jsonrpc.jl index 48cca41..3c844a6 100644 --- a/src/jsonrpc.jl +++ b/src/jsonrpc.jl @@ -108,20 +108,18 @@ function jsonrpc_call( push!(header_pairs, normalize_pair("Mcp-Timeout-Ms", string(timeout_value))) end response = submit_jsonrpc_request(client, body; headers=header_pairs, timeout=timeout) + if String(method) == JSONRPC_METHOD_INITIALIZE + session_header = http_header_value(response.headers, "MCP-Session-Id") + if session_header !== nothing && !isempty(strip(String(session_header))) + client.session_id = String(session_header) + end + end notification && return nothing isempty(response.body) && throw(mcp_error(:jsonrpc_error, "JSON-RPC response from $(client.transport.url) was empty")) if is_event_stream_response(response) data = extract_streamed_jsonrpc_response(client, response, payload["id"]) else data = parse_jsonrpc_response(response.body) - get(data, "id", nothing) == payload["id"] || - throw(mcp_error(:jsonrpc_error, "JSON-RPC response did not match request $(payload["id"])")) - end - if method_str == JSONRPC_METHOD_INITIALIZE - session_header = http_header_value(response.headers, "MCP-Session-Id") - if session_header !== nothing && !isempty(strip(String(session_header))) - client.session_id = String(session_header) - end end return get(data, "result", nothing) end diff --git a/test/response_ids.jl b/test/response_ids.jl deleted file mode 100644 index e7672c0..0000000 --- a/test/response_ids.jl +++ /dev/null @@ -1,81 +0,0 @@ -using Test, HTTP, JSON, ModelContextProtocol - -@testset "Client response IDs" begin - response_id = Ref{Any}(:matching) - response_error = Ref(false) - notifications = Ref(0) - stub = HTTP.serve!("127.0.0.1", 0; verbose=false) do req - payload = JSON.parse(String(req.body)) - if !haskey(payload, "id") - notifications[] += 1 - return HTTP.Response(202) - end - body = Dict{String,Any}("jsonrpc" => "2.0") - if response_id[] !== :missing - body["id"] = response_id[] === :matching ? payload["id"] : - response_id[] === :numeric ? parse(Int, payload["id"]) : response_id[] - end - if response_error[] - body["error"] = Dict("code" => -32600, "message" => "Invalid Request", "data" => "detail") - else - body["result"] = Dict("tools" => Any[]) - end - HTTP.Response(200, ["Content-Type" => "application/json", "MCP-Session-Id" => "test-session"], JSON.json(body)) - end - try - port = ModelContextProtocol.bound_http_port(stub) - transport = ModelContextProtocol.MCPTransportDescriptor(kind=:http, url="http://127.0.0.1:$(port)/mcp") - discovery = ModelContextProtocol.MCPDiscovery(manifest=Dict{String,Any}(), transports=[transport], default_transport=transport) - for version in (ModelContextProtocol.DEFAULT_PROTOCOL_VERSION, ModelContextProtocol.PROTOCOL_VERSION_2026_07_28) - @testset "$version" begin - client = prepare_manual_client(discovery; config=MCPClientConfig(protocol_version=version)) - client.initialized = true - @test list_tools(client)["tools"] == Any[] - for id in ("another-request", :missing, nothing, :numeric, true) - response_id[] = id - @test_throws ModelContextProtocol.MCPError list_tools(client) - end - response_id[] = :matching - @test list_tools(client)["tools"] == Any[] - response_error[] = true - for id in (:matching, :missing, nothing) - response_id[] = id - err = try - list_tools(client) - catch err - err - end - @test err isa ModelContextProtocol.MCPError - @test occursin("code=-32600", sprint(showerror, err)) - @test occursin("detail", sprint(showerror, err)) - end - response_error[] = false - response_id[] = :matching - end - end - client = prepare_manual_client(discovery) - response_error[] = true - for id in (:matching, :missing, nothing) - response_id[] = id - @test_throws ModelContextProtocol.MCPError initialize_client!(client) - @test client.session_id === nothing - @test client.session === nothing - @test !client.initialized - @test notifications[] == 0 - end - response_error[] = false - response_id[] = "another-request" - @test_throws ModelContextProtocol.MCPError initialize_client!(client) - @test client.session_id === nothing - @test client.session === nothing - @test !client.initialized - @test notifications[] == 0 - response_id[] = :matching - @test initialize_client!(client)["tools"] == Any[] - @test client.session_id == "test-session" - @test client.initialized - @test notifications[] == 1 - finally - close(stub) - end -end diff --git a/test/runtests.jl b/test/runtests.jl index 19846e7..0146754 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,8 +5,6 @@ using OAuth using Sockets using ModelContextProtocol -include("response_ids.jl") - @testset "Authentication parameter whitespace" begin for whitespace in (" ", "\t", " \t "), quoted in (false, true) metadata = "https://example.com/meta"