mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-31 19:43:19 +02:00
- Create OpenTelemetryMiddleware with automatic span creation for all MCP operations - Add opentelemetry as optional dependency (pip install fastmcp[opentelemetry]) - Gracefully degrades to no-op when OpenTelemetry not installed - Configuration options for privacy (include_arguments) and performance (max_argument_length) - Update documentation to reflect built-in support - Update example to use built-in middleware - Add comprehensive tests Co-authored-by: William Easton <strawgate@users.noreply.github.com>
189 lines
5.8 KiB
Python
189 lines
5.8 KiB
Python
"""
|
|
OpenTelemetry Integration Example
|
|
|
|
This example demonstrates how to integrate OpenTelemetry with FastMCP for
|
|
comprehensive observability. It shows:
|
|
|
|
1. Configuring OpenTelemetry tracing and logging
|
|
2. Creating custom middleware that emits spans
|
|
3. Attaching OpenTelemetry to FastMCP's logger
|
|
4. Exporting to console (easily switch to OTLP for production)
|
|
|
|
To run this example:
|
|
uv run examples/opentelemetry_example.py
|
|
|
|
For production, replace ConsoleSpanExporter/ConsoleLogExporter with:
|
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
|
|
|
Requirements:
|
|
pip install opentelemetry-api opentelemetry-sdk
|
|
"""
|
|
|
|
from opentelemetry import trace
|
|
from opentelemetry._logs import set_logger_provider
|
|
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, ConsoleLogExporter
|
|
from opentelemetry.sdk.resources import Resource
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
|
|
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.middleware.opentelemetry import OpenTelemetryMiddleware
|
|
from fastmcp.utilities.logging import get_logger
|
|
|
|
# ============================================================================
|
|
# OpenTelemetry Configuration
|
|
# ============================================================================
|
|
|
|
# Define service metadata
|
|
resource = Resource(
|
|
attributes={
|
|
"service.name": "fastmcp-weather-server",
|
|
"service.version": "1.0.0",
|
|
"deployment.environment": "development",
|
|
}
|
|
)
|
|
|
|
# Configure tracing
|
|
trace_provider = TracerProvider(resource=resource)
|
|
trace_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
|
|
trace.set_tracer_provider(trace_provider)
|
|
|
|
# Configure logging
|
|
logger_provider = LoggerProvider(resource=resource)
|
|
logger_provider.add_log_record_processor(BatchLogRecordProcessor(ConsoleLogExporter()))
|
|
set_logger_provider(logger_provider)
|
|
|
|
# ============================================================================
|
|
# FastMCP Server Setup
|
|
# ============================================================================
|
|
|
|
# Create FastMCP server
|
|
mcp = FastMCP("Weather Server")
|
|
|
|
# Attach OpenTelemetry to FastMCP's logger
|
|
logger = get_logger("weather")
|
|
logger.addHandler(LoggingHandler(logger_provider=logger_provider))
|
|
|
|
# Add OpenTelemetry middleware
|
|
mcp.add_middleware(OpenTelemetryMiddleware())
|
|
|
|
# ============================================================================
|
|
# Server Tools
|
|
# ============================================================================
|
|
|
|
|
|
@mcp.tool()
|
|
def get_weather(city: str) -> dict:
|
|
"""Get current weather for a city.
|
|
|
|
Args:
|
|
city: Name of the city
|
|
|
|
Returns:
|
|
Weather information including temperature and conditions
|
|
"""
|
|
logger.info(f"Fetching weather for {city}")
|
|
|
|
# Simulate weather lookup
|
|
weather_data = {
|
|
"city": city,
|
|
"temperature": 72,
|
|
"condition": "sunny",
|
|
"humidity": 45,
|
|
}
|
|
|
|
logger.info(
|
|
f"Weather retrieved: {weather_data['condition']}, {weather_data['temperature']}°F"
|
|
)
|
|
|
|
return weather_data
|
|
|
|
|
|
@mcp.tool()
|
|
def get_forecast(city: str, days: int = 3) -> dict:
|
|
"""Get weather forecast for a city.
|
|
|
|
Args:
|
|
city: Name of the city
|
|
days: Number of days to forecast (1-7)
|
|
|
|
Returns:
|
|
Forecast data for the specified number of days
|
|
"""
|
|
logger.info(f"Fetching {days}-day forecast for {city}")
|
|
|
|
if days < 1 or days > 7:
|
|
logger.warning(f"Invalid days parameter: {days}. Must be 1-7.")
|
|
raise ValueError("Days must be between 1 and 7")
|
|
|
|
# Simulate forecast data
|
|
forecast = {
|
|
"city": city,
|
|
"days": days,
|
|
"forecast": [
|
|
{"day": i + 1, "temp": 70 + i, "condition": "partly cloudy"}
|
|
for i in range(days)
|
|
],
|
|
}
|
|
|
|
logger.info(f"Forecast retrieved for {days} days")
|
|
|
|
return forecast
|
|
|
|
|
|
@mcp.tool()
|
|
def convert_temperature(temp: float, from_unit: str, to_unit: str) -> dict:
|
|
"""Convert temperature between Fahrenheit and Celsius.
|
|
|
|
Args:
|
|
temp: Temperature value to convert
|
|
from_unit: Source unit ('F' or 'C')
|
|
to_unit: Target unit ('F' or 'C')
|
|
|
|
Returns:
|
|
Converted temperature value
|
|
"""
|
|
logger.debug(f"Converting {temp}°{from_unit} to °{to_unit}")
|
|
|
|
# Validate units
|
|
if from_unit not in ["F", "C"] or to_unit not in ["F", "C"]:
|
|
logger.error(f"Invalid units: {from_unit} or {to_unit}")
|
|
raise ValueError("Units must be 'F' or 'C'")
|
|
|
|
# Perform conversion
|
|
if from_unit == to_unit:
|
|
result = temp
|
|
elif from_unit == "F" and to_unit == "C":
|
|
result = (temp - 32) * 5 / 9
|
|
else: # from_unit == "C" and to_unit == "F"
|
|
result = (temp * 9 / 5) + 32
|
|
|
|
logger.info(f"Converted {temp}°{from_unit} to {result:.1f}°{to_unit}")
|
|
|
|
return {
|
|
"original": {"value": temp, "unit": from_unit},
|
|
"converted": {"value": round(result, 1), "unit": to_unit},
|
|
}
|
|
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 70)
|
|
print("FastMCP + OpenTelemetry Example")
|
|
print("=" * 70)
|
|
print("\nThis example demonstrates OpenTelemetry integration with FastMCP.")
|
|
print("Watch the console for:")
|
|
print(" - Trace spans showing tool execution timing")
|
|
print(" - Log entries from FastMCP's logger")
|
|
print("\nFor production, replace console exporters with OTLP exporters")
|
|
print("to send data to Grafana, Jaeger, or other observability platforms.")
|
|
print("=" * 70)
|
|
print()
|
|
|
|
# Run the server
|
|
mcp.run()
|