This chapter describes revision 2026-07-28. The running request is: “Show the incident and deployments for checkout.” Understanding it requires four distinct roles.
The host is the application managing the user interaction, model, context, and execution policy. A client is the host-side protocol component talking to a particular server. A server exposes capabilities. The domain service performs the underlying work. They may share processes, but the responsibilities remain different.
A server does not automatically see the conversation or other servers’ results. The host chooses what crosses each boundary. A host connected to two servers is not creating an automatic server-to-server network.
The three server primitives
| Primitive | Example | Typical control model | Protocol operations |
|---|---|---|---|
| Tool | Find accessible incidents for a service | Model proposes an operation; host dispatches | tools/list, tools/call |
| Resource | A runbook identified by a URI | Application chooses contextual data | resources/list, resources/read, templates |
| Prompt | An incident-review template with arguments | User or application selects an interaction | prompts/list, prompts/get |
These control models are useful conventions, not mandatory UI rules. A read can be a tool; a resource URI is not necessarily a filesystem path or a publicly fetchable URL. A prompt is reusable content, not higher-priority authority over the host’s policies. See the primary definitions for tools, resources, and prompts.
For our incident product, start with a tool for search and retrieval. Add a runbook resource if clients can use it effectively. Expose a prompt only if a reusable investigation template has product value; implementing all primitives is not a maturity requirement.
A complete ordinary interaction
Host MCP client Incident server
| | |
| configure endpoint/identity | |
|----------------------------->| |
| | server/discover (optional) |
| |--------------------------->|
| | versions + capabilities |
| |<---------------------------|
| | tools/list |
| |--------------------------->|
| select useful tool schemas | tool contracts |
|<-----------------------------|<---------------------------|
| ask model with task + tools | |
| model proposes a call | |
| validate host policy | |
|----------------------------->| tools/call |
| |--------------------------->|
| | authorize + query |
| use bounded result | result / error |
|<-----------------------------|<---------------------------|
Endpoint configuration and OAuth discovery are not the same as discovering a server’s tool catalog. Likewise, a schema becoming visible does not authorize invoking it. The host can restrict exposure; the server must independently check an invocation.
The modern core carries protocol version and client capabilities on each request. server/discover lets clients inspect supported versions and capabilities without creating a protocol session. A mismatch is a protocol error, not permission to guess. The versioning source describes compatibility with earlier initialization-based revisions.
Read one wire request
This is an illustrative authenticated HTTP request. The token is a placeholder; JSON-RPC ID 41 correlates this request and response.
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer <access-token-for-this-resource>
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_incident
{
"jsonrpc": "2.0",
"id": 41,
"method": "tools/call",
"params": {
"name": "get_incident",
"arguments": { "id": "inc-acme-1" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "incident-assistant", "version": "1.0.0"
}
}
}
}
The body and mirrored headers must agree. The client name is self-reported metadata, not authenticated identity. A JSON-RPC ID is also not an idempotency key: a retry can have a fresh request ID while representing the same business operation. The base message rules and HTTP binding define the envelopes; application replay semantics are separate.
A successful tool result can include text blocks and structured data. The wire result carries resultType: "complete"; higher-level SDKs may handle wire-only fields for you. Validate structured data before treating it as a domain object. Successful parsing does not establish truth or freshness.
Transports: same meaning, different deployment boundary
With stdio, the client launches a subprocess and exchanges newline-delimited JSON-RPC over standard streams. Standard output belongs to protocol messages; diagnostic text belongs on standard error. Local process permissions, installed code, environment variables, and executable provenance become important.
With Streamable HTTP, each client message is a POST to the MCP endpoint. A request receives JSON or a request-scoped SSE response. A long-lived notification subscription is still a specific request, not an ambient conversation. Streaming progress does not imply durable execution or guaranteed recovery after disconnect. These distinctions follow the transport overview.
For a customer-facing provider, HTTP usually gives a manageable deployment boundary. For a local filesystem integration, stdio may be simpler. Neither choice supplies tenant isolation by itself.
What changed in 2026
| Earlier tutorial pattern | 2026-07-28 baseline | Design consequence |
|---|---|---|
initialize / notifications/initialized handshake | Per-request metadata and discovery | Do not attach authority to initialization state |
Protocol session and Mcp-Session-Id | No core protocol session | Keep business state explicit and independently stored |
| Server-initiated requests | Input requirements returned through MRTR | Model interactive work as resumable application phases |
| Resumable SSE with event replay | No core SSE redelivery | Reconcile interrupted work using business identifiers |
| Experimental core tasks | Optional Tasks extension | Test actual extension support |
Roots, Sampling, and Logging are deprecated in this revision; deprecated does not mean removed. Older clients still exist, so support policy must distinguish protocol revisions from SDK package majors. The versioned changelog is the authority for this comparison.
The practical SDK trap is subtler: a manually constructed v2 client defaults to legacy behavior. Our lab opts into versionNegotiation: { mode: { pin: "2026-07-28" } } and uses serveStdio on the server. Tests assert the selected era. The SDK migration guide explains these entry points. A modern package name is insufficient evidence of a modern exchange.
Capabilities and extensions are compatibility contracts
Capabilities tell peers which protocol features they can use. They are not OAuth scopes. “This client supports elicitation” does not mean “this user may approve a production rollback.”
The Tasks extension supports work represented by a durable task handle and later status/result retrieval. It is useful when a request outlives a normal interactive wait. Our design recommendation is to keep task ownership, retention, cancellation policy, and result authorization in the application service. A handle identifies work; possession alone should not grant access.
MCP Apps and other extensions add capabilities beyond the core. Require them only when a workflow needs them, and record support by host version. A simple tools-only client should not be surprised by a mandatory UI or long-running task it cannot handle.
Interactive requests without hidden conversation state
Multi Round-Trip Requests let a server return input_required with requests for additional input. The client fulfills supported input requests and retries the original operation with inputResponses and echoed requestState. The Ruby SDK’s MRTR guide provides a readable account of this protocol pattern.
For an application designer, the important question is what happens between rounds. If the user supplies a missing reason, authorization may have changed before the retry. If state crosses the client, treat it as untrusted until verified. Bind any approval artifact to the actor, target, exact intended effect, expiry, and relevant object version. A returned form answer is not automatically a business authorization grant.
Discovery freshness and cache boundaries
List/read results have freshness and sharing hints, ttlMs and cacheScope. The caching guide describes how a client can use them. These hints help avoid repeatedly downloading stable catalogs and data.
Our application recommendation is stricter than merely checking a TTL: partition private entries by authorization context, tenant, endpoint, revision, and other inputs that affect visibility. A tool list can change when permissions change. A cached description can help the host choose a call; it must never replace the server’s fresh authorization check.
Three failure layers
| Failure | Example | Correct engineering response |
|---|---|---|
| Transport/authentication | Connection reset, HTTP 401 | Restore connectivity or authorization; preserve uncertainty about writes |
| Protocol | Unknown method, unsupported version, malformed envelope | Correct compatibility or request construction |
| Tool/domain | Incident inaccessible, invalid business transition | Interpret the domain outcome; do not blindly retry |
MCP can communicate errors, but it cannot choose your recovery policy. For the local lab, expected domain failures return isError: true; the client helper raises them for deterministic tests. Production clients should classify errors before retrying or involving a user.
The key test of understanding is this: the server committed a note, then the response disappeared. What does a new JSON-RPC request ID solve? Only correlation. What prevents a duplicate note? An application operation key, a durable atomic record, and a defined retry contract. That is where phase two begins.