OpenTelemetry tracing
MCP 2026-07-28 (SEP-414) folds tracing into the protocol: a client that wants its child operations attached to a parent span passes the W3C Trace Context (traceparent / tracestate) in the request _meta block. Servers that re-emit traces use these headers to build the parent / child relationship.
mcptest ships the library for that propagation today. Wiring it to a real OpenTelemetry SDK so a run lands spans in Jaeger / otel-cli / any OTLP collector is a follow-up.
Library surface
mcptest_core::otel:
| Item | Purpose |
|---|---|
TraceParent { trace_id, parent_id, flags } | Typed W3C traceparent per RFC 9462. |
TraceState(String) | Opaque vendor list (round-trip lossless). |
TraceFlags(u8) | Sampling + reserved bits with .sampled() accessor. |
TraceContextError | Typed parse errors (BadShape / UnsupportedVersion / BadTraceId / ...). |
TraceParent::parse(s) | Strict parser (lowercase hex, length-checked, refuses all-zero ids). |
TraceParent::to_wire() | Format back to the wire form, byte-stable. |
inject_into_meta(&mut meta, parent, state) | Set _meta.traceparent + optional tracestate. |
extract_from_meta(meta) -> Option<...> | Read trace context off _meta. |
RUN_SPAN_NAME / TEST_SPAN_NAME / tool_call_span_name(method) | Stable span-name conventions. |
Wire shape
A request that propagates a parent span:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "weather",
"arguments": { "loc": "SFO" },
"_meta": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
"tracestate": "vendor1=opaqueValue,vendor2=opaqueValue"
}
}
}
traceparent is required when propagating. tracestate is optional (an empty vendor list is omitted by spec); the library mirrors that by skipping the key when the value is empty.
Strict parsing
TraceParent::parse matches RFC 9462 exactly:
- Four dash-separated fields:
version-trace_id-parent_id-flags. - Version must be
00(the only supported value). trace_idis 32 lowercase hex characters; uppercase is rejected.parent_idis 16 lowercase hex characters.flagsis 2 lowercase hex characters.- All-zero
trace_idorparent_idis rejected (the W3C spec reserves them).
Anything else returns a typed [TraceContextError].
Span naming
The conventions in span_names.rs align with the OTel semantic convention for RPC clients:
| Span | Name | Notes |
|---|---|---|
| Top-level run | mcptest.run | One per mcptest run invocation. |
| Per-test | mcptest.test | Test name on the span attributes (mcptest.test.name), not the span name (cardinality). |
| Per tool call | mcp/<method> | Tool name is an attribute. |
What ships today
- Typed
TraceParent/TraceState/TraceFlags. - Strict parser and inject / extract helpers.
- Stable span-name constants and
tool_call_span_namebuilder. - Unit tests covering the happy path, every reject path, the empty-tracestate skip, the round-trip stability, and the all-zero ID guard.
Planned follow-up
- Real OTel SDK integration (
opentelemetry-sdk,opentelemetry-otlpfor a default exporter). - Span emission for
mcptest.run/mcptest.test/mcp/<method>across the runner lifecycle, with the attributes the spec calls for (mcptest.test.name,mcp.tool.name,mcp.method, etc.). - CLI flags:
--otel-exporter <otlp|none>and--otel-endpoint <url>; env vars:OTEL_EXPORTER_OTLP_ENDPOINTper OTel convention. - A suite-level assertion (
assert_traceparent_presentor similar) so a YAML can require the server echoed the context back.
Cross-references
mcptest_core::otelfor the library.- SEP-414 + RFC 9462 for the spec.