Designing MCP servers agents can actually use: tool curation and the context budget

13 min read

Designing MCP servers agents can actually use: tool curation and the context budget

At MCP Dev Summit Seoul we gave two talks that, on the surface, sound like separate problems. One was about tool curation — how we went from 40 tools to 14. The other was about the context budget — why MCP servers quietly drown agents in schemas and responses.

They are not separate problems. They are two symptoms of the same mistake, and two halves of the same fix. This post walks through both, and shows where they meet.

The common origin: the API wrapper

Almost every MCP server starts the same way. You have a product with an API, so you wrap it. Every endpoint becomes a tool: create this, get that, update this, list that, link those two objects. If an endpoint exists, it becomes a candidate tool.

That is a perfectly good way to start. A wrapper is a fast prototype. It teaches you which operations people actually reach for, what vocabulary they use, and where the product has gaps.

The mistake is treating the prototype as the finished design.

An API is built for a developer reading documentation, assembling calls in code, and reasoning about a resource model over time. An agent is a different consumer entirely. It has a single user request, a finite context window, and a set of choices it must make under uncertainty. When you expose the API's structure directly, you force the model to reconstruct the product's workflows on every turn — and to do it inside a context window that is filling up with the very schemas that describe those workflows.

So the wrapper fails in two directions at once:

  • It makes the agent choose badly, because it sees dozens of near-identical, endpoint-shaped tools. That is the tool-curation problem.
  • It makes the agent reason badly, because tool schemas and raw responses crowd out the space it needs to think. That is the context-budget problem.

Both talks are really answers to the same question: how do you design a server for how an agent works, not for how your API happened to be organized?

Half one: curate tools around how the agent reasons

The way out of tool sprawl was to stop asking “which endpoints should we expose?” and start asking “what is the agent trying to do at each point in a task?”

That reframing produced a simple spine that every task follows: Search → Context → Act.

Search: traverse the system reliably

A user may start from an exact ID, a known type, a set of constraints, or a fuzzy description. “Find the login outage issue.” “What's our refund policy?” “Which tickets are tied to this incident?” A good search primitive lets the agent traverse the system from any of those starting points, instead of forcing it to reproduce the backend's traversal logic through a chain of low-level calls.

There isn't one kind of search — there are at least three, and the agent should reach for the one that fits:

  • Semantic search for meaning across records. “Find similar login incidents” should match on intent even when the wording differs.
  • RAG over documents for answers that live in prose. “What's our retention policy?” should retrieve passages, not an object ID.
  • Graph search for relationships. “Which tickets are tied to INC-48?” is a traversal problem, not a keyword one.

search(query: "login outage", types: [issue]) → ISS-4521 → get_context("ISS-4521")

The output matters as much as the routing. A useful result is a stable ID, a type label, and a short snippet — enough to choose the next call. Search narrows and connects the world; it does not dump it into the prompt. Hold that thought — it is the first place tool design and context budget converge.

Context: return a situation, not a record

Once the agent has an object, it needs context. This is where many servers stop too early, offering a get that returns a flat record: title, stage, owner, priority. That lets the agent say the object exists. It does not let the agent understand what is happening.

A context tool answers a different question — not “what is this object?” but “what is the situation around it?”

Return a ticket's title, priority, and stage and the agent can say “this is open and high priority.” Include the comment thread and the agent sees the customer replied three days ago, an engineer flagged a likely duplicate, and the last message says “any update?” Now it can reason about the right next action — respond, merge, escalate, or close.

Relationships work the same way. A bare linked_to: ISS-9032 forces the agent to either ignore the reference or spend another call to resolve it. Instead, resolve linked IDs into compact summary objects:

linked_to: ISS-9032 → { id, title: "Payment retries failing", stage: "closed" }

That one summary lets the agent notice the ticket is linked to a closed issue — the likely reason it looks stuck — without another round-trip.

Context design is a depth problem. Bare IDs are too little; full nested records are too much. Summary objects, comments, current ownership, and linked-state are the useful middle. And notice the second convergence: choosing that middle depth is a context-budget decision. A context tool that resolves relationships well is also a context tool that doesn't force five follow-up calls.

Act: expose repeatable outcomes, not just APIs

Now the agent understands the situation and needs to act. The wrong default is to expose every low-level operation and hope the model composes the workflow correctly.

Take “track this customer's latest order.” Under the hood that might be: look up the customer by email, list their orders, sort them, fetch the latest, then retrieve shipment status and ETA. A wrapper makes the agent orchestrate all of it — several calls, IDs to carry, endpoints to choose, a result to assemble. An MCP server can instead expose track_latest_order(email) and do the deterministic lookups itself.

This matters because chains compound risk. If each call succeeds 95% of the time, three chained calls give you about 85.7%, and five give you about 77.4%. And tool-call failures are usually hard failures — a wrong ID or wrong parameter breaks the whole trajectory. Every step you move out of the model's decision loop and into deterministic server code is reliability you win back.

Two refinements keep this from becoming its own kind of sprawl:

  • Consolidate structurally similar operations. link_ticket_with_issue, link_incident_with_ticket, and friends differ only by resource types in their names. One link_objects(source, target, relationship) tool, with server-side validation, is better. The heuristic: if two tools share a shape and differ only by type names, they probably want to be one parameterized tool. But don't over-generalize — “resolve an incident” and “approve an expense” look like updates at the API layer while being entirely different business outcomes.
  • Write tool descriptions as prompts. “Updates a ticket” is not enough. A description should say what the tool does, when to use it, which inputs matter, what comes back, and what errors or alternatives exist — ideally with a concrete example.

When a customer report is caused by an engineering defect: link_objects(source_id: TKT-891, target_id: ISS-4521, relationship: is_caused_by)

For action-heavy servers with hundreds of operations, you can't put everything in one flat tools/list. Two exposure models help: toolsets (named capability bundles a human enables before the session — tickets, incidents, parts) and generic layered tools (a small set of meta-tools where operations become discoverable data via Discover → Plan → Execute). Toolsets give a human-configured boundary; generic layers handle a long tail too variable to curate. Both beat exposing hundreds of object-level operations at once.

Half two: spend the context budget deliberately

The tool-curation talk is about the agent's decisions. The context-budget talk is about the agent's room to think. And the second talk starts exactly where the first one's failure mode lands.

Connect a well-behaved agent to a small dataset and everything looks great. Connect the same server to a real enterprise and the agent gets slower, less precise, and starts making odd mid-task choices. The model didn't get worse. Its context filled up — not with the user's problem, but with tool schemas it never uses and response payloads far larger than the next step needs.

Context is a budget. Every token spent on overhead is a token the agent can't spend understanding, planning, or explaining. The API wrapper leaks that budget in two directions: schema dumps on the way in (every endpoint arrives with descriptions, parameters, enums, validation) and response dumps on the way out (ask for a record, receive every field, nested relationship, and history).

At enterprise scale these aren't theoretical. We saw a single list call return 161,000 tokens. Tool schemas alone consumed 37% of a 200,000-token window before any work began. One customer's sprint retrieval returned over 80,000 tokens and left almost no room to reason. Worse, it's unpredictable — the same logical tool can be tiny in one tenant and enormous in another, so no single global limit is safe for everyone.

Tool search helps — but only partway

The natural first fix is tool search, or deferred loading: don't inject every tool definition upfront; let the agent load only the ones the task needs. That's a real improvement, and it's the context-budget counterpart to curation — fewer, clearer tools is good for both selection and tokens.

But it only addresses the input side. Tool search doesn't stop one list or get from returning a huge payload, and client-side workarounds (like saving a big result to a file) are inconsistent across clients and don't make the server response any smaller. The durable controls have to live in the server contract.

Progressive disclosure: load capability at the speed of intent

Enterprise systems also break the “stable catalog” assumption. A “ticket” isn't one thing — one tenant has forty custom fields, another has fifteen different ones, another defines bug reports and onboarding requests as distinct subtypes, another invents custom objects entirely. The mental model is an expanding matrix: object types × subtypes × tenant-specific custom fields.

Expose a fully specified tool per combination and the catalog is effectively unbounded and different per org. The fix is progressive disclosure: separate the stable tool from the variable schema it needs.

create_object · discover_schema (small, stable contracts) → discover_schema(type: ticket, subtype: bug_report) → required fields, allowed values, validation for THIS subtype in THIS org → create_object(type: ticket, values: …)

Flat exposure scales with everything the product could do. Progressive disclosure scales with what the agent is trying to do now. It's the same idea as layered action tools from the first talk — reveal capability one slice at a time — applied to schemas instead of operations.

Projection: keep unused fields out of responses

Progressive disclosure fixes the input side. Responses can still dump. An agent asking for in-progress tickets may need only title, status, and owner, yet receive forty fields per ticket. The fix is projection — the caller tells the server which fields it needs:

list_tickets({ projection: ["title", "status", "owner"] })

With named presets for common shapes — minimal (ID + title), summary (+ status, owner), detail (full record). Two rules make projection work:

  • It must happen server-side. Client-side filtering is too late — the full payload has already entered context. You changed the display, not the token bill.
  • It's per-tool. Structured records project cleanly; a PDF, chart, or free-text answer only makes sense whole. Each tool should advertise whether it supports projection and which fields are valid.

This echoes the search principle from the first talk — return enough to decide, not everything — now enforced as an explicit parameter.

SQL: keep large joins and aggregates out of the model entirely

Projection solves field width. It doesn't solve row volume. Questions like “which customer accounts have open support tickets tied to high-priority engineering issues?” are relational. Answered through chained tools, the agent lists issues, then per issue lists tickets, then per ticket fetches accounts, then joins and counts by hand — dozens of calls and thousands of intermediate rows moving through context.

The real question is one query that touches four object types, filtered, joined, and aggregated where the data lives — twenty answer rows instead of thousands of intermediate records. The right interface is small and governed: a get_table_schema that returns a question-specific schema brief, and an execute_sql_query that validates fields, joins, permissions, timeouts, and limits, rejects SELECT *, and returns a bounded, typed result. Small brief in, compact answer out; the large scan never enters the model.

Where the two talks meet

Read together, the pattern is unmistakable. Both talks take the same villain — the API wrapper — and attack it from two angles that keep pointing at each other. Each tool-curation move has a direct context-budget counterpart:

  • Narrow before you return: one broad search that returns type + snippet + ID (curation) is the same instinct as projection returning only the fields the next step needs (budget).
  • Right-size every payload: resolving links into summary objects (curation) mirrors projection right-sizing structured responses (budget).
  • Load at the speed of intent: layered discovery revealing operations on demand (curation) is the same idea as progressive disclosure revealing schemas on demand (budget).
  • Do deterministic work on the server: outcome tools collapsing multi-call chains (curation) parallels SQL collapsing multi-object joins (budget).
  • Serve the model, not the API: design for how the agent reasons (curation) so you preserve the room it needs to reason (budget).

Curation reduces the number of choices the agent has to make. Context discipline preserves the room it needs to make them well. Neither is sufficient alone: a perfectly curated tool that returns a 161K-token response still breaks the agent, and a perfectly bounded response from a confusing tool still gets called at the wrong time.

The single sentence that carries both talks:

Design MCP servers for how an agent reasons through a task — and don't make the model carry anything the server can filter, structure, validate, or compute before reasoning begins.

Start with the wrapper if you need to learn. But instrument it: watch which tools get confused, which results trigger another call, which workflows the agent keeps reconstructing, and how much of the window is gone before the real work starts. Those signals tell you what to consolidate, what to enrich with context, what to turn into an outcome, and what to stop sending. Going from 40 tools to 14 — and from 161K-token responses to twenty answer rows — was never about subtraction. It was about moving complexity out of the model's decision loop and into a server that makes the right things easy.

See how DevRev builds agent-facing infrastructure — request a demo.

Frequently Asked Questions

DEVREV

See Computer work for you

Your AI teammate that finds answers, takes action, and gets work done across every tool.