Almost every confused MCP implementation traces back to the same mistake: treating “client” and “server” as the whole story. MCP actually defines three roles, not two — a host application, one or more clients inside it, and the servers those clients connect to — and the distinction between the first two is where most of the design leverage lives. The role model also breaks two intuitions people import from HTTP: a client here is a connection rather than an application, and the server can send requests back toward the client rather than only answering them. This article is about that model and the contract it implies — who owns the user, who owns the model, who is allowed to decide, and what each side must not assume.

Three roles, not two

The two-party mental model — an app calls a service — is what most developers bring to MCP, and it is subtly wrong. The specification names three participants. A host is the LLM application the user actually interacts with: an IDE assistant, a chat product, an agent runtime. A client is a component the host creates and owns, and its entire job is to hold one protocol connection. A server is a separate program that exposes capabilities — tools, resources, prompts — over that connection.

Collapsing host and client into one word is the error that causes real damage, because they answer different questions. The host answers “should this happen at all?” The client answers “does the peer on the other end of my connection support this?” Those are policy and plumbing respectively, and they belong in different places. Once you can say which of the three you are writing, most architectural questions in MCP answer themselves.

Advertisement

The host owns the user and the model

The host is the only participant with a relationship to the human. It renders the interface, it holds the conversation, and critically, it holds the model — the API key, the completion budget, the system prompt, the context window. No server has an LLM of its own in this architecture; when a server needs inference, it must ask the host for it.

That monopoly makes the host the natural home for everything that requires judgment. Consent is the clearest case: only the host can show a user a prompt and read their answer, so only the host can legitimately authorize a consequential action. Policy sits there too — which servers are permitted to run, which tools are exposed to the model at all, which operations need per-invocation approval, what the model is allowed to see. So does aggregation: the host is where several servers’ capabilities are merged into the single surface the model reasons over. If a decision requires knowing what the user wants or what the organization allows, it belongs to the host and nowhere else.

A client is a connection, not an application

This is the definition that surprises people. In MCP a client is not “the app”; it is a connector object the host instantiates, and the relationship with a server is deliberately one client per server. Connect a host to five servers and it runs five clients internally, each holding exactly one session.

What a client owns is narrow and mechanical: it establishes the connection over the chosen transport, performs the initialization exchange, remembers what that particular peer declared it can do, routes requests and responses by their JSON-RPC identifiers, surfaces notifications, and tears the connection down cleanly when the host says so. It tracks the capability set of its server — not a global one — because two servers in the same host may support entirely different feature sets and even negotiate different protocol revisions. What a client should not own is judgment. A client that decides on its own to approve a destructive tool call has quietly stolen the host’s job, and the user’s.

Why the one-to-one isolation matters

The one-client-per-server rule looks like an implementation detail and is actually the security and reliability backbone of the model. Because each connection is a separate object with separate state, a server sees only its own session: its own negotiated capabilities, its own request identifiers, its own notifications, its own lifecycle. It has no channel to another server and no visibility into one.

The consequences are concrete. A malicious or merely buggy server cannot read or inject into another server’s traffic, because there is no shared bus to reach. A server that hangs, crashes, or floods its connection degrades one client inside the host rather than the whole application — the host can drop that client and keep the others alive. Credentials scoped to one integration stay in that integration’s connection. And because the host sits between every pair, it can enforce different policy per server: full trust for a first-party server, tight confirmation gating for one a user installed yesterday. Take away the isolation and every one of those guarantees goes with it.

What a server owns, and what it must not assume

A server’s side of the contract is refreshingly small: implement capabilities and declare them honestly. It exposes tools the model may invoke, resources the host may read as context, and prompts the user may choose to trigger. It validates its own inputs, enforces its own authorization against the system it fronts, and reports failures in the form the protocol expects.

The harder half of the contract is the list of things a server must not assume. It does not know which host it is talking to, or whether that host is a chat UI, an IDE, or a headless agent with no human present at all. It does not know which model is in play, or whether one exists. It cannot assume a user will see anything it returns, or that an approval dialog will be shown, or that any particular optional feature is available — that is precisely what capability declaration exists to settle. A server that hard-codes assumptions about the host is not a server; it is one application’s plugin wearing protocol clothing.

Advertisement

Bidirectionality breaks the usual intuition

In HTTP, clients ask and servers answer, permanently. MCP is built on JSON-RPC 2.0, where either side of an established connection may originate a request, and the protocol uses that fully. The direction of the connection and the direction of a request are independent facts.

Three features make this unmistakable. With sampling, a server asks the host to run a model completion on its behalf — the server has no model, so it borrows the host’s. With elicitation, a server asks the host to collect additional input from the user, because the server has no user of its own. With roots, a server asks the client which filesystem or workspace boundaries it is allowed to operate within. In each case the arrow points from server back toward host, and in each case the reason is the same: the server needs something only the host owns. Read the role model this way and bidirectionality stops being an oddity — it is the direct consequence of putting the model, the user, and the workspace on one side and the capability on the other.

Trust is asymmetric by design

The two sides are not peers in trust, and pretending otherwise is how integrations get dangerous. A server is frequently third-party code: something a user installed from a registry, something a vendor ships, something a colleague wrote. It runs with whatever access its own backend grants it, and its tool descriptions — the text the model reads to decide what to call — are attacker-controllable content from the host’s point of view.

The role model gives you the correct response to that asymmetry without any extra machinery: the host is the only party with a trust relationship to the user, so it is the only party that can grant consent. A client cannot approve what the host has not authorized, and a server cannot approve anything at all — it can only request. This is why server-initiated features route through the host rather than executing directly, and why any hint a server attaches to a tool is advisory input rather than a decision. Trust flows downward from the user through the host; it never flows upward from the server.

Many servers, one host

The composition story is the payoff for all this separation. A single host commonly runs several clients at once — a filesystem server, a ticketing server, an internal database server — and merges their offerings into one capability surface for the model. None of those servers knows the others exist, and none needs to.

That leaves the host holding the composition problems, which is the right place for them. Name collisions are the obvious one: two servers may both expose a tool called search, so the host must qualify or namespace them before the model ever sees the list. Volume is the quieter one — thirty tools across four servers can crowd a context window and degrade the model’s selection accuracy, so the host decides what to surface. Servers come and go independently, so the aggregated list is dynamic. And because each server carries its own trust level, the host can apply per-server policy to a surface that looks uniform to the model but is not.

Failure modes of a blurred role model

The value of the distinction shows up as the bugs you avoid. When consent logic drifts into a client, each connector grows its own approval behavior and no auditor can say who authorized what. When a server assumes a human is watching, it blocks forever inside a headless agent. When a host treats a tool description as trusted instruction rather than untrusted content, prompt injection has a clean path to the model.

Two more are worth naming. Sharing one client across several servers destroys the isolation that makes per-server policy and blast-radius containment possible. And a server that only works against one host forfeits the portability that is MCP’s reason for existing. The discipline is simple to state: put judgment in the host, plumbing in the client, capability in the server, and let no role reach across the line.

MCP has three roles, and the host/client split is the one that matters most. The host owns the user, the model, consent, and policy; a client is a single connection object that handles lifecycle and capability tracking for one peer; a server implements capabilities and assumes nothing about the host it is attached to. One client per server is what makes per-server trust, blast-radius containment, and independent lifecycles possible. Because servers own no model and no user, they must ask the host for both — which is why the protocol is bidirectional and why trust flows only downward from the user through the host. Keep judgment in the host, plumbing in the client, and capability in the server.