Wiring MCP Tools Into a LangGraph Agent Without Losing Control

The Model Context Protocol solved a real integration problem. Before it, every agent tool was bespoke glue: a function wrapper, a schema written by hand, an auth path invented per integration. Now a vendor ships an MCP server, you point a client at it, and your agent has fourteen new tools by lunch.

That is the problem. The cost of adding a tool fell to nearly zero, and nothing about the model's ability to choose the right tool improved to match. Almost every MCP-related failure we have looked at is a selection-and-schema failure, not a transport failure.

This is how we wire MCP servers into LangGraph agents in client repositories, and what we measure before calling it done.

Treat tool count as a budget, not a feature

Each connected server contributes its whole catalog: tool names, descriptions, and JSON schemas, all of it in the system context on every turn. Two consequences, both measurable.

First, token cost. A handful of servers can easily add several thousand tokens of tool definitions to every single request, including the requests that need none of them. On a chatty agent that is a line item you will find later in the bill and struggle to explain.

Second — and worse — selection accuracy degrades as the catalog grows. Near-duplicate tools are the reliability killer: search_issues from one server and find_tickets from another, distinguished only by a description the model skims. The model picks plausibly and wrongly, and the trace looks fine until you read it.

So run an explicit allowlist. Load the server's catalog, then filter to the tools this agent actually needs for its job, by name. Ten purposeful tools beat forty available ones. If a workflow genuinely needs breadth, do not flatten it into one giant agent — split it, below.

Rewrite the descriptions you were given

MCP tool descriptions are written by the server author for a general audience. Your agent is not a general audience. Wrapping a tool so you can override its name, description, and schema is a few lines in LangChain, and it is the highest-leverage change available:

  • Say when not to use it. "Use for open issues only; for closed issues use get_issue_history." Negative guidance disambiguates near-duplicates better than positive guidance does.
  • Cut optional parameters the agent should never set. Every optional field is a chance to hallucinate an argument. If your workflow always wants project_id=X, bind it in code and remove it from the schema the model sees.
  • Constrain enums and formats explicitly. A date field described as "date" gets three different formats across a hundred calls. YYYY-MM-DD in the schema description gets one.
  • Keep names verb-first and distinct across every server you have loaded, not just within one.

When a client tells us their agent "calls the wrong tool," this step alone usually moves the number more than a model upgrade.

Put trust boundaries in the graph, not in the prompt

MCP servers are code someone else wrote, returning text your model will treat as instructions. Two separate risks, and neither is handled by asking the model nicely.

Tool output is untrusted input. A retrieved ticket comment, a web page, a file in a shared drive — any of it can contain text aimed at your agent. If your agent can both read arbitrary content and take consequential actions, the read path can drive the write path. That is not a hypothetical; it is the standard shape of the exploit.

Mitigate structurally:

  1. Separate read and write tools by node. Retrieval happens in one node; mutations happen in another that receives only structured, validated arguments — not free text carried over from tool output.
  2. Gate the consequential calls. LangGraph's interrupt-before-node behavior with a checkpointer gives you a real approval step for anything that spends money, emails a customer, or writes to a system of record. Human-in-the-loop is cheap when it applies to three tools instead of thirty.
  3. Scope credentials per server. The agent's token should permit exactly the operations its allowlisted tools need. Assume any single tool call may be attacker-influenced and ask what it can reach.
  4. Cap the loop. Recursion limits and per-turn tool-call ceilings turn a runaway agent into a failed request instead of an incident.

Split by tool group when the catalog gets wide

Past roughly a dozen tools, one flat ReAct-style agent starts to wobble. The fix is scoping, not a longer prompt: a supervisor node that routes to sub-agents, each holding one server's worth of tools and one clear job. The router sees five route options instead of forty schemas; each sub-agent sees only tools relevant to its lane.

This costs you an extra model call per turn. It buys back selection accuracy and, usually, tokens — most turns no longer carry the full catalog. Measure both before and after; on narrow agents the flat version wins and you should keep it.

Make failures boring

MCP calls cross a process or network boundary, so they fail in the ordinary ways: timeouts, connection drops, servers that return a 500 with an HTML body. Decide deliberately what the model sees.

Our default: catch the error, return a short structured message the model can act on ("tool unavailable, do not retry" versus "invalid argument: due_date must be YYYY-MM-DD"), and never surface a raw stack trace. Argument-validation errors are worth returning verbatim-ish, because models correct them reliably. Infrastructure errors are not — the model will retry a dead server until it hits the recursion limit. Set per-tool timeouts well inside your overall request budget, and log every failure with the arguments that produced it. That log is your next eval set.

Measure tool selection, not just final answers

An answer-level eval will pass an agent that took six wrong turns and recovered. That agent is expensive, slow, and one prompt change from breaking. Evaluate the trajectory.

Build a set of 40–60 real requests, each labeled with the tool call — or short sequence — that should have happened, then score:

  • First-call accuracy: was the first tool chosen the right one? This is the metric that moves when you fix descriptions.
  • Argument validity: did the arguments parse and satisfy the schema, before any server saw them?
  • Trajectory length: calls per resolved request. Watch the tail, not the mean; the 95th percentile is where the cost lives.
  • Unnecessary-call rate: calls whose results never influenced the answer. This is the number that tells you your catalog is too wide.

Run it in CI on every prompt, schema, or model change, and diff per example against the last accepted run. When you add a new MCP server, this suite is how you find out that it degraded selection on the workflows that were already working — which happens often enough that we now treat "connect a server" as a change requiring an eval delta, same as a prompt edit.

The honest trade-off

MCP is worth adopting where you are integrating tools you do not own, or where several agents need the same catalog and you would otherwise write the glue twice. It is not worth the moving parts if your agent calls three internal functions in your own codebase. A plain typed function with a hand-written schema is fewer processes, fewer failure modes, and a schema you fully control — and control of the schema, as above, is where most of the accuracy lives.

Start with the tools your workflow actually needs, write their descriptions yourself, gate the ones with consequences, and measure selection. The protocol is the easy part.