Skip to content
opentel-mcp

Changelog

Version history for opentel-mcp, current version v0.10.0, sourced from the project's real commit history, README, ADRs, and known-gaps tracker — cross-checked against CHANGELOG.md where it agreed, not reconstructed from memory.

v0.10.0MCP v2 support, hardened server detectionAugust 11, 2026

  • Changed: detectServerKind() now additionally requires an McpServer-shaped object's .server property to be instanceof a real, resolved Server class from a supported SDK — not merely an object exposing a setRequestHandler method. An object that passes the outer shape check but fails that instanceof check now throws at instrumentMcpServer() call time, instead of succeeding and producing zero telemetry — a plain Error, not a named or exported error class (internally tracked as UNWRAPPABLE_MCPSERVER_ERROR, but that identifier is neither exported nor catchable — only the message string is real), so it can't be caught by type or a .code property. No escape hatch was added. Closes known-gaps entry 7.
  • Added: @modelcontextprotocol/server (MCP v2, protocol revision 2026-07-28) support — two separate, OPTIONAL peer dependencies (@modelcontextprotocol/sdk for v1, @modelcontextprotocol/server for v2), install whichever one(s) you actually use. Same Server/McpServer API shapes as v1; detection and wrapping resolved automatically, once per instrumentMcpServer() call. Spans, standard attributes (including jsonrpc.request.id, now read from v2's ctx.mcpReq.id), deep failure fingerprinting, and mcp.failure.channel/mcp.failure.validation_paths classification all work the same as v1. Full design: ADR 015.
  • Fixed: isSingleConnectionTransport() no longer auto-detects the transport createMcpHandler builds internally (PerRequestHTTPServerTransport) as single-connection — it now requires positive confirmation (transport.constructor.name === 'StdioServerTransport') for v2 specifically, closing a confirmed false positive. thrashConnectionFallbackSessionId is also now registry-backed via instanceKey, so the fallback session id is shared across repeated instrumentMcpServer() calls instead of regenerated fresh on every one. Together, these fix known-gaps entry 8 outright and close entry 6's fallback-id half — entry 6 itself is partially fixed, not closed; a structural limitation survives (see Tracker State Under Stateless HTTP). One narrower thing stays open: the internal flag tracking whether a server has ever proven itself session-aware isn't registry-backed yet — see Tracker State Under Stateless HTTP.

Behavior change on upgrade — read before updating

An McpServer-shaped object whose .server isn't a recognized Server instance from either supported SDK now throws — a plain Error, not a named or exported error class, so it can't be caught by type or a .code property — instead of silently instrumenting nothing. If you hit this on an object you believe genuinely is a Server/McpServer, suspect a duplicate or mismatched SDK install (check for multiple resolved copies — npm dedupe — or confirm normal node_modules resolution from wherever opentel-mcp itself is installed, if it's a monorepo/hoisting issue rather than a duplicate). A real Server/McpServer from a single, consistently-resolved SDK install is unaffected.

v0.9.0instanceKey, broadened auth classifierAugust 10, 2026

  • Fixed: the auth failure classifier missed "permission denied" and "access denied" — standard Unix/git/AWS IAM/GCP phrasing for a permission failure — recognizing only HTTP-status-derived wording (unauthorized, forbidden, authenticat(e|ion)), 401/403 codes, and known auth-library error names. Now also matches "not authorized", "permission(s) denied", "access denied", "insufficient permission(s)", and Node's EACCES/EPERM. Does not amend ADR 006's closed 8-category taxonomy — auth already existed; this is pattern coverage for when it fires.
  • Added: instanceKey, a string option on instrumentMcpServer() (or the OTEL_MCP_INSTANCE_KEY env var) that lets repeated instrumentMcpServer() calls sharing the same key share Agent Thrash Detection, budget tracking, schema drift detection, and the toolOutcome counter's state, instead of each call constructing all four fresh. Fixes the gap under "stateless" Streamable HTTP deployments (a fresh Server/McpServer re-instrumented per request). Backed by a bounded, TTL-evicting registry (1,000 keys/process, 24h TTL renewed on use). Omitting it (the default) is byte-identical to every prior version. Full design: ADR 012. See Tracker State Under Stateless HTTP.

Fingerprint values changed on upgrade for auth-classified messages

Because category is a hashed input to computeFingerprint(), the broadened auth classifier above changes mcp.failure.fingerprint for any message that now classifies as auth instead of internal. If you alert or dashboard on a specific fingerprint value for a permission error, expect a new value after upgrading.

v0.8.0Schema drift, observation contract, sampling signalAugust 7, 2026

  • Added: tool schema drift detection. Watches every tools/list response, hashes each tool's inputSchema (never description), and flags changes against the last-observed hash for that tool. Six drift kinds: field_added, field_removed, type_changed, required_changed, multiple, unknown. New mcp.tool.schema_drift.detected counter and matching span event on a new tools/list span, plus a schemaDrift config block (enabled, default true; maxTrackedTools, default 1000), each overridable via OTEL_MCP_SCHEMA_DRIFT_* env vars. Full design: ADR 010 (docs/adr/010-schema-drift.md).
  • Added: getObservationState(), a two-axis observation contract returning toolOutcome ({ success, failure, unknown }) and observationIntegrity ('DEGRADED' | 'UNKNOWN') — detects the case where instrumentation silently no-ops because no TracerProvider/MeterProvider was ever registered, so a failed tool call in that state doesn't read as indistinguishable from one that never failed. Prompted by external review from Massimiliano Brighindi. Full design: ADR 008 (docs/adr/008-observation-liveness.md, "Update (2026-08-05)" section).
  • Added: mcp.tool.thrash_detected boolean span attribute, set alongside the existing mcp.loop.detected span event — gives an OpenTelemetry Collector tailsamplingprocessor an attribute-level signal to key a boolean_attribute policy on, deliberately named differently from the mcp.tool.loop.detected metric counter. No new cost-threshold attribute or config — mcp.tool.cost.usd/mcp.tool.cost.budget_exceeded (v0.5.0) already suffice; the threshold itself lives in the Collector policy YAML. Full design: ADR 011 (docs/adr/011-cost-aware-sampling.md).

Behavior change on upgrade — read before updating

Because schemaDrift.enabled defaults to true, the existing instrument-first requirement now also covers tools/list. Low-level Server users who call setRequestHandler(ListToolsRequestSchema, ...) before instrumentMcpServer() will now get INSTRUMENT_FIRST_ERROR where they previously did not — no other code change required to hit it. McpServer users are unaffected (it registers tools/list and tools/call together, atomically). Migration: reorder the tools/list registration to after instrumentMcpServer(), or pass schemaDrift: { enabled: false }.

v0.7.0Channel-aware thrash thresholdsAugust 3, 2026

  • Added: mcp.failure.channel span attribute classifying where a tools/call failure originated — execution, protocol.input, protocol.not_found, protocol.output, protocol.other, unknown. Agent Thrash Detection picks a threshold per channel instead of treating every repeat identically.
  • Added: thrashDetection.inputThreshold (env OTEL_MCP_THRASH_INPUT_THRESHOLD, default 5) — a higher bar for protocol.input, since an agent retrying with adjusted arguments may be converging on a correct call.
  • Added: thrashDetection.notFoundThreshold (env OTEL_MCP_THRASH_NOT_FOUND_THRESHOLD, default 1) — an immediate flag; retrying a tool name that doesn't exist is never convergence.
  • Added: a classifyFailureChannel() recovery path for the high-level McpServer, reading the MCP error {code}: wrapper back out of errors McpServer already converted to isError: true before this library ever sees them.
  • Fixed: protocol.output failures — the tool's own output failing its declared output schema, the server author's bug, unfixable by any argument the agent supplies — are now excluded from Agent Thrash Detection entirely. This was a false positive present in every published version through v0.6.1.
  • Unchanged: the default threshold (3) still applies to execution, protocol.other, and unknown.

Reachability differs by server API

The low-level Server reaches all six mcp.failure.channel values directly. The high-level McpServer reaches the protocol channels only through the recovery path above, which is coupled to the installed SDK's exact error-message prose and degrades safely to execution (never a wrong specific answer) if that prose changes.

v0.6.1TypeScript declaration fixAugust 3, 2026

  • Fixed: src/index.d.ts re-exported values (computeFingerprint, toSpanAttributes, ATTRIBUTE_KEYS, METRIC_SAFE_ATTRIBUTES, DEFAULT_CLASSIFIERS, DEFAULT_PRICING, defaultExtractor, calculateCost) from six .js modules that had no corresponding .d.ts file, so any consumer with strict/noImplicitAny got a TS7016 error just from importing the package. Pre-existing since v0.4.0 (fingerprinting) and v0.5.0 (cost tracking) — first caught verifying the v0.6.0 published tarball.

v0.5.0Cost & Token AttributionJuly 29, 2026

  • Major feature: automatic LLM cost and token attribution on MCP tool calls that wrap model calls. See Cost & Token Attribution.
  • costTracking option (default enabled) on instrumentMcpServer() — recognizes Anthropic, OpenAI, and Bedrock usage field-name conventions, the MCP _meta.usage extension point, and JSON-in-text inside content[0].text. Never throws; unrecognized shapes resolve to null.
  • New span attributes: mcp.tool.tokens.input, mcp.tool.tokens.output, mcp.tool.tokens.total, mcp.tool.model, mcp.tool.cost.usd, and mcp.tool.cost.currency, plus gen_ai.response.model co-emitted alongside mcp.tool.model for GenAI semantic-convention dashboard compatibility.
  • Two new metric instruments, same @opentelemetry/api-only pattern as the four existing mcp.tool.* metrics: mcp.tool.tokens.total (counter, unit tokens) and mcp.tool.cost.total (counter, unit USD).
  • Budget guardrails (costTracking.budget: perSessionUsd, perToolUsd) — observability flags, not enforcement. Sets mcp.tool.cost.budget_exceeded / mcp.tool.cost.budget_scope on the span once a configured limit is crossed; never blocks or throws.
  • New exports: DEFAULT_PRICING (15+ models across five providers — Anthropic, OpenAI, Google, AWS Bedrock, DeepSeek), defaultExtractor, calculateCost. New types: CostTrackingOptions, TokenUsage, UsageExtractor, ModelPricing, PricingTable, BudgetConfig.

Pricing accuracy and budget scope

DEFAULT_PRICING is a convenience default, last verified 2026-07-29 — not a maintained price list. Provider pricing changes frequently; production users must override costTracking.pricingTable. Budget tracking is in-memory and per instrumentMcpServer() call — it resets on process restart, and session-scoped limits are skipped gracefully (not enforced against a fallback key) for transports with no session id, like stdio.

v0.4.0Deep Failure FingerprintingJuly 24, 2026

  • Adds computeFingerprint() — a stable, 16-hex-character identity (SHA-256, truncated) for every thrown error and every tool-level failure (isError: true), so repeated occurrences of the same underlying bug group under one fingerprint. See Deep Failure Fingerprinting.
  • Five new mcp.failure.* span attributes: fingerprint, signature, category (8 values), origin, and error class.
  • METRIC_SAFE_ATTRIBUTES — a frozen array structurally limiting which fingerprint-derived values can reach a metric label to category and origin (24 combinations maximum), so nothing unbounded can leak into a metrics backend.
  • fingerprinting option (default true) on instrumentMcpServer().
  • computeFingerprint, toSpanAttributes, ATTRIBUTE_KEYS, METRIC_SAFE_ATTRIBUTES, and DEFAULT_CLASSIFIERS re-exported from the package root.

Type-declaration gap in v0.4.0 — since resolved

The fingerprinting option was implemented and defaulted to true in src/config.js and src/instrument.js, but wasn't listed in the InstrumentOptions TypeScript interface in src/index.d.ts as of this version — it worked at runtime, but TypeScript consumers could see a type error passing { fingerprinting: false } anyway. Resolved in v0.6.0: fingerprinting?: boolean landed on InstrumentOptions. A narrower, related gap is still not resolved as of v0.10.0: computeFingerprint()'s classifiers/stackFrames options remain unwired through instrumentMcpServer()'s own options — no target version has been set for that part. See API Reference.

v0.3.0MetricsJuly 20, 2026

  • Four mcp.tool.* OpenTelemetry metrics instruments via @opentelemetry/api's Metrics API — no bundled SDK or exporter, same host-app-provides-the-SDK pattern tracing already used: mcp.tool.calls (counter, every call), mcp.tool.errors (counter, thrown/rejected handlers), mcp.tool.silent_failures (counter, isError: true), and mcp.tool.duration (histogram, ms). See Metrics.
  • mcp.tool.silent_failures fires from the same isError check that marks the span ERROR — extracted into one shared isToolResultError() helper so the detection logic isn't duplicated between traces and metrics.
  • enableMetrics option (default true) to opt out of metric emission without affecting tracing.
  • Metrics are a zero-overhead no-op until the host application registers a MeterProvider — default @opentelemetry/api behavior, not special-cased.
  • README: new Metrics section with an instrument table and a SigNoz OTLP/HTTP wiring example.

v0.1.1Patch releaseJuly 12, 2026

  • Fix: serviceName became optional — required (a non-empty string) only when setupNodeSdk: true, throwing if missing in that mode; no effect otherwise, with a once-per-process diag.warn if passed anyway.
  • Build: removed the published workspaces array from package.json; added prepack/postpack hooks (strip-workspaces.js/restore-workspaces.js) to strip it before publish and restore it after.
  • Docs: README gained an ESM requirement note ("type": "module" or .mjs), an npm version badge, an Install section, and a 0.x semantic-conventions stability note.

v0.2.0TypeScript declarations and semantic-convention alignmentJuly 13, 2026

  • Hand-written TypeScript declarations (.d.ts) so TypeScript consumers get accurate types without a build step.
  • Span attributes realigned to the MCP semantic conventions: mcp.tool.name renamed to gen_ai.tool.name, mcp.request.id renamed to jsonrpc.request.id, mcp.tool.error.type renamed to error.type, and span name changed to {mcp.method.name} {tool}, kind SERVER.
  • Docs: ADR 004 documents the semantic-conventions alignment.

v0.1.0Initial releaseJuly 9, 2026

  • instrumentMcpServer() — supports both the low-level Server and high-level McpServer APIs from @modelcontextprotocol/sdk.
  • One OpenTelemetry span per tool invocation, marked ERROR on a thrown exception or on CallToolResult.isError: true — the silent-failure detection this package exists for from the first release. See Silent Failures.
  • Respects an existing global TracerProvider; optional zero-config setupNodeSdk dev mode, exporting to stderr so stdio-transport JSON-RPC isn't corrupted.
  • Privacy by design: argument counts captured, never argument values.
  • Cross-platform (Windows/macOS/Linux), pure JavaScript, zero native dependencies.
  • 25 tests, 3 ADRs, 2 working examples at initial release.

Where do I go from here?