2026-08-13 · Architecture
MCP in a bank: connecting agents to core systems without the blast radius
Model Context Protocol won the integration argument quickly and for good reasons. Writing one adapter per system per framework was miserable work with no intellectual content, and a common protocol makes that problem go away. Through 2026 the adoption curve inside financial services has been steep, with agents wired into lending, claims, underwriting and servicing systems, and the security research has been arriving at roughly the same pace.
The recurring finding in that research is not subtle. Tool poisoning, where instructions are embedded in a tool's description or parameter metadata, is the most prevalent client-side issue, and it works because the description is text the model reads and treats as guidance. No breach is needed. Whoever controls the description controls a channel into the agent's reasoning, and the resulting action executes with the agent's own credentials through an approved path, which is exactly the shape that logging and monitoring were not built to notice.
Tool descriptions are untrusted input #
Start here, because it reorders everything else. If tool metadata can steer behaviour, then the trust boundary sits at the server, and a server you did not write is a supplier with write access to your agent's instructions.
Three consequences follow directly:
- Dynamic tool discovery in production is a bad default. A server that can change the set of tools, or the text of their descriptions, after review has an unreviewed path into behaviour. Pin what is available and treat changes as changes.
- Third-party servers need supplier diligence, not a pull request. The question is not whether the code is good, it is who can push to it, what it can reach, and what happens to that access if the project is abandoned or transferred.
- Composition creates paths nobody drew. An agent holding a read tool for one system and a write tool for another has an exfiltration path between them that appears in no diagram, because each tool was approved separately.
The identity question underneath it #
Agent access sits on top of a governance gap that predates agents. Machine identities already outnumber human ones in a typical enterprise by a wide margin, and the 2026 industry reporting is consistent that most organisations cannot say how many they have, what those identities can reach, or when anyone last reviewed the grants. Surveys through the year have put the share of organisations without a formal policy for AI agent credentials in the high seventies.
An agent makes this acute because its scope is variable per task. The same agent acts for different customers at different moments, and the naive implementation gives it one service account with the union of everything any customer interaction might need. That account is then, functionally, a standing grant to the entire customer base, exercised by a component that can be talked to by strangers.
The correction is unexciting and effective. Credentials scoped to the action, authorised per invocation, resolved against the authenticated session rather than against anything the model produced. If a customer identifier can appear as a model-chosen tool argument, you have built a cross-customer lookup and given it a helpful name.
Pin the surface, version the config #
Whatever tools an agent can reach should be enumerable from your repository, not discoverable at runtime. In a configuration-first setup that falls out naturally: the agent is YAML plus Markdown, the tool set is declared, and the config hash lands in the audit record next to every session. Which means "what could this agent do on the ninth of March" is a lookup rather than an archaeology project.
Pinning also gives you the diff. A new tool appearing in an agent's surface should show up in code review as a line someone approved, in the same way a new IAM policy does. Silent capability expansion is the failure mode that turns a contained deployment into an uncontained one over six months, with no single decision to point at afterwards.
Wrap the server, do not expose it #
The pattern we would defend in a review: MCP servers, if you use them, sit behind your own typed tool layer rather than being handed to the model directly.
Concretely, the agent calls a Python function you wrote. That function validates arguments, resolves identity from the session, calls the MCP server or the underlying API, validates what comes back, and returns a narrow structured result. It costs you a thin adapter per tool, and it buys four things worth more than the adapter:
- Descriptions you control. What the model reads about the tool is your text, in your repository, in the diff.
- Response validation. Free-text fields coming back from a third party get treated as data rather than as instructions, and can be truncated, redacted or rejected.
- Guardrails on the call path. Contact caps, PII redaction before any provider call, and hard blocks all run as middleware around the invocation rather than being expressed as advice to the model.
- One audit format. Every tool call, whether it terminates in an MCP server or a mainframe adapter, produces the same record with the same fields.
This is the same argument as guardrails belonging in config, applied to integration: the enforcement point has to be somewhere the model does not reach.
A review checklist for a proposed server #
- Who operates it, and where does it run relative to your perimeter?
- Can its tool list or descriptions change without a deployment on your side?
- What identity does it authenticate as downstream, and what is that identity's scope?
- Does any tool accept a customer or account identifier the model can choose?
- Which tools write, and what are the numeric bounds on those writes?
- Does customer data traverse anything outside your data residency boundary in the process? The redaction playbook covers the ordering that keeps this answerable.
- Is there a synthetic conversation that exercises this tool and asserts on the guardrail firing? See the synthetics playbook.
Anything unanswered on that list is not a blocker in itself. It is a risk position that somebody senior should be knowingly accepting rather than inheriting by default.
When not to use it #
MCP earns its keep where the alternative is genuinely many adapters against many systems, or where you want an ecosystem of tools maintained by someone else. Inside a bank, the second benefit is often the one you least want, because the tools that matter are your own core systems and the adapters are written once against APIs that change on a release calendar you control.
For that case, typed functions registered directly are simpler, cheaper to reason about, and remove an entire class of risk by removing the dynamic surface. Fewer moving parts is not a nostalgic preference here, it is a smaller thing to explain to a supervisor. Use MCP deliberately, at the edges, behind a wrapper, with the identity question answered first.
Related: why prompt injection is an architecture problem, and what the third-party regime says about dependencies you did not choose.