11"Weather MCP tool example"
22
3+ import asyncio
4+ import functools
35import json
46import logging
57import os
68import sys
79
8- import asyncio
9- import functools
10-
1110import requests
1211import uvicorn
1312from fastmcp import FastMCP
14- from requests .adapters import HTTPAdapter
15- from starlette .middleware import Middleware
16- from starlette .middleware .base import BaseHTTPMiddleware
17- from urllib3 .util .retry import Retry
18-
19- from opentelemetry import trace , context as otel_context
20- from opentelemetry .sdk .trace import TracerProvider
21- from opentelemetry .sdk .trace .export import BatchSpanProcessor
22- from opentelemetry .sdk .resources import Resource , SERVICE_NAME
13+ from opentelemetry import context as otel_context
14+ from opentelemetry import trace
15+ from opentelemetry .baggage .propagation import W3CBaggagePropagator
2316from opentelemetry .exporter .otlp .proto .http .trace_exporter import OTLPSpanExporter
2417from opentelemetry .propagate import extract , set_global_textmap
2518from opentelemetry .propagators .composite import CompositePropagator
26- from opentelemetry .trace .propagation .tracecontext import TraceContextTextMapPropagator
27- from opentelemetry .baggage .propagation import W3CBaggagePropagator
19+ from opentelemetry .sdk .resources import SERVICE_NAME , Resource
20+ from opentelemetry .sdk .trace import TracerProvider
21+ from opentelemetry .sdk .trace .export import BatchSpanProcessor
2822from opentelemetry .trace import Status , StatusCode
23+ from opentelemetry .trace .propagation .tracecontext import TraceContextTextMapPropagator
24+ from requests .adapters import HTTPAdapter
25+ from starlette .middleware import Middleware
26+ from starlette .middleware .base import BaseHTTPMiddleware
27+ from urllib3 .util .retry import Retry
2928
3029mcp = FastMCP ("Weather" )
3130logger = logging .getLogger (__name__ )
@@ -64,8 +63,7 @@ def _build_resilient_session() -> requests.Session:
6463def setup_tracing () -> None :
6564 """Initialize OpenTelemetry tracing with W3C trace context propagation."""
6665 otlp_endpoint = os .getenv (
67- "OTEL_EXPORTER_OTLP_ENDPOINT" ,
68- "http://otel-collector.kagenti-system.svc.cluster.local:8335"
66+ "OTEL_EXPORTER_OTLP_ENDPOINT" , "http://otel-collector.kagenti-system.svc.cluster.local:8335"
6967 )
7068 service_name = os .getenv ("OTEL_SERVICE_NAME" , "weather-mcp-tool" )
7169
@@ -74,38 +72,33 @@ def setup_tracing() -> None:
7472
7573 resource = Resource (attributes = {SERVICE_NAME : service_name })
7674 provider = TracerProvider (resource = resource )
77- provider .add_span_processor (
78- BatchSpanProcessor (OTLPSpanExporter (endpoint = otlp_endpoint ))
79- )
75+ provider .add_span_processor (BatchSpanProcessor (OTLPSpanExporter (endpoint = otlp_endpoint )))
8076 trace .set_tracer_provider (provider )
8177
82- set_global_textmap (CompositePropagator ([
83- TraceContextTextMapPropagator (),
84- W3CBaggagePropagator (),
85- ]))
78+ set_global_textmap (
79+ CompositePropagator (
80+ [
81+ TraceContextTextMapPropagator (),
82+ W3CBaggagePropagator (),
83+ ]
84+ )
85+ )
8686
8787 logger .info (f"Tracing initialized: service={ service_name } otlp={ otlp_endpoint } " )
8888
8989
90-
9190@mcp .tool (annotations = {"readOnlyHint" : True , "destructiveHint" : False , "idempotentHint" : True })
9291async def get_weather (city : str ) -> str :
9392 """Get weather info for a city"""
94- # Extract W3C traceparent from the incoming MCP HTTP request so this tool's
95- # span becomes a child of the MCP gateway span (which is itself a child of
96- # the agent span), giving a full agent → gateway → tool trace chain.
97- # Enrich FastMCP's own span with gen_ai attributes rather than creating a
98- # child span — FastMCP already creates a tools/call span, so a second one
99- # with the same name is misleading. Adding to the current span merges both
100- # sets of attributes into a single, complete span.
93+ # Enrich FastMCP's span with gen_ai attributes rather than creating a child span.
10194 span = trace .get_current_span ()
10295 span .set_attribute ("gen_ai.operation.name" , "execute_tool" )
10396 span .set_attribute ("gen_ai.tool.name" , "get_weather" )
10497 span .set_attribute ("gen_ai.tool.call.arguments" , json .dumps ({"city" : city }))
10598
10699 logger .debug (f"Getting weather info for city '{ city } '." )
107100
108- loop = asyncio .get_event_loop ()
101+ loop = asyncio .get_running_loop ()
109102
110103 try :
111104 base_url = "https://geocoding-api.open-meteo.com/v1/search"
@@ -151,7 +144,6 @@ async def get_weather(city: str) -> str:
151144 raise
152145
153146
154-
155147async def _trace_propagation_middleware (request , call_next ):
156148 """Extract W3C traceparent from HTTP headers before FastMCP creates its span.
157149
0 commit comments