Skip to content

Commit 1625a4d

Browse files
committed
⚗️ Continue try connecting trace
Signed-off-by: Evaline Ju <69598118+evaline-ju@users.noreply.github.com>
1 parent 40a930d commit 1625a4d

4 files changed

Lines changed: 33 additions & 34 deletions

File tree

a2a/weather_service/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ dependencies = [
1818
"langchain-mcp-adapters>=0.1.0",
1919
"python-keycloak>=5.5.1",
2020
"opentelemetry-exporter-otlp",
21+
# Auto-instrument httpx so outgoing MCP tool calls propagate traceparent
22+
"opentelemetry-instrumentation-httpx>=0.49b0",
2123
# OpenTelemetry GenAI semantic convention instrumentation
2224
# Emits spans with gen_ai.* attributes for MLflow compatibility
2325
"opentelemetry-instrumentation-openai>=0.34b0",

a2a/weather_service/src/weather_service/agent.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,10 @@ async def execute(self, context: RequestContext, event_queue: EventQueue):
119119
# Test MCP connection first
120120
logger.info(f'Attempting to connect to MCP server at: {os.getenv("MCP_URL", "http://localhost:8000/sse")}')
121121

122-
# Extract current trace context for propagation to Envoy/MCP
123-
traceparent = None
124-
root_span = get_root_span()
125-
if root_span and root_span.is_recording():
126-
span_context = root_span.get_span_context()
127-
if span_context.is_valid:
128-
# Format traceparent according to W3C Trace Context spec
129-
# Format: version-trace_id-span_id-trace_flags
130-
traceparent = f"00-{format(span_context.trace_id, '032x')}-{format(span_context.span_id, '016x')}-{format(span_context.trace_flags, '02x')}"
131-
logger.info(f'Propagating trace context to MCP: {traceparent}')
132-
133-
mcpclient = get_mcpclient(traceparent=traceparent)
122+
# Trace context propagation to MCP gateway is handled automatically
123+
# by opentelemetry-instrumentation-httpx, which injects the current
124+
# span's traceparent header on every outgoing httpx request.
125+
mcpclient = get_mcpclient()
134126

135127
# Try to get tools to verify connection
136128
try:

a2a/weather_service/src/weather_service/graph.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,18 @@
1212
class ExtendedMessagesState(MessagesState):
1313
final_answer: str = ""
1414

15-
def get_mcpclient(traceparent: str | None = None):
16-
"""
17-
Create an MCP client with optional trace context propagation.
15+
def get_mcpclient():
16+
"""Create an MCP client.
1817
19-
Args:
20-
traceparent: W3C Trace Context traceparent header value
21-
Format: "00-{trace-id}-{parent-id}-{trace-flags}"
18+
Trace context propagation (traceparent headers) to the MCP gateway is
19+
handled automatically by opentelemetry-instrumentation-httpx, which
20+
injects the current span's context on every outgoing HTTP request.
2221
"""
23-
server_config = {
24-
"url": os.getenv("MCP_URL", "http://localhost:8000/mcp"),
25-
"transport": os.getenv("MCP_TRANSPORT", "streamable_http"),
26-
}
27-
28-
# Add traceparent header if provided for distributed tracing through Envoy
29-
if traceparent:
30-
server_config["headers"] = {
31-
"traceparent": traceparent
32-
}
33-
3422
return MultiServerMCPClient({
35-
"math": server_config
23+
"math": {
24+
"url": os.getenv("MCP_URL", "http://localhost:8000/mcp"),
25+
"transport": os.getenv("MCP_TRANSPORT", "streamable_http"),
26+
}
3627
})
3728

3829
async def get_graph(client) -> StateGraph:

a2a/weather_service/src/weather_service/observability.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,18 @@ def setup_observability() -> None:
125125
W3CBaggagePropagator(),
126126
]))
127127

128+
# Instrument httpx for automatic traceparent propagation on outgoing requests.
129+
# This is critical for distributed tracing: langchain-mcp-adapters uses httpx
130+
# for streamable_http transport, so each MCP tool call will automatically carry
131+
# the current span's traceparent header to the MCP gateway (Envoy).
132+
try:
133+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
134+
HTTPXClientInstrumentor().instrument()
135+
logger.info("httpx instrumented for automatic trace context propagation")
136+
except ImportError:
137+
logger.warning("opentelemetry-instrumentation-httpx not available - "
138+
"MCP tool calls will not propagate trace context")
139+
128140
# Instrument OpenAI for GenAI semantic conventions
129141
try:
130142
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
@@ -421,10 +433,12 @@ async def tracing_middleware(request: Request, call_next):
421433
except Exception as e:
422434
logger.debug(f"Could not parse request body: {e}")
423435

424-
# Break parent chain to make this a true root span
425-
# Without this, the span would inherit parent from W3C Trace Context headers
426-
empty_ctx = context.Context()
427-
detach_token = context.attach(empty_ctx)
436+
# Extract incoming W3C Trace Context from request headers.
437+
# If the request carries a traceparent (e.g., from Envoy/MCP gateway or
438+
# an upstream orchestrator), the root span becomes a child of that trace.
439+
# This is what connects the agent's spans to the MCP gateway's spans.
440+
incoming_ctx = extract(dict(request.headers))
441+
detach_token = context.attach(incoming_ctx)
428442

429443
try:
430444
# Create root span with correct GenAI naming convention

0 commit comments

Comments
 (0)