One tools/list call cost 529 KB
November 2026 MCP clients do not ship a catalogue to the model once and cache it. They ask the server for tools/list, and whatever comes back is what the model gets to see β every session, as part of its context. For a
November 2026
MCP clients do not ship a catalogue to the model once and cache it. They ask the server for tools/list, and whatever comes back is what the model gets to see β every session, as part of its context. For a server with a dozen tools that is a few kilobytes and nobody thinks about it. For a server that maps a marketplace β one tool per service β it is a different object entirely.
On 11 November 2026 we measured ours, on the live endpoint, with the request a stock client sends:
POST /mcp {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
β 200, 529,287 bytes, 1,859 tools, no nextCursor
529 KB, at roughly 4 bytes per token, is about 132,000 tokens β before the system prompt, before the user's question, before a single tool is called. That is larger than the context window of a good number of the models these clients are wired to. On 22 October the same call was 439,656 bytes over 1,711 tools. It grew 90 KB in 20 days, because the catalogue grew by 148 services.
What happens to a client when the list is that big
Nothing crashes. That is the problem. A client with a smaller budget truncates the list, or leaves it out, or the model sees the first few hundred tools and treats the rest as absent. The failure mode is not an error message β it is an agent that connected successfully, saw a partial catalogue, and called something else. From the server side that looks like "nobody uses this", and it is indistinguishable from having no demand.
The same thing happens at a much smaller scale whenever a tool's
inputSchemais verbose. We once shipped an object-level description on every tool; it added 212 KB to the payload β one sentence repeated 1,610 times. It was reverted the same day. Tool-list cost is the sum of per-tool costs, and per-tool costs are usually decided by whoever wrote the last schema.
Paging already existed. The default did not.
Our tools/list has accepted limit, cursor, category and query since 26 October, with the cursor bound to the filters that produced it (reusing a cursor after changing the filter is refused, not silently applied to another result set). Measured the same day:
| params | bytes | tools returned | β tokens |
|---|---|---|---|
{} |
529,287 | 1,859 | β 132,000 |
{"limit":200} |
53,008 | 202 | β 13,000 |
{"limit":100} |
27,977 | 102 | β 7,000 |
{"limit":50} |
14,427 | 52 | β 3,600 |
{"limit":20} |
6,306 | 22 | β 1,600 |
So the fix looked like it already existed. Except that the feature was strictly opt-in: a request without params returned the whole list, byte for byte as before, because that was the compatibility promise made when paging landed. The real decision β what does a client get when it asks for nothing? β was deferred, and "deferred" meant "the 529 KB answer".
MCP hosts do not let a user say "please pass limit: 100". The client builds that request. A server that pages only on request is a server that pages only for callers who already knew to ask.
The decision: no limit means one page
We are changing the default so that no limit β including no params at all β returns the first page of 100 service tools plus nextCursor, and the rest come from following the cursor. Explicit limit is unchanged, up to the existing 500 ceiling. Three properties keep this from being a downgrade:
-
The first page is the most-used 100, not the alphabetical 100. The query has ordered by
trial_count DESC, idsince an earlier fix, so the ordering is already a popularity ranking. -
Unlisted services stay callable.
tools/callresolves a name against the active catalogue; it never checked whether that name appeared in the list you were served. A model can search, get an id fromservice_discovery(still the first tool in the list), and call it. - The tail is reachable two ways β the cursor, and the search tool that was always there.
Why 100 and not 20 or 200
We measured how concentrated usage actually is, across the 1,675 services that have ever been called (50,393 usage rows):
| first page | usage rows covered | share |
|---|---|---|
| 50 tools | 42,580 | 84.5% |
| 100 tools | 45,060 | 89.4% |
| 200 tools | 46,894 | 93.1% |
Going from 50 to 100 buys 4.9 points for about 14 KB; going from 100 to 200 buys 3.7 points for another 28 KB. 100 is where the curve bends. The row counts are not independent callers β bots and our own probes inflate them β but the decision needs the relative shape, and the shape is stable.
The honest cost of this change: a client that never follows
nextCursorwill now see 100 tools instead of 1,859. That is deliberate. Today that same client sees a 529 KB list that, in practice, it truncates anyway β except now the truncation is at a boundary we chose and the client can see, with a cursor that says there is more. We would rather be the server that says "here is page one" than the server that hands over a payload larger than the context and lets the client decide what to drop.
What we would tell another MCP server author
-
Measure your
tools/listbytes, not your tool count. Bytes per tool is the number that matters, and it drifts upward with every schema edit. Ours is ~285 B/tool today. - Treat the no-params case as the real default. Most clients will never pass an option you added for large catalogues. If the safe behaviour only happens on request, the unsafe behaviour is your default.
- Order the first page by use, not by id. A bounded page is only acceptable if the first page is the one people wanted.
- Keep a search tool on page one. It is the escape hatch that makes a small default honest instead of lossy.
- Say what your cap costs. "We return 100 of 1,859" is a fact a caller can work with. "We return the catalogue" was not true in practice, because the caller could not fit it.
The change is written, reviewed against the deployed source, and covered by a test that seeds more services than the default page holds β so a future edit that raises the default, or removes it, fails instead of silently restoring the 529 KB answer. It is queued behind the gateway binary deploy; this post will be updated with the live before/after when it ships.
Originally published at minia2a.uk.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.