I put a proxy on the MCP pipe for 90 trials. Most of one client's calls never reached the server A developer's proxy-based study of MCP server calls across 90 trials found that one client's calls failed internally before reaching the server, mimicking a low-effort model. The run, covering three servers and two clients, logged 87 successes and revealed per-call token costs vary by client, not just server. In the last post https://dev.to/lopster568/i-measured-what-14-mcp-servers-cost-a-context-window-claude-counts-them-64-higher-than-tiktoken-10pj , on 2026-08-18, I published what 14 MCP servers cost a context window before an agent does any work, and said the next thing was Tier 2: real clients, real tasks, every frame logged. That has now run. Ninety trials, three servers, two clients, fifteen scripted tasks, three trials each, through a proxy on the MCP stdio pipe. The finding worth the post is not in the token table. It came out of the shakedown hours earlier that same day, on the client version the run then replaced: one of the clients was failing calls inside itself, before a byte reached the server, and on the wire that looked exactly like a model that tried very little and answered wrong. The matrix is filesystem , playwright and github , five scripted tasks each, three trials per task per client, suite version 1.0.1. Ninety trials, 87 successes. Servers pinned at @modelcontextprotocol/server-filesystem@2026.7.10 , @playwright/mcp@0.0.79 , and the ghcr.io/github/github-mcp-server container, launched untagged, which reported itself as v1.9.0. Clients: Claude Code 2.1.235 on claude-sonnet-5 , Gemini CLI 0.55.1 on gemini-2.5-flash , both model ids read back out of each trial's own client JSON rather than assumed from the flag. Claude Code also invokes claude-haiku-4-5 on every trial for its own bookkeeping, under a thousand input tokens a time. That never touches the MCP pipe and is in none of the figures below, but it is in the manifest, so it is worth knowing it is there. The three Gemini failures are one per server and they are three different things: an off-by-one line count on FS-04, a response its own tool layer rejected on GH-05 after six calls reached the server, and a PW-01 final message that declared the task done without restating the price the check looks for. Three trials per cell buys per-cell counts and nothing statistical, so these stay counts and never rates, per section 3.3 of the spec: Claude Code finished 45 of 45 trials, Gemini CLI 42 of 45. 87 of 90 state checks passed, and 87 of 90 trials also classify as tool use success ; the two are different fields that happen to agree this run. Three failures do not tell you one client is more reliable than the other. Median call tokens per trial, meaning the tools/call arguments plus the results the server sent back, counted with o200k base so the figure shares Tier 1's token basis. Each column is a median over 15 trials, five scripted tasks by three trials, so a different task mix moves these figures. They are not a per-server price: | server | Claude Code | Gemini CLI | |---|---|---| | filesystem | 525 | 170 | | playwright | 629 | 522 | | github | 1,698 | 223 | Both clients median one tool call on github. On GH-01 both called get file contents with identical arguments, and the answer measured 1,561 result tokens for Claude Code against 161 for Gemini CLI. Every response in the Claude Code session carried a meta block, io.modelcontextprotocol/serverInfo , holding the server's name, version and two PNG icons inlined as base64: 2,215 characters wrapped around a 472-character answer. Across the 15 github trials on Claude Code, all 81 responses carried it. Across the 15 on Gemini CLI, none of the 93 did. The two sessions negotiated different protocol revisions, 2026-07-28 against 2025-06-18 . One server version against two clients does not prove the server keys on the revision. It does establish that a per-call cost figure is not a property of the server alone, which is the reason no row here publishes one number. None of this is in the 90. The shakedown that preceded the run, one trial per task, ran on Gemini CLI 0.18.4, and its rows survive as superseded records in the same day's manifest. All five playwright tasks failed there, each trial with exactly one call on the wire. Read from the frames alone, the classifier bucketed it as a capability failure, once as a decline because the model apologised. That reading is false. 0.18.4 validated each call's arguments against the schema the server advertised, and its bundled validator had no JSON Schema draft 2020-12 meta-schema registered. @playwright/mcp@0.0.79 declares 2020-12 on all 24 of its tools, so most of its calls died inside the client with no schema with key or ref "https://json-schema.org/draft/2020-12/schema" and never reached the pipe: each trial got exactly one onto the wire and lost three or four more inside. The same client ran filesystem and github clean the same day, because @modelcontextprotocol/server-filesystem declares draft-07 and github-mcp-server declares no $schema at all. The dialect decided it, not the server. What caught it was the tool-call gap field, which exists for a different purpose spec 4.2 . The tool-call gap takes every tool name the server advertised, subtracts the wire frames the proxy logged from the calls the client's own usage output attributes to that tool, and sums the positive differences. Zero is the normal state. Those five trials read 3, 4, 4, 4 and 3, meaning the client formed calls to tools the server offered and those calls never left it. No classification could have said that, because a classification only sees what reached the wire. Upstream had already fixed the validator in 0.28.0, before this shakedown ran gemini-cli issue 14970, PR 15060 : it dispatches a dedicated 2020-12 instance on the schema's own $schema and falls back to skip-and-warn for unknown dialects. The stale client was mine. Upgrading to 0.55.1 and re-running the five tasks gave five passes, a zero gap on every trial, and no schema error in stderr. The upgrade nearly broke the detector: the log keys the server's bare tool name, but the client's usage output moved to mcp