Monitoring Model Context Protocol (MCP) servers

Model Context Protocol (MCP) is a standard that enables AI assistants like Claude Desktop and GitHub Copilot to securely connect to external data sources and tools. Monitor your MCP server and client interactions with Instana to gain visibility into tool execution, request flows, and performance.

Before you begin

Make sure that the following prerequisites are met.

  • Python 3.8 or later
  • An MCP server implementation
  • Instana is configured for your application, see Getting started

About this task

Instana integrates with OpenLLMetry to provide distributed tracing for MCP applications, automatically generating spans for key operations including tool execution, request processing, and client-server interactions.

Procedure

To instrumenting MCP servers, complete the following steps.
  1. Install OpenLLMetry in your MCP server environment.
    # Using pip
    pip install traceloop-sdk
    
    # Using uv
    uv add traceloop-sdk
  2. Add OpenLLMetry initialization to your MCP server code.
    import os
    import sys
    
    # Disable default OTel exporters
    os.environ.setdefault("OTEL_TRACES_EXPORTER", "none")
    os.environ.setdefault("OTEL_METRICS_EXPORTER", "none")
    os.environ.setdefault("OTEL_LOGS_EXPORTER", "none")
    
    try:
        from traceloop.sdk import Traceloop
        from traceloop.sdk.decorators import workflow
    except ImportError:
        Traceloop = None
        print("OpenLLMetry not installed. Install with: pip install traceloop-sdk", file=sys.stderr)
    
    # Initialize OpenLLMetry
    if Traceloop is not None:
        try:
            Traceloop.init(app_name="your-mcp-server-name", disable_batch=True)
        except Exception as e:
            print(f"OpenLLMetry initialization warning: {e}", file=sys.stderr)
  3. Decorate your server entry point.

    Use the @workflow decorator on your server's main function to create a root span for the entire server lifecycle:

    @workflow(name="mcp_server_workflow")
    def start_server():
        """Start MCP server."""
        # Your server initialization code
        mcp.run(transport="stdio")  # or "streamable-http"
    
    if __name__ == "__main__":
        start_server()

Weather MCP server

An MCP server with observability enabled is shown in the following example:

from __future__ import annotations

import os
import sys
from typing import Any, Dict, Optional

# Configure OTel exporters
os.environ.setdefault("OTEL_TRACES_EXPORTER", "none")
os.environ.setdefault("OTEL_METRICS_EXPORTER", "none")
os.environ.setdefault("OTEL_LOGS_EXPORTER", "none")

try:
    from traceloop.sdk import Traceloop
    from traceloop.sdk.decorators import workflow
except ImportError:
    Traceloop = None

import httpx
from mcp.server.fastmcp import FastMCP

# Initialize OpenLLMetry
if Traceloop is not None:
    try:
        Traceloop.init(app_name="Weather-MCP-Server", disable_batch=True)
    except Exception as e:
        print(f"OpenLLMetry initialization warning: {e}", file=sys.stderr)

mcp = FastMCP(name="Weather")
NWS_API_BASE = "https://api.weather.gov"
NWS_USER_AGENT = "Weather-MCP-Server (dev@example.com)"

async def make_nws_request(url: str) -> Optional[Dict[str, Any]]:
    """Make a GET request to NWS API."""
    headers = {
        "User-Agent": NWS_USER_AGENT,
        "Accept": "application/geo+json",
    }
    async with httpx.AsyncClient(timeout=30.0) as client:
        try:
            resp = await client.get(url, headers=headers)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            print(f"NWS request failed: {e}", file=sys.stderr)
            return None

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.

    Args:
        latitude: Latitude coordinate
        longitude: Longitude coordinate

    Returns:
        Weather forecast as formatted string
    """
    try:
        lat, lon = float(latitude), float(longitude)
    except ValueError:
        return "Latitude and longitude must be numeric."

    # Get forecast URL from points API
    points_url = f"{NWS_API_BASE}/points/{lat},{lon}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "Unable to fetch forecast data for this location."

    forecast_url = (points_data.get("properties") or {}).get("forecast")
    if not forecast_url:
        return "Forecast URL not available for this location."

    # Get forecast data
    forecast_data = await make_nws_request(forecast_url)
    if not forecast_data:
        return "Unable to fetch detailed forecast."

    periods = (forecast_data.get("properties") or {}).get("periods") or []
    if not periods:
        return "No forecast periods available."

    # Format first 5 periods
    forecasts = []
    for period in periods[:5]:
        name = period.get("name", "N/A")
        temp = period.get("temperature", "N/A")
        unit = period.get("temperatureUnit", "")
        detail = period.get("detailedForecast", "N/A")
        forecasts.append(f"{name}: {temp}°{unit}\n{detail}")

    return "\n---\n".join(forecasts)

@workflow(name="weather_mcp_workflow")
def start_server():
    """Start MCP server."""
    mcp.run(transport="streamable-http")

if __name__ == "__main__":
    try:
        start_server()
    except KeyboardInterrupt:
        sys.exit(0)

What to do next

Configuring MCP clients

Configure your MCP client to include observability environment variables.

Procedure

Edit your Claude Desktop configuration file (location varies by OS).
{
  "mcpServers": { 
    "Weather MCP Server": { 
      "command": "npx", 
      "args": ["mcp-remote", "http://localhost:8000/mcp"], 
      "env": { 
        "TRACELOOP_BASE_URL": "<instana-endpoint>", 
        "TRACELOOP_HEADERS": "x-instana-key=<agent-key>", 
        "OTEL_EXPORTER_OTLP_INSECURE": "true" 
      } 
    } 
  }
}
Note:

For setup instructions, see Connect to local MCP servers.

GitHub Copilot

Configure GitHub Copilot to use your MCP server, see GitHub Copilot MCP.

About this task

Note:

Add observability environment variables in the `env` section of your configuration.

Running your MCP server

About this task

STDIO mode: In STDIO mode, the MCP client launches the server automatically based on its configuration file. Simply start your client (Claude Desktop, GitHub Copilot, and so on.) and it will start the server in the background.

Note: Some clients don't show the root span until you close the session. For Claude Desktop, quit the app after receiving responses. For GitHub Copilot, stop Copilot after getting responses.

Streamable HTTP mode

Procedure

  1. Export observability environment variables.
    export TRACELOOP_BASE_URL=<instana-endpoint> 
    export TRACELOOP_HEADERS="x-instana-key=<agent-key>" 
    export OTEL_EXPORTER_OTLP_INSECURE=true
  2. Start the server.
    python weather.py
  3. Restart your MCP client to reconnect with the updated configuration.

Deployment modes

Procedure

  • Agent mode: Send traces through a local Instana agent:
    export TRACELOOP_BASE_URL=localhost:4317
    export TRACELOOP_HEADERS="x-instana-key=<agent-key>"
    export OTEL_EXPORTER_OTLP_INSECURE=true
  • Agentless mode: Send traces directly to the Instana backend:
    export TRACELOOP_BASE_URL=<instana-otlp-endpoint>:4317
    export TRACELOOP_HEADERS="x-instana-key=<agent-key>"
    export OTEL_EXPORTER_OTLP_INSECURE=false
    Note:

    For backend endpoints, see Sending OpenTelemetry data to the Instana backend.

Viewing data on Instana

After your MCP server runs with observability enabled, data will appear in the Instana Gen AI observability dashboard.

About this task

Figure 1. MCP tool response traces
MCP tool response traces
Figure 2. Claude Desktop weather tool
Claude Desktop weather tool

For more details on viewing and analyzing traces, see Viewing telemetry data.

Troubleshooting

You might encounter the following issues with MCP.

No spans visible

  • Verify OpenLLMetry is installed:
    pip list | grep traceloop-sdk
  • Confirm environment variables are set correctly.
  • Check whether Traceloop.init() was called successfully.

Missing spans

Procedure
  • Ensure both server and client have observability enabled
  • Verify MCP operations are actually running
  • Check application logs for errors or warnings
Client doesn't show root span

Some clients require closing the session before the root span appears.

Procedure
  • Claude Desktop: Quit the application after receiving responses
  • GitHub Copilot: Stop Copilot after getting responses