Skip to content

Commit fdfb26c

Browse files
committed
feat: add Aspire telemetry to HTTP server
document how to run the Aspire dashboard and view traces add OpenTelemetry middleware helper with OTLP exporters wire middleware into basic_mcp_http.py and auto-configure when OTEL endpoint set
1 parent 1f69dee commit fdfb26c

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A demonstration project showcasing Model Context Protocol (MCP) implementations
1212
- [Use with GitHub Copilot](#use-with-github-copilot)
1313
- [Debug with VS Code](#debug-with-vs-code)
1414
- [Inspect with MCP inspector](#inspect-with-mcp-inspector)
15+
- [View traces with Aspire Dashboard](#view-traces-with-aspire-dashboard)
1516
- [Run local Agents <-> MCP](#run-local-agents---mcp)
1617
- [Deploy to Azure](#deploy-to-azure)
1718
- [Deploy to Azure with private networking](#deploy-to-azure-with-private-networking)
@@ -156,6 +157,28 @@ The inspector provides a web interface to:
156157
- Inspect server responses and errors
157158
- Debug server communication
158159

160+
### View traces with Aspire Dashboard
161+
162+
You can use the [.NET Aspire Dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) to view OpenTelemetry traces, metrics, and logs from the MCP server.
163+
164+
> **Note:** Aspire Dashboard integration is only configured for the HTTP server (`basic_mcp_http.py`).
165+
166+
1. Start the Aspire Dashboard:
167+
168+
```bash
169+
docker run --rm -d -p 18888:18888 -p 4317:18889 --name aspire-dashboard \
170+
mcr.microsoft.com/dotnet/aspire-dashboard:latest
171+
```
172+
173+
2. Set the environment variable and start the HTTP server:
174+
175+
```bash
176+
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
177+
uv run servers/basic_mcp_http.py
178+
```
179+
180+
3. View the dashboard at: http://localhost:18888
181+
159182
---
160183

161184
## Run local Agents <-> MCP

servers/basic_mcp_http.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
import csv
22
import logging
3+
import os
34
from datetime import date
45
from enum import Enum
56
from pathlib import Path
67
from typing import Annotated
78

89
from fastmcp import FastMCP
10+
from opentelemetry_middleware import OpenTelemetryMiddleware, configure_aspire_dashboard
911

1012
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
1113
logger = logging.getLogger("ExpensesMCP")
1214

1315

16+
# Configure Aspire Dashboard telemetry if OTEL_EXPORTER_OTLP_ENDPOINT is set
17+
if os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"):
18+
logger.info("Setting up Aspire Dashboard instrumentation (OTLP)")
19+
configure_aspire_dashboard(service_name="expenses-mcp")
20+
21+
1422
SCRIPT_DIR = Path(__file__).parent
1523
EXPENSES_FILE = SCRIPT_DIR / "expenses.csv"
1624

1725

18-
mcp = FastMCP("Expenses Tracker")
26+
mcp = FastMCP(
27+
"Expenses Tracker",
28+
middleware=[OpenTelemetryMiddleware(tracer_name="expenses.mcp")]
29+
)
1930

2031

2132
class PaymentMethod(Enum):

servers/opentelemetry_middleware.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,66 @@
1+
import logging
2+
import os
3+
from urllib.parse import urlparse
4+
15
from fastmcp.server.middleware import Middleware, MiddlewareContext
2-
from opentelemetry import trace
6+
from opentelemetry import metrics, trace
7+
from opentelemetry._logs import set_logger_provider
8+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
9+
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
10+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
11+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
12+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
13+
from opentelemetry.sdk.metrics import MeterProvider
14+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
15+
from opentelemetry.sdk.resources import Resource
16+
from opentelemetry.sdk.trace import TracerProvider
17+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
318
from opentelemetry.trace import Status, StatusCode
419

520

21+
def configure_aspire_dashboard(service_name: str = "expenses-mcp"):
22+
"""Configure OpenTelemetry to send telemetry to the Aspire standalone dashboard."""
23+
otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
24+
parsed_endpoint = urlparse(otlp_endpoint)
25+
use_insecure = parsed_endpoint.scheme not in ("https", "grpcs")
26+
27+
# Create resource with service name
28+
resource = Resource.create({"service.name": service_name})
29+
30+
# Configure Tracing
31+
tracer_provider = TracerProvider(resource=resource)
32+
tracer_provider.add_span_processor(
33+
SimpleSpanProcessor(OTLPSpanExporter(endpoint=otlp_endpoint, insecure=use_insecure))
34+
)
35+
trace.set_tracer_provider(tracer_provider)
36+
37+
# Configure Metrics
38+
metric_reader = PeriodicExportingMetricReader(
39+
OTLPMetricExporter(endpoint=otlp_endpoint, insecure=use_insecure)
40+
)
41+
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
42+
metrics.set_meter_provider(meter_provider)
43+
44+
# Configure Logging
45+
logger_provider = LoggerProvider(resource=resource)
46+
logger_provider.add_log_record_processor(
47+
BatchLogRecordProcessor(OTLPLogExporter(endpoint=otlp_endpoint, insecure=use_insecure))
48+
)
49+
set_logger_provider(logger_provider)
50+
51+
# Add logging handler to send Python logs to OTLP
52+
root_logger = logging.getLogger()
53+
handler_exists = any(
54+
isinstance(existing, LoggingHandler)
55+
and getattr(existing, "logger_provider", None) is logger_provider
56+
for existing in root_logger.handlers
57+
)
58+
59+
if not handler_exists:
60+
handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider)
61+
root_logger.addHandler(handler)
62+
63+
664
class OpenTelemetryMiddleware(Middleware):
765
"""Middleware that creates OpenTelemetry spans for MCP operations."""
866

0 commit comments

Comments
 (0)