Inject W3C Trace Context into vMCP backend HTTP requests#3814
Merged
ChrisJBurns merged 4 commits intomainfrom Feb 21, 2026
Merged
Inject W3C Trace Context into vMCP backend HTTP requests#3814ChrisJBurns merged 4 commits intomainfrom
ChrisJBurns merged 4 commits intomainfrom
Conversation
c1b9f09 to
048a9e4
Compare
When vMCP calls backend MCP servers, traces appeared as separate, unlinked traces in Tempo instead of being part of the same distributed trace. Add a tracePropagatingRoundTripper to the HTTP transport chain that injects traceparent/tracestate headers into outgoing requests, linking vMCP client spans with backend server spans. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
048a9e4 to
90ef6c4
Compare
jerm-dro
previously approved these changes
Feb 12, 2026
11 tasks
The merge from main into this branch brought in a refactoring that moved per-transport sizeLimitedTransport and httpClient declarations inside each switch case (with different behavior for SSE vs streamable-http). The outer declarations from this branch's original code became unused, causing lint and build failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jerm-dro
approved these changes
Feb 20, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3814 +/- ##
=======================================
Coverage 67.13% 67.14%
=======================================
Files 442 442
Lines 44467 44474 +7
=======================================
+ Hits 29853 29860 +7
- Misses 12277 12280 +3
+ Partials 2337 2334 -3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Summary
tracePropagatingRoundTripperto the vMCP HTTP transport chain that injectstraceparent/tracestateheaders into outgoing backend requests, linking vMCP and backend spans in the same distributed tracetelemetryBackendClient's tracer injection patternotel.GetTextMapPropagator().Inject()instead ofotelhttp.NewTransport()to avoid creating duplicate client spans (telemetry.go already creates them)authRoundTripperand thehttp.RoundTrippercontractContext
When vMCP calls backend MCP servers, traces appeared as separate, unlinked traces in Tempo. The vMCP telemetry middleware correctly creates client spans, but outgoing HTTP requests never received W3C Trace Context headers, so backends created new root spans instead of continuing the trace.
Confirmed via Tempo:
tools/call yardstick-traced_echoandtools/call echohad different trace IDs despite being the same request flow.How trace context flows
This PR closes the gap on the outgoing side of vMCP → backend communication. The full trace chain now works:
Incoming (already implemented): The telemetry middleware (
pkg/telemetry/middleware.go:167-177) extracts trace context from both HTTP headers and the MCP_metafield, with_metataking priority per the MCP OTEL spec.Outgoing (this PR): The
tracePropagatingRoundTripperinjects trace context into HTTP headers on all outgoing requests to backends. This works for every MCP method (tools/call,tools/list,resources/read,prompts/get,initialize, etc.), unlike_metainjection which would only cover methods that carry_metain their params.Callers providing
_metaIf a caller includes
traceparentin the MCP_metafield:{ "jsonrpc": "2.0", "method": "tools/call", "params": { "_meta": { "traceparent": "00-abcdef1234567890abcdef1234567890-1234567890abcdef-01" }, "name": "yardstick-traced_echo", "arguments": {"input": "hello"} }, "id": 2 }The vMCP spans will appear as children within that trace, and the backend spans will chain from there — giving a complete distributed trace from client through vMCP to backend.
Design decisions
_metainjection: HTTP header propagation is the primary mechanism because it works universally for all MCP methods. TheInjectMetaTraceContextinfrastructure exists inpkg/telemetry/propagation.gobut is not used for outgoing requests — this could be a follow-up for methods that support_meta.otelhttp.NewTransport(): That would create duplicate client spans sincetelemetryBackendClientinpkg/vmcp/server/telemetry.goalready createsSpanKindClientspans for each backend operation.otel.GetTextMapPropagator()on every request, the propagator is captured at client creation time. This makes tests parallel-safe (no global state mutation) and is consistent with how the tracer is injected intelemetryBackendClient.Test plan
go test ./pkg/vmcp/client/... -racepasses (all tests run in parallel)task lint-fixreports 0 issues🤖 Generated with Claude Code