Skip to content
Last9
Book demo

FastMCP

Instrument your FastMCP (Model Context Protocol) server with OpenTelemetry to send traces, metrics, and logs to Last9

Use OpenTelemetry to instrument your FastMCP server and send telemetry data to Last9. FastMCP includes built-in OpenTelemetry tracing for MCP operations (tool calls, resource reads, prompts), and auto-instrumentation adds spans for underlying libraries like httpx.

You can either run OpenTelemetry Collector as a separate service or send telemetry directly from your application to Last9.

Prerequisites

Before setting up FastMCP monitoring, ensure you have:

  • Python 3.10 or higher installed
  • FastMCP application running
  • Last9 account with integration credentials
  • pip package manager available
  1. Install OpenTelemetry Packages

    Install the required OpenTelemetry packages alongside FastMCP:

    pip install fastmcp opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-distro

    You can also add these packages to your requirements.txt file and install them using:

    pip install -r requirements.txt
  2. Set Environment Variables

    Configure OpenTelemetry environment variables for your FastMCP server. Replace the placeholder values with your actual Last9 credentials:

    export OTEL_SERVICE_NAME=<service_name>
    export OTEL_EXPORTER_OTLP_ENDPOINT={{ .Logs.WriteURL }}
    export OTEL_EXPORTER_OTLP_HEADERS="Authorization={{ .Logs.AuthValue }}"
    export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
    export OTEL_TRACES_EXPORTER=otlp
    export OTEL_METRICS_EXPORTER=otlp
    export OTEL_LOGS_EXPORTER=otlp
    export OTEL_RESOURCE_ATTRIBUTES="service.name=<service_name>,service.version=1.0.0,deployment.environment=local"
    export OTEL_TRACES_SAMPLER="always_on"
    export OTEL_LOG_LEVEL=error

    Environment Variables Explained:

    • OTEL_SERVICE_NAME: Name of your FastMCP service
    • OTEL_EXPORTER_OTLP_ENDPOINT: Last9 endpoint URL for telemetry data
    • OTEL_EXPORTER_OTLP_HEADERS: Authentication header for Last9
    • OTEL_METRICS_EXPORTER: Set to otlp to export runtime and library metrics
    • OTEL_LOGS_EXPORTER: Set to otlp to export logs with trace correlation
    • OTEL_RESOURCE_ATTRIBUTES: Service metadata including name, version, and environment
  3. Bootstrap Automatic Instrumentation

    FastMCP supports automatic instrumentation with OpenTelemetry. Run the following command to detect and install the required instrumentation packages for your application:

    opentelemetry-bootstrap -a requirements

    This command analyzes your dependencies and outputs the OpenTelemetry instrumentation packages you need. For example:

    • If you use httpx, it will suggest opentelemetry-instrumentation-httpx
    • If you use requests, it will suggest opentelemetry-instrumentation-requests
    • If you use sqlalchemy, it will suggest opentelemetry-instrumentation-sqlalchemy
  4. Install Instrumentation Packages

    Install all the recommended instrumentation packages automatically:

    opentelemetry-bootstrap -a install

    This command installs all the necessary instrumentation packages based on your application’s dependencies.

    Recommendation: Last9 recommends using automatic instrumentation for most applications as it provides comprehensive coverage with minimal configuration.

  5. Run Your FastMCP Server

    Start your FastMCP server with OpenTelemetry instrumentation:

    opentelemetry-instrument fastmcp run server.py

    This command starts your FastMCP server with automatic OpenTelemetry instrumentation enabled. The instrumentation will:

    • Collect traces for every MCP tool call, resource read, and prompt render
    • Monitor outbound HTTP requests made inside tool handlers
    • Track database queries and external API calls from tool logic
    • Send all telemetry data to Last9

What Gets Instrumented

When you use automatic instrumentation with FastMCP, OpenTelemetry automatically captures:

MCP Operations (FastMCP Built-in)

FastMCP creates spans for every MCP operation with no code changes:

  • Tool Calls: tools/call {name} — every tool invocation with arguments and results
  • Resource Reads: resources/read {uri} — every resource access
  • Prompt Renders: prompts/get {name} — every prompt template render

Each span includes attributes like rpc.system=mcp, rpc.method, session ID, server name, and component type.

HTTP Requests (Auto-instrumented)

  • Outbound httpx and requests calls from tool handlers
  • Request method, URL, status code, and timing
  • Error details for failed requests

Database Operations

  • SQL queries and execution times (if using sqlalchemy, psycopg2, etc.)
  • Database connection details and query parameters

Metrics

With OTEL_METRICS_EXPORTER=otlp, the auto-instrumentation collects runtime and library metrics automatically:

  • httpx — request duration histograms, active connections
  • Python runtime — GC counts, memory usage, CPU time
  • Process — open file descriptors, thread count

These metrics give you request rate, error rate, and latency (RED metrics) out of the box in Last9.

Logs (Trace-Log Correlation)

With OTEL_LOGS_EXPORTER=otlp, opentelemetry-instrument patches Python’s logging module so that every log record automatically includes trace_id, span_id, and trace_flags. This enables:

  • Logs exported to Last9 via OTLP alongside traces
  • Click a trace in Last9 Grafana to see its correlated log lines
  • No manual trace context injection needed in your code
import logging
logger = logging.getLogger("my-server")
@mcp.tool()
async def add_note(title: str, content: str) -> str:
logger.info("Note '%s' added", title) # trace_id auto-injected

Advanced Configuration

Error Recording on Spans

MCP tools return errors as strings, so spans look healthy by default even when the operation failed. Use span.record_exception() and span.set_status(StatusCode.ERROR) to make errors visible in the trace waterfall:

from opentelemetry.trace import StatusCode
from fastmcp.telemetry import get_tracer
@mcp.tool()
async def fetch_url(url: str) -> str:
tracer = get_tracer()
with tracer.start_as_current_span("fetch_url_request") as span:
try:
async with httpx.AsyncClient() as client:
resp = await client.get(url, timeout=10)
resp.raise_for_status()
return resp.text
except httpx.HTTPStatusError as exc:
span.record_exception(exc)
span.set_status(StatusCode.ERROR, f"HTTP {exc.response.status_code}")
return f"Error: HTTP {exc.response.status_code}"

This makes failed tool calls show up as red error spans in Last9, filterable via StatusCode=ERROR.

Custom Spans in Tool Handlers

Use fastmcp.telemetry.get_tracer() to add custom spans inside your tools:

from fastmcp import FastMCP
from fastmcp.telemetry import get_tracer
mcp = FastMCP("my-server")
@mcp.tool()
async def process_data(input: str) -> str:
tracer = get_tracer()
with tracer.start_as_current_span("parse_input") as span:
span.set_attribute("input.length", len(input))
parsed = parse(input)
with tracer.start_as_current_span("transform_data") as span:
span.set_attribute("data.count", len(parsed))
result = transform(parsed)
return result

Custom spans are automatically nested under the parent tools/call span.

Custom Service Metadata

Enhance your service metadata by updating the OTEL_RESOURCE_ATTRIBUTES environment variable:

export OTEL_RESOURCE_ATTRIBUTES="service.name=mcp-server,service.version=2.1.0,deployment.environment=production,service.instance.id=mcp-1,team=backend"

Sampling Configuration

Control trace sampling to manage data volume:

# Always sample (development)
export OTEL_TRACES_SAMPLER="always_on"
# Sample 10% of traces (production)
export OTEL_TRACES_SAMPLER="traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"

Verification

  1. Check Application Startup

    When you start your FastMCP server, you should see OpenTelemetry initialization messages in the console output.

  2. Generate Test Traffic

    Use an MCP client or the FastMCP dev tools to invoke your tools:

    # Using fastmcp dev mode
    fastmcp dev server.py

    Or use any MCP-compatible client (Claude Desktop, Cursor, etc.) to call your tools.

  3. Verify Data in Last9

    Log into your Last9 account and check that traces are being received in Grafana.

    Look for:

    • tools/call spans for each tool invocation
    • resources/read spans for resource access
    • Child spans from httpx or database calls within tools
    • Error spans (red) when validation fails or HTTP requests error out
    • Correlated log lines attached to each trace
    • Runtime and httpx metrics in the metrics explorer

Troubleshooting

Common Issues

Missing Instrumentation Packages If some operations aren’t being traced, run the bootstrap command again:

opentelemetry-bootstrap -a requirements
opentelemetry-bootstrap -a install

Environment Variables Not Set Verify your environment variables are properly configured:

env | grep OTEL_

Connection Issues Check if your application can reach Last9:

curl -v -H "Authorization: <your-auth-header>" <your-endpoint>

Performance Considerations

  • Use sampling in production to control data volume
  • Monitor the overhead of instrumentation on your application
  • Consider using the OpenTelemetry Collector as a buffer for high-traffic applications

Best Practices

  • Set meaningful service names that reflect your architecture
  • Use consistent environment naming across services
  • Include version information in resource attributes
  • Add custom spans for critical business operations within tool handlers
  • Use get_tracer() from fastmcp.telemetry instead of creating your own tracer

Supported Libraries

OpenTelemetry provides automatic instrumentation for many popular Python libraries commonly used with FastMCP:

  • HTTP Clients: httpx, requests, aiohttp
  • Databases: sqlalchemy, psycopg2, pymongo, redis
  • Message Queues: celery, pika (RabbitMQ)
  • Cloud Services: boto3 (AWS), google-cloud

For a complete list of supported libraries, visit the OpenTelemetry Python Contrib documentation.

Need Help?

If you encounter any issues or have questions: