Skip to main content
Status: the cap question is settled and shipped; the proposals below are still design. This page existed to settle how the MCP surface should grow before the next feature argued about it, because the budget was over-subscribed and the rule everyone was arguing under turned out not to be a protocol rule at all. That argument is over: the number is gone and the thing it stood in for is measured. What remains open is the proposal list.

The cap was ours — and it is gone

MAX_TOOLS = 12 and MAX_PROMPTS = 2 were 3ngram’s own discipline. They entered at the v1.0.0 launch commit, were not sourced to anything upstream, and were removed once the property they proxied for could be measured. AGENTS.md hard rule 8 is now an evidence test rather than a number, gated by the tool-selection eval slice (eval/src/tool-selection.mjs, floors in eval/fixtures/floors.json). The verification below is kept because it is why the number could go. Verified two ways:
  • The 2026-07-28 tools specification defines no maximum or recommended tool count. What it constrains is tool name length (1–128 characters), and it gives tools/list pagination — which anticipates servers with more tools than fit in one response.
  • The pinned SDK enforces no count limit anywhere.
This mattered because “the protocol says 12” would have been a reason to stop designing. “We chose 12 to force evidence” was a reason to produce the evidence and then re-choose, which is what happened. The number was a forcing function attached to the mcp-design.mdx rule, never a ceiling with independent authority.
The real constraint is model behaviour, and it is about description quality rather than count: tool-selection accuracy degrades as the list grows, and every description is re-sent on every tools/list. Two well-separated tools cost less than one overloaded one. That is what to optimise; the number was only ever a proxy. So the proxy was replaced by the measurement. The tool-selection eval slice scores every registered description against five agent utterances per tool and gates three numbers: selection_accuracy_at_1 and selection_margin as floors, and max_description_overlap — the largest pairwise cosine between two tool descriptions — as a ceiling. A twelfth tool is not blocked; a twelfth tool that reads like an existing one is. That is the constraint the count was always trying to express.

Choosing between a tool, a resource and a prompt

Two corrections to intuitions that have already caused mistakes here: Resources are not a general pressure valve. They add no description to tools/list, so they cost nothing against selection accuracy — but a resource needs a URI. search could never be one: a fused, filtered, ranked query has no address. Reach for a resource when there is a name or id, not merely because the tool surface feels crowded. Cacheability is a separate question from addressability. Per MCP resources, a long TTL requires an immutable body. A mutable thing can still be a resource; it just carries a short TTL and buys only the description saving, not the caching win.

The budget today

Eleven tools registered, and no slot ledger any more — a twelfth tool needs a JTBD no existing tool covers, scenarios in the eval, and a run that holds the recorded floors and the overlap ceiling. Recorded at 11 tools: selection_accuracy_at_1 0.8545, selection_margin 0.1097, max_description_overlap 0.6737 (briefing ~ handoff, the closest pair on the current surface and the one to watch). The get_facts chronological time-series read (range: {from?, to?}, MCP design) is a concrete instance of the JTBD-first test working against a candidate tool rather than for one: a get_series-shaped tool would have covered the same job, but the job was already reachable by widening get_facts’s existing query axis, so no new description entered tools/list at all — the cheapest possible outcome under either regime.
Resolved 2026-08-08: the manage_context reservation is released. It had held the last slot since the v1.0.0 launch commit with no design document, no issue, and no specification — four references, all of which merely reserved the name — while threads had a complete design and needed it.The standoff was an artifact of treating 12 as though it came from the protocol. It does not. The question was never who gets slot 12; it is whether each tool earns its share of the description budget, because description overlap is what actually degrades model selection accuracy. If manage_context becomes real, it competes on that footing with a design attached.

Proposals

get_memories exists for exactly one job — “the follow-up read for a truncated: true search/handoff line” — and the protocol has a content type built for that job:
ResourceLinkSchema is part of ContentBlockSchema, so the pinned SDK can already emit these in a tool result. A client could then fetch bodies itself, from a cache, with no second tool call — and avoid the 2.11× text-mirror cost on the largest payload we serve (see the output-size discipline note in MCP design).
This must be additive. Do not retire get_memories on the strength of it.The specification places no MUST on clients to follow a resource link, and explicitly notes that linked resources need not appear in resources/list. A client is free to ignore the block entirely. Retiring the tool would then delete the truncated-read path for those clients and leave a search result that cannot be expanded at all.This is the same trap as the text mirror in ok(): the fallback exists for clients whose behaviour we cannot observe, and removing it converts an unknown into a silent failure.
Staged, with an evidence gate:
  1. Emit resource_link blocks alongside today’s structured hits. Purely additive, no client can regress.
  2. Gather evidence that real clients follow them.
  3. Only then consider retiring get_memories — and that becomes a genuine JTBD argument, not a budget trick.
Honest tradeoff at step 3: twenty bodies becomes twenty resource reads rather than one batched call. Chattier on a cold cache, cheaper on every later session.

2. Threads as an index resource plus one curation tool

Threads decompose along the read/write seam:
  • threengram://thread/{name} — the index: member ids and minimal metadata. Short TTL, because membership mutates. Small by construction, so no firehose.
  • threengram://memory/{id} — the bodies. Immutable, long TTL, already built.
A client walks the index and fetches bodies that are likely already cached from any earlier read. The expensive half is the half that caches well. This does not remove the need for a curation tool — create, rename, archive, delete, pin, unpin are writes, and MCP resources are read-only. Threads already considered and rejected folding these into configure_scope: scopes are exclusive partitions, threads are overlapping collections, and one tool describing both models confuses the agent reading the description. That argument stands. What the resource does buy: threads never need a second tool for reading, and the thread filter on search becomes a convenience rather than the only way in.

3. manage_context — done

Released, see above. Kept here so the reasoning survives the decision: a reservation with no design cannot be weighed against a feature that has one.

4. Prompts are under-used

MAX_PROMPTS = 2 was self-imposed and is gone with its tool-side counterpart; the protocol never set a limit. Prompts carry no tenant data and add no tool description, so nothing but a JTBD stands between the surface and a third one. The one clear gap is review_proposals has no prompt. It is the tool an agent is least likely to invoke spontaneously — nothing in a session naturally suggests “check what the consolidator proposed.” briefing and debrief orient session start and end; a review prompt would orient the loop that keeps the corpus clean. Note that SERVER_INSTRUCTIONS now carries cross-tool policy, so some of what a prompt would have done reaches the model without one.

Sequencing

Resolved 2026-08-12: both caps are removed, and the eval slice replaced them. This row said to re-set the cap last, and to record the reasoning as description overlap and selection accuracy rather than a number inherited from launch day. What shipped does the second half and skips the first: there is no new number. MAX_TOOLS and MAX_PROMPTS are deleted, and tool-selection moved from report-only to gated — floors on selection_accuracy_at_1 and selection_margin, a ceiling on max_description_overlap, baselined from the observed run at 11 tools.Re-setting the cap to 14 or 16 would have reproduced the original mistake at a higher number: a figure with no measurement under it, which the next feature would argue about on the same footing. A regression in routing now fails a required status check, and the failure names the offending pair — which is the argument the count could never have.The ordering still held. The cap went last, after manage_context was settled and after the slice had run report-only long enough to baseline from.

What is deliberately not here

  • subscriptions/listen — held-open streams defeat the stateless deploy model. See the catalog-staleness note in MCP design.
  • resources/list for memories — corpus enumeration; the memory template returns nothing by design.
  • Completion for the memory {id} — suggesting memory ids is the same enumeration through a different door.
  • Roots and Sampling — client capabilities a server cannot use, and Sampling is deprecated in this revision besides.
  • Logging — deprecated by SEP-2577; OpenTelemetry covers it.
Multi-round-trip confirmation and the Tasks extension are tracked separately and remain the most valuable unbuilt protocol features.