Class: MCP::RequestEnvelope
- Inherits:
-
Object
- Object
- MCP::RequestEnvelope
- Defined in:
- lib/mcp/request_envelope.rb
Overview
The per-request _meta envelope of the stateless "modern" lifecycle (MCP 2026-07-28, SEP-2575).
The modern lifecycle has no initialize handshake: every request identifies its protocol version
and client capabilities through reserved _meta keys (plus an optional client identity),
and the server validates each request independently. Servers MUST NOT infer capabilities from
prior requests, which is why the envelope is a per-request value object rather than session state.
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
Constant Summary collapse
- PROTOCOL_VERSION_META_KEY =
"io.modelcontextprotocol/protocolVersion"- CLIENT_INFO_META_KEY =
"io.modelcontextprotocol/clientInfo"- CLIENT_CAPABILITIES_META_KEY =
"io.modelcontextprotocol/clientCapabilities"- LOG_LEVEL_META_KEY =
Optional per-request log level, replacing the
logging/setLevelRPC in the modern lifecycle. Deprecated as of 2026-07-28 (SEP-2577) but still part of the wire format. "io.modelcontextprotocol/logLevel"- SUBSCRIPTION_ID_META_KEY =
Notification-side reserved key (SEP-2575): correlates a notification delivered on a
subscriptions/listenstream (and the stream's closing result) with the JSON-RPC id of thesubscriptions/listenrequest that opened it. Not part of the request envelope triple. "io.modelcontextprotocol/subscriptionId"- SERVER_INFO_META_KEY =
Result-side counterpart of the request envelope: the server's identity rides in the result's
_metaas an optional stamp, not as a top-level field, since the SEP was finalized (spec PR modelcontextprotocol/modelcontextprotocol#3002). A server MAY omit it. "io.modelcontextprotocol/serverInfo"- REQUIRED_META_KEYS =
clientInfois deliberately absent: it became optional after the SEP was finalized (spec PR modelcontextprotocol/modelcontextprotocol#3002), so servers MUST accept envelopes without it. The TypeScript and Python SDKs validate the same required pair. [ PROTOCOL_VERSION_META_KEY, CLIENT_CAPABILITIES_META_KEY, ].freeze
Instance Attribute Summary collapse
-
#client_capabilities ⇒ Object
readonly
client_infoisnilwhen the client chose not to identify itself, which is legal: it is self-reported data and MUST NOT drive behavior or security decisions anyway. -
#client_info ⇒ Object
readonly
client_infoisnilwhen the client chose not to identify itself, which is legal: it is self-reported data and MUST NOT drive behavior or security decisions anyway. -
#log_level ⇒ Object
readonly
client_infoisnilwhen the client chose not to identify itself, which is legal: it is self-reported data and MUST NOT drive behavior or security decisions anyway. -
#protocol_version ⇒ Object
readonly
client_infoisnilwhen the client chose not to identify itself, which is legal: it is self-reported data and MUST NOT drive behavior or security decisions anyway.
Class Method Summary collapse
-
.modern?(params) ⇒ Boolean
A request claims the modern lifecycle when its
_metacarriesio.modelcontextprotocol/protocolVersion, matching the TypeScript SDK's envelope claim and the Python SDK's_has_modern_envelope. -
.parse!(params, request: nil) ⇒ Object
Parses and validates the envelope:
protocolVersionandclientCapabilitiesare required,clientInfois optional.
Instance Method Summary collapse
-
#initialize(protocol_version:, client_capabilities:, client_info: nil, log_level: nil) ⇒ RequestEnvelope
constructor
A new instance of RequestEnvelope.
Constructor Details
#initialize(protocol_version:, client_capabilities:, client_info: nil, log_level: nil) ⇒ RequestEnvelope
Returns a new instance of RequestEnvelope.
109 110 111 112 113 114 115 |
# File 'lib/mcp/request_envelope.rb', line 109 def initialize(protocol_version:, client_capabilities:, client_info: nil, log_level: nil) @protocol_version = protocol_version @client_info = client_info @client_capabilities = client_capabilities @log_level = log_level freeze end |
Instance Attribute Details
#client_capabilities ⇒ Object (readonly)
client_info is nil when the client chose not to identify itself, which is legal:
it is self-reported data and MUST NOT drive behavior or security decisions anyway.
107 108 109 |
# File 'lib/mcp/request_envelope.rb', line 107 def client_capabilities @client_capabilities end |
#client_info ⇒ Object (readonly)
client_info is nil when the client chose not to identify itself, which is legal:
it is self-reported data and MUST NOT drive behavior or security decisions anyway.
107 108 109 |
# File 'lib/mcp/request_envelope.rb', line 107 def client_info @client_info end |
#log_level ⇒ Object (readonly)
client_info is nil when the client chose not to identify itself, which is legal:
it is self-reported data and MUST NOT drive behavior or security decisions anyway.
107 108 109 |
# File 'lib/mcp/request_envelope.rb', line 107 def log_level @log_level end |
#protocol_version ⇒ Object (readonly)
client_info is nil when the client chose not to identify itself, which is legal:
it is self-reported data and MUST NOT drive behavior or security decisions anyway.
107 108 109 |
# File 'lib/mcp/request_envelope.rb', line 107 def protocol_version @protocol_version end |
Class Method Details
.modern?(params) ⇒ Boolean
A request claims the modern lifecycle when its _meta carries io.modelcontextprotocol/protocolVersion,
matching the TypeScript SDK's envelope claim and the Python SDK's _has_modern_envelope.
Classification is deliberately looser than validation: a claimed-but-malformed envelope is
rejected by parse! with -32602 instead of silently flowing through the legacy path,
while _meta without the claim key (progressToken, trace context) stays legacy.
43 44 45 46 47 48 |
# File 'lib/mcp/request_envelope.rb', line 43 def modern?(params) = (params) return false unless .is_a?(Hash) !read(, PROTOCOL_VERSION_META_KEY).nil? end |
.parse!(params, request: nil) ⇒ Object
Parses and validates the envelope: protocolVersion and clientCapabilities are required,
clientInfo is optional. A missing or mistyped field is Invalid params (-32602) naming
the offending keys, the code and shape the spec mandates and the reference SDKs emit.
request is only used to enrich the raised error; callers dispatching notifications can omit it.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/mcp/request_envelope.rb', line 54 def parse!(params, request: nil) = (params) = {} unless .is_a?(Hash) protocol_version = read(, PROTOCOL_VERSION_META_KEY) client_info = read(, CLIENT_INFO_META_KEY) client_capabilities = read(, CLIENT_CAPABILITIES_META_KEY) invalid_keys = [] invalid_keys << PROTOCOL_VERSION_META_KEY unless protocol_version.is_a?(String) invalid_keys << CLIENT_CAPABILITIES_META_KEY unless client_capabilities.is_a?(Hash) invalid_keys << CLIENT_INFO_META_KEY unless client_info.nil? || client_info.is_a?(Hash) unless invalid_keys.empty? raise Server::RequestHandlerError.new( "Invalid params: missing or invalid `#{invalid_keys.join("`, `")}` in `_meta`", request, error_type: :invalid_params, error_code: JsonRpcHandler::ErrorCode::INVALID_PARAMS, ) end unless Configuration.modern_protocol_version?(protocol_version) raise Server::UnsupportedProtocolVersionError.new(protocol_version, request) end new( protocol_version: protocol_version, client_info: client_info, client_capabilities: client_capabilities, log_level: read(, LOG_LEVEL_META_KEY), ) end |