You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Support W3C Trace Context Propagation via _meta per SEP-414
## 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#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.
0 commit comments