---
title: "MCP Analytics: Which Tools Agents Call, and Which They Abandon"
description: "MCP analytics counts which tools agents call, which were listed and never called, and which die in repeated errors. The log schema is a separate article."
canonical: "https://www.agentrail.co/blog/mcp-analytics"
author: "Harsh Raj"
published: "2026-10-17T00:00:00Z"
last_updated: "2026-10-22T18:13:14.686Z"
---

# MCP Analytics: Which Tools Agents Call, and Which They Abandon

MCP analytics counts which tools agents call, which were listed and never called, and which die in repeated errors. The log schema is a separate article.

![Cursor with sparkles on a deep red blur](https://images.ctfassets.net/1htfyfnn1o5y/6aSIbFFy2Sj4DK3gKkD6nb/4bf54a315e988ca52b8500e67355a1d9/hero_image-1.png?w=1600&fm=webp&q=72)

MCP analytics is the set of counts you run on MCP call records. It answers which tools agents invoke, which tools you offered and they never called, and which calls stop on an error or on a request for more input.

The fields those counts read are specified in [MCP observability](https://www.agentrail.co/blog/mcp-observability). This page uses `tool_name`, `outcome`, `client_name`, `listed_tool_names`, `request_state`, and `duration_ms`. It does not redefine them. [Anthropic counted](https://www.anthropic.com/news/donating-the-model-context-protocol-and-establishing-of-the-agentic-ai-foundation) more than 10,000 active public MCP servers in December 2025. Volume at that scale is a reason to count your own server. It is not a published abandonment rate. We did not find a primary study that says what share of tools agents leave untouched, so this guide does not print one.

[Agent analytics](https://www.agentrail.co/blog/agent-analytics) is the wider view, across website, docs, APIs, SDKs, and MCPs. The cuts below stay on the MCP records.

> **Key Takeaways**
>
> The first cut is calls per tool. The second is the same calls split by the self-reported client name.
>
> A tool that appears in `tools/list` and in zero `tools/call` rows, in the same window, was abandoned at discovery. A cached list can hide that fact.
>
> A string of tool errors is a different failure from a tool nobody calls. A later completed call on the same handle is recovery.
>
> `outcome: complete` means the tool did not flag an execution error. It does not mean the agent's task finished.
>
> On revision `2026-07-28` there is no protocol session. Join a retry on `request_state` or a tool handle. The retry carries a new request id.

## Count calls, then split them by client

Start with a count of `tools/call` rows grouped by `tool_name`, over a window you can compare next week. Sort by calls. The top of that list is what agents actually reach. The bottom is a hint, not yet an abandonment finding, because a quiet tool may be rare on purpose.

Split the same count by `client_name`. A tool that is busy for one client and quiet for another is a client fact, not a server-wide fact. `duration_ms` belongs beside the count. A tool with a long tail and a clean `complete` outcome is a latency question. A tool with a short duration and `tool_error` is a contract question. Keep those charts separate.

[PostHog's sample queries](https://posthog.com/docs/mcp-analytics/queries) are one implementation of this cut. They group `$mcp_tool_call` by tool name, compute an error rate from `$mcp_is_error`, and count sessions by `$mcp_client_name`. The page also says PostHog runs queries of that shape on its own MCP server. It doesn't publish the resulting shares, rates, or latencies. Use the query shape. Don't borrow a number the page never printed.

If your pipeline uses those event names, map them back to the record in the observability article before you compare vendors. `$mcp_tool_name` is their name for `tool_name`. The count is the same count.

## A listed tool with zero calls was abandoned at discovery

Discovery abandonment means you offered the tool and, inside the window, no call record used that name.

The join is straightforward. Take the distinct names in `listed_tool_names` from `tools/list` records. Subtract the distinct `tool_name` values on `tools/call` records. What remains was advertised and never invoked. PostHog documents this as advertised tools that never get called, over a 30-day window. The same page warns that the Go SDK sends no list event, so the query returns nothing for Go servers. If your list record is missing, you can't make this claim. Fix the record before you declare a tool dead.

The [tools specification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) puts `ttlMs` and `cacheScope` on `tools/list` results. `ttlMs` is a freshness hint. A client may call a tool from a list it cached before your window opened. That call is real usage of a name you might not have listed in-window. The opposite also happens. You listed a tool, the client cached the page, and it never called. Your list row is inside the window and the call row never arrives. That is the abandonment case.

Two practical bounds follow:

- Run the join on a window longer than your largest `ttlMs`, or accept that fresh caches will look like silence.
- Treat a name with calls and no in-window list as used from cache.

A name that shows up only as a protocol error for an unknown tool is not in this set. You never offered it. Count those names in the next section.

## A repeated tool error is a different failure

The tools specification says clients should pass tool execution errors to the model so it can correct the arguments. A retry is that advice working. Analytics has to tell a retry that recovers from a retry that stops.

Read `outcome` in order for one tool and one correlation key. The correlation key is the explicit handle hash from the observability record, or `request_state` when the call stopped for more input. Revision `2026-07-28` removed protocol sessions, so the TCP connection is not that key. The [base protocol](https://modelcontextprotocol.io/specification/2026-07-28/basic) says an open connection is not a conversation.

Three patterns are worth naming:

- **Recovery.** One or more `tool_error` rows, then a `complete` row for the same tool and handle. The model used the error text.
- **Stuck tool.** Two or more `tool_error` rows and no later `complete` for that handle. The model had a chance to correct the call, and the call still failed. That repeated failure is the pattern this page owns.
- **Unknown name.** `outcome` is `protocol_error` and `error_code` is `-32602`, the code in the specification's unknown-tool example. Agents asked for a tool you do not serve.

Keep protocol errors and tool errors in different columns. A malformed request means the client built the call wrong. A tool error means the tool ran and refused the arguments or the downstream call.

PostHog's error-rate query divides errors by all calls for a tool. Pair that rate with the recovery count. High errors plus high recovery means a sharp validator. High errors and no recovery means a dead end.

## input\_required that never resumed is an unfinished call

Some calls end because the server asked for more input. `result_type` is `input_required`, and `input_methods` names the request, often `elicitation/create`. The specification says the client's retry must use a new JSON-RPC id, and may echo `requestState`.

Count an unfinished input when you have an `input_required` row and no later `complete` or `tool_error` row that shares `request_state` and `tool_name`. The new id is why the request id will not match. If `request_state` was absent, report the stop and stop there. You can say how often calls pause for input. You can't say how often they resumed.

Leave the form body out of the count. `input_methods` is the analytic field. The user's answers are not a usage metric.

This pattern is abandonment of that call. A task that also included a doc page or an API request is a wider path. That path is [agent journey analytics](https://www.agentrail.co/blog/agent-journey-analytics).

## Check the task after the tool returns

`outcome: complete` means `resultType` was `complete` and the tool did not set `isError`. The result can still be empty, off-topic, or useless to the model. `result_bytes` and `content_types` are the checks you have without reading the body. A completed call whose text payload is a few bytes, week after week, deserves a look. It is still a hypothesis until someone reads a sample.

Task completion is a different metric. It asks whether the agent finished the job. A tool that returned without an error flag is a narrower fact. The definition lives in [agent task completion rate](https://www.agentrail.co/blog/agent-task-completion-rate). MCP analytics should not publish a completion percentage under this keyword.

The same limit applies to latency. `duration_ms` says how long the tool took. It does not say the agent was satisfied. Keep p95 next to the call count if you need an operations view. Label it as latency.

## Read the client mix before you change the tool

Rewrite a tool after you know which clients call it. A description that one client follows and another ignores is a client bug, a schema bug, or both. A single total hides that split.

`client_name` is the self-reported `clientInfo` label. The MCP specification says that label is for display, logging, and debugging. Implementations should not treat it as a security decision. Two products can send the same string.

PostHog's [event reference](https://posthog.com/docs/mcp-analytics/events) shows how thin the label is on HTTP. A User-Agent can separate clients that share a name, including Claude Code's CLI, SDK, VS Code, and desktop builds. Claude.ai web, desktop, and mobile share a User-Agent and a vendor header. Those requests resolve to one label, "Claude.ai." A bucket of "Other" means the label and the headers were missing or unrecognized. Stdio has no User-Agent, so the client name is all you get.

Use that as a bound on the mix chart. Report the label you stored. If you also stored a User-Agent on HTTP, show it as a second column, not as a silent rewrite of the name. Finer grouping of many requests into one task uses more than the client string. That problem is outside this page.

When one client never calls a tool that others call, the next change is usually the description, the schema, or the examples that client sees. Making that change is a separate job from counting it.

## Run a weekly cut on your own traffic

Five cuts cover the questions this keyword actually asks. Run them on your rows. Compare them with the previous week. Skip any cut whose record is missing.

1. **Calls per tool,** split by `client_name`.
2. **Tool-error rate per tool,** beside a recovery count for the same tool.
3. **Listed and never called,** after you respect `ttlMs` and after you confirm list records exist.
4. **Unknown tool names,** from protocol errors, ranked by count.
5. **Unfinished \`input\_required\`,** joined on `request_state` when present.

There is no industry baseline to color these red or green. A tool with zero calls is a finding only against the list you served. A stuck tool is a finding only against the retries you recorded. Publish the window, the era (`modern` or `legacy`), and the share of rows with an empty `client_name`. Readers can then judge the cut.

If a cut says the same tool fails for every client, the schema or the downstream API is the suspect. If it fails for one client, the client's arguments are the suspect. Either way, the edit to the tool is the next piece of work, not a sixth chart.

## FAQ

### What is MCP analytics?

MCP analytics is the practice of counting MCP call records. The counts show which tools agents call, which listed tools they never call, which tool errors they fail to recover from, and which input requests never resume. The shape of each record is defined separately, in the observability article.

### How do I find tools agents abandon?

Join tool names from `tools/list` to tool names on `tools/call` over one window. Names that were listed and never called were abandoned at discovery. Also count `input_required` rows that never gain a later result with the same `request_state`. Keep unknown-tool errors in a third list. They are names you did not offer.

### Does a successful tool result mean the task worked?

A `complete` outcome means the tool returned without `isError: true`. The agent's task can still be unfinished, especially when the task also touched docs or an API. Task completion is defined in the task-completion article. Journey-level paths are defined in the journey article.

### Why do two clients with the same name disagree?

`clientInfo.name` is a self-reported label. Distinct products can share it, and one vendor's surfaces can collapse to one label, as PostHog documents for Claude.ai web, desktop, and mobile. Check User-Agent on HTTP before you treat the string as a unique client. On stdio you will not have that header.

## Count the records you actually stored

MCP analytics is only as honest as the rows behind it. List records, outcome values, and a correlation key that survives a stateless retry are the whole method. The schema for those rows is [MCP observability](https://www.agentrail.co/blog/mcp-observability). The task those calls sit inside is [agent analytics](https://www.agentrail.co/blog/agent-analytics).

We use these counts as one reading of production MCP traffic, next to the rest of the path an agent took. The published meter is on [pricing](https://www.agentrail.co/pricing): 100 journeys free per organization each month, then $0.10 per journey.
