|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require "test_helper" |
| 4 | + |
| 5 | +module MCP |
| 6 | + class RequestEnvelopeTest < ActiveSupport::TestCase |
| 7 | + test "exposes the reserved SEP-2575 meta key names" do |
| 8 | + # The exact strings are wire vocabulary shared with other SDKs. |
| 9 | + assert_equal "io.modelcontextprotocol/protocolVersion", RequestEnvelope::PROTOCOL_VERSION_META_KEY |
| 10 | + assert_equal "io.modelcontextprotocol/clientInfo", RequestEnvelope::CLIENT_INFO_META_KEY |
| 11 | + assert_equal "io.modelcontextprotocol/clientCapabilities", RequestEnvelope::CLIENT_CAPABILITIES_META_KEY |
| 12 | + assert_equal "io.modelcontextprotocol/logLevel", RequestEnvelope::LOG_LEVEL_META_KEY |
| 13 | + end |
| 14 | + |
| 15 | + test ".modern? returns true when the full required triple is present" do |
| 16 | + assert RequestEnvelope.modern?(modern_params) |
| 17 | + end |
| 18 | + |
| 19 | + test ".modern? returns true for string keys" do |
| 20 | + params = { |
| 21 | + "_meta" => { |
| 22 | + "io.modelcontextprotocol/protocolVersion" => "2026-07-28", |
| 23 | + "io.modelcontextprotocol/clientInfo" => { "name" => "c", "version" => "1" }, |
| 24 | + "io.modelcontextprotocol/clientCapabilities" => {}, |
| 25 | + }, |
| 26 | + } |
| 27 | + |
| 28 | + assert RequestEnvelope.modern?(params) |
| 29 | + end |
| 30 | + |
| 31 | + test ".modern? returns false for a partial triple" do |
| 32 | + params = modern_params |
| 33 | + params[:_meta].delete(:"io.modelcontextprotocol/clientCapabilities") |
| 34 | + |
| 35 | + refute RequestEnvelope.modern?(params) |
| 36 | + end |
| 37 | + |
| 38 | + test ".modern? returns false for legacy _meta entries such as progressToken" do |
| 39 | + refute RequestEnvelope.modern?({ name: "echo", _meta: { progressToken: "token" } }) |
| 40 | + end |
| 41 | + |
| 42 | + test ".modern? returns false without params or _meta" do |
| 43 | + refute RequestEnvelope.modern?(nil) |
| 44 | + refute RequestEnvelope.modern?({}) |
| 45 | + refute RequestEnvelope.modern?({ name: "echo" }) |
| 46 | + refute RequestEnvelope.modern?({ _meta: nil }) |
| 47 | + end |
| 48 | + |
| 49 | + test ".parse! returns a frozen envelope with the triple and optional log level" do |
| 50 | + params = modern_params |
| 51 | + params[:_meta][:"io.modelcontextprotocol/logLevel"] = "warning" |
| 52 | + |
| 53 | + envelope = RequestEnvelope.parse!(params) |
| 54 | + |
| 55 | + assert_predicate envelope, :frozen? |
| 56 | + assert_equal "2026-07-28", envelope.protocol_version |
| 57 | + assert_equal({ name: "test_client", version: "1.0.0" }, envelope.client_info) |
| 58 | + assert_equal({ elicitation: {} }, envelope.client_capabilities) |
| 59 | + assert_equal "warning", envelope.log_level |
| 60 | + end |
| 61 | + |
| 62 | + test ".parse! leaves log_level nil when absent" do |
| 63 | + assert_nil RequestEnvelope.parse!(modern_params).log_level |
| 64 | + end |
| 65 | + |
| 66 | + test ".parse! raises UnsupportedProtocolVersionError with the SEP-2575 data shape" do |
| 67 | + params = modern_params(version: "2025-11-25") |
| 68 | + |
| 69 | + error = assert_raises(Server::UnsupportedProtocolVersionError) do |
| 70 | + RequestEnvelope.parse!(params) |
| 71 | + end |
| 72 | + |
| 73 | + assert_equal ErrorCodes::UNSUPPORTED_PROTOCOL_VERSION, error.error_code |
| 74 | + assert_equal Configuration::SUPPORTED_MODERN_PROTOCOL_VERSIONS, error.error_data[:supported] |
| 75 | + assert_equal "2025-11-25", error.error_data[:requested] |
| 76 | + end |
| 77 | + |
| 78 | + test ".parse! raises an invalid request error when the triple is incomplete" do |
| 79 | + params = modern_params |
| 80 | + params[:_meta].delete(:"io.modelcontextprotocol/clientInfo") |
| 81 | + |
| 82 | + error = assert_raises(Server::RequestHandlerError) do |
| 83 | + RequestEnvelope.parse!(params) |
| 84 | + end |
| 85 | + |
| 86 | + assert_equal :invalid_request, error.error_type |
| 87 | + end |
| 88 | + |
| 89 | + test ".parse! raises an invalid request error when a triple member has the wrong type" do |
| 90 | + params = modern_params |
| 91 | + params[:_meta][:"io.modelcontextprotocol/clientCapabilities"] = "not-a-hash" |
| 92 | + |
| 93 | + error = assert_raises(Server::RequestHandlerError) do |
| 94 | + RequestEnvelope.parse!(params) |
| 95 | + end |
| 96 | + |
| 97 | + assert_equal :invalid_request, error.error_type |
| 98 | + end |
| 99 | + |
| 100 | + private |
| 101 | + |
| 102 | + def modern_params(version: "2026-07-28") |
| 103 | + { |
| 104 | + name: "echo", |
| 105 | + _meta: { |
| 106 | + "io.modelcontextprotocol/protocolVersion": version, |
| 107 | + "io.modelcontextprotocol/clientInfo": { name: "test_client", version: "1.0.0" }, |
| 108 | + "io.modelcontextprotocol/clientCapabilities": { elicitation: {} }, |
| 109 | + }, |
| 110 | + } |
| 111 | + end |
| 112 | + end |
| 113 | +end |
0 commit comments