Repository navigation
feat: propagate HTTP context for MCP requests and prefix all span names with mcp - #683
Merged
Merged
Conversation
wangzlei
previously approved these changes
Mar 19, 2026
wangzlei
previously approved these changes
Mar 19, 2026
wangzlei
approved these changes
Mar 19, 2026
liustve
added a commit
that referenced
this pull request
Jul 22, 2026
… headers (#827) ## Summary Even with `OTEL_MCP_SUPPRESS_HTTP_INSTRUMENTATION=true` (the default from #695), inject `traceparent`/`tracestate`/`baggage` into the outgoing HTTP request headers so MCP servers that read trace context only from HTTP headers can join the caller's trace. Fixes #826. ## Background The default from #695 correctly deduplicates the redundant HTTP CLIENT span, but it does so via `suppress_http_instrumentation()`, which also short-circuits the HTTP client instrumentation's own header injection — the only mechanism that would otherwise set `traceparent` on the wire. MCP servers that only participate in HTTP-header propagation (API Gateways, service meshes, non-Python MCP servers, any HTTP endpoint without ADOT MCP instrumentation) therefore receive no context and start a fresh trace, silently breaking E2E propagation for a large class of real-world topologies. #826 has before/after trace evidence from a real AWS deployment showing the client trace dead-ending at `mcp.session` while every server-side hop lands on its own fresh trace ID. ## Design Compatible with the [OTel MCP semantic convention](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/mcp.md): > MCP server instrumentation SHOULD, by default, use context extracted from MCP `params._meta` as a parent for MCP server span and SHOULD link current ambient context, if it's present. The spec designates `_meta` as the MCP parent channel and HTTP context as an orthogonal transport signal (a span link on ADOT-instrumented servers). Nothing in the spec forbids injecting into HTTP headers as well; the existing ADOT server-side wrapper only extracts from `_meta`, so double-parenting on ADOT-instrumented servers is not a risk. This change adds HTTP-header propagation as a compatible fallback for non-ADOT servers while leaving the existing `_meta` propagation and the span-suppression semantics unchanged. ## Implementation `ClientWrapper.wrap_http_client` now wraps the transport's `httpx_client_factory` (or supplies its own pre-built `http_client` for the newer `streamable_http_client(url, http_client=...)` API) so every `httpx.AsyncClient` the MCP transport uses has a request event hook that calls `self._propagators.inject(carrier=request.headers)`. The hook takes the OTel context *at request-send time*, so per-tool-call parents (which change during a shared MCP session) are honored correctly. If httpx or MCP is not importable, or the wrapped function signature doesn't match, the patcher no-ops silently. The change is purely additive with respect to `OTEL_MCP_SUPPRESS_HTTP_INSTRUMENTATION`: | `OTEL_MCP_SUPPRESS_HTTP_INSTRUMENTATION` | HTTP CLIENT span | `traceparent` HTTP header | | --- | --- | --- | | `true` (default) — before this PR | suppressed ✓ | **absent ✗** (bug) | | `true` (default) — after this PR | suppressed ✓ | **present ✓** | | `false` — before this PR | present | present | | `false` — after this PR | present | present | Span suppression, ping suppression, active-parent respect, tool/prompt/resource spans, and error handling across stdio+http+sse transports are unchanged. ## Testing **New tests** in `test_mcp_instrumentor.py`: - `test_http_headers_carry_trace_context_regardless_of_suppression` — exercises `OTEL_MCP_SUPPRESS_HTTP_INSTRUMENTATION` in `{true, false, default}` and asserts every outgoing MCP HTTP request carries a well-formed W3C `traceparent` header (validates format: `00-<32hex trace-id>-<16hex span-id>-<2hex flags>`). - `test_http_headers_prebuilt_client_gets_hook` — verifies the hook is installed on a user-supplied pre-built `http_client` (newer `streamable_http_client(url, http_client=...)` API). **Existing tests** (15 total including the two new ones): ``` $ python -m pytest test_mcp_instrumentor.py ======================== 15 passed, 1 warning in 20.89s ======================== ``` **Lint**: ``` $ black --check aws-opentelemetry-distro/src/.../_wrappers.py .../test_mcp_instrumentor.py All done! ✨ 🍰 ✨ 2 files would be left unchanged. $ flake8 aws-opentelemetry-distro/src/.../_wrappers.py .../test_mcp_instrumentor.py (no output) $ pylint --rcfile pyproject.toml aws-opentelemetry-distro/src/.../_wrappers.py Your code has been rated at 9.01/10 (previous run: 9.01/10, +0.00) ``` ## Related PRs - #683 — first added HTTP context propagation for MCP requests (via `_meta` + `_handle_post_request` restoration) - #695 — introduced `OTEL_MCP_SUPPRESS_HTTP_INSTRUMENTATION` defaulting to `true` (this is the PR whose default silently broke non-ADOT-server topologies) --------- Co-authored-by: Steve Liu <liustve@amazon.com>
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.
Description of changes:
prefixes all MCP span names with mcp for easier identification in traces similar to http spans
unsuppress and fix HTTP span context propagation by wrapping StreamableHTTPTransport._handle_post_request to restore trace context from the JSON-RPC _meta field, so http spans are correctly parented under their MCP request span instead of the session span
example trace:

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.