Support W3C Trace Context Propagation via _meta per SEP-414#397
Open
koic wants to merge 1 commit into
Open
Conversation
## Motivation and Context SEP-414 (modelcontextprotocol/modelcontextprotocol#414, merged for the 2026-07-28 spec release) documents OpenTelemetry trace context propagation conventions for MCP: the un-prefixed `_meta` keys `traceparent`, `tracestate`, and `baggage` are reserved (an explicit exception to the reverse-DNS prefix rule) so trace context can flow between MCP clients and servers per the W3C Trace Context and Baggage specifications. This follows the TypeScript SDK's approach (typescript-sdk#2270): guarantee `_meta` passthrough, export constants for the reserved key names, and lock the behavior in with regression tests and documentation, without adding an OpenTelemetry dependency (the Python SDK's python-sdk#2381 makes `opentelemetry-api` a hard dependency instead, which conflicts with this SDK's keep-dependencies-minimal policy): - New `MCP::TraceContext` module exposing `TRACEPARENT_META_KEY`, `TRACESTATE_META_KEY`, `BAGGAGE_META_KEY`, and `META_KEYS`. - The server side already passes incoming request `_meta` to tool and prompt handlers untouched via `server_context[:_meta]`; new regression tests pin that guarantee for the three reserved keys. - Every `MCP::Client` request method (`call_tool`, `read_resource`, `get_prompt`, `complete`, `ping`, and the `list_*` methods) gains a `meta:` keyword so Ruby clients can inject trace context (or any other `_meta` entries) into outgoing requests; per SEP-414, trace context should flow on every request, not only tool calls. The caller's hash is never mutated. On `call_tool`, `progress_token` takes precedence over a `progressToken` entry in `meta`, and an empty merge result omits `_meta` entirely, preserving the existing wire format. - README documents the convention and how to bridge the values to a tracing system such as the `opentelemetry-ruby` gems. Scope is intentionally client-to-server: server-initiated requests (`sampling/createMessage`, `roots/list`, `elicitation/create`) do not accept `meta:` yet; propagating trace context on those is a separate follow-up. Resolves modelcontextprotocol#374. ## How Has This Been Tested? - New `test/mcp/trace_context_test.rb` pins the exact reserved key names and the frozen `META_KEYS` list. - New tests in `test/mcp/server_test.rb` assert that `tools/call` and `prompts/get` deliver `traceparent`, `tracestate`, and `baggage` (alongside `progressToken`) to handlers through `server_context[:_meta]` unchanged. - New tests in `test/mcp/client_test.rb` cover the `meta:` keyword: the trace keys are sent in `_meta` on `call_tool` and `read_resource`, a table-driven test exercises the remaining request methods (`tools/list`, `resources/list`, `resources/templates/list`, `prompts/list`, `prompts/get`, `completion/complete`, `ping`), `progress_token` overrides a `progressToken` entry in `meta` without duplicating the key, the caller's `meta` hash is not mutated, and requests without `meta:` serialize exactly as before (wire-format regressions for both the empty `meta` hash and the no-params list request). ## Breaking Changes None. `MCP::TraceContext` and the `meta:` keyword are purely additive, and requests without `meta:` serialize exactly as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
SEP-414 (modelcontextprotocol/modelcontextprotocol#414, merged for the 2026-07-28 spec release) documents OpenTelemetry trace context propagation conventions for MCP: the un-prefixed
_metakeystraceparent,tracestate, andbaggageare reserved (an explicit exception to the reverse-DNS prefix rule) so trace context can flow between MCP clients and servers per the W3C Trace Context and Baggage specifications.This follows the TypeScript SDK's approach (typescript-sdk#2270): guarantee
_metapassthrough, export constants for the reserved key names, and lock the behavior in with regression tests and documentation, without adding an OpenTelemetry dependency (the Python SDK's python-sdk#2381 makesopentelemetry-apia hard dependency instead, which conflicts with this SDK's keep-dependencies-minimal policy):MCP::TraceContextmodule exposingTRACEPARENT_META_KEY,TRACESTATE_META_KEY,BAGGAGE_META_KEY, andMETA_KEYS._metato tool and prompt handlers untouched viaserver_context[:_meta]; new regression tests pin that guarantee for the three reserved keys.MCP::Clientrequest method (call_tool,read_resource,get_prompt,complete,ping, and thelist_*methods) gains ameta:keyword so Ruby clients can inject trace context (or any other_metaentries) into outgoing requests; per SEP-414, trace context should flow on every request, not only tool calls. The caller's hash is never mutated. Oncall_tool,progress_tokentakes precedence over aprogressTokenentry inmeta, and an empty merge result omits_metaentirely, preserving the existing wire format.opentelemetry-rubygems.Scope is intentionally client-to-server: server-initiated requests (
sampling/createMessage,roots/list,elicitation/create) do not acceptmeta:yet; propagating trace context on those is a separate follow-up.Resolves #374.
How Has This Been Tested?
test/mcp/trace_context_test.rbpins the exact reserved key names and the frozenMETA_KEYSlist.test/mcp/server_test.rbassert thattools/callandprompts/getdelivertraceparent,tracestate, andbaggage(alongsideprogressToken) to handlers throughserver_context[:_meta]unchanged.test/mcp/client_test.rbcover themeta:keyword: the trace keys are sent in_metaoncall_toolandread_resource, a table-driven test exercises the remaining request methods (tools/list,resources/list,resources/templates/list,prompts/list,prompts/get,completion/complete,ping),progress_tokenoverrides aprogressTokenentry inmetawithout duplicating the key, the caller'smetahash is not mutated, and requests withoutmeta:serialize exactly as before (wire-format regressions for both the emptymetahash and the no-params list request).Breaking Changes
None.
MCP::TraceContextand themeta:keyword are purely additive, and requests withoutmeta:serialize exactly as before.Types of changes
Checklist