Telemetry

Add optional MCP usage telemetry or OpenTelemetry export. Record tool outcomes and argument paths without recording values or awaiting network delivery.

Schema diffing tells you what changed. Telemetry tells you whether anything depends on it.

Hosted preview status: ingest is deployed at https://ingest.mcprobe.dev. Usage queries and nightly rollups are pending an analytics read credential. Package publication and paid checkout are pending; the plan table below describes the implemented plans.

import { instrumentServer } from "@mcp-inspect/instrumentation";

instrumentServer(server, {
  apiKey: process.env.MCP_INSPECT_KEY,
  endpoint: "https://ingest.mcprobe.dev",
});

It patches the handlers for tools/call, resources/read and prompts/get — the three methods that represent use. It never patches tools/list: counting discovery as usage would make every unused tool look used, which is the exact question this exists to answer.

#The event

{
  "entityKind": "tool",
  "entityName": "search",
  "argumentsPresent": ["limit", "query"],
  "durationMs": 42,
  "success": true
}

Everything else — client name and version, protocol version, server version, surface fingerprint, environment, an anonymous per-process session id — is optional enrichment. A field the SDK cannot determine is omitted, never guessed: inventing an identity the protocol did not supply makes every downstream conclusion a lie.

#Guarantees, in order

  1. It never throws into your handler.
  2. It never delays a response.
  3. It never grows without bound.
  4. It probably delivers your events.

Events are buffered in memory (bounded, oldest dropped at capacity) and flushed on a timer, at a batch size, or on flush(). On failure a batch is dropped, not retried — a telemetry backlog that survives in memory is a memory leak in your production server.

#OpenTelemetry

If you would rather keep the data:

import { otelExporter } from "@mcp-inspect/instrumentation/otel";

instrumentServer(server, { emit: otelExporter(tracer) });

Attributes follow the published conventions (mcp.tool.name, mcp.request.outcome, mcp.protocol.version), so the spans are readable by tooling that has never heard of this product. MCP reserves traceparent, tracestate and baggage in _meta as an explicit exception to its prefix rules; the bridge reads and writes those rather than inventing a correlation id, so a span joins whatever trace the caller already had.

#What you get back

search.limit has never been supplied in 2,712,441 observed calls over 90 days. Telemetry shows the absence of observed usage, not the absence of all possible consumers.

That caveat travels with every number. There is no safeToRemove anywhere in the product: the strongest available phrasing is "candidate for deprecation", always beside its observation window and confidence.

#Quotas

Whatever the plan, going over an allowance never returns an error your SDK might act on. A quota that can take your server down is an outage with a billing explanation attached. What differs is what happens to the events.

PlanIncluded per monthPast the allowanceRetention
Free1,000,000 eventsdropped, and reported to you90 days
Team — $100/mo50,000,000 eventsbilled at $2 per additional million365 days

On the free plan events past the cap are dropped and the count is shown in the console, so you can see exactly what you did not keep.

On a paid plan nothing is dropped. Paying for a limit and losing data at it would be the worst of both models, so the allowance becomes a billing boundary rather than a wall: past 50 million events you are charged $2 per additional million. A partial million is not billed — 50.4M events costs $100, not $102.

Usage is metered out of band from a nightly job, never from the ingest request, so a billing provider being down cannot affect whether your telemetry is accepted. The console shows the running total, and the invoice comes from the billing portal.