Every agent demo runs against a clean REST API. Every enterprise agent project eventually hits a system where that assumption collapses.
The pattern is consistent enough to be predictable. A team designs an agent, maps its tools, and discovers that one of them is a SOAP endpoint whose WSDL has 340 operations and no documentation. Or a nightly batch job. Or an ERP module reachable only through a proprietary connector. Or a screen-scraping integration someone built in 2011 that nobody is willing to touch.
This is not an edge case. It is where the enterprise value is. The systems of record that hold the customer master, the order book, the general ledger, and the inventory position are rarely the systems with a modern API. And an agent that cannot reach them is an agent that can summarise documents and little else.
MCP does not solve this. MCP standardises how an agent talks to a tool server; it says nothing about what the tool server talks to. The hard work is behind the protocol, and it is integration work — a discipline with thirty years of accumulated practice that agent-era teams are frequently rediscovering from first principles.
Why legacy interfaces break agents specifically
It is worth being precise about the failure modes, because they are not simply “the API is old.”
Operation granularity is wrong. Legacy SOAP services and ERP APIs were designed for system-to-system integration, where a caller knows exactly which of 340 operations it needs. An agent has to choose, and choosing well from 340 near-identical operations with names like ZFI_GET_DOC_HDR_02 is a task models perform badly. Worse, every one of those operations consumes context budget if exposed as a tool.
Semantics live outside the interface. A field named STATUS with permissible values 01 through 47 is fully specified in a document that is not machine-readable, and possibly in a colleague’s memory. Agents read specifications literally; when the specification omits meaning, the agent fills the gap with a plausible guess. This is the single most common source of quietly wrong agent behaviour against enterprise systems.
Latency profiles are incompatible with conversation. A mainframe query that returns in eight seconds is well within normal for a batch-oriented system and fatal for an interactive agent. Worse are the interfaces that return in eight seconds usually — agents amplify tail latency because a single task may make a dozen calls.
There is no transaction envelope. Modern API design gives you idempotency keys and clear failure semantics. Many legacy interfaces give you neither, so a retry after a timeout may duplicate a posting. Agents retry more than traditional integrations do, because error recovery is part of how they operate.
Batch is not request-response. The most fundamental mismatch. An agent asks a question and expects an answer; a batch interface accepts a file and produces output four hours later. No amount of protocol adaptation makes that synchronous.
The anti-pattern: exposing legacy directly
The tempting shortcut is to wrap the legacy interface thinly — generate MCP tools from the WSDL, expose all 340 operations, and let the model figure it out.
This fails reliably, for reasons worth naming so the argument can be made in a design review:
The tool descriptions consume enormous context before any work happens. Operation selection accuracy degrades sharply with the number of near-identical options. Every legacy quirk becomes the model’s problem, solved probabilistically and differently each time. Coded field values get guessed. And the agent’s security boundary becomes the union of everything those operations can do, which for an ERP interface is close to unlimited.
The generated wrapper is fast to build and expensive to operate. It optimises for the wrong milestone.
The pattern: a capability layer
What works is an intermediate layer that exposes business capabilities rather than technical operations. Not a new idea — this is what API management has been arguing for since the beginning — but agents make the argument decisive rather than aesthetic.
A capability is defined by the task, not the system. get_customer_credit_position(customer_id) is a capability. It may internally call three SOAP operations, join their results, translate two coded fields into meaning, and apply a business rule about which subsidiary’s data takes precedence. The agent sees one tool with one clear purpose.
Four properties make a capability layer work for agents:
Task-shaped granularity. Design the surface from the agent’s task decomposition, not from the legacy system’s structure. If your agent’s tasks need eleven distinct pieces of information, you need roughly eleven capabilities — not 340 operations and not one god-tool with a mode parameter.
Semantics resolved, not forwarded. Translate coded values into meaningful ones at this layer. status: "awaiting_credit_approval" rather than STATUS: "17". This is where the tribal knowledge gets encoded once, in code, reviewably — instead of being re-guessed by a model on every call. It is the highest-value single thing the layer does.
Explicit knowledge/action separation. Split read capabilities from write capabilities cleanly, so the knowledge tool and action tool distinction maps onto the interface. This is what makes differentiated governance possible: read capabilities can be broadly available, write capabilities gated.
Idempotency and failure semantics added. If the legacy interface cannot offer idempotency, the capability layer implements it — a request key, a deduplication window, a record of what was already posted. This is unglamorous and it is what stops an agent retry from double-posting a journal entry.
Handling the genuinely asynchronous
For batch and long-running interfaces, the answer is not to hide the asynchrony. It is to model it honestly.
Expose the work as a job: a capability that submits and returns a handle, and a second that checks status. The agent’s workflow then has an explicit waiting state rather than a blocked tool call. Where the underlying system produces events, queued connections and notifications are the right primitives, and AsyncAPI is the right way to describe them.
The design question this forces is a useful one: does the business process actually tolerate a four-hour latency? Often it does, and the agent should be designed as a workflow with agentic steps rather than a conversation. Sometimes it does not, in which case the honest finding is that this use case requires a change to the underlying system, and that finding is worth surfacing in week two rather than month five.
The pattern to avoid is the synchronous facade over an asynchronous system — a capability that blocks for four hours, or worse, polls internally and times out at ninety seconds. It converts a manageable design constraint into an unpredictable failure mode.
Reading versus writing: the strategy that actually ships
The most reliable sequencing for legacy-facing agent projects separates the two directions and treats them very differently.
Reads first, and reads broadly. Read capabilities are lower-risk, easier to govern, and deliver a surprising share of the value. An agent that can answer “what is the status of this order across our four systems” is genuinely useful and cannot damage anything. Build the read surface across as many systems as you can reach.
Writes narrowly, through existing controlled paths. For state changes, prefer routing through whatever validated path the business already uses — the same service the current application calls, with the same validation and audit. Do not build a new write path for the agent. A new write path means new validation logic, new audit gaps, and a second place where business rules live.
Propose-and-approve before propose-and-execute. For the first production increment, have the agent produce the write payload and route it to a human approval. This is not timidity — it is how you accumulate the evidence needed to justify raising autonomy later, and it means the operable autonomy profile matches what your governance can actually support on day one.
The unglamorous prerequisites
Three things determine whether a legacy-facing agent project succeeds, and none of them are agent technology.
Someone must own the semantics. The translation from coded values to meaning has to be authoritative, or you have moved the guessing from the model into a config file nobody trusts. This usually means finding the two or three people who actually know the system and getting their knowledge into code. Budget real time for it.
The capability layer needs an owner and a lifecycle. It is a product, not a project artefact. Left unowned, it accretes special cases until it is another legacy system — this time one your agents depend on. The same API governance discipline applies, for the same reasons.
Test data is the constraint you will underestimate. Legacy systems frequently have no usable non-production environment, or one whose data bears no resemblance to production. Since your evaluation suite depends on realistic cases, this is on the critical path, and discovering it late has sunk more integration projects than any technical issue.
What this does not solve
A capability layer adds a hop, and therefore latency and an operational dependency. For a system that is already slow, this makes it slightly slower. That trade is nearly always worth it, but it should be a stated decision rather than a discovered surprise.
It also does not rescue a system that fundamentally cannot support the use case. If the data is only correct after the nightly close, no integration pattern gives you accurate intraday answers. The capability layer makes that constraint visible and explicit, which is genuinely valuable — but it makes it visible rather than absent.
And it does not reduce the reasoning burden on the agent for genuinely hard judgements. It removes the accidental difficulty of bad interfaces so the model can spend its reliability budget on the actual task. That is the whole point, and it is a large improvement, but it is not a different kind of improvement.
Key takeaways
Enterprise agent value sits behind the systems least likely to have modern APIs, and MCP does not help there — it standardises how an agent reaches a tool server, not what that server integrates with. Legacy interfaces break agents in specific ways: operation granularity designed for system-to-system callers who already know which of 340 operations they need, semantics documented outside the machine-readable interface so the model guesses coded field values, latency profiles incompatible with interactive use, missing idempotency in a context where agents retry more than traditional integrations, and batch interfaces that are not request-response at all. Thinly wrapping a WSDL into generated tools fails predictably by consuming context, degrading operation-selection accuracy, and expanding the security boundary to everything those operations can do. What works is a capability layer exposing task-shaped business capabilities with semantics resolved in code rather than forwarded, knowledge and action capabilities cleanly separated, and idempotency implemented where the legacy system cannot offer it — with genuinely asynchronous work modelled honestly as submit-and-poll rather than hidden behind a synchronous facade. Sequence reads broadly first, route writes through existing validated paths rather than new ones, and start with propose-and-approve to accumulate the evidence that justifies more autonomy later. The prerequisites that decide the outcome are organisational: an authoritative owner for the semantics, a product lifecycle for the capability layer, and realistic test data — the last of which is on the critical path and routinely discovered too late.