The problem: N applications, M systems, N x M connectors

A language model on its own is a closed box. It becomes useful when it can reach the world — read a repository, query a warehouse, call an internal service. The obvious way to build that is one integration at a time: your chat app grows a GitHub connector, then a Postgres connector, then a Jira connector, each with its own auth handling, schema format, and error conventions.

The trouble is that this cost is multiplicative. With N applications that want context and M systems that hold it, the industry writes on the order of N × M connectors, most re-solving the same problems badly. Worse, none of that work is portable: a connector built for one assistant’s plugin format is dead weight for every other. The pattern is familiar — the same pressure produced ODBC for databases and the Language Server Protocol for editors. In each case the fix had the same shape: stop writing adapters, agree on a protocol, and turn N × M into N + M.

Advertisement

What MCP actually is: an open protocol, not a framework

MCP is a specification for messages, not a library you are obliged to adopt. Its message layer is built on JSON-RPC 2.0: requests carry a method, params, and an id; responses echo that id with a result or an error; notifications are one-way and expect no reply. That deliberately boring foundation means an MCP implementation can be written in any language that can serialize JSON and move it over a pipe or a socket; the exact envelope shapes and method families are the subject of the protocol deep dive.

Two consequences follow from “open protocol.” First, the spec is public and versioned, so implementations from different vendors interoperate without a business relationship. Second, the official SDKs are a convenience, not the standard — they save you from hand-rolling framing and lifecycle logic, but a server that speaks the right JSON is a valid server no matter how it was built. MCP describes the conversation; it does not prescribe your architecture, your language, or your agent loop.

Advertisement

Three roles: host, client, and server

MCP splits the world into three parts. The host is the application the human uses — a desktop assistant, an IDE, an agent runtime. It owns the model, the user interface, and the trust decisions. The client is a connector inside the host that maintains one dedicated session with one server; a host running five integrations runs five clients. The server is the separate program that exposes capabilities — filesystem, database, ticketing — and knows nothing about the model or the user.

The one-client-per-server rule keeps the model clean: each connection is independently negotiated, scoped, and killable, so a misbehaving server cannot reach into another server’s session. The diagram below compresses host and client into a single box, as informal usage often does; the client/server article separates them properly.

MCP rolesMCP ClientLLM appMCP Servertools + dataJSON-RPCover transportOne server, many clients; one client, many servers — enables ecosystem
MCP structure.

The three primitives, and who is allowed to invoke each

Servers offer three kinds of capability, and the design idea most newcomers miss is that they differ by who pulls the trigger, not just by what they contain.

PrimitiveWhat it isControlled by
ToolsCallable functions with typed inputs and side effectsThe model decides to call one
ResourcesReadable, URI-addressed context: files, records, documentsThe application decides what to attach
PromptsParameterized templates and workflowsThe user explicitly picks one

That control axis is a safety property as much as an architectural one. Tools are the only primitive the model can reach for on its own, which is exactly why they are the primitive that needs consent and annotations. Resources are inert — reading one has no side effects — so the host can attach them freely. Prompts surface as visible affordances such as slash commands, so nothing happens until a human chooses one. Each primitive has its own article covering discovery, schemas, and edge cases.

The other direction: sampling, roots, and elicitation

The three primitives point one way, server to host. MCP also defines capabilities that point back, and they are what separate it from a plain remote-procedure-call layer.

Sampling lets a server ask the host to run a model completion on its behalf. A summarization server needs no API key of its own; it asks the host, which stays in control of cost, model choice, and whether the request is shown to the user. Roots let the host tell the server which directories or URIs are in scope, so a filesystem server is bounded by the workspace the user opened rather than the whole disk. Elicitation lets a server ask the user for a missing input mid-operation instead of failing with a vague error. Together these keep secrets, model access, and scope on the host side, where the trust already lives, while still letting servers be interactive.

The connection lifecycle at a glance

Every MCP session follows the same three-act shape. First, initialization: the client opens a connection and sends an initialize request declaring the protocol version it speaks and the capabilities it supports; the server replies with its own version, capability set, and identity; the client acknowledges, and the session is live. Nothing else may happen before that exchange completes.

Second, operation: the client discovers what exists by listing tools, resources, and prompts, then calls and reads them as the conversation demands. Servers can push notifications — a tool list changed, a resource was updated, a long job made progress — and both sides can cancel in-flight work. Third, shutdown: the connection closes, and whatever session state existed goes with it. The consequence of the handshake is that neither side assumes features the other did not advertise, which is how a spec that keeps evolving stays backward-compatible; the dedicated article on capability negotiation covers that exchange in full.

Transports: how the bytes actually move

MCP separates what is said from how it travels. The message layer is JSON-RPC regardless; the transport decides the pipe. Two families dominate. stdio runs the server as a local child process of the host and exchanges newline-delimited JSON over standard input and output — no ports, no network, no TLS, with the operating system’s process boundary as the security perimeter. It is the natural fit for a server that touches local files or credentials. HTTP-based transports carry the same messages to a remote server over the network, with server-to-client streaming layered on so notifications and progress updates flow without polling; that is what makes a shared, multi-user, independently deployed server possible at all. Because the message layer is identical, the same server logic can usually be exposed either way. The transport articles cover framing, streaming, and the operational trade-offs.