MCP
The autotel monorepo ships two Model Context Protocol packages with different jobs. Pick the one you need:
| Package | Purpose |
|---|---|
autotel-mcp-instrumentation | Instrument your own MCP servers and clients with OpenTelemetry. |
autotel-mcp | A standalone MCP server that lets AI agents query your traces, metrics, and logs. |
If you’re building an MCP-based product → use autotel-mcp-instrumentation.
If you want Claude / Cursor / Windsurf to investigate observability data
→ install autotel-mcp.
autotel-mcp-instrumentation
Section titled “autotel-mcp-instrumentation”Automatic instrumentation for MCP servers and clients. W3C Trace Context
propagates through the _meta field, which works across stdio, HTTP, SSE, or
any custom transport. Bundle ~7KB total.
Installation
Section titled “Installation”npm install autotel-mcp-instrumentation @modelcontextprotocol/sdk autotelServer-side
Section titled “Server-side”import { Server } from '@modelcontextprotocol/sdk/server/index';import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio';import { instrumentMcpServer } from 'autotel-mcp-instrumentation/server';import { init } from 'autotel';
init({ service: 'mcp-weather-server', endpoint: 'http://localhost:4318' });
const server = new Server({ name: 'weather', version: '1.0.0' });
const instrumented = instrumentMcpServer(server, { captureArgs: true, captureResults: false, // off by default; PII concern});
instrumented.registerTool({ name: 'get_weather', description: 'Get current weather for a location', inputSchema: { type: 'object', properties: { location: { type: 'string' } }, required: ['location'], }, handler: async (args) => { // Auto-traced; parent context extracted from _meta const weather = await fetchWeather(args.location); return { content: [{ type: 'text', text: `Temp: ${weather.temp}°F` }] }; },});
await server.connect(new StdioServerTransport());Client-side
Section titled “Client-side”import { Client } from '@modelcontextprotocol/sdk/client/index';import { instrumentMcpClient } from 'autotel-mcp-instrumentation/client';import { init } from 'autotel';
init({ service: 'mcp-weather-client', endpoint: 'http://localhost:4318' });
const client = new Client({ name: 'weather-client', version: '1.0.0' });
const instrumented = instrumentMcpClient(client, { captureArgs: true, captureResults: false,});
// _meta is injected with traceparent/tracestate/baggage automaticallyconst result = await instrumented.callTool('get_weather', { location: 'NYC' });Configuration
Section titled “Configuration”interface McpInstrumentationConfig { captureArgs?: boolean; // default true captureResults?: boolean; // default false (PII risk) captureErrors?: boolean; // default true customAttributes?: (ctx: { type, name, args, result }) => Attributes;}Span attributes
Section titled “Span attributes”Server spans:
mcp.type—tool,resource,promptmcp.tool.name/mcp.resource.name/mcp.prompt.namemcp.tool.args(whencaptureArgs: true)mcp.tool.result(whencaptureResults: true)
Client spans:
mcp.client.operation—callTool,getResource,getPromptmcp.client.name,mcp.client.args,mcp.client.result
Distributed tracing via _meta
Section titled “Distributed tracing via _meta”Because trace context lives in the JSON payload’s _meta field, the same
propagation works across stdio, HTTP, SSE, WebSocket, or any custom transport
without any header plumbing:
import { injectOtelContextToMeta, extractOtelContextFromMeta, activateTraceContext,} from 'autotel-mcp-instrumentation/context';import { context } from '@opentelemetry/api';
// Client side: injectconst request = { method: 'tools/call', params: { name: 'my-tool', arguments: {} }, _meta: injectOtelContextToMeta({}),};
// Server side: extract and activateconst ctx = activateTraceContext(request._meta);return context.with(ctx, () => runHandler(request));Custom attributes & PII redaction
Section titled “Custom attributes & PII redaction”customAttributes runs per span. Use it to redact PII or attach
business-specific tags:
const instrumented = instrumentMcpServer(server, { captureArgs: false, // disable raw arg capture customAttributes: ({ type, name, args, result }) => ({ 'tool.location': args?.location, // safe to log 'tenant.id': args?.tenantId, ...(type === 'tool' && name === 'search' ? { 'search.results.count': result?.items?.length ?? 0 } : {}), }),});Security observability
Section titled “Security observability”MCP is where untrusted data crosses into your agent. The agentic-web threat model (Chrome/Google, June 2026) has two vectors: malicious manifests (hidden instructions in a tool’s name/description/annotations) and contaminated outputs (injection smuggled inside legitimate tool results). Detecting these in production is an observability problem — and this package makes it observable at the MCP boundary.
On by default (no extra config), every tool span carries:
- Annotation hints →
mcp.tool.read_only,mcp.tool.destructive,mcp.tool.idempotent,mcp.tool.open_world,mcp.tool.untrusted_content - Payload sizes →
mcp.tool.arguments.size,mcp.tool.result.size(sizes only, no content) — a token-exhaustion / contaminated-output tell
Opt-in — a pluggable classifier (Model Armor, Promptfoo, an LLM critic, or the built-in heuristic) plus output budgets:
import { instrumentMcpServer, heuristicInjectionClassifier, MCP_CHAR_BUDGETS,} from 'autotel-mcp-instrumentation';
instrumentMcpServer(server, { securityClassifier: heuristicInjectionClassifier(), // or your own (sync/async) outputCharBudget: MCP_CHAR_BUDGETS.TOOL_OUTPUT, // 1500 (WebMCP recommendation)});The classifier scans tool arguments (server + client) and results (the
contaminated-output vector the agent receives), recording mcp.security.injection.*
and emitting a mcp.security.injection_suspected event on non-clean verdicts.
Classifier failures never break the traced call. The built-in heuristic is a
cheap tripwire — feed its signal to a real classifier, don’t gate actions on it.
Helpers (autotel-mcp-instrumentation/security, runtime-agnostic — works in
Workers/edge):
import { spotlight, validateToolBudget } from 'autotel-mcp-instrumentation/security';
const safe = spotlight(userComment); // <untrusted>…</untrusted> (or { method: 'base64' })const violations = validateToolBudget({ name, description, parameters }); // WebMCP char limitsGuard bridge — detection → enforcement. Pass an autotel-genai guard and
every tool call is recorded as a step; a stop rule throws to halt a runaway run:
import { createGenAiBudget } from 'autotel-genai/guard';
const guard = createGenAiBudget({ maxToolCalls: 50, maxCostUsd: 5 });instrumentMcpClient(client, { guard }); // duck-typed — no genai dependency addedSignals reference
| Signal | Kind | Meaning |
|---|---|---|
mcp.tool.* (hints) | span attr | tool trust profile / manifest vector |
mcp.tool.{arguments,result}.size | span attr | payload size (token-exhaustion tell) |
mcp.security.injection.* | span attr | classifier verdict / score / categories |
mcp.security.injection_suspected | event | non-clean classifier verdict |
mcp.security.budget_exceeded | event | output over outputCharBudget |
mcp.security.events | counter | aggregate security-signal count |
Query them in production with autotel security mcp (see the
investigate CLI).
Entry points
Section titled “Entry points”| Import | Contents | Size |
|---|---|---|
autotel-mcp-instrumentation | Everything | ~7KB |
autotel-mcp-instrumentation/server | instrumentMcpServer | ~5KB |
autotel-mcp-instrumentation/client | instrumentMcpClient | ~4KB |
autotel-mcp-instrumentation/context | inject*/extract*/activate* | ~2KB |
autotel-mcp-instrumentation/security | classifier, spotlight, budgets | ~2KB |
Examples
Section titled “Examples”example-mcp-server— MCP server withinstrumentMcpServer().example-mcp-client— MCP client withinstrumentMcpClient()and distributed tracing.
autotel-mcp
Section titled “autotel-mcp”A standalone MCP server that gives AI agents (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Goose) the ability to investigate your OpenTelemetry traces, metrics, and logs. It ships with a built-in OTLP collector on port 4318, so any instrumented app can send data directly. You don’t need Jaeger, Tempo, or Grafana.
- Backend-agnostic. Built-in OTLP collector accepts data from any OTel-instrumented app.
- All three signals. Traces, metrics, and logs with cross-signal correlation.
- Agent-optimised. 33 tools designed for progressive investigation: discover → diagnose → correlate → root-cause.
- Zero infrastructure. In-memory by default; persistent with
--persist.
Requirements
Section titled “Requirements”- Node.js 20+
- An MCP-compatible client
Standard config
Section titled “Standard config”{ "mcpServers": { "autotel": { "command": "npx", "args": ["autotel-mcp"] } }}Claude Code
Section titled “Claude Code”claude mcp add autotel npx autotel-mcpOnce installed, ask the agent things like “What slowed down checkout in the
last hour?” or “Show traces with errors for service.name=api”. The agent
will discover services, query traces, correlate logs, and return a written
investigation.
Related
Section titled “Related”- AI-Assisted Observability — patterns for letting agents investigate production traces.
- MCP example app — autotel instruments a Node app, Claude queries the traces via MCP.