Merge pull request #463 from koic/work_conformance_client_merged

[Conformance] Report expected client terminal failures quietly
This commit is contained in:
Koichi ITO 2026-07-20 01:05:02 +09:00 • committed by GitHub
commit 72400ca12b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -163,20 +163,28 @@ oauth = scenario.start_with?("auth/") ? build_provider_for(scenario, conformance
transport = MCP::Client::HTTP.new(url: server_url, oauth: oauth) transport = MCP::Client::HTTP.new(url: server_url, oauth: oauth)
client = MCP::Client.new(transport: transport) client = MCP::Client.new(transport: transport)
capabilities = scenario == "elicitation-sep1034-client-defaults" ? { elicitation: {} } : {} capabilities = scenario == "elicitation-sep1034-client-defaults" ? { elicitation: {} } : {}
client.connect( # The conformance harness asserts on the observed protocol exchange, not on the client's exit status,
# and echoes the client's output only when the exit status is non-zero. Several negative auth scenarios
# (e.g. `auth/resource-mismatch`, `auth/scope-retry-limit`) are expected to drive the client into
# a terminal failure: the SDK correctly refuses a mismatched resource or stops after the scope step-up
# retry limit. Report such an expected failure as a one-line reason and exit cleanly so the run stays
# free of noise, while letting any unexpected error (a real SDK bug) still raise with a full backtrace
# and a failing exit status.
begin
client.connect(
client_info: { name: "ruby-sdk-conformance-client", version: MCP::VERSION }, client_info: { name: "ruby-sdk-conformance-client", version: MCP::VERSION },
capabilities: capabilities, capabilities: capabilities,
) )
case scenario case scenario
when "initialize" when "initialize"
client.tools client.tools
when "tools_call" when "tools_call"
tools = client.tools tools = client.tools
add_numbers = tools.find { |t| t.name == "add_numbers" } add_numbers = tools.find { |t| t.name == "add_numbers" }
abort("Tool add_numbers not found") unless add_numbers abort("Tool add_numbers not found") unless add_numbers
client.call_tool(tool: add_numbers, arguments: { a: 1, b: 2 }) client.call_tool(tool: add_numbers, arguments: { a: 1, b: 2 })
when "sse-retry" when "sse-retry"
# SEP-1699: the server closes the tools/call SSE stream right after a priming event. # SEP-1699: the server closes the tools/call SSE stream right after a priming event.
# The transport waits the server's `retry:` interval, reconnects with a GET carrying `Last-Event-ID`, # The transport waits the server's `retry:` interval, reconnects with a GET carrying `Last-Event-ID`,
# and receives the tool result on the resumed stream; the harness verifies the reconnect, # and receives the tool result on the resumed stream; the harness verifies the reconnect,
@ -185,7 +193,7 @@ when "sse-retry"
test_reconnection = tools.find { |t| t.name == "test_reconnection" } test_reconnection = tools.find { |t| t.name == "test_reconnection" }
abort("Tool test_reconnection not found") unless test_reconnection abort("Tool test_reconnection not found") unless test_reconnection
client.call_tool(tool: test_reconnection, arguments: {}) client.call_tool(tool: test_reconnection, arguments: {})
when "elicitation-sep1034-client-defaults" when "elicitation-sep1034-client-defaults"
# SEP-1034: the server sends `elicitation/create` on the tools/call SSE stream with every property declaring # SEP-1034: the server sends `elicitation/create` on the tools/call SSE stream with every property declaring
# a `default` and none required. The handler accepts and fills the omitted fields from those defaults. # a `default` and none required. The handler accepts and fills the omitted fields from those defaults.
client.on_elicitation do |params| client.on_elicitation do |params|
@ -198,7 +206,7 @@ when "elicitation-sep1034-client-defaults"
abort("Tool test_client_elicitation_defaults not found") unless defaults_tool abort("Tool test_client_elicitation_defaults not found") unless defaults_tool
client.call_tool(tool: defaults_tool, arguments: {}) client.call_tool(tool: defaults_tool, arguments: {})
when %r|\Aauth/| when %r|\Aauth/|
# Auth-only scenarios: the protocol-level checks (PRM/AS metadata, DCR, PKCE, token usage) # Auth-only scenarios: the protocol-level checks (PRM/AS metadata, DCR, PKCE, token usage)
# are observed by the conformance server during `connect` and the subsequent request below. # are observed by the conformance server during `connect` and the subsequent request below.
# Listing tools forces a second authenticated MCP request so the bearer token usage check fires. # Listing tools forces a second authenticated MCP request so the bearer token usage check fires.
@ -212,6 +220,10 @@ when %r|\Aauth/|
abort("No tool exposed by conformance server for #{scenario}") unless tool abort("No tool exposed by conformance server for #{scenario}") unless tool
client.call_tool(tool: tool, arguments: {}) client.call_tool(tool: tool, arguments: {})
end end
else else
abort("Unknown or unsupported scenario: #{scenario}") abort("Unknown or unsupported scenario: #{scenario}")
end
rescue MCP::Client::OAuth::Flow::AuthorizationError, MCP::Client::RequestHandlerError, Faraday::Error => e
warn("#{scenario}: client ended in the expected terminal failure (#{e.class}: #{e.message})")
exit
end end