# FastMCP

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

Source: https://last9.io/docs/integrations/frameworks/python/fastmcp/

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:

   ```bash
   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:

   ```bash
   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:

   ```bash
   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:

   ```bash
   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:

   ```bash
   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:

   ```bash
   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

```python
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:

```python
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:

```python
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:

```bash
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:

```bash
# 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:

   ```bash
   # 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](https://app.last9.io/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:

```bash
opentelemetry-bootstrap -a requirements
opentelemetry-bootstrap -a install
```

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

```bash
env | grep OTEL_
```

**Connection Issues**
Check if your application can reach Last9:

```bash
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](https://opentelemetry-python-contrib.readthedocs.io/en/latest/index.html).

## Need Help?

If you encounter any issues or have questions:

- Join our [Discord community](https://discord.com/channels/652153247672729619/652153247672729621) for real-time support
- Contact our support team at [support@last9.io](mailto:support@last9.io)
