The Model Context Protocol: hosts, clients, servers, and where the adapter work went
MCP is usually introduced with an analogy, USB-C for AI, and a claim, that it turns N×M integrations into N+M. Both are true and neither explains much. This note says precisely what the protocol fixes and what it leaves alone, names the three parties and the three primitives in a way that is hard to forget, and answers the question that trips most people up after the introduction: if MCP removed the custom glue, why is there still an adapter layer?
MCP is a wire protocol between an AI application and a program that offers it tools or context. It standardises the message format, the handshake, how capabilities are discovered, and the shapes of calls and results. It says nothing about which model is used, how the model decides to call, what a tool does inside, or who is allowed to call it. The host is the app, the client is the host's connection to one server, and the server wraps one service. Tools are triggered by the model, resources by the application, prompts by the user. One server per service and one client per app replaces one integration per pair. The adapter work did not vanish: the service-specific part moved into the server, written once per service, and the policy part moved into the host, written once per app.
Contents
01What the protocol fixes, and what it does not
The Model Context Protocol, published by Anthropic in late 2024 as an open specification, is a protocol in the ordinary networking sense: a fixed format for messages exchanged between two programs. The two programs are an AI application and something that can give it tools, data or templates. The messages are JSON-RPC 2.0, the same request-and-response convention the Language Server Protocol uses between editors and language tooling, which is the closer of the two usual analogies.
Being clear about the boundary is most of understanding MCP.
| MCP standardises | MCP does not standardise |
|---|---|
| The message envelope and error format | Which model is on the other side, or that there is a model at all |
| The handshake: each side declares what it supports | How the model decides whether to call a tool |
| Discovery: list the tools, resources and prompts on offer | What a tool does when called |
| The shape of a tool call and its result | Who is allowed to call it, or with what arguments |
| How a resource is addressed and read | How credentials for the underlying service are obtained |
| Notifications when the offer changes | Logging, rate limits, retries against the service |
Everything in the right column still has to exist. The protocol decides where it lives, which is the subject of section 07.
02Host, client, server
┌─────────────────────────────────────────────────────────┐ │ HOST the application the person is using │ │ owns the model, the conversation, the UI, │ │ and every permission decision │ │ │ │ ┌───────────────┐ ┌───────────────┐ │ │ │ client A │ │ client B │ │ │ │ one connection│ │ one connection│ │ │ └───────┬───────┘ └───────┬───────┘ │ └───────────┼──────────────────────────┼──────────────────┘ │ stdio │ HTTP ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ SERVER │ │ SERVER │ │ wraps the local │ │ wraps GitHub, │ │ filesystem │ │ runs remotely │ └─────────────────┘ └─────────────────┘
- The host is the program with the person in front of it: Claude Desktop, an IDE, a chat product, your own agent. It runs the model loop, holds the conversation, shows the user what is happening, and decides what the model is allowed to do. It is also the only party that knows the user.
- The client is a component inside the host, one per server the host connects to. It performs the handshake, sends requests, receives results and notifications, and nothing else. You do not interact with it; the host creates one when you add a server. "Client" in MCP never means the end user.
- The server is a separate program that wraps one service or capability. It knows how to talk to Postgres, or the filesystem, or GitHub, and presents that as MCP primitives. It knows nothing about the model or the user beyond what arrives in requests.
The naming confuses people because the server is often a tiny local process and the host is the big application, the reverse of the usual intuition. Think of it as the editor-and-language-server relationship: the editor is the host, and it spawns a small server per language.
03Transports
The protocol is the same over any transport; the transport only moves the bytes.
- Standard input and output, for a local server. The host launches the server as a child process and the two exchange JSON lines over the process's stdin and stdout. Simple, fast, no network, and the server inherits whatever the host's user can access.
- HTTP, for a remote server. The current specification calls this Streamable HTTP: a single endpoint that accepts requests and can stream responses and notifications back using server-sent events. Older material describes a separate "SSE transport" with two endpoints; that was the original design and was superseded in 2025. Remote servers carry their own authentication, typically OAuth, which is specified alongside the protocol.
04Tools, resources, prompts: who triggers what
A server can offer three kinds of thing. They are easiest to tell apart by asking who decides to use them.
┌──────────────┐
│ SERVER │
└──────┬───────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌─────────┐
│ tools │ │ resources │ │ prompts │
│ actions │ │ data │ │ templates│
└─────────┘ └───────────┘ └─────────┘
triggered triggered triggered
by the by the app by the
model or user user
Tools: the model decides
Tools are function calling carried over the protocol. The server advertises each tool with a name, a description and a JSON schema, exactly as in the tool schema note. The host passes those definitions to the model; the model emits a call; the host routes it to the right client, which sends it to the server, which runs it and returns a result; the host appends the result as an observation. Tools do things: run a query, post a message, create an issue. Because the model triggers them, the host is expected to ask the user before letting a tool with side effects run.
Resources: the application decides
Resources are read-only content addressed by URI, such as file:///project/README.md or postgres://main/schema. The host lists them and can read one and attach it to the model's context, or let the user pick one from a menu. No tool call is involved and the model does not choose; it is the "attach a file" action made standard, where the file can come from any server. A server can also notify the client when a resource changes, so a host can keep a view current.
Prompts: the user decides
Prompts are templates the server offers, which hosts usually surface as slash commands or menu entries. Choosing one expands it into a structured request, possibly after asking the user for arguments and possibly pulling in resources. A database server might offer /explain-slow-query, which asks for the query, attaches the schema resource and frames an analysis request. They encode a workflow the server's author knows well, so every host gets it without writing it.
| Primitive | Triggered by | Direction | Closest everyday analogy |
|---|---|---|---|
| Tools | The model | Does something, returns a result | An API endpoint |
| Resources | The application or user | Reads content into context | A file you attach |
| Prompts | The user | Starts a structured interaction | A form template or slash command |
The protocol also defines a few things in the other direction, where the server asks something of the host: sampling, which lets a server request a model completion through the host; elicitation, which lets it ask the user a question; and roots, which tell a server which directories it may work in. They are less used, and the three primitives above are what most servers offer.
05Lifecycle and discovery
A connection follows a fixed sequence, which is what lets a host work with a server it has never seen.
host → server initialize protocol version, what the host supports server → host initialize result what the server supports: tools? resources? prompts? host → server initialized ready host → server tools/list names, descriptions, schemas host → server resources/list URIs and descriptions host → server prompts/list names and arguments … the model works … host → server tools/call name + arguments → result server → host notifications/tools/list_changed the offer changed; list again
Discovery is the part that matters. Nothing about a server's tools is hard-coded in the host. The host asks, at connection time, and asks again when told the list changed. That is why a new server works in every host the day it is written, and why a host that supports MCP gains every server that exists.
06The N×M problem and why it was N×M
Before a shared protocol, every AI application defined its own way of plugging in tools. One had a plugin manifest format, another had a function-registration API in its own SDK, an IDE had its own extension interface. The formats differed, the way tools were executed differed, local process versus hosted sandbox, and authentication and error handling differed. A team that wanted their service, say GitHub, available in five applications wrote five integrations, each against a different convention, and maintained all five as each application's format evolved.
before N apps × M services = N×M integrations app 1 ──custom──► GitHub app 1 ──custom──► Postgres app 2 ──custom──► GitHub app 2 ──custom──► Postgres app 3 ──custom──► GitHub app 3 ──custom──► Postgres 3 apps, 2 services, 6 integrations; a seventh for every new pair after N clients + M servers = N+M app 1 ──client──┐ ┌──server──► GitHub app 2 ──client──┼──── MCP ─────┤ app 3 ──client──┘ └──server──► Postgres 3 clients, 2 servers, 5 pieces; one more for every new app or service
The multiplication came from the absence of a shared contract: each pair had to agree privately. A protocol replaces the private agreements with one public one, so each party implements it once. The service's author writes one server, the application's author writes one client, and every combination works without either having heard of the other.
07Where the adapter work went
This is the point at which explanations usually contradict themselves. Having said that MCP removes the custom glue, they go on to list everything an "adapter layer" must still do: translate the model's arguments into native API calls, hold credentials, check permissions, retry on failure, log every call. All of that is real. The resolution is that MCP did not eliminate the work; it decided where it lives, and in doing so changed how many copies there are.
┌────────────────────────────────────────────────────────┐ │ HOST app-side responsibilities, written once per app │ │ · which servers to connect and which tools to expose │ │ · ask the user before a side-effecting call │ │ · who the user is; what this user may do │ │ · log every call on whose behalf it ran │ │ · manage context: what results to keep, truncate │ └───────────────────────────┬────────────────────────────┘ │ MCP: standard calls and results ┌───────────────────────────┴────────────────────────────┐ │ SERVER service-side responsibilities, once per service│ │ · translate tool arguments into the native API call │ │ · hold the service credentials; refresh tokens │ │ · retries, backoff, timeouts against the service │ │ · shape messy responses into compact results │ │ · know the service's quirks, limits, pagination │ └───────────────────────────┬────────────────────────────┘ │ native API, SDK, driver ┌──────┴──────┐ │ service │ └─────────────┘
- Service-specific adapter work moved into the server. Knowing that the GitHub API paginates this way, that Postgres needs a connection pool, that this endpoint rate-limits at sixty a minute, which fields of the response the model actually needs. Written once by whoever knows the service, and every app benefits. This is the glue that used to be written N times.
- Policy and identity work moved into the host. Only the host knows the user, so only the host can decide what this user may do, ask before a destructive call, and log on whose behalf a call ran. Written once per app, and it applies uniformly to every server the app connects to. This work was never per-service and never should have been.
So the phrase "an adapter layer for each AI app" is half right and misleading. Each app has one policy layer that covers all servers. Each service has one server that covers all apps. The two ends of the protocol each carry their own share, and the multiplication is gone. What a protocol can standardise is the conversation between the two ends; what the ends do internally is their own business, and that is exactly where the remaining work sits.
Ask whether the logic depends on the service or on the user. "How do I call this API and read its response" depends on the service, so it belongs in the server. "Is this person allowed to do this, and should they be asked first" depends on the user, so it belongs in the host. Credentials are the one that splits: the service credential lives with the server; the user's identity and session live with the host, and a server that acts on a user's behalf receives a token from the host rather than holding user secrets itself.
08What MCP leaves to you
- Tool selection quality. The model still chooses by reading descriptions. A badly described MCP tool is a badly described tool; the protocol does not improve it.
- Trust. A server's tool descriptions and results enter the model's context. A malicious or compromised server can inject instructions through either. Hosts should treat server output as data and show the user what a tool is about to do.
- Context budget. Connecting many servers means many tool definitions in every request. Hosts that expose only the relevant tools, or load them on demand, do better than ones that pass everything through.
- Authorisation. MCP specifies how a remote server authenticates a connection. It does not specify, and cannot, whether this user may run this tool with these arguments. That remains the host's decision and the server's enforcement, as it was before.
09Takeaways
- MCP is a wire protocol: JSON-RPC messages, a handshake, discovery, call and result shapes. Nothing about models, decisions or permissions.
- Host is the app, client is one connection inside it, server wraps one service. The small process is the server.
- Tools are triggered by the model, resources by the application, prompts by the user. That is the whole distinction.
- Discovery at connection time is what makes any server work in any host.
- N×M came from private agreements between pairs; one public contract makes it N+M.
- The adapter work did not disappear. Service-specific work moved into the server, once per service; policy and identity moved into the host, once per app.
- Descriptions, trust, context and authorisation are still yours. The protocol standardises the conversation, not the judgement at either end.