All posts
· 3 min read

Designing an MCP Tool Surface Agents Don't Fumble

M
MCP System

Designing an MCP Tool Surface Agents Don't Fumble

Give an agent five tools and it does fine. Give it five hundred and something interesting happens: the bottleneck is no longer the model's intelligence — it's your **tool design**. A large tool surface is a UI for a non-human user, and most of what we know about good API design applies with the volume turned up.

After running a 500+ tool MCP server in production — with agents calling it thousands of times a day across a dozen projects — these are the patterns that separate tools agents use correctly from tools they fumble.

1. Names are the primary interface

Agents select tools by name before they ever read a description. `flukebase_feedback_update` gets picked; `flukebase_modify_record` gets misused. The conventions that work:

- **Verb_first, domain_second**: `article_publish`, `roadmap_create`, `session_continue`. The agent greps its own tool list by intent.

- **One verb per tool**: a tool that "creates or updates depending" will be called with the wrong mental model. Split it.

- **Consistency beats cleverness**: if listing is `*_list` in forty tools, the forty-first must not be `*_enumerate`.

2. Descriptions are error-prevention, not documentation

The description field is where you stop the top three failure modes *before they happen*. For every tool, write down:

- **When NOT to use it** ("use X instead when..."). Negative routing is the highest-leverage sentence in the schema.

- **Preconditions** ("requires `flukebase index` to have been run first").

- **The footgun** ("never pass raw diffs — pass `diff_sha256` only").

A description that reads like a warning label outperforms one that reads like marketing copy, every time.

3. Make invalid states unrepresentable-ish

JSON Schema can't express everything, but it can express more than most servers use:

- **Enums everywhere** a value is closed-world. Every free-text status field is a future typo: `in-progress`, `in_progress`, `inprogress`.

- **Defaults that encode the safe choice**: `brief: true`, `validate: true`, `dry_run: false` only where mutation is the explicit intent.

- **Required arrays over optional singletons** when batching is natural — one `roadmap_batch_update` call beats twenty round trips and twenty chances to fail halfway.

4. Errors should teach the next call

An agent recovering from an error has exactly one asset: your error message. Compare:

```

{"error": "invalid input"}

```

```

{"error": "oldString not found in content. Read the file first, then match exact indentation including tabs."}

```

The second message is a remediation plan. Across thousands of daily calls, teachable errors are the difference between an agent that retries intelligently and one that loops the same mistake until the step budget dies. Also: return **structured** errors — an agent can branch on `error.code`; it can't branch on a paragraph.

5. Compose, don't accumulate

The graveyard of tool surfaces is full of near-duplicates: `update_task_status`, `assign_task`, `task_add_blocker` — three tools, one entity, overlapping mutations. Consolidate into one `update_task` with optional fields and the agent's success rate climbs, because there's exactly one obvious tool for the job. We periodically deprecate clusters like this; every consolidation measurably reduces miscalls.

The same principle scales up: a `context_bundle` tool that returns one ranked, deduplicated, token-budgeted slice of memory + conventions beats making the agent orchestrate three retrieval tools and merge results itself. Agents are bad at merging ranked lists. Don't make them.

6. Budget tokens like an API budgets bytes

Every tool response lands in a context window that costs money and attention per token. Defaults matter: truncate lists, offer `brief` modes, return IDs and summaries with pointers to full records. A tool that dumps 50KB of JSON "just in case" is quietly taxing every subsequent decision the agent makes.

The meta-lesson

Treat your tool surface like a public API with one unusually honest user: the agent does *exactly* what your names, descriptions, and schemas imply — no more, no less, and with no embarrassment about calling it wrong fifty times. The fumbling is never the model's fault for long. It's the surface telling the wrong story.

Audit your tools the way you'd audit a UI: watch real sessions, count the miscalls, fix the labels. The model will meet you more than halfway.

---

*Part of the FlukeBase supervised-autonomy series. Next: approval gates that don't become bottlenecks.*

Early access open

Build with the open MCP platform

Persistent memory, session intelligence, and 200+ tools for your AI agents. Join the waitlist.