Topic 20

The MCP Model

Protocol Model

MCP has three moving parts: a client inside your agent process, a server in front of each capability, and a transport between them. Before the first tool call the client learns what the server offers. The revision current in 2026 does that statelessly: every request carries its protocol version and capabilities, and a client that wants the answer up front calls server/discover; the revisions before it required an initialize handshake first, and clients that must talk to both still fall back to one. Either way, after discovery the client asks for the tool list and your agent holds a set of capabilities it never compiled in.

A server can publish three kinds of thing — tools, resources and prompts. Tools map directly onto Chapter 3 and carry nearly all of the real usage. The other two are worth knowing precisely so you do not reach for them when a tool is the right answer, which is the mistake that doubles a ticket's context for nothing.

Three moving parts, and the three things a server may publish
Agent process · one client per connection, created at start-up
The loop and its dispatcherlocal and remote, one path
The clientdiscovery, then the tool list
transport — a child process on standard input and output, or a service reached over the network; your client code is the same either way, the security story is not
Server · one capability, no loop — it answers and it executes
Tools — nearly all real usagecalled when the model decidesfour order tools · ~520 tokens
Resourcescontent at a URI, preloaded
Promptsmessage templates, offered

Client, Server, and Transport

The client lives in the agent process, one per connection, created by whatever assembles your tool list at start-up. The server sits in front of exactly one capability: Sundry ends up running three of its own — orders, the policy library, and the carrier integration — plus one it did not write. A server is not an agent and holds no loop. It answers questions about what it offers and executes what it is asked to execute.

Two transports carry that conversation. Over stdio, the agent starts the server as a child process and talks to it on standard input and output: no network, no ports, nothing to authenticate, and a process that lives and dies with yours. Over HTTP, the server is a service somewhere else, reached the way you reach any other service, with ordinary transport security and whatever auth you put in front of it. Your client code is the same either way. What changes is the deployment and the security story, and that difference is the last section of this page.

Tools, Resources, and Prompts

Tools are callable functions: a name, a description in prose, and a JSON Schema for the arguments — the same three things Chapter 3 said the model actually sees, published instead of compiled. The client fetches them at connect time, includes them in every request to the model, and executes one when the model asks for it. Everything else in this chapter is scaffolding around that.

Resources are readable content addressed by a URI: a document, a record, a file the server is willing to hand over. Prompts are named, parameterized message templates the server suggests, which a client can offer to a user as a starting point. Both are real parts of the protocol and both are thin on the ground — as of 2026, the overwhelming majority of MCP traffic is tool calls. The protocol also defines things the client can offer back to the server, most usefully sampling, where the server asks your agent to run a model call on its behalf; the 2026-07-28 revision deprecates it in favour of calling the model provider directly, so do not design around it.

The asymmetry matters for design, not just for trivia. A resource is pulled into context by your code, before anyone knows whether the ticket needs it. A tool is called by the model at the moment it decides it needs the answer. Sundry's policy library is roughly 2,000 seller supplements plus five core documents; published as resources and preloaded, it is a context bill on every ticket for text almost every ticket ignores. Published as search_policy, it costs nothing until the question is asked. When the content is large, prefer the tool.

Discovery

Connecting is not just opening a socket. The client has to learn what the server supports, so it never has to guess: a server that offers no prompts says so, and a client that does not implement a feature says so on every request it sends. Since the 2026-07-28 revision that exchange is stateless — there is no session to set up and nothing to remember between calls, which is also why a broken connection costs one in-flight request rather than a re-handshake. Then the client asks for the tool list, and gets back an entry per tool.

What the orders server answers when the client asks for its tools
{
  "tools": [
    {
      "name": "get_order",
      "description": "Full detail for one order: items, charges, seller, delivery state.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "order_id": {"type": "string", "pattern": "^SU-[0-9]{5}$"}
        },
        "required": ["order_id"]
      }
    }
    /* search_orders, start_return, offer_replacement — ~520 tokens in total */
  ]
}

Read one entry in words: a name the model refers to, a sentence of prose telling it when this tool is the right one, and a schema saying what an argument has to look like. That is the whole of what the model receives, and the middle field is the one that decides selection. It was written by the orders team, it is not code, and nothing validates whether it is any good.

Discovery happens once per connection; the tool list is billed on every turn after it. Sundry's nine local tools already cost about 680 tokens of schema per request (Chapter 2). Four order tools add roughly 520 more, the policy and carrier servers about 300 between them, and a third-party server publishing eighteen tools would add about 2,200 — on every turn of every one of 4,200 tickets a week. Connecting a server is a cost decision before it is anything else.

Results and Errors

Chapter 3's discipline does not change across the boundary: return a result shaped for a model to read rather than a database row, make errors structured and specific enough to be acted on, and mark truncation explicitly instead of silently cutting a list at ten. What changes is that the boundary itself can now fail in ways a function call cannot. The subprocess died. The socket hung. The remote returned a 503 with an HTML error page in the body.

Those failures are yours to translate, because the model cannot act on a stack trace. A slow server is a slow ticket, and slowness is the failure that hides: Sundry's target is a p95 of 9 seconds to the first useful message, so a carrier server that takes 6 seconds to answer has eaten two-thirds of the budget before the model has read a word of the result. Every server gets a timeout on your side, and every timeout gets a defined result the loop can continue from.

What Runs Where

A local server started over stdio runs on the agent host as your process's child. It sees the filesystem your agent can see, the environment variables your agent was started with — including your provider API key — and whatever network the host can reach. Anything it can read, it can put in a tool result, and anything in a tool result lands in the model's context. That is a large grant, made by adding one line to a config file, and it is the subject of Topic 23.

A remote server sees whatever you send it. Sending order ids, buyer emails and item descriptions to a service another company operates is a data-handling decision that deserves the review any other processor gets, not an architecture preference settled on a whiteboard. Neither choice is safer by default: local trades data egress for host access, remote trades host access for data egress. Chapter 12 works through what an attacker does with either.

MCP tools vs plain function tools

Function tools live in your codebase. They are the fastest to call, the simplest to reason about, fully under your control, and duplicated in every agent that needs the same capability. When the model asks for one, the only thing between the request and the effect is code you wrote.

MCP tools live behind a protocol. They are discoverable at runtime, owned by the team with the domain knowledge, and reusable across agents — at the cost of a process boundary, a transport, a start-up sequence that can fail, and a trust decision about descriptions somebody else writes.

Sundry ends up with both, split along one line: money-moving tools stay local. issue_refund, start_return's approval path and the escalation hook are functions in Vera's repository, where the $150 ceiling and the audit record live. The order system, the policy library and the carrier come in over MCP. That split — read-heavy domain capabilities remote, consequential actions local — is where most teams land, and it is a defensible default rather than a compromise.

Common Mistakes
  • Loading every available server at start-up because the config allows it — each tool list is tokens on every turn and one more set of near-duplicate names for the model to mis-select between, so a fifth server can cost accuracy and money at the same time.
  • Putting a money-moving tool behind a remote server and treating the server's own checks as the control — authorization, the $150 ceiling and the audit record have to sit on your side, because a server you do not operate cannot be the thing that enforces your policy.
  • Leaving the process boundary out of the latency budget — a subprocess that takes 400 ms to start is 400 ms of customer wait on the first call of a ticket, against a p95 target of 9 seconds to the first useful message.
  • Using resources to preload documents "so the model has context" — pulling the policy library in ahead of the question is exactly the pattern Chapter 6 replaces with retrieval, and it bills on every ticket including the ones that never mention policy.
Best Practices
  • Load only the servers a task needs, and narrow the tool list per task type — an order-status ticket has no business seeing the returns surface.
  • Keep authorization, spend ceilings and the audit record on your side of the boundary regardless of what the server claims to enforce.
  • Give every server an explicit timeout and count its start-up cost in the loop's latency budget, not in a footnote.
  • Prefer a tool the model calls over a resource you preload whenever the content is larger than a page.
Comparable toolsMCP SDKs client and server in most languagesOpenAPI clients schema-first, generated at build timegRPC the same client/server/schema triangleLanguage Server Protocol capability negotiation over stdio

Knowledge Check

An MCP server can publish three kinds of thing. Which description is accurate about how they are used in practice?

  • Tools, resources and prompts — tools are callable functions and carry nearly all real usage
  • Tools, resources and prompts — prompts are injected into the system message on every single turn
  • Tools, resources and prompts — resources are the ones the model calls when it needs a lookup
  • Tools, resources and memory — the server retains conversation state between an agent's turns

Sundry connects a fourth server that publishes eighteen tools. Discovery takes 200 ms once. What is the ongoing cost?

  • About 2,200 tokens of tool schema on every turn of every ticket, plus more similar names for the model to confuse
  • A one-off registration of eighteen schemas with the model provider, after which the names are referred to cheaply
  • Another 200 ms discovery round before each turn, since capabilities are re-checked whenever the context changes
  • A growing per-ticket cost on the server side, because each server keeps the ticket's history for the loop's duration

The orders team offers to enforce the $150 refund ceiling inside their MCP server, so Vera's agent does not have to. Why is that the wrong place for it?

  • A control you do not operate can be changed by someone else's deploy, and the audit record has to be on your side
  • MCP servers are not permitted to reject a tool call, so the ceiling could never be enforced there in the first place
  • The extra round trip would add latency to every refund, and refunds are already the slowest path in the loop
  • The model reads tool descriptions, so the ceiling belongs in the system prompt where it will reliably be obeyed

A team proposes running a third-party server as a local subprocess rather than calling the vendor's hosted version, on the grounds that local is safer. What is the honest comparison?

  • Local grants filesystem, environment and network access on your host; remote sends your data to somebody else
  • Local is genuinely safer, because a subprocess is isolated from the agent's environment and credentials by default
  • Remote is genuinely safer, because the vendor operates the process and therefore carries the security responsibility
  • The two are equivalent, because the transport only affects wire encoding and the model sees identical tool definitions

You got correct