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 🙏

© 2025 – Pkg Stats / Ryan Hefner

autolemetry-mcp

v2.0.0

Published

OpenTelemetry instrumentation for Model Context Protocol (MCP) with distributed tracing support

Readme

autolemetry-mcp

OpenTelemetry instrumentation for Model Context Protocol (MCP) with automatic distributed tracing.

Automatically instrument MCP servers and clients with OpenTelemetry tracing. Uses W3C Trace Context propagation via the _meta field to enable distributed tracing across MCP boundaries.

Features

  • Automatic instrumentation - One function call to instrument all tools, resources, and prompts
  • Distributed tracing - W3C Trace Context propagation via _meta field
  • Transport-agnostic - Works with stdio, HTTP, SSE, or any MCP transport
  • Node.js runtime - Full support for Node.js applications with autolemetry
  • Tree-shakeable - Import only what you need (~7KB total, 2-5KB per module)
  • Zero MCP modifications - Uses Proxy pattern, no changes to MCP SDK required

Installation

npm install autolemetry-mcp @modelcontextprotocol/sdk autolemetry

Quick Start

Server-Side Instrumentation

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { instrumentMcpServer } from 'autolemetry-mcp/server';
import { init } from 'autolemetry';

// Initialize OpenTelemetry
init({
  service: 'mcp-weather-server',
  endpoint: 'http://localhost:4318',
});

const server = new Server({
  name: 'weather',
  version: '1.0.0',
});

// Instrument the server (automatic tracing for all tools/resources/prompts)
const instrumented = instrumentMcpServer(server, {
  captureArgs: true, // Log tool arguments
  captureResults: false, // Don't log results (PII concerns)
});

// Register tools normally - they're automatically traced!
instrumented.registerTool({
  name: 'get_weather',
  description: 'Get current weather for a location',
  inputSchema: {
    type: 'object',
    properties: {
      location: { type: 'string' },
    },
    required: ['location'],
  },
  handler: async (args) => {
    // This handler is automatically traced with parent context from _meta
    const weather = await fetchWeather(args.location);
    return {
      content: [
        {
          type: 'text',
          text: `Temperature in ${args.location}: ${weather.temp}°F`,
        },
      ],
    };
  },
});

await server.connect(new StdioServerTransport());

Client-Side Instrumentation

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { instrumentMcpClient } from 'autolemetry-mcp/client';
import { init } from 'autolemetry';

// Initialize OpenTelemetry
init({
  service: 'mcp-weather-client',
  endpoint: 'http://localhost:4318',
});

const client = new Client({
  name: 'weather-client',
  version: '1.0.0',
});

// Instrument the client (automatic trace context injection)
const instrumented = instrumentMcpClient(client, {
  captureArgs: true,
  captureResults: false,
});

await client.connect(new StdioClientTransport(/* ... */));

// Tool calls automatically create spans and inject _meta with trace context
const result = await instrumented.callTool('get_weather', {
  location: 'New York',
  // _meta field is automatically injected with traceparent/tracestate/baggage
});

API Reference

Server Instrumentation

instrumentMcpServer(server, config?)

Wraps an MCP server to automatically trace all registered tools, resources, and prompts.

Parameters:

  • server - MCP Server instance
  • config - Optional instrumentation configuration

Returns: Instrumented server (Proxy)

Configuration Options:

interface McpInstrumentationConfig {
  captureArgs?: boolean; // Capture tool/resource arguments (default: true)
  captureResults?: boolean; // Capture results - may contain PII (default: false)
  captureErrors?: boolean; // Capture errors and exceptions (default: true)
  customAttributes?: (context) => Attributes; // Custom span attributes
}

Span Attributes Set:

  • mcp.type - Operation type ('tool', 'resource', 'prompt')
  • mcp.tool.name / mcp.resource.name / mcp.prompt.name - Name
  • mcp.tool.args - Arguments (if captureArgs: true)
  • mcp.tool.result - Result (if captureResults: true)

Client Instrumentation

instrumentMcpClient(client, config?)

Wraps an MCP client to automatically create spans and inject trace context for all requests.

Parameters:

  • client - MCP Client instance
  • config - Optional instrumentation configuration

Returns: Instrumented client (Proxy)

Span Attributes Set:

  • mcp.client.operation - Operation type ('callTool', 'getResource', 'getPrompt')
  • mcp.client.name - Tool/resource/prompt name
  • mcp.client.args - Arguments (if captureArgs: true)
  • mcp.client.result - Result (if captureResults: true)

Context Utilities

extractOtelContextFromMeta(meta?)

Extract OpenTelemetry context from MCP _meta field.

import { extractOtelContextFromMeta } from 'autolemetry-mcp/context';
import { context } from '@opentelemetry/api';

const handler = async (args, _meta) => {
  const parentContext = extractOtelContextFromMeta(_meta);
  return context.with(parentContext, async () => {
    // Your traced code with parent context
  });
};

injectOtelContextToMeta(ctx?)

Inject OpenTelemetry context into MCP _meta field.

import { injectOtelContextToMeta } from 'autolemetry-mcp/context';

const meta = injectOtelContextToMeta();
// Returns: { traceparent, tracestate, baggage }

await client.callTool('my_tool', { arg1: 'value', _meta: meta });

activateTraceContext(meta?)

Extract and immediately activate trace context from _meta field.

import { activateTraceContext } from 'autolemetry-mcp/context';
import { context } from '@opentelemetry/api';

const ctx = activateTraceContext(_meta);
return context.with(ctx, () => {
  // Traced code with parent context active
});

How It Works

W3C Trace Context Propagation

MCP requests can include a _meta field for metadata. autolemetry-mcp uses this field to propagate W3C Trace Context headers across client-server boundaries:

┌─────────────┐                    ┌─────────────┐
│ MCP Client  │                    │ MCP Server  │
│             │                    │             │
│  Span A     │──── callTool ────▶│  Span B     │
│             │    { args,         │             │
│             │      _meta: {      │ (parent: A) │
│             │        traceparent │             │
│             │        tracestate  │             │
│             │        baggage }}  │             │
└─────────────┘                    └─────────────┘

Distributed Trace:
  Span A (client) → Span B (server, child of A)

Client Side:

  1. Creates span for tool call
  2. Injects W3C trace context into _meta field
  3. Sends request with _meta

Server Side:

  1. Receives request with _meta field
  2. Extracts parent trace context
  3. Creates child span with parent context
  4. Executes tool handler

Transport Agnostic

Because context is in the JSON payload itself (not HTTP headers), this works with any MCP transport:

  • stdio (standard input/output)
  • HTTP/SSE (server-sent events)
  • WebSocket
  • Custom transports

Runtime Support

import { instrumentMcpServer } from 'autolemetry-mcp/server';
import { init } from 'autolemetry';

init({ service: 'my-mcp-server', endpoint: 'http://localhost:4318' });
const instrumented = instrumentMcpServer(server);

Bundle Size

  • Core context utilities: ~2KB
  • Server instrumentation: ~3KB
  • Client instrumentation: ~2KB
  • Total (all modules): ~7KB

Tree-shakeable - import only what you need:

// Import just server instrumentation (~5KB)
import { instrumentMcpServer } from 'autolemetry-mcp/server';

// Import just client instrumentation (~4KB)
import { instrumentMcpClient } from 'autolemetry-mcp/client';

// Import just context utilities (~2KB)
import {
  extractOtelContextFromMeta,
  injectOtelContextToMeta,
} from 'autolemetry-mcp/context';

Custom Attributes

Add custom span attributes based on your application logic:

const instrumented = instrumentMcpServer(server, {
  customAttributes: ({ type, name, args, result }) => {
    const attrs: Attributes = {};

    // Add tenant ID from arguments
    if (args?.tenantId) {
      attrs['tenant.id'] = args.tenantId;
    }

    // Add result metadata
    if (result?.metadata) {
      attrs['result.metadata'] = JSON.stringify(result.metadata);
    }

    // Add operation-specific attributes
    if (type === 'tool' && name === 'search') {
      attrs['search.query'] = args?.query;
      attrs['search.results.count'] = result?.items?.length ?? 0;
    }

    return attrs;
  },
});

Security Considerations

PII in Arguments/Results

By default, captureResults is disabled to prevent PII leakage:

const instrumented = instrumentMcpServer(server, {
  captureArgs: true, // May contain PII
  captureResults: false, // DISABLED by default - may contain sensitive data
});

For production:

  • Review what data is in tool arguments
  • Disable captureArgs if arguments contain PII
  • Never enable captureResults in production unless you control the data

Custom PII Redaction

Use customAttributes to redact PII:

const instrumented = instrumentMcpServer(server, {
  captureArgs: false, // Disable default arg capture
  customAttributes: ({ args }) => {
    // Manually redact PII before logging
    return {
      'tool.location': args?.location, // Safe to log
      // Omit args.email, args.userId, etc.
    };
  },
});

Examples

See the apps/ directory for complete working examples:

  • apps/example-mcp-server - Instrumented MCP server with stdio transport
  • apps/example-mcp-client - Instrumented MCP client calling the server

Integration with Observability Backends

Works with any OTLP-compatible backend:

import { init } from 'autolemetry';

// Honeycomb
init({
  service: 'mcp-server',
  endpoint: 'https://api.honeycomb.io',
  headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
});

// Datadog
init({
  service: 'mcp-server',
  endpoint: 'https://http-intake.logs.datadoghq.com',
  headers: { 'DD-API-KEY': process.env.DD_API_KEY },
});

License

MIT

Contributing

Issues and PRs welcome at github.com/jagreehal/autolemetry