# 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.

```ts
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

```json
{
  "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:

```ts
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.

| Plan           | Included per month | Past the allowance                  | Retention |
| -------------- | ------------------ | ----------------------------------- | --------- |
| Free           | 1,000,000 events   | dropped, and reported to you        | 90 days   |
| Team — $100/mo | 50,000,000 events  | billed at $2 per additional million | 365 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.
