Skip to content
opentel-mcp

OpenTelemetry MCP Server Instrumentation

OpenTelemetry MCP server instrumentation in one function call: install opentel-mcp, wrap your server with instrumentMcpServer(), see your first trace.

By Thirumalaiboobathi BLast updated: August 12, 2026Edit this page on GitHub

OpenTelemetry MCP server instrumentation with opentel-mcp starts with one function call: instrumentMcpServer(server, options), wrapping an MCP server's tools/call handler so every call gets a span — including the CallToolResult.isError check that sets span status to ERROR on the silent failures standard instrumentation misses. This page walks through installing the package, wrapping a minimal MCP server, and seeing a real trace print to your terminal — the same 30-second quickstart from opentel-mcp's own README, copied verbatim so what's here is exactly what's tested and shipped.

TL;DR

npm install opentel-mcp @opentelemetry/api, call instrumentMcpServer(server, { serviceName: '...', setupNodeSdk: true }) before registering any tools, then register tools as usual. With setupNodeSdk: true you get traces printed to your terminal immediately — no collector, no dashboard, nothing else to configure.

What do I need before I start?

An MCP server built on either supported SDK — @modelcontextprotocol/sdk (v1) or @modelcontextprotocol/server (v2, protocol revision 2026-07-28, v0.10.0+) — using either SDK's high-level McpServer or low-level Server class. Both SDKs are OPTIONAL peer dependencies of opentel-mcp; install whichever one your project actually uses. The quickstart below uses v1's McpServer, StdioServerTransport, and zod (for the example tool's input schema); install whichever of those your project doesn't already have alongside opentel-mcp itself:

bash
npm install opentel-mcp @opentelemetry/api

opentel-mcp is an ES module — add "type": "module" to your package.json if it isn't there already.

How do I instrument my server?

Call instrumentMcpServer() on your server before registering any tools — before any .tool()/.registerTool() call on McpServer, or before server.setRequestHandler(CallToolRequestSchema, ...) on a low-level Server. Registering a tool first means that handler was never wrapped; see API Reference for the full InstrumentOptions reference. Shown below using v1's @modelcontextprotocol/sdk import paths — @modelcontextprotocol/server (v2)'s equivalents work the same way; see API Reference's "Does opentel-mcp support MCP v2?" section for what differs.

js
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { instrumentMcpServer } from 'opentel-mcp';
import { z } from 'zod';
 
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
 
// Wraps every tool registered below. Must run BEFORE server.tool() —
// see the ordering note above for why.
instrumentMcpServer(server, {
  serviceName: 'my-mcp-server', // shows up on your traces
  setupNodeSdk: true, // dev mode: prints traces to your terminal
});
 
// A normal tool, registered exactly as usual.
server.tool('echo', { text: z.string() }, async ({ text }) => ({
  content: [{ type: 'text', text: `you said: ${text}` }],
}));
 
const transport = new StdioServerTransport();
await server.connect(transport);

Using @modelcontextprotocol/server (v2) instead?

The same instrumentMcpServer() call works — but v2's createMcpHandler/serveStdio construct a fresh server per request via a factory function, so instrumentMcpServer() must run inside that factory on every invocation, not once at module load like the snippet above. See API Reference's "Does opentel-mcp support MCP v2?" section for the factory pattern and why instanceKey matters there.

That's the whole integration. setupNodeSdk: true is a dev-mode convenience — it creates and registers a NodeTracerProvider for you that prints spans to stderr, so there's nothing else to wire up to see this working locally. In production, register your own TracerProvider (pointed at SigNoz, Jaeger, Honeycomb, or any OTLP-compatible backend) and leave setupNodeSdk unset — opentel-mcp picks up whatever provider your application already registered.

How do I see the trace?

Run the snippet above and call the echo tool once. This is a real, captured run from opentel-mcp's own README — not a synthesized example:

text
name: 'tools/call echo'
kind: 1                    // SpanKind.SERVER
status: { code: 1 }        // OK
attributes: {
  'mcp.method.name': 'tools/call',
  'gen_ai.tool.name': 'echo',
  'mcp.tool.argument_count': 1,
  'jsonrpc.request.id': '1'
}

No dashboard needed for this — setupNodeSdk: true's dev exporter printed this directly to your terminal. Point a real backend at exporterUrl (or register your own provider) once you're ready to move past local development.

Where do I go from here?

  • Silent Failures — what changes on this same span when a tool call returns isError: true instead of succeeding.
  • Deep Failure Fingerprinting — the stable 16-character identifier attached to every failure on that same span.
  • Metrics — register a MeterProvider alongside the tracer above to get the six mcp.tool.* instruments too.
  • Migration Guide — already have @opentelemetry/api instrumentation on your MCP server? Start here instead.