Model Context Protocol / The Protocol
How a client and server connect and stay in sync.
Reviewed by Yuvaraj
Every MCP interaction, however sophisticated the tool or resource behind it, reduces to structured messages moving across a transport. Understanding that message layer, the wire format, the two standard transports, and the strict startup handshake, is what separates developers who can debug a stuck server from those who can only guess. This lesson traces an MCP session from the first byte to graceful shutdown.
MCP does not invent its own protocol. Every message is a JSON-RPC 2.0 object, and there are exactly three kinds:
| Message | Has id? | Expects a reply? | Purpose |
|---|---|---|---|
| Request | Yes | Yes | Invoke a method (e.g. tools/call) and await a result |
| Response | Yes (echoes the request id) | No | Carries a result or an error back to the caller |
| Notification | No | No | Fire-and-forget signal (e.g. notifications/initialized) |
Because notifications carry no id, the sender never learns whether they were processed, that asymmetry matters when you reason about the handshake below. Both sides of an MCP connection can send requests; the server is not merely a passive responder.
Ask about this lesson, or about anything in AI. Answers cite the lessons they draw on.
Finished this lesson?
Mark it complete to earn XP, keep your streak, and schedule a review.
The transport decides how those JSON-RPC bytes physically travel. The spec defines two.
Same messages, different pipe
The JSON-RPC payloads are identical across transports. Switching from stdio to
Streamable HTTP changes deployment, latency, and auth, never the shape of an
initialize or a tools/call.
Before any tool can be called, the client and server negotiate. The lifecycle is strictly ordered.
Capability negotiation is the load-bearing step. If the server never advertised a tools capability, a client that calls tools/list should expect an error, the feature was never on the table.
The client opens with a request. Note the id, the method, and the negotiated fields inside params:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": { "name": "example-client", "version": "1.0.0" }
}
}
The server answers with a response echoing id: 1, declaring what it can do:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"prompts": { "listChanged": true }
},
"serverInfo": { "name": "example-server", "version": "1.0.0" }
}
}
Finally the client sends a notification, no id, no reply, to open normal operation:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
For a curious beginner
Two people agree which languages and topics they both handle before the real conversation starts. You do not ask a question in a language your partner never claimed to speak.
How it is actually used
Each side declares capability objects during initialize. Before invoking a
peer method, check that the peer advertised the matching capability;
otherwise handle the error path rather than assuming success. Client
capabilities like roots and sampling enable server-to-client requests;
server capabilities like tools enable client-to-server requests.
The underlying mechanism
The set of methods a party may legitimately invoke is bounded by what the other side declared, per direction: . Negotiation does not add features, it fixes the intersection both sides will honor.
Common mistakes
initialize response arrives. The handshake
must complete first. - Forgetting the notifications/initialized message,
some servers refuse operational requests until they receive it. - Treating
initialized as a request and waiting for a reply. It is a notification and
carries no id. - Embedding a raw newline inside a stdio message. Each
JSON-RPC message must be a single line; newlines delimit messages. - Calling a
method for a capability the peer never advertised, then being surprised by the
error. - Hard-coding one protocolVersion and failing when a peer proposes a
different one, always read the version the peer agreed to.