npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@theharithsa/opentelemetry-instrumentation-mcp

v1.0.4

Published

A package to auto instrument the Model Context Protocol (MCP) SDK with OpenTelemetry

Readme

OpenTelemetry Instrumentation for Model Context Protocol (MCP)

npm version npm downloads License: ISC Build Status

Automatic OpenTelemetry instrumentation for the Model Context Protocol SDK, enabling observability and telemetry collection for MCP-based applications with zero configuration required.

Version 1.0.2 - Production ready with stable API and parent span stitching solution.

Features

  • 🔄 Automatic instrumentation of MCP tool calls
  • 📊 Built-in OpenTelemetry setup with NodeSDK
  • 🚀 Drop-in solution - no manual OpenTelemetry configuration needed
  • 📈 OTLP export with Dynatrace support out of the box
  • 🔍 Comprehensive tracing of tool execution with error handling
  • Zero-code integration - just import and go
  • 🔗 Parent span stitching - maintains trace context across tool executions

Installation

npm install @theharithsa/opentelemetry-instrumentation-mcp

Quick Start

Option 1: Auto-Registration (Recommended)

Simply import the register module at the very top of your application entry point:

// At the top of your src/index.ts or main file
import '@theharithsa/opentelemetry-instrumentation-mcp/register';

// Your existing MCP server code...
import { McpServer } from '@modelcontextprotocol/sdk';
// ... rest of your application

Option 2: Manual Setup

If you need more control over the OpenTelemetry configuration:

import { McpInstrumentation } from '@theharithsa/opentelemetry-instrumentation-mcp';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';

const sdk = new NodeSDK({
  instrumentations: [
    getNodeAutoInstrumentations(),
    new McpInstrumentation(),
  ],
  // Your custom configuration...
});

sdk.start();

Environment Variables

When using the auto-registration approach, configure these environment variables:

# Required: OTLP endpoint for trace export
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-dynatrace-endpoint.com/api/v2/otlp/v1/traces

# Required: OTLP headers including authorization
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Api-Token <Token>

Header Format

The OTEL_EXPORTER_OTLP_HEADERS environment variable supports comma-separated key=value pairs:

# Single header
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Api-Token your-token-here

# Multiple headers
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Api-Token your-token,Custom-Header=value,Another=header-value

What Gets Instrumented

The instrumentation automatically creates spans for:

  • MCP Tool Calls: Each tool invocation gets its own span named mcp.tool:{toolName}
  • Error Tracking: Exceptions are recorded and spans are marked with error status
  • Execution Context: Full OpenTelemetry context propagation

Parent Span Stitching Solution

The Challenge

By default, MCP tool executions may not maintain proper parent-child span relationships, leading to disconnected traces in complex applications.

The Solution: Tool Wrapper Pattern

Create a custom wrapper function around your tool definitions that establishes parent spans. The McpInstrumentation will automatically create child spans within this context:

import { trace, context, SpanStatusCode, SpanKind } from '@opentelemetry/api';

const tracer = trace.getTracer('your-application', '1.0.0');

const tool = (
  name: string,
  description: string,
  paramsSchema: ZodRawShape,
  cb: (args: z.infer<z.ZodObject<ZodRawShape>>, _extra?: any) => Promise<string>
) => {
  server.tool(name, description, paramsSchema, async (args, _extra) => {
    return await tracer.startActiveSpan(
      `Tool.${name}`,
      {
        kind: SpanKind.SERVER,
        attributes: {
          'Tool.name': name,
          'Tool.args': JSON.stringify(args),
        },
      },
      async (span) => {
        try {
          const result = await context.with(trace.setSpan(context.active(), span), async () => {
            return await cb(args, _extra);
          });
          
          span.setStatus({ code: SpanStatusCode.OK });
          span.setAttributes({
            'mcp.tool.result.length': result.length,
            'mcp.tool.success': true,
          });
          
          return {
            content: [{ type: 'text', text: result }],
          };
        } catch (error: any) {
          span.recordException(error);
          span.setStatus({
            code: SpanStatusCode.ERROR,
            message: error.message,
          });
          span.setAttributes({
            'mcp.tool.success': false,
            'mcp.tool.error': error.message,
          });
          
          return {
            content: [{ type: 'text', text: `Unexpected error: ${error.message}` }],
            isError: true,
          };
        } finally {
          span.end();
        }
      }
    );
  });
};

How It Works

  1. Parent Span Creation: The wrapper creates a parent span named Tool.${name}
  2. Automatic Child Spans: McpInstrumentation detects the active context and creates child spans mcp.tool:${name}
  3. Context Propagation: All operations within the tool callback inherit the proper trace context
  4. No Manual Span Management: Individual tools don't need to create or manage spans manually

Benefits

  1. Automatic Hierarchy: Parent-child span relationships are established automatically
  2. Zero Manual Work: Individual tools don't need span management code
  3. Consistent Tracing: Every tool gets proper instrumentation without code duplication
  4. Error Handling: Exception recording and span status management is centralized
  5. Context Inheritance: All async operations within tools inherit the correct trace context

Adding Custom Span Attributes

Since the wrapper creates an active span context, you can easily add custom attributes in your tool implementations:

tool('create_workflow', 'Create a workflow', { model: z.string() }, async ({ model }) => {
  // Get the active span (created by the wrapper)
  const span = trace.getSpan(context.active());
  if (span) {
    span.setAttribute('model.name', model);
    span.setAttribute('operation.category', 'workflow');
  }

  // Perform the actual work
  const result = await createWorkflow({ model });
  return `Workflow created: ${result.id}`;
});

Example Trace Output

With the tool wrapper pattern, you'll see properly structured traces:

Tool.get_environment_info (Parent Span - from wrapper)
  ├── mcp.tool:get_environment_info (Child Span - from McpInstrumentation)
  │   ├── Duration: 45ms
  │   ├── Status: OK
  │   └── Attributes: tool execution details
  └── Additional child spans from API calls...

Tool.execute_dql (Parent Span - from wrapper)
  ├── mcp.tool:execute_dql (Child Span - from McpInstrumentation)
  │   ├── Duration: 120ms
  │   ├── Status: ERROR
  │   └── Exception: QuerySyntaxError
  └── Additional context...

Requirements

  • Node.js >= 16.0.0
  • MCP SDK (@modelcontextprotocol/sdk)
  • Environment variables for OTLP export

Package Structure

@theharithsa/opentelemetry-instrumentation-mcp/
├── index.js          # McpInstrumentation class
└── register.js       # Auto-registration with NodeSDK

Migration from Manual Setup

If you were previously setting up OpenTelemetry manually:

  1. Remove your custom OpenTelemetry setup code

  2. Delete manual instrumentation imports

  3. Add the single import line at the top of your entry point:

    import '@theharithsa/opentelemetry-instrumentation-mcp/register';
  4. Set the required environment variables

  5. Implement the tool wrapper pattern for proper span hierarchy

  6. Restart your application

Best Practices

Tool Wrapper Implementation

  • Create the wrapper once and reuse it for all tools
  • Include essential attributes like tool name and arguments
  • Let McpInstrumentation handle the detailed MCP-specific instrumentation
  • Add custom attributes within tool callbacks as needed

Error Handling

  • The wrapper handles top-level errors and span status
  • Individual tools should focus on business logic
  • Thrown exceptions are automatically recorded and propagated

License

ISC

Changelog

See CHANGELOG.md for detailed version history and breaking changes.

Contributing

Issues and pull requests are welcome on GitHub.