fix(adk): propagate trace context on outbound LLM and MCP HTTP calls (#2809)
## Problem
The Go ADK runtime extracts inbound W3C trace context and emits a clean
span tree (`POST /` → `invocation` → `invoke_agent` → `generate_content`
/ `execute_tool`), but the HTTP clients it builds for LLM providers and
for HTTP/SSE MCP servers never injected `traceparent`/`tracestate`. A
tracing-aware proxy on that path (e.g. agentgateway) starts a fresh root
trace for every LLM and MCP egress, so those hops are orphaned from the
end-to-end trace and cost/request logs keyed by trace id cannot be
joined back to the invocation.
`#1295` fixed propagation on the inbound controller → agent hop; this is
the outbound half.
## Fix
Wrap the transports built by `models.BuildHTTPClient` and
`mcp.createTransport` with `otelhttp.NewTransport`, the same layer
`remote_a2a_tool.go` already uses for sub-agent A2A calls. The
propagator (TraceContext + Baggage) is already configured globally in
`telemetry.Setup`, so outbound requests now carry the active span
context. The `otelhttp` layer is outermost, after TLS / connect-timeout
/ header injection, so existing header behaviour is unchanged
(`headerRoundTripper` clones the request; the test asserts static
headers still arrive alongside `traceparent`).
Coverage: every provider that goes through `BuildHTTPClient` — OpenAI /
Azure OpenAI / Foundry, Anthropic, Bedrock, Ollama, Gemini (`agent.go`),
embeddings — plus HTTP and SSE MCP transports. `sapaicore.go` builds its
own `http.Client` and is not touched here.
Side effect worth noting: `otelhttp.NewTransport` also records an HTTP
client span per outbound request under the active `generate_content` /
`execute_tool` span (as it already does for remote A2A calls). If a
header-only injection is preferred, this can be swapped for a
`propagation.TraceContext{}.Inject` round-tripper like
`go/core/internal/a2a/client_interceptors.go` uses.
## Tests
- New `TestBuildHTTPClient_InjectsTraceContext` and
`TestCreateTransport_InjectsTraceContext`: start a span with an SDK
tracer provider, issue a request through the built client, and assert
the `traceparent` header carries the parent trace id (the MCP test also
asserts a static header survives).
- Two existing tests asserted on the concrete `*http.Transport` behind
the client and now fail the type assertion because of the new outer
layer. They are reworked to check the same behaviour without depending
on the chain's outermost type: `TestBuildHTTPClient_ConnectTimeout`
asserts against `withConnectTimeout` directly, and
`TestEmbeddingHTTPClientTLS` verifies `TLSInsecureSkipVerify` end-to-end
against an `httptest.NewTLSServer` (strict client rejected, insecure
client accepted).
- `go vet ./adk/...` and `go test -race -skip 'TestE2E.*' ./adk/...`
pass locally (Go 1.27).
Not verified end-to-end against a kind cluster with agentgateway; the
issue reporter's setup would be the ideal confirmation.
Fixes #2550
---
This change was prepared with Claude Code assistance; the diff was
reviewed and the tests above were run locally before opening the PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Signed-off-by: MichaelRunchangYang <runchangyang@gmail.com> T
TigerKid committed
81af4dbe685c8762bc5e24cb9d5d773664959a6e
Parent: ea77d73
Committed by GitHub <noreply@github.com>
on 9/14/2026, 1:01:57 PM