Module: MCPClient::RequestMetadata
- Included in:
- JsonRpcCommon
- Defined in:
- lib/mcp_client/request_metadata.rb
Overview
The metadata a request carries and the fingerprint a cached result is
bound to (MCP 2026-07-28 basic/index "_meta", server/utilities/caching):
the reserved protocol keys, the effective parameters of the request this
thread last built, and the evaluation of the host's request_meta that a
cache decision holds for the request it leads to.
Each slot is named after the transport's object_id, so a worker thread
that builds and discards transports keeps nothing of theirs for its life.
Defined Under Namespace
Classes: HeldRequestMeta
Constant Summary collapse
- CACHE_NEUTRAL_META_KEYS =
_metakeys that identify one request rather than what it asks for (the progress token and the W3C trace identifiers): a cached result does not depend on them.baggageis deliberately not among them -- it carries application-defined context (a tenant, a locale), which a server may well vary its result by, so a result cached under one baggage is never served under another. %w[progressToken traceparent tracestate].freeze
- UNRECORDED_PARAMS =
Marks an attempt that has not built its request yet: the parameters of the previous request on this thread say nothing about it.
:unrecorded- OPAQUE_PARAMS =
Marks a request whose effective parameters the transport cannot read: host middleware may rewrite the body after the transport built it, so the parameters the server answers are not the ones a fingerprint of the request would describe. It is not a fingerprint and matches none, so no result is served across it whatever its
cacheScope— "public" permits sharing across callers, not across result-affecting parameters. :opaque- META_PROTOCOL_VERSION =
Reserved
_metakeys (MCP 2026-07-28 basic/index "_meta"). 'io.modelcontextprotocol/protocolVersion'- META_CLIENT_INFO =
'io.modelcontextprotocol/clientInfo'- META_CLIENT_CAPABILITIES =
'io.modelcontextprotocol/clientCapabilities'- META_LOG_LEVEL =
'io.modelcontextprotocol/logLevel'- META_SERVER_INFO =
'io.modelcontextprotocol/serverInfo'- META_SUBSCRIPTION_ID =
'io.modelcontextprotocol/subscriptionId'- PROTECTED_META_KEYS =
Per-request protocol fields the client owns. A host-supplied
_metamay carry anything else (progressToken, trace context, vendor keys), but these are always set from the transport's own state so the body can never disagree with what the transport negotiated (on HTTP the MCP-Protocol-Version header must match the body). [META_PROTOCOL_VERSION, META_CLIENT_INFO, META_CLIENT_CAPABILITIES].freeze
Instance Method Summary collapse
-
#adoptable_request_meta_hold(offered, innermost, method) ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
The offer when it really is the reservation open here, still waiting for the request it was made for.
-
#claimable_request_meta_hold ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
That reservation while the request it was made for may still claim it.
-
#close_request_meta_hold ⇒ void
Close the innermost hold scope.
-
#current_params_fingerprint ⇒ String
The fingerprint of the effective parameters the next request on this transport would carry.
-
#deep_sort_keys(value) ⇒ Object
The value with every nested Hash sorted by key.
-
#held_request_meta ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
The reservation of the innermost operation open on this thread.
-
#held_request_meta_key ⇒ Symbol
This transport's thread-local key for held metadata.
-
#holding_request_meta(method) { ... } ⇒ Object
Reserve the evaluation of the host's
request_metafor the requestmethodthis operation leads to, for the operation's dynamic extent and no longer: however it ends -- a value returned, a reconnect that raised, a caller that swallowed the error -- the reservation goes with it, so no later request on this thread can carry it. -
#note_request_params(params) ⇒ void
Remember the effective parameters a request goes out with, so a result cached from it is bound to them (MCP 2026-07-28 caching: a server may vary a result by host metadata such as a vendor tenant key).
- #note_request_params_pending ⇒ void
-
#offer_request_meta_hold(reservation) ⇒ void
Hand a reservation to the one operation it was opened for: the next operation this transport opens adopts it, and only if it really is the reservation currently innermost here.
-
#offered_request_meta_key ⇒ Symbol
This transport's thread-local key for the reservation offered to the operation about to be opened on it.
-
#open_request_meta_hold(method) ⇒ MCPClient::RequestMetadata::HeldRequestMeta
Open a hold scope without a block, for a caller whose operation spans several transports (a client listing across its servers).
-
#outside_request_meta_hold { ... } ⇒ Object
The boundary a transport crosses when it hands control to host code: a notification listener, a handler for a server-initiated request.
-
#params_fingerprint_of(params) ⇒ String
A stable fingerprint of the metadata that shapes a result: the effective
_metawithout the protocol version, the log level and the per-request identifiers. -
#recorded_request_params ⇒ String, ...
The fingerprint this thread holds, exactly as it stands — never the transport's reading of it.
-
#release_held_request_meta ⇒ void
Drop a held evaluation of the host's request_meta: the decision that took it leads to no request of its own, so the next one evaluates afresh rather than sending metadata read some time ago.
-
#request_params_fingerprint ⇒ String?
The fingerprint of the effective parameters of the request this thread last built.
-
#request_params_key ⇒ Symbol
This transport's thread-local key for the request parameters.
- #restore_request_params(record) ⇒ void
-
#take_offered_request_meta_hold ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
The offer, which no later operation can take up again.
- #withdraw_request_meta_hold ⇒ void
Instance Method Details
#adoptable_request_meta_hold(offered, innermost, method) ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
Returns the offer when it really is the reservation open here, still waiting for the request it was made for.
132 133 134 135 136 |
# File 'lib/mcp_client/request_metadata.rb', line 132 def (offered, innermost, method) return nil if offered.nil? || !offered.equal?(innermost) offered if offered.request_method == method && !offered.spent end |
#claimable_request_meta_hold ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
Returns that reservation while the request it was made for may still claim it.
201 202 203 204 |
# File 'lib/mcp_client/request_metadata.rb', line 201 def held = held unless held.nil? || held.spent end |
#close_request_meta_hold ⇒ void
This method returns an undefined value.
Close the innermost hold scope.
183 184 185 186 187 188 189 190 |
# File 'lib/mcp_client/request_metadata.rb', line 183 def stack = Thread.current[] return nil unless stack.is_a?(Array) stack.pop Thread.current[] = nil if stack.empty? nil end |
#current_params_fingerprint ⇒ String
Returns the fingerprint of the effective parameters the next request on this transport would carry. Reading it evaluates the host's request_meta, and the open operation holds that evaluation for the request the decision leads to instead of spending it on the decision alone. Outside any operation nothing is held at all: an evaluation that no request is waiting for is never kept.
212 213 214 |
# File 'lib/mcp_client/request_metadata.rb', line 212 def current_params_fingerprint params_fingerprint_of(({}, claim: :model)) end |
#deep_sort_keys(value) ⇒ Object
Returns the value with every nested Hash sorted by key.
264 265 266 267 268 269 270 |
# File 'lib/mcp_client/request_metadata.rb', line 264 def deep_sort_keys(value) case value when Hash then value.map { |k, v| [k.to_s, deep_sort_keys(v)] }.sort_by(&:first).to_h when Array then value.map { |v| deep_sort_keys(v) } else value end end |
#held_request_meta ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
Returns the reservation of the innermost operation open on this thread.
194 195 196 197 |
# File 'lib/mcp_client/request_metadata.rb', line 194 def stack = Thread.current[] stack.last if stack.is_a?(Array) end |
#held_request_meta_key ⇒ Symbol
Returns this transport's thread-local key for held metadata.
232 233 234 |
# File 'lib/mcp_client/request_metadata.rb', line 232 def :"mcp_client_held_request_meta_#{object_id}" end |
#holding_request_meta(method) { ... } ⇒ Object
Reserve the evaluation of the host's request_meta for the request
method this operation leads to, for the operation's dynamic extent
and no longer: however it ends -- a value returned, a reconnect that
raised, a caller that swallowed the error -- the reservation goes with
it, so no later request on this thread can carry it.
95 96 97 98 99 100 101 102 |
# File 'lib/mcp_client/request_metadata.rb', line 95 def (method) (method) begin yield ensure end end |
#note_request_params(params) ⇒ void
This method returns an undefined value.
Remember the effective parameters a request goes out with, so a result cached from it is bound to them (MCP 2026-07-28 caching: a server may vary a result by host metadata such as a vendor tenant key).
29 30 31 |
# File 'lib/mcp_client/request_metadata.rb', line 29 def note_request_params(params) Thread.current[request_params_key] = params_fingerprint_of(params) end |
#note_request_params_pending ⇒ void
This method returns an undefined value.
70 71 72 |
# File 'lib/mcp_client/request_metadata.rb', line 70 def note_request_params_pending Thread.current[request_params_key] = UNRECORDED_PARAMS end |
#offer_request_meta_hold(reservation) ⇒ void
This method returns an undefined value.
Hand a reservation to the one operation it was opened for: the next operation this transport opens adopts it, and only if it really is the reservation currently innermost here. The offer is taken up once; an opener that never invokes the operation withdraws it.
144 145 146 147 |
# File 'lib/mcp_client/request_metadata.rb', line 144 def (reservation) Thread.current[] = reservation nil end |
#offered_request_meta_key ⇒ Symbol
Returns this transport's thread-local key for the reservation offered to the operation about to be opened on it.
238 239 240 |
# File 'lib/mcp_client/request_metadata.rb', line 238 def :"mcp_client_offered_request_meta_#{object_id}" end |
#open_request_meta_hold(method) ⇒ MCPClient::RequestMetadata::HeldRequestMeta
Open a hold scope without a block, for a caller whose operation spans
several transports (a client listing across its servers). Every opener
closes it from an ensure.
The operation adopts a reservation only when that very reservation was handed to it (#offer_request_meta_hold) -- the opener naming the operation it made it for, immediately before invoking it. Sharing a method name is not enough: an operation that begins meanwhile (a list a notification listener runs on a server the opener's loop has not reached) would otherwise spend an evaluation weighed for somebody else, sending its tenant, baggage or nonce on the wrong request and leaving the right one to go out under an evaluation nothing weighed.
118 119 120 121 122 123 124 |
# File 'lib/mcp_client/request_metadata.rb', line 118 def (method) stack = (Thread.current[] ||= []) reservation = (, stack.last, method) || HeldRequestMeta.new(method, false, nil, false) stack.push(reservation) reservation end |
#outside_request_meta_hold { ... } ⇒ Object
The boundary a transport crosses when it hands control to host code: a
notification listener, a handler for a server-initiated request. The
reservation the open operation holds is out of reach behind it, so
whatever that code issues -- a nested list, a raw rpc_request, a
fetch_prompts_list, whatever method it names -- reads the host afresh
and is an operation of its own.
171 172 173 174 175 176 177 178 179 |
# File 'lib/mcp_client/request_metadata.rb', line 171 def stack = (Thread.current[] ||= []) stack.push(nil) begin yield ensure end end |
#params_fingerprint_of(params) ⇒ String
A stable fingerprint of the metadata that shapes a result: the
effective _meta without the protocol version, the log level and the
per-request identifiers. The client identity and capabilities stay
in: a server may vary a result by who asks and by the extensions and
features a request advertises, and those change when the host sets
client_info, drops it, declares an extension or registers a handler.
250 251 252 253 254 255 |
# File 'lib/mcp_client/request_metadata.rb', line 250 def params_fingerprint_of(params) = params.is_a?(Hash) ? (params['_meta'] || params[:_meta]) : nil = .is_a?(Hash) ? .transform_keys(&:to_s) : {} = .except(META_PROTOCOL_VERSION, META_LOG_LEVEL, *CACHE_NEUTRAL_META_KEYS) Digest::SHA256.hexdigest(JSON.generate(deep_sort_keys())) end |
#recorded_request_params ⇒ String, ...
The fingerprint this thread holds, exactly as it stands — never the transport's reading of it. Taken when an exchange starts and put back when it ends (HttpTransportBase::CacheSupport#exchange_jsonrpc): a request nested inside it notes parameters of its own, and the host may go on rewriting the metadata it handed the transport while the response is on its way back. A fingerprint is taken when the request is built, so what it describes is the request as sent.
47 48 49 |
# File 'lib/mcp_client/request_metadata.rb', line 47 def recorded_request_params Thread.current[request_params_key] end |
#release_held_request_meta ⇒ void
This method returns an undefined value.
Drop a held evaluation of the host's request_meta: the decision that took it leads to no request of its own, so the next one evaluates afresh rather than sending metadata read some time ago. The scope drops it too when the operation ends; this is for a decision that settles before that.
222 223 224 225 226 227 228 229 |
# File 'lib/mcp_client/request_metadata.rb', line 222 def held = return nil unless held held.evaluated = false held.value = nil nil end |
#request_params_fingerprint ⇒ String?
Returns the fingerprint of the effective parameters of the request this thread last built.
35 36 37 |
# File 'lib/mcp_client/request_metadata.rb', line 35 def request_params_fingerprint Thread.current[request_params_key] end |
#request_params_key ⇒ Symbol
Returns this transport's thread-local key for the request parameters.
258 259 260 |
# File 'lib/mcp_client/request_metadata.rb', line 258 def request_params_key :"mcp_client_request_params_#{object_id}" end |
#restore_request_params(record) ⇒ void
This method returns an undefined value.
53 54 55 |
# File 'lib/mcp_client/request_metadata.rb', line 53 def restore_request_params(record) Thread.current[request_params_key] = record end |
#take_offered_request_meta_hold ⇒ MCPClient::RequestMetadata::HeldRequestMeta?
Returns the offer, which no later operation can take up again.
157 158 159 160 161 |
# File 'lib/mcp_client/request_metadata.rb', line 157 def offered = Thread.current[] Thread.current[] = nil unless offered.nil? offered end |
#withdraw_request_meta_hold ⇒ void
This method returns an undefined value.
150 151 152 153 |
# File 'lib/mcp_client/request_metadata.rb', line 150 def Thread.current[] = nil nil end |