04 Action 17 min of exploration Intermediate

MCP — Giving an AI Hands

The question

How can an AI reach thousands of external systems without every integration being a project of its own?

04 / 05Action

One connection per system

The previous chapter left the agent able to know. What remains is reaching the systems your organisation actually runs on — and for a long time each integration was its own project: its authentication, its formats, its error handling, its maintenance.

Before, after

AI application

MCP
  • Files
  • Git repo
  • Database
  • Calendar
  • ERP
  • Sensors
  • CRM

Seven systems, seven bespoke integrations. Every new system is a project, and every new AI application starts the same work over. Seven systems, one interface to implement on each side. A server written once serves every application that speaks the protocol.

Flip the switch: the connections do not disappear, they stop being all different.

Text version

Before: one AI application wired to seven systems through seven different integrations. After: the same seven systems exposed through one common interface, MCP, which the application implements once.

That is the whole idea of the Model Context Protocol: a standard way to connect, so integration effort is paid once per system instead of once per system-application pair.

Three roles not to conflate

The commonest confusion is saying “the model called the API”. It did not, and the distinction is not vocabulary pedantry: it tells you where a control can be placed.

Host, client, server

Host — the AI application

Model

  • MCP client
  • MCP client
  • MCP client

The host is your application. It decides which servers are connected, what it exposes to the model, and what it does with a proposed call.

Process boundary

  • Calendar server

    Calendar

  • ERP server

    ERP

  • File server

    Files

An MCP client is a connector inside the host: it talks to one server and one only. A server is a service exposing context and capabilities. It may live on the same machine or elsewhere, and it knows nothing about the application calling it.

The model proposes. The client sends. The server executes. Three responsibilities, three different places to intervene.

Text version

The host is the AI application. It contains the model and one MCP client per connected server. Each client talks to one server, which exposes an external system. The request crosses a process boundary between host and server.

Keep the dashed line in mind. Until it is crossed, what the model produced is a proposal made of text. What turns it into a call is the host.

What a server can expose

A server does not only offer functions. The specification separates three things, and the separation is about who drives them.

Server primitives

Choose a primitive

Functions for the AI model to execute.

Driven by The model, from its understanding of the context and the request. This is the primitive that acts.

Examples

  • create_ticket(title, description)
  • read_sensor(id)
  • send_message(recipient, body)

This is where the rest of the chapter happens: a tool is a code execution path, and the protocol says nothing about who may take it.

Three different things, routinely collapsed into the word “tool”.

Text version

Three server primitives. Tools: functions for the AI model to execute — the only one that acts. Resources: context and data for the user or the model, driven by the application. Prompts: templated messages and workflows, triggered by the user.

A server exposing only resources can be entirely harmless. The tipping point is the first function.

A discovery, then a call

Follow one request end to end. The agent does not know the available capabilities in advance: it asks, then chooses.

Discovery and invocation

Request Open a ticket for the defect I just described.

  1. Client → Server The client asks the server for its capabilities. { "jsonrpc": "2.0", "id": 1, "method": "tools/list" }
  2. Server → Client The server replies with the available tools, each described by a name, a description and an input schema. { "result": { "resultType": "complete", "tools": [ { "name": "create_ticket", "description": "Opens a ticket", "inputSchema": { … } } ], "ttlMs": 300000 } }
  3. Host → Model The application places those descriptions in the model’s context, along with the user’s request.
  4. Model → Host The model proposes a call: the create_ticket tool, with arguments it wrote.
  5. Host The application receives a proposal. It can execute it, refuse it, or put it to someone. At this exact moment, nothing in the protocol has yet decided whether this call is allowed, or by whom.
  6. Client → Server The call goes out. { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "create_ticket", "arguments": { "title": "Frozen screen at startup", "priority": "high" } } }
  7. Server → Client The server executes and returns the result. { "result": { "resultType": "complete", "content": [ { "type": "text", "text": "Ticket OPS-4182 created." } ], "isError": false } }
  8. Host → Model The result joins the context, and the loop from chapter 02 starts again.

Messages follow specification revision 2026-07-28, checked on 14 August 2026. The protocol moves: that date is there so you know when to stop believing me.

Step through it. The switch above reveals the messages actually exchanged, for those who want them.

Text version

The client sends tools/list; the server replies with the available tools and their schemas; the application places those descriptions in the context; the model proposes a call to create_ticket; the application decides to execute; the client sends tools/call; the server executes and returns the result, which joins the context.

The fifth step is the only one that matters for what follows. The protocol arrives there with a proposal and leaves with a call, and nothing in the protocol says what must happen in between.

What revision 2026-07-28 changed

The protocol became stateless. The initialize / notifications/initialized handshake was removed: every request now carries its protocol version and the client’s capabilities in its _meta field. Protocol-level sessions and the Mcp-Session-Id header are gone from the Streamable HTTP transport, and the list endpoints (tools/list, resources/list, prompts/list) no longer vary per connection.

One practical, counter-intuitive and useful consequence: a server needing memory between two calls can no longer lean on the connection. It mints an explicit handle and receives it back as an ordinary argument on the next call. The specification puts it plainly: from the wire’s perspective a handle is an ordinary string in a tool result and an ordinary argument to subsequent tool calls.

Three features are deprecated in this revision: Roots, Sampling and Logging. Server-initiated requests — including elicitation, which asks the user for information — now go through the MRTR pattern: the server returns an input_required result, and the client retries the request carrying the responses.

These details will move again. What will not move is the shape: discover, choose, call, receive.

Three calls the protocol treats identically

Here is where convenience becomes an architecture problem.

Blast radius

read_invoice(id) Returns an invoice. Changes nothing.

Reach

For this to be acceptable That the caller may see this invoice — already a question, but a familiar one.

refund_customer(id, amount) Moves money. Reversible in theory, expensive in practice.

Reach

For this to be acceptable A ceiling, a recorded reason, and an identifiable person answering for the decision.

delete_database(name) Destroys data. No way back without a backup.

Reach

For this to be acceptable Nothing, in an agentic context, makes this call acceptable without an explicit and recorded human decision.

The protocol does not distinguish these three lines. Your organisation cannot afford not to.

Three tools, three incomparable consequences. On the wire, the three calls look alike.

Text version

Three tools of growing reach: reading an invoice changes nothing; refunding a customer moves money; deleting a database destroys data. All three calls have exactly the same shape on the wire, and the protocol treats them the same way.

The specification acknowledges this itself, in plain terms. It asks that a tool’s behavioural descriptions be treated as untrusted unless they come from a trusted server, and that there “should always be a human in the loop with the ability to deny tool invocations”. Then it adds the sentence that decides the whole next chapter: MCP itself cannot enforce these security principles at the protocol level.

That is not a gap. An access protocol has no business carrying your policy — it does not know it. But someone has to.

Who answers for what

Take the zones a call crosses, and ask each what it can decide.

Trust boundaries

  1. Model

    Can decide
    Which tool appears to fit the request, and with what arguments.
    Cannot know
    Who the user is, what policy permits, and what the call will actually produce.
  2. Host

    Can decide
    Which servers are connected, what is exposed to the model, and whether a proposal becomes a call.
    Cannot know
    What the tool really does server-side — it only has the description, which the specification says to treat as untrusted.
  3. MCP server

    Can decide
    Whether to execute, validating inputs and applying its own access controls.
    Cannot know
    The reason for the call, the conversation that produced it, and whether a human wanted it.
  4. External system

    Can decide
    Whether the bearer of the presented token may perform this operation.
    Cannot know
    That a model originated the request. To it, the call looks like any other authenticated call.

Each zone is competent at home and blind next door. One decision is left that nobody made: this call, now, with these arguments, for this person — is it allowed?

Each zone knows things the others do not. None knows everything.

Text version

The model picks a tool but does not know who is asking or what policy permits. The host decides what becomes a call but only knows the tool’s description. The server executes and validates its inputs but does not know the reason or whether a human agreed. The external system checks a token and does not even know a model originated the request.

What can go wrong

What standardising access does not standardise

A tool description is data, not a guarantee. The model chooses from what the server declares it does. The specification explicitly asks that such declarations be treated as untrusted outside a trusted server — which moves the question rather than closing it: what does “trusted” mean, and who decides?

Identity thins out along the way. The user talks to the host, the host to the server, the server to the system. Without deliberate effort, what arrives at the far end is the server’s identity, not the person’s — and the external system’s log will record a service, not a requester.

The number of connections is a surface. Every added server widens what the model can reach. There is no visible threshold at which the set becomes too wide: the convenience that makes MCP useful is exactly what makes its perimeter hard to hold.

Nothing records why. A call result says what happened. It does not say what request motivated it, what reasoning selected it, or whether anyone approved — and those three are precisely what you want on the day you need to understand.

The agent has hands.

It knows which capabilities exist, because it asked. It knows how to call them, because the protocol is the same everywhere. It knows what to do, because it has the context from the previous chapter.

And it can now refund a customer, stop a line, delete a database — provided someone connected the matching server.

The protocol did its job: it made capability reachable. It never claimed to decide who may use it. That question is still nobody’s.

A connected agent

  • Retrieval
  • Protocol
  • Tool calls

Identity

Policy

Approval

Evidence

A governable system

  • Retrieval
  • Protocol
  • Tool calls
  • Identity
  • Policy
  • Approval
  • Evidence

It can refund, halt, delete. Nobody has yet decided who may, or kept anything to show it.

You are here

  1. 01 Model
  2. 02 Agent
  3. 03 Knowledge
  4. 04 Action
  5. 05 Control

The agent has hands. Who decides what those hands are allowed to do?

Updated August 14, 2026