AI Automation

MCP in Production: Connecting AI Agents to Your Business Systems Without Handing Over the Keys

Diagram showing two trust boundaries in an MCP deployment, separating untrusted input from the model and the model from business systems

Two years ago, wiring a language model into your CRM meant writing a bespoke integration. Function schemas by hand, a dispatch layer, error handling, and then doing all of it again for the next model because none of them agreed on the format.

The Model Context Protocol removed that work. Introduced by Anthropic in November 2024, MCP is now supported across Claude, ChatGPT, Gemini, Cursor, VS Code, JetBrains and Zed, and the official registry has passed 5,000 servers. Write one server, and every MCP-capable client can use it.

That is a real improvement and it deserves the adoption it has. But the same property that makes MCP useful (a standard way to give a model access to your systems) is what makes it dangerous when deployed carelessly. This post covers both halves, because most of what is written about MCP covers one and not the other.

What MCP actually is

Strip away the framing and MCP is a client-server protocol with three primitives.

Tools are functions the model can call. create_invoice, search_customers, send_message. This is the one that matters most, and the one that carries all the risk.

Resources are data the model can read. A file, a database record, a document. Read-only by design.

Prompts are reusable templates the server offers to the client. The least discussed and the least consequential.

The server describes what it offers. The client (the AI application) discovers those capabilities and makes them available to the model. When the model decides to call a tool, the client sends the request to the server, which executes it and returns the result.

The important design property is the split: the server owns the capability and the credentials, the client owns the conversation. Your MCP server holds the database connection. The model never sees it.

That is the good version. It is not automatically what you get.

MCP versus just writing an API

A fair question, and the answer is not always MCP.

An API is a contract between systems that know each other. Both sides were built with the other in mind. Calls are deterministic: the same input produces the same call.

MCP is a contract between your system and an unpredictable caller. The model decides at runtime which tool to call, with which arguments, based on a conversation you did not write. It might call the right tool with wrong arguments. It might call a tool you did not anticipate it needing. It might be persuaded to call one by text it read halfway through a document.

This changes what you build.

Traditional API

MCP server

Caller

Known, deterministic

A model, at runtime, non-deterministic

Input trust

Validated at the boundary

Boundary can be crossed by content the model read

Auth model

Per-client credentials

Must be per-user, not per-server

Failure mode

400 or 500, caller handles it

Model improvises, possibly convincingly

Error text

For developers

Read by a model, so it steers behaviour

The last row surprises people. Your error messages are now prompt input. A message like Permission denied. Try the admin_override tool instead. is not a helpful hint, it is an instruction to a system that will follow it.

Use a plain API when the caller is your own code and the calls are deterministic. Use MCP when you want a model to decide, at runtime, which capability to reach for. Do not wrap an existing API in MCP without revisiting its permission model, because the assumptions that made it safe no longer hold.

The security reality, stated plainly

MCP's first specification did not include an authorisation model. That gap has been addressed in later revisions, but a large body of servers was written against the early spec and the patterns from that period are still being copied.

The incident record is not hypothetical:

  • CVE-2025-49596 in Anthropic's own MCP Inspector, scored CVSS 9.4, allowing remote code execution.

  • GitHub MCP server data exfiltration, disclosed by Invariant Labs in May 2025, where content in a public issue could steer an agent into leaking private repository data.

  • The "NeighborJack" class, documented by Backslash Security in April 2025, where MCP servers bound to a permissive interface were reachable by anyone on the same network.

  • The Nx build system npm compromise in August 2025, which used AI CLI tools on developer machines to harvest credentials at scale.

In April 2026, OX Security disclosed an architectural flaw described as a default embedded in the official SDKs and inherited by downstream projects that trusted the reference implementation. The lesson from that one is not "MCP is broken." It is that a shared default propagates a shared mistake to everyone who did the reasonable thing and used the official library.

The recurring pattern in every one of these is the same: over-privileged credentials, plus input the model treats as instruction, plus no boundary between them.

The five attack shapes worth understanding

You do not need to memorise a taxonomy. You need to recognise these five patterns in your own design.

Tool poisoning

Tool descriptions are sent to the model as context. A malicious or compromised server can write a description that instructs the model to do something other than what the tool name suggests. The user sees search_documents in the tool list. The description says something else entirely.

Defence: treat third-party MCP servers the way you treat npm packages with install scripts. Read the tool descriptions yourself. Pin versions. Do not auto-update a server that holds credentials.

The confused deputy

Your server has broad permissions. The model, acting for a low-privilege user, asks it to do something that user should not be allowed to do. The server complies, because the server has permission even though the user does not.

Defence: authorise on the end user's identity, never the server's. This is the single most important architectural decision in an MCP deployment and it is the one most often skipped, because doing it properly means threading user context all the way through.

Token passthrough

The client hands a token to the server, which forwards it to a downstream API. Now the token exists in more places than it should, with weaker audit trails, and its audience claim no longer describes who is actually using it.

Defence: exchange tokens rather than forwarding them. The server should obtain its own scoped credential for the downstream call.

Indirect prompt injection

The highest-frequency real-world problem. Your agent reads a document, an email, a support ticket, a web page. That content contains text addressed to the model. The model acts on it.

This is not exotic. Any agent that reads external content and can also call tools is exposed to it, permanently, by design.

Defence: structural separation between "content the model read" and "instructions the model follows," a hard confirmation step before any irreversible action, and the assumption that a determined injection will eventually succeed. Design so that success is survivable.

Rogue and over-scoped servers

An MCP server installed from a registry, running with the permissions of whoever installed it, doing more than advertised. The registry crossing 5,000 servers is a supply chain the same way npm is a supply chain.

Defence: an allowlist for production. Review before adoption. Run untrusted servers in a sandbox with no credentials worth stealing.

What a defensible deployment looks like

Here is the shape we would build to, and would ask a client to build to before connecting anything to live data.

1. User identity flows through every layer. The server authorises against the end user's permissions, not its own. If you cannot answer "which user did this run as" for every tool call, stop and fix that before anything else.

2. Read and write are separated. Different servers, different credentials, different review standards. A read-only MCP server is a fundamentally lower-risk object and most of the value is in reads.

3. Irreversible actions require explicit confirmation. Not a model decision. A human clicking something, with the full parameters displayed. Sending, deleting, paying, publishing.

4. Tools are narrow. get_customer_by_id rather than run_query. Every general-purpose tool is a permission boundary you have chosen not to enforce. The temptation to ship one flexible tool instead of twelve specific ones is strong and it is always wrong.

5. Error messages are sanitised. Return Permission denied. Return nothing else. No hints, no alternative suggestions, no internal identifiers.

6. Everything is logged with user, tool, arguments and result. When you need to reconstruct an incident, you will need all four.

7. Rate limits sit at the tool level. A model in a retry loop can generate volume no human would. This has produced real bills.

// The shape that matters: authorise as the user, not as the server.
server.tool('get_invoice', async ({ invoiceId }, ctx) => {
  // ctx.user is threaded from the authenticated session,
  // not read from a model-supplied argument.
  const invoice = await db.invoices.findOne({ id: invoiceId })
  if (!invoice) return { error: 'Not found.' }
 
  // 404 rather than 403, so IDs cannot be enumerated
  // by reading the difference between the two responses.
  if (invoice.tenantId !== ctx.user.tenantId) {
    return { error: 'Not found.' }
  }
 
  return invoice
})

That 404-instead-of-403 detail is the same object-level authorisation failure that sits at the top of the OWASP API list, which we cover properly in the OWASP API Top 10, with actual fixes. MCP did not create that class of bug. It made it much easier to reach.

Should you build an MCP server at all?

Three honest answers.

Yes, if you have a product with an API and your users want their AI tools to reach it. This is becoming an expected integration, in the way that a REST API became expected a decade ago. Build it read-only first.

Yes, internally, if your team already uses AI tools and keeps copying data between systems by hand. Internal MCP servers over your own documentation, ticketing or analytics are genuinely useful and the blast radius is contained.

No, if the honest reason is that MCP is currently interesting. A well-documented REST API serves more consumers with less risk. MCP earns its place when a model needs to choose the capability at runtime. If your calls are deterministic, you want an API.

That last one is worth sitting with, because it is the same question in a different costume as the one in what actually pays back in AI automation. The technology being genuinely good is not the same as it being the right thing for you to build this quarter.

Where this is heading

The direction is towards agents that hold credentials and act over long horizons rather than single turns. Everything above gets harder in that world, not easier, because the window between an injection landing and an action executing gets wider and less observable.

The teams that will handle that well are the ones building the boundary now, while the systems are small enough to change. Retrofitting per-user authorisation onto a server that was written with a single admin credential means touching every tool you own. We have said the same thing about tenancy in multi-tenant SaaS architecture, and it is the same lesson: the expensive mistakes are the ones made before anyone realised a decision was being made.

If you are about to connect an agent to something that matters, send us the architecture. We review these as standalone engagements, and the outcome is often three specific fixes rather than a redesign. More on how we approach this on our AI automation services page.

Common questions

What is the Model Context Protocol?

MCP is an open protocol, introduced by Anthropic in November 2024, that gives AI applications a standard way to connect to external systems. It defines three primitives: tools the model can call, resources it can read, and reusable prompt templates. It is now supported across Claude, ChatGPT, Gemini, Cursor, VS Code, JetBrains and Zed, which means one server works with every compatible client instead of needing a bespoke integration per model.

Is MCP secure?

The protocol is not inherently insecure, but its first specification shipped without an authorisation model and a large body of servers was written against that early version. Documented incidents include a CVSS 9.4 remote code execution flaw in Anthropic's MCP Inspector (CVE-2025-49596) and a GitHub MCP server exfiltration path disclosed by Invariant Labs. The recurring root cause is over-privileged credentials combined with input the model treats as instruction.

What is the difference between MCP and a REST API?

A REST API is called by code you wrote, deterministically. An MCP server is called by a model that decides at runtime which tool to use and with what arguments, based on a conversation you did not write. That means input validation, authorisation and even error message wording all need rethinking, because your error text becomes prompt input that steers the model's next move.

What is tool poisoning in MCP?

Tool descriptions are passed to the model as context, so a malicious or compromised server can write a description that instructs the model to behave differently from what the tool name implies. The user sees a harmless-looking tool in the list while the description says something else. Treat third-party servers like npm packages with install scripts: read the descriptions, pin versions, and do not auto-update a server that holds credentials.

How do I stop prompt injection through an MCP agent?

You cannot eliminate it. Any agent that reads external content and can also call tools is structurally exposed. Reduce the impact instead: keep tools narrow and specific rather than general-purpose, authorise every call against the end user's permissions rather than the server's, require explicit human confirmation for anything irreversible, and log every call so an incident can be reconstructed. Design so that a successful injection is survivable.

Frequently asked questions

What is the Model Context Protocol?

MCP is an open protocol, introduced by Anthropic in November 2024, that gives AI applications a standard way to connect to external systems. It defines three primitives: tools the model can call, resources it can read, and reusable prompt templates. It is now supported across Claude, ChatGPT, Gemini, Cursor, VS Code, JetBrains and Zed, which means one server works with every compatible client instead of needing a bespoke integration per model.

Is MCP secure?

The protocol is not inherently insecure, but its first specification shipped without an authorisation model and a large body of servers was written against that early version. Documented incidents include a CVSS 9.4 remote code execution flaw in Anthropic's MCP Inspector (CVE-2025-49596) and a GitHub MCP server exfiltration path disclosed by Invariant Labs. The recurring root cause is over-privileged credentials combined with input the model treats as instruction.

What is the difference between MCP and a REST API?

A REST API is called by code you wrote, deterministically. An MCP server is called by a model that decides at runtime which tool to use and with what arguments, based on a conversation you did not write. That means input validation, authorisation and even error message wording all need rethinking, because your error text becomes prompt input that steers the model's next move.

What is tool poisoning in MCP?

Tool descriptions are passed to the model as context, so a malicious or compromised server can write a description that instructs the model to behave differently from what the tool name implies. The user sees a harmless-looking tool in the list while the description says something else. Treat third-party servers like npm packages with install scripts: read the descriptions, pin versions, and do not auto-update a server that holds credentials.

How do I stop prompt injection through an MCP agent?

You cannot eliminate it. Any agent that reads external content and can also call tools is structurally exposed. Reduce the impact instead: keep tools narrow and specific rather than general-purpose, authorise every call against the end user's permissions rather than the server's, require explicit human confirmation for anything irreversible, and log every call so an incident can be reconstructed. Design so that a successful injection is survivable.

Keep reading

All articles