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.
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
- ▸An observability backend running locally or in the cloud (e.g., Jaeger for traces, Prometheus for metrics).
- ▸The
@opentelemetry/apiand@opentelemetry/sdk-nodenpm packages installed in your custom MCP server.
Architecture Diagram
[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.
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
argumentsJSON 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
traceparentheader 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
SimpleSpanProcessorin high-throughput production environments; it blocks the event loop. Switch toBatchSpanProcessor.
Related Guides
How to Build a Custom MCP Server from Scratch
Learn how to create your own MCP server using the TypeScript or Python SDK. Expose custom tools and resources for AI agents to use.
Dev ToolsHow to Use the Puppeteer MCP Server for Web Scraping & Automation
Automate web browsers using the Puppeteer MCP server. Navigate websites, take screenshots, extract data, and automate web interactions through AI.
Dev ToolsHow to Use the Git MCP Server for Version Control Operations
Manage local Git repositories through AI agents. Clone repos, create branches, make commits, and view diffs using the Git MCP server.