Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions a2a/weather_service/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dependencies = [
"langchain-mcp-adapters>=0.1.0",
"python-keycloak>=5.5.1",
"opentelemetry-exporter-otlp",
# Auto-instrument httpx so outgoing MCP tool calls propagate traceparent
"opentelemetry-instrumentation-httpx>=0.49b0",
# OpenTelemetry GenAI semantic convention instrumentation
# Emits spans with gen_ai.* attributes for MLflow compatibility
"opentelemetry-instrumentation-openai>=0.34b0",
Expand Down
6 changes: 6 additions & 0 deletions a2a/weather_service/src/weather_service/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ class ExtendedMessagesState(MessagesState):
final_answer: str = ""

def get_mcpclient():
"""Create an MCP client.

Trace context propagation (traceparent headers) to the MCP gateway is
handled automatically by opentelemetry-instrumentation-httpx, which
injects the current span's context on every outgoing HTTP request.
"""
return MultiServerMCPClient({
"math": {
"url": os.getenv("MCP_URL", "http://localhost:8000/mcp"),
Expand Down
21 changes: 16 additions & 5 deletions a2a/weather_service/src/weather_service/observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ def setup_observability() -> None:
W3CBaggagePropagator(),
]))

# Instrument httpx for automatic traceparent propagation on outgoing requests.
# langchain-mcp-adapters uses httpx for streamable_http transport, so each MCP
# tool call will automatically carry the current span's traceparent header
try:
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
HTTPXClientInstrumentor().instrument()
logger.info("httpx instrumented for automatic trace context propagation")
except ImportError:
logger.warning("opentelemetry-instrumentation-httpx not available - "
"MCP tool calls will not propagate trace context")

# Instrument OpenAI for GenAI semantic conventions
try:
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
Expand Down Expand Up @@ -396,7 +407,7 @@ def create_tracing_middleware():

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

tracer = get_tracer()
Expand All @@ -421,10 +432,10 @@ async def tracing_middleware(request: Request, call_next):
except Exception as e:
logger.debug(f"Could not parse request body: {e}")

# Break parent chain to make this a true root span
# Without this, the span would inherit parent from W3C Trace Context headers
empty_ctx = context.Context()
detach_token = context.attach(empty_ctx)
# Extract incoming W3C Trace Context from request headers to connect
# agent spans to MCP gateway spans
incoming_ctx = extract(dict(request.headers))
detach_token = context.attach(incoming_ctx)

try:
# Create root span with correct GenAI naming convention
Expand Down
33 changes: 28 additions & 5 deletions a2a/weather_service/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.