Dev Tools·
advanced
·12 min read·Jul 9, 2026

Implementing OpenTelemetry, Metrics, and Distributed Tracing in MCP Servers

Instrument your custom MCP servers with OpenTelemetry. Track tool performance latency, monitor request volumes, and configure distributed tracing pipelines.

OpenTelemetrytracingmetricsobservabilityPrometheusarchitecture

OpenTelemetry, Metrics, and Distributed Tracing in MCP Servers

When MCP servers run inside production pipelines, tracking tool execution times, monitoring JSON-RPC latency, and recording request volumes becomes critical. OpenTelemetry standardizes how logs, traces, and metrics are emitted from your Node.js MCP server to downstream observability platforms like Prometheus, Jaeger, or Datadog.

Prerequisites

  1. An observability backend running locally or in the cloud (e.g., Jaeger for traces, Prometheus for metrics).
  2. The @opentelemetry/api and @opentelemetry/sdk-node npm packages installed in your custom MCP server.

Architecture Diagram

code
[MCP Client] --(JSON-RPC Tool Call)--> [MCP Server]
                                           | (Instrumented with OpenTelemetry SDK)
                                           +---> [Metrics Collector (Prometheus)]
                                           +---> [Distributed Tracing (Jaeger)]

Implementing OpenTelemetry Tracing

By wrapping your tool execution blocks in an Active Span, you can measure exactly how long your AI agent's requested operations take to complete.

typescript
import { trace, context } from '@opentelemetry/api';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';

// Initialize the OTLP Exporter (defaults to )
const provider = new NodeTracerProvider();
const exporter = new OTLPTraceExporter({ url: '/v1/traces' });
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();

const tracer = trace.getTracer('mcp-server-tracer');

// Trace execution of custom tools
async function executeWithTracing(toolName: string, executeFn: () => Promise<any>) {
  return tracer.startActiveSpan(`mcp-tool::${toolName}`, async (span) => {
    try {
      const result = await executeFn();
      span.setStatus({ code: 0 }); // Ok
      return result;
    } catch (error: any) {
      span.recordException(error);
      span.setStatus({ code: 1, message: error.message }); // Error
      throw error;
    } finally {
      span.end();
    }
  });
}

Best Practices

  • Sanitize Payloads: Do not blindly inject the arguments JSON payload from the AI agent into your Trace attributes. Agents frequently generate enormous base64 strings or PII that will bloat your tracing backend.
  • Context Propagation: If your MCP server acts as a proxy and makes downstream HTTP calls to other microservices, ensure you inject the traceparent header into those outgoing fetch requests to maintain distributed tracing continuity.

Troubleshooting

  • No Traces Appearing: Ensure your Jaeger/OTLP collector is actually listening on (HTTP) or (gRPC). Ensure your exporter matches the protocol you are using.
  • Memory Leaks: Never use a SimpleSpanProcessor in high-throughput production environments; it blocks the event loop. Switch to BatchSpanProcessor.

Related Guides