Skip to content

Commit 1091806

Browse files
authored
Merge pull request #145 from evaline-ju/weather-trace-langchain
feat: Trace weather agent to MCP gateway tool calls
2 parents 672063e + 22ce0f9 commit 1091806

4 files changed

Lines changed: 55 additions & 6 deletions

File tree

a2a/weather_service/pyproject.toml

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

a2a/weather_service/src/weather_service/graph.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ class ExtendedMessagesState(MessagesState):
1717

1818

1919
def get_mcpclient(headers=None):
20-
"""Create MCP client, optionally forwarding headers (e.g. Authorization)."""
20+
"""Create MCP client, optionally forwarding headers (e.g. Authorization).
21+
22+
Trace context propagation (traceparent headers) to the MCP gateway is
23+
handled automatically by opentelemetry-instrumentation-httpx, which
24+
injects the current span's context on every outgoing HTTP request.
25+
"""
2126
mcp_config = {
2227
"url": os.getenv("MCP_URL", "http://localhost:8000/mcp"),
2328
"transport": os.getenv("MCP_TRANSPORT", "streamable_http"),

a2a/weather_service/src/weather_service/observability.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ def setup_observability() -> None:
134134
)
135135
)
136136

137+
# Instrument httpx for automatic traceparent propagation on outgoing requests.
138+
# langchain-mcp-adapters uses httpx for streamable_http transport, so each MCP
139+
# tool call will automatically carry the current span's traceparent header
140+
try:
141+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
142+
143+
HTTPXClientInstrumentor().instrument()
144+
logger.info("httpx instrumented for automatic trace context propagation")
145+
except ImportError:
146+
logger.warning(
147+
"opentelemetry-instrumentation-httpx not available - MCP tool calls will not propagate trace context"
148+
)
149+
137150
# Instrument OpenAI for GenAI semantic conventions
138151
try:
139152
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
@@ -406,7 +419,7 @@ def create_tracing_middleware():
406419

407420
async def tracing_middleware(request: Request, call_next):
408421
# Skip non-API paths (health checks, agent card, etc.)
409-
if request.url.path in ["/health", "/ready", "/.well-known/agent-card.json"]:
422+
if request.url.path in ["/health", "/ready", "/.well-known/agent-card.json", "/.well-known/agent.json"]:
410423
return await call_next(request)
411424

412425
tracer = get_tracer()
@@ -431,10 +444,12 @@ async def tracing_middleware(request: Request, call_next):
431444
except Exception as e:
432445
logger.debug(f"Could not parse request body: {e}")
433446

434-
# Break parent chain to make this a true root span
435-
# Without this, the span would inherit parent from W3C Trace Context headers
436-
empty_ctx = context.Context()
437-
detach_token = context.attach(empty_ctx)
447+
# Extract incoming W3C Trace Context from request headers to connect
448+
# agent spans to MCP gateway spans. Callers without traceparent still
449+
# get root spans (extract returns empty context), while callers with traceparent
450+
# (like MCP gateway) get connected end-to-end traces
451+
incoming_ctx = extract(dict(request.headers))
452+
detach_token = context.attach(incoming_ctx)
438453

439454
try:
440455
# Create root span with correct GenAI naming convention

a2a/weather_service/uv.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)