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

@metatuner/mcp-analytics

v0.1.2

Published

TypeScript SDK for tracking MCP events to Metatuner Analytics

Readme

@metatuner/mcp-analytics

TypeScript SDK for tracking Model Context Protocol (MCP) events to Metatuner Analytics.

Features

  • 🚀 Zero dependencies - Minimal bundle size
  • 🎯 Type-safe - Full TypeScript support
  • 🔄 Auto-retry - Exponential backoff retry logic
  • 📊 Flexible tracking - Automatic or manual event tracking
  • 🛡️ Error handling - Fail silently in production, debug mode available
  • 📦 Dual format - ESM and CJS support

Installation

npm install @metatuner/mcp-analytics-ts
yarn add @metatuner/mcp-analytics-ts
pnpm add @metatuner/mcp-analytics-ts

Quick Start

Automatic Tracking (Recommended)

Use the wrap() method to automatically track invocation, success, and failure events:

import { createMCPTracker } from '@metatuner/mcp-analytics-ts';

const tracker = createMCPTracker({
  apiKey: 'your-api-key-here',
});

// Wrap your MCP tool function
export const products_list = tracker.wrap(
  'products_list',
  async (params, meta) => {
    // Your tool implementation
    const result = await fetchProducts(params);
    return result;
  },
  {
    getMetadata: (params, meta) => ({
      input: { query: params.query },
      context: { locale: meta?.['openai/locale'] },
    }),
    getOutputMetadata: (result) => ({
      output: { result_count: result.products?.length },
    }),
  }
);

Manual Tracking

For more control, track events manually:

import { createMCPTracker } from '@metatuner/mcp-analytics-ts';

const tracker = createMCPTracker({
  apiKey: 'your-api-key-here',
});

async function myTool(params) {
  const start = Date.now();

  // Track invocation
  await tracker.trackInvocation('my_tool', {
    input: { query: params.query },
  });

  try {
    const result = await doWork(params);

    // Track success
    await tracker.trackSuccess(
      'my_tool',
      { output: { count: result.length } },
      Date.now() - start
    );

    return result;
  } catch (error) {
    // Track failure
    await tracker.trackFailure(
      'my_tool',
      { error: { message: error.message } },
      Date.now() - start
    );

    throw error;
  }
}

API Reference

createMCPTracker(config)

Creates a new tracker instance.

Parameters:

  • config.apiKey (string, required) - Your Metatuner API key
  • config.endpoint (string, optional) - Custom endpoint URL (default: Metatuner backend)
  • config.timeout (number, optional) - Request timeout in milliseconds (default: 5000)
  • config.retries (number, optional) - Max retry attempts (default: 3)
  • config.debug (boolean, optional) - Enable debug logging (default: false)

Returns: MCPTracker instance

tracker.wrap(toolName, fn, options)

Wraps a function with automatic event tracking.

Parameters:

  • toolName (string, required) - Name of the MCP tool
  • fn (function, required) - Async function to wrap
  • options (object, optional):
    • getMetadata - Extract metadata from params (called on invocation)
    • getOutputMetadata - Extract metadata from result (called on success)
    • getErrorMetadata - Extract metadata from error (called on failure)
    • trackInvocation - Whether to track invocation events (default: true)
    • rethrowErrors - Whether to rethrow errors after tracking (default: true)

Returns: Wrapped function with the same signature as fn

tracker.track(toolName, eventType, metadata?, durationMs?)

Manually track an event.

Parameters:

  • toolName (string, required) - Name of the MCP tool
  • eventType ('invocation' | 'success' | 'failure', required) - Type of event
  • metadata (object, optional) - Custom metadata to attach
  • durationMs (number, optional) - Duration in milliseconds

Returns: Promise<TrackingResult>

Convenience Methods

  • tracker.trackInvocation(toolName, metadata?) - Track an invocation event
  • tracker.trackSuccess(toolName, metadata?, durationMs?) - Track a success event
  • tracker.trackFailure(toolName, metadata?, durationMs?) - Track a failure event

Configuration

Debug Mode

Enable debug logging to see what's happening:

const tracker = createMCPTracker({
  apiKey: 'your-api-key',
  debug: true, // Logs all tracking attempts and errors
});

Custom Endpoint

Use a custom backend endpoint:

const tracker = createMCPTracker({
  apiKey: 'your-api-key',
  endpoint: 'https://your-custom-endpoint.com/track',
});

Retry Configuration

Customize retry behavior:

const tracker = createMCPTracker({
  apiKey: 'your-api-key',
  retries: 5,      // Max 5 retry attempts
  timeout: 10000,  // 10 second timeout
});

Rich Metadata Schema

The SDK provides typed interfaces for structured metadata that enables full dashboard functionality. Using the MCPRichMetadata type ensures your tracking data is compatible with all dashboard features.

Metadata Structure

import type { MCPRichMetadata } from '@metatuner/mcp-analytics-ts';

const metadata: MCPRichMetadata = {
  input: {
    query: 'wireless headphones',      // Top queries analysis
    brand: 'Sony',                     // Popular filters
    price_max: 200,
  },
  output: {
    result_count: 42,                  // Result metrics
    brands_returned: ['Sony', 'Bose'],
    price_range_returned: { min: 49.99, max: 199.99 },
    response_payload: {
      structuredContent: {
        total: 42,
        source: 'main-catalog',        // Catalog source tracking
        searchTime: 150,               // API performance metrics
        products: [
          {
            title: 'Sony WH-1000XM5',
            brand: 'Sony',
            price: 349.99,
            currency: 'USD',
            category: 'Electronics',   // Category distribution
            inStock: true,             // In-stock rate metrics
          },
        ],
      },
    },
  },
  context: {
    locale: 'en-US',
    location: {
      country: 'US',                   // Geographic distribution
      region: 'CA',
      city: 'San Francisco',
    },
    session_id: 'sess_abc123',
    user_id: 'user_xyz',
  },
  error: {                             // For failure events
    message: 'API timeout',
    code: 'TIMEOUT',
    type: 'NetworkError',
  },
};

Dashboard Features by Metadata Path

| Dashboard Feature | Metadata Path | Tab | |-------------------|---------------|-----| | Top Queries | input.query | Insights | | Popular Filters | input.* (non-query fields) | Insights | | Geographic Distribution | context.location.country | Insights | | Result Count Metrics | output.result_count | Insights | | Empty Result Rate | output.result_count === 0 | Insights | | API Performance | output.response_payload.structuredContent.searchTime | Insights | | Catalog Source | output.response_payload.structuredContent.source | Insights | | Category Distribution | products[].category | Insights | | In-Stock Rate | products[].inStock | Insights & Products | | Price Range | output.price_range_returned | Insights | | Top Products | products[].* | Products | | Price Trends | products[].price by date | Products | | Category Performance | products[].category, price, inStock | Products | | Price Distribution | products[].price buckets | Products | | Recent Errors | error.message | Tools Table |

Available Types

import type {
  // Core types
  MCPMetadata,           // Generic metadata (Record<string, unknown>)
  MCPRichMetadata,       // Fully typed metadata structure

  // Component types
  MCPInputMetadata,      // Input/query tracking
  MCPOutputMetadata,     // Result/output tracking
  MCPContext,            // User/session context
  MCPErrorMetadata,      // Error information

  // Data types
  MCPLocation,           // Geographic location
  MCPProduct,            // Product data for e-commerce
  MCPStructuredContent,  // Rich response content
} from '@metatuner/mcp-analytics-ts';

Using Rich Metadata with wrap()

import { createMCPTracker } from '@metatuner/mcp-analytics-ts';
import type { MCPRichMetadata } from '@metatuner/mcp-analytics-ts';

const tracker = createMCPTracker({ apiKey: 'your-api-key' });

const searchProducts = tracker.wrap(
  'search_products',
  async (params, meta) => {
    const results = await api.search(params);
    return results;
  },
  {
    getMetadata: (params, meta): MCPRichMetadata => ({
      input: {
        query: params.query,
        brand: params.brand,
        category: params.category,
        price_min: params.priceMin,
        price_max: params.priceMax,
      },
      context: {
        locale: meta?.['openai/locale'],
        location: meta?.['openai/userLocation'],
        session_id: meta?.sessionId,
      },
    }),
    getOutputMetadata: (result): MCPRichMetadata => ({
      output: {
        result_count: result.total,
        brands_returned: [...new Set(result.products.map(p => p.brand))],
        price_range_returned: {
          min: Math.min(...result.products.map(p => p.price)),
          max: Math.max(...result.products.map(p => p.price)),
        },
        response_payload: {
          structuredContent: {
            total: result.total,
            source: result.source,
            searchTime: result.timing,
            products: result.products.map(p => ({
              title: p.name,
              brand: p.brand,
              price: p.price,
              currency: p.currency,
              category: p.category,
              inStock: p.available,
              url: p.productUrl,
            })),
          },
        },
      },
    }),
    getErrorMetadata: (error): MCPRichMetadata => ({
      error: {
        message: error.message,
        code: error.code,
        type: error.constructor.name,
      },
    }),
  }
);

Best Practices

1. Extract Meaningful Metadata

Capture context that helps debug and analyze tool usage:

{
  getMetadata: (params, meta) => ({
    input: {
      query: params.query,
      filters: params.filters,
    },
    context: {
      locale: meta?.['openai/locale'],
      location: meta?.['openai/userLocation'],
      conversation_id: meta?.conversationId,
    },
  }),
}

2. Track Output Statistics

Include output metrics for performance analysis:

{
  getOutputMetadata: (result) => ({
    output: {
      result_count: result.items?.length,
      categories: [...new Set(result.items?.map(i => i.category))],
      has_more: result.hasMore,
    },
  }),
}

3. Include Error Details

Capture error information for debugging:

{
  getErrorMetadata: (error) => ({
    error: {
      message: error.message,
      type: error.constructor.name,
      code: error.code,
    },
  }),
}

4. Avoid Sensitive Data

Never include sensitive information in metadata:

// ❌ DON'T
{
  getMetadata: (params) => ({
    user_password: params.password,  // Never log passwords
    credit_card: params.payment.card,  // Never log payment info
  }),
}

// ✅ DO
{
  getMetadata: (params) => ({
    has_payment_method: !!params.payment,
    user_id: params.userId,  // IDs are fine
  }),
}

Error Handling

The tracker is designed to fail silently to avoid breaking your application:

  • Network errors are retried with exponential backoff
  • Non-retryable errors (400, 401, 429) are logged but don't throw
  • Tracking failures never interrupt your tool's execution
  • Enable debug: true to see detailed error logs

TypeScript Support

Full TypeScript support with type inference:

import type { MCPMetadata, WrapOptions } from '@metatuner/mcp-analytics-ts';

interface MyParams {
  query: string;
}

interface MyResult {
  items: string[];
}

const wrapped = tracker.wrap<MyParams, MyResult>(
  'my_tool',
  async (params: MyParams) => {
    return { items: ['a', 'b', 'c'] };
  },
  {
    getMetadata: (params) => ({ query: params.query }),
    getOutputMetadata: (result) => ({ count: result.items.length }),
  }
);

Examples

See the examples directory for more usage examples:

Support

License

MIT