Pedro Sousa
‹ ← Articles

Article · Blog

MCP is not integration. It's a boundary. And you're probably drawing it in the wrong place.

Most projects using MCP treat the protocol like a glorified adapter. The real problem isn't technical: it's where you decide the agent ends and the system begins. Drawing that boundary in the wrong place is more costly than getting the implementation wrong.

MCP is not integration. It's a boundary. And you're probably drawing it in the wrong place.

In an AI agent project I led recently, the MCP server was working. Tools responding. The model calling the right functions. Green build, flawless demo.

Two weeks later, the agent started making decisions nobody could trace.

It wasn't a bug. It was the consequence of a boundary drawn in the wrong place from the start.


What most people understand MCP to be

Model Context Protocol became a hot topic fast. And like every topic that blows up quickly, most people absorbed the surface layer: it's a standardized way for the model to call external tools. You define tools, expose a server, the model decides when to use each one.

So far, correct.

The problem is that stopping at that definition is the same as describing Clean Architecture as "separating things into folders."

MCP isn't just the mechanism. It's where you formalize what the agent can do. And what it can't. And, more importantly, what it doesn't need to know.


The decision that looks technical but isn't

When you start mapping tools to an MCP server, the first temptation is to expose operations that mirror your internal API. getUserById, updateOrder, fetchTransactions.

It feels natural. You already have those endpoints. It's just an adaptation.

What happens in practice: the model becomes an API call orchestrator with no business semantics whatsoever. It doesn't know what a transaction means in your domain. It only knows it can fetch, update, and delete.

In a financial app I worked on, we did exactly that in a prototype. The agent worked. But it solved tasks by assembling sequences of calls that made sense syntactically and were wrong semantically. It would do fetchTransactions followed by updateOrder in situations where the business rule required an intermediate check that only existed implicitly in the production code.

The check was in the application's service layer. It wasn't exposed as a tool. And the model had no way of knowing it was supposed to exist in that flow.


The real problem: who knows the domain?

When you expose CRUD operations as tools, you are delegating domain knowledge to the model.

Except the model doesn't have that knowledge. It has language. It infers patterns. It generalizes from what you described in the tool definitions.

If the tool description says "updates the status of an order," the model will call that tool when it needs to update the status of an order. Simple. But the rule that says "only update if the order is in state X and the user has permission Y" isn't in the tool. It's in your system.

If you don't encapsulate that rule inside the tool, you have two bad paths: either you duplicate the rule in the description (and it goes stale when the product changes), or you trust the model to infer correctly (and it won't, not always).

A tool that exposes an operation without domain semantics doesn't protect the system. It opens the system.


Where the boundary should be

The analogy that works best for me is the use case.

A well-designed MCP tool acts like a use case, not like a repository. It receives an intent, executes a sequence of operations with business rules applied, and returns a result that carries meaning.

Instead of fetchTransactions + updateOrder, you expose approveRefundRequest. The model only needs to know it can request a refund for a user on an order. What happens underneath, what the validation sequence is, which states are allowed: that's the system's responsibility, not the model's.

This significantly changes what the agent can do. And it also changes what it can do wrong.

An agent that only has access to well-defined use cases makes mistakes within bounds. An agent with access to atomic database operations can escalate a reasoning error into damage that spans multiple entities.


The role of RAG in this equation

RAG comes up in this conversation because it's the other half of the context the model receives.

Most people implement RAG as semantic search over documentation or history. It works for questions. It fails for decision-making.

What I noticed after getting it wrong in a few prototypes: RAG for decision-making agents needs to bring state context, not just knowledge context.

There's no point in retrieving "how the refund flow works" if the model doesn't know that that specific user has already had three refunds approved this month and that a limit policy exists. That state context needs to be retrieved and injected in a structured way, not as free text.

When you mix domain knowledge with operational state in a single RAG pipeline, the model receives everything together and decides based on a blend you don't control well.

Separating the sources, and being explicit about what is static knowledge and what is dynamic state, measurably changes the quality of the agent's decisions.


What I would do differently today

In the project where the boundary was wrong, the signal appeared late because the problem was intermittent. The agent got it right in most cases. It failed on domain edge cases. And a domain edge case in a financial system isn't just a bug, it's an incident.

If I were starting over:

Before implementing any tool, I would write out the system's use cases in plain language. Then I would map each tool to exactly one use case. If a tool mapped to more than one use case, or to half of one, I would redesign the granularity.

The tool description would need to include what it validates, not just what it does. The model uses the description to reason about when to call the tool. If the description doesn't mention the business guards, the model doesn't know they exist.

And RAG would come in two distinct layers: one for static domain context, another for user and session state context. Injected separately, with different structure, so the model can distinguish what is a rule from what is a situation.


What this has to do with mobile architecture

More than it seems.

Over the past few years I've watched teams try to integrate AI into mobile apps as if it were just a new feature. A different API call, a result displayed on screen.

The problem starts when the agent needs to act within the app's context. Calling system APIs. Navigating flows. Reacting to local state.

At that point, the same boundary question comes up. What can the agent do inside the app? What counts as a legitimate user intent? What is an operation that requires explicit confirmation?

Apps that integrated agents without defining that boundary turned into a field of untraceable decisions. The user didn't know what the agent had done. The team couldn't reproduce the state the agent had generated.

State traceability isn't an AI problem. It's an architecture problem. AI just makes the problem more urgent.


There is a difference between a system that uses AI and a system that thinks in terms of AI.

The first treats the model as a component. The second reorganizes responsibility boundaries with the model in mind from the start.

MCP is a good tool. But like every good tool, it amplifies what you already have. If what you have are operations without domain semantics, the agent will use them without domain semantics.

The question worth asking before exposing any tool isn't "can the model call this?" It's "can the model get this wrong without you noticing?"