MCP, Harness engineering, and Loop engineering: architecture of AI agents in production
Where the protocol ends and reliability engineering begins: architecture, security, observability, and the trade-offs of bringing AI agents into production.

Every engineer who has put an AI agent into production has hit the same wall. The model reasons well, writes well, decides well, but knows nothing about its database, its file system, or its internal API. Each new data source becomes a new integration, handwritten, with its own contract, error handling, and maintenance. The Model Context Protocol exists to solve exactly this problem, and it is worth understanding deeply why it matters before treating it as just another library to plug tools into LLM.
The problem is not tool calling, it's interoperability
Before the MCP, connecting a model to an external data source meant writing a specific adapter for that model, for that provider, and for that source. If I had three models and five internal systems, I didn't have eight integrations; I potentially had fifteen, one for each combination. This is the classic N x M integration problem, the same one that distributed systems solved decades ago with messaging standards and well-defined APIs.
I need to be precise here because it's easy to exaggerate the argument: the MCP does not eliminate the need for integration. Someone still needs to define tools, schemas, authentication, policies, and error behavior for that specific data source. What the MCP eliminates is the need for each consumer to reinvent the integration contract from scratch. I write an MCP server once, and any client compatible with the protocol can communicate with it, just as JDBC, ODBC, or HTTP itself function as standardized interfaces between consumer and data source.
Architecture: host, client, server
Host (Claude Desktop, IDE, agent)
→ MCP Client
→ JSON-RPC / MCP
→ MCP Server (Tools | Resources | Prompts)
From the MCP Server, three possible destinations:
- → APIs
- → Databases
- → File systems
The host is the application that provides the execution environment. The client lives inside the host and maintains communication with a specific server, one client per server, which isolates failure by design: if a server crashes or misbehaves, the blast radius is contained within that connection. The server is an independent, usually lightweight process that exposes context and capabilities for the model to consume.
The shift to stateless
This is the most important architectural change in the protocol since its launch, and it deserves its own section instead of a line lost in the middle of the text. Until the revision 2025-11-25, the protocol, especially in Streamable HTTP transport, was session-oriented: the client initiated interaction with the handshake initialize/initialized, and servers that needed to maintain state could issue a Mcp-Session-Id. When this identifier was issued, the client needed to echo it in subsequent requests, which tied each client to a specific server instance and made horizontal scaling an infrastructure problem before it was a product problem.
The specification 2026-07-28 removes the handshake and session at the protocol level. Each request now carries its own version of the protocol and capabilities, and is self-contained.
Previously, the client sent initialize, the server responded with a session id, and every subsequent request needed to echo that id, which tied the client to a specific server instance. Today, each client request arrives self-contained, with the protocol version in the header MCP-Protocol-Version and identity plus capabilities in _meta, the server responds, and any instance can handle any request.
In practice, this means that a remote MCP server can live behind a common load balancer, without sticky sessions, without shared session storage, without a gateway doing packet inspection to route to the right instance. It’s the difference between treating MCP as a laptop curiosity and treating MCP as conventional HTTP infrastructure.
Fundamental server primitives
The protocol defines three central primitives on the server side, and the distinction between them is not about side effects, it’s about who decides the use.
- Tools → controlled by the model → the LLM decides to call
- Resources → controlled by the application → the host decides to load
- Prompts → controlled by the user → the user decides to invoke
Tools are executable capabilities that the model discovers and invokes autonomously. This does not mean that every Tool alters state: a Tool may simply query data, with no side effects at all. The distinction that matters for production is another, and it’s the one I use to design access policy:
- Read-only →
consultar_cliente(),buscar_documento() - Idempotent write →
atualizar_status(),reprocessar_item() - Destructive/privileged →
deletar_registro(),cancelar_pagamento()
Each category requires a different level of validation, confirmation, and auditing. Treating all Tools with the same rigor is either too permissive for destructive ones or too bureaucratic for read ones.
Resources are structured data that the server makes available, and it is the host, not the model, that decides when to load them into context. Prompts are instruction templates that the user deliberately triggers; the model does not choose to use a Prompt on its own.
What has been deprecated, and what has come in its place
Until the previous revision, the protocol also included Roots, Sampling, and Logging among its core functionalities. In the specification 2026-07-28, these three were formally deprecated, with a minimum window of twelve months before any removal. They continue to function, but new implementations should no longer rely on them.
With the stateless revision, the server-initiated request pattern used by Sampling, Elicitation, and Roots has been replaced by Multi Round-Trip Requests (MRTR). Previously, if a server needed help from the client in the middle of a call, it would open a request back (sampling/createMessage, elicitation/create, roots/list), which required a bidirectional channel to always be open, incompatible with a stateless protocol. Now the server returns an incomplete result signaling input_required, the client resolves what is missing and redoes the original call with the response attached.
Before, the server would open a request back to the client in the middle of the call, sampling/createMessage, over a bidirectional channel that was always open. With MRTR, the server now returns an incomplete result (input_required), the client resolves what is missing and redoes the original call with inputResponses attached, and every leg of the flow becomes a common client→server request, without an open channel.
How MCP traffics
MCP is not the transport; it is the contract layer on top of JSON-RPC. The two transports that matter in practice are:
- stdio → local process, CLI, IDE, child process
- Streamable HTTP → remote server, cloud, gateway, load balancing
The legacy HTTP+SSE transport is officially deprecated in the revision 2026-07-28, with the same twelve-month transition period. If you are designing a new server today, there is no reason to start from it.
An example that shows the protocol in production
The most didactic case is connecting an assistant to a PostgreSQL database via an MCP server. The user asks a question in natural language, the model identifies that it needs structured data, discovers via Tools which operations the server exposes, formulates the call, and the server executes the authorized operation against the database.
Here, an important precision correction is worth mentioning: the Tool call does not automatically return a Resource. It returns a Tool result, which may contain textual content, structured content, or references to Resources. This result goes back to the host, which incorporates it into the context used by the model to compose the final response.
The point that often goes unnoticed is that a well-designed MCP server does not expose arbitrary SQL to the model. It exposes controlled operations, with defined schema, input validation, and scope limits, just as I would treat any API layer aimed at an untrusted consumer.
MCP as a trust boundary
MCP standardizes how capabilities are exposed. It does not transform insecure capabilities into secure ones. The MCP server should be treated as an API aimed at a partially untrusted consumer, because that is exactly what it is: a surface that a model, whose behavior is probabilistic, will autonomously call.
The revision 2026-07-28 reinforced this boundary, with issuer validation according to RFC 9207 to mitigate mix-up attacks between authorization servers, and a formal transition from Dynamic Client Registration to client metadata documents. The protocol also maintains, since previous revisions, the explicit prohibition of token passthrough: the MCP server must not, under any circumstances, pass the access token received from the client to a downstream service. If the server needs to call an upstream API, it acts as an OAuth client for that API and obtains its own credential, issued specifically for that resource, never reusing the token it received.
For the corporate environment, the missing piece arrived in June 2026: the Enterprise-Managed Authorization (EMA) extension moved from preview to stable. Instead of each user authorizing each MCP server individually through consent in a browser, the company’s identity provider becomes the decision point. An administrator approves the server once in the IdP, and every authorized user connects automatically via a short-lived identity grant, without consent screens per server. This solves a real scalability problem: one hundred internal MCP servers should not mean one hundred manual consents per employee.
Before connecting any third-party MCP server to a production system, I ask:
- Who wrote this server and what is the real access surface it exposes.
- Does the server validate the parameters of each Tool or blindly trusts what the model sends.
- Is there logging and tracing of every Tool call, with who called, when, and with what result.
- Is there tenant isolation, or does a single server serve multiple clients without scope separation.
- Do destructive operations require human confirmation, or can the model execute them end-to-end on its own.
- Does the server have read and write access, and is this distinction reflected in real permission, not just in the goodwill of the prompt.
The central thesis: probabilistic layer decides, deterministic layer executes
This is the idea that underpins the entire article, and it deserves to be stated explicitly: the probabilistic layer decides the intent, the deterministic layer validates and executes.
User → LLM / Agent (probabilistic, decides the intent) → Policy layer (deterministic, validates):
- can the user do this?
- is the tool authorized?
- are the arguments valid?
- does it need human confirmation?
- is the tenant correct?
→ MCP Client → MCP Server → Real system
The model should never have implicit permission to execute. It proposes, the deterministic layer decides if the operation is allowed, and only then does the MCP server execute it against the real system. The MCP standardizes the interaction contract between these layers; it does not replace the need for having this second layer.
MCP is not enough on its own: harness engineering and loop engineering
This final caveat deserves to be developed, because it is where most agent projects stumble after having already solved the integration part. A useful way to organize the recent evolution of this discipline is to see four levels: prompt engineering defines what to ask the model, context engineering defines which instructions, data, and tools are available, harness engineering connects the model to an executable environment with files, memory, and feedback mechanisms, and loop engineering defines how the system observes, acts, verifies, and recovers repeatedly. There is no formal industry standard saying that these are necessarily four universal layers, but it is a taxonomy that helps situate where the MCP fits and where it stops: the protocol solves part of context engineering and part of harness by standardizing the exposure and invocation of tools and data. It does not solve the other two on its own.
Harness engineering starts from a formulation that gained strong traction in agent engineering in 2026: agent equals model plus harness. The model contains the raw


