Class 6: Capability Negotiation
Duration: ~25 minutes | Level: Intermediate | Prerequisites: Class 5: Transports
This class describes the initialize handshake, which spec revision 2026-07-28 removed. The current protocol does not have a negotiation handshake at all: every request carries its own protocol version and client capabilities in _meta, and the server accepts or rejects each request independently.
The handshake is still worth reading, and everything below is accurate for 2025-11-25. It is what most deployed clients and servers speak, and it is what the Java MCP SDK 2.0.0 implements. Class 8 summarises what replaced it.
| What | 2025-11-25 (this class) | 2026-07-28 |
|---|---|---|
| Agreeing a protocol version | sent once, in the initialize request | io.modelcontextprotocol/protocolVersion in every request's _meta |
| Declaring client capabilities | sent once, in the initialize request | io.modelcontextprotocol/clientCapabilities in every request's _meta |
| Confirming the handshake | notifications/initialized | removed |
| Tasks | a core tasks capability | an extension, io.modelcontextprotocol/tasks (SEP-2663) |
Client roots | roots carries a listChanged sub-field | roots is an empty object, and the feature is deprecated |
| Optional add-ons | experimental only | extensions, a map of extension name to settings |
An extension is an optional feature specified outside the core protocol and identified by a prefixed name such as io.modelcontextprotocol/tasks. Both sides declare the ones they support in the extensions map.
Why Negotiation Matters
Protocols evolve and features are added. Without a negotiation mechanism, clients would have to guess what a server supports, or servers would have to support every feature forever.
MCP's initialize handshake settles it before any tool calls, resource reads or prompt retrievals happen: client and server exchange capability declarations first.
The Three Messages
Capability negotiation always involves exactly three messages:
The third message is a notification, so it does not carry an id, and the client does not wait for a reply before sending its first real request.
1. initialize request (Client → Server)
The client sends the protocol version it implements, the optional capabilities it supports, and its own name and version:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {},
"elicitation": {}
},
"clientInfo": {
"name": "Claude Desktop",
"version": "0.7.5"
}
}
}
2. initialize response (Server → Client)
The server answers with the version it will use, its own capabilities, and its name and version:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
},
"logging": {}
},
"serverInfo": {
"name": "acme-database-server",
"version": "2.1.0"
}
}
}
3. initialized notification (Client → Server)
The client confirms it has received and processed the initialize response:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
Capability Anatomy
Capabilities are nested objects. The presence of a key indicates support for a feature, and sub-fields indicate optional behaviours within it.
Server Capabilities
| Capability | Sub-fields | Meaning |
|---|---|---|
tools | listChanged | Server has tools; listChanged: true means it will send notifications/tools/list_changed |
resources | subscribe, listChanged | Server has resources; subscribe enables resources/subscribe requests; listChanged enables list-change notifications |
prompts | listChanged | Server has prompts; listChanged: true enables list-change notifications |
logging | (none) | Server will accept logging/setLevel and send notifications/message log events. Deprecated in 2026-07-28 (SEP-2577). The same revision removed logging/setLevel (SEP-2575): the level now travels per request in io.modelcontextprotocol/logLevel. The suggested migration is stderr on stdio, or OpenTelemetry, the vendor-neutral standard for traces, metrics and logs |
completions | (none) | Server supports argument autocompletion via completion/complete |
tasks | list, cancel, requests | Server supports task-augmented requests (long-running operations the client can poll via tasks/get and cancel); introduced experimentally in 2025-11-25. A server that cannot tell one caller from another SHOULD NOT declare tasks.list, because the list would show task metadata to anyone who asks. Removed from core in 2026-07-28: tasks are now an extension, declared under extensions as io.modelcontextprotocol/tasks |
experimental | (free-form) | Declares support for non-standard experimental features |
Client Capabilities
| Capability | Sub-fields | Meaning |
|---|---|---|
roots | listChanged | Client can expose filesystem roots to the server; listChanged means client will notify when roots change. Deprecated in 2026-07-28 (SEP-2577). The same revision removed notifications/roots/list_changed (SEP-2575) and reduced roots to an empty object. The suggested migration is to pass directories or files through tool parameters, resource URIs or server configuration |
sampling | context, tools (both optional) | Client supports server-initiated sampling requests (the server can ask the client to make an LLM inference). In 2025-11-25, context signals support for the includeContext parameter. That parameter is soft-deprecated: its "thisServer" and "allServers" values still work, but servers SHOULD avoid them, and 2026-07-28 moved them onto the formal deprecation list. tools signals support for tool calling in sampling (SEP-1577). An empty sampling: {} remains valid and means plain sampling, so a server MUST NOT send a tool-enabled sampling request to a client that left sampling.tools undeclared. Deprecated in 2026-07-28 (SEP-2577); the suggested migration is to integrate directly with an LLM provider API from inside the server |
elicitation | form, url (both optional) | Client supports server-initiated elicitation requests (the server can ask the client to gather structured information from the user). Introduced in 2025-06-18. In 2025-11-25 the optional form and url sub-fields say which elicitation modes the client supports. An empty elicitation: {} remains valid and means form mode only. A client MUST support at least one mode, and a server MUST NOT send a mode the client left undeclared. Not deprecated: elicitation survives into 2026-07-28, where it moved to the Multi Round-Trip Requests pattern from Class 5. There the server answers with resultType: "input_required", and the client retries the request carrying the answer |
tasks | list, cancel, requests | Client supports task-augmented versions of the requests it can receive, with requests covering sampling and elicitation on the client side; introduced experimentally in 2025-11-25. Removed from core in 2026-07-28: SEP-2663 also dropped client-hosted sampling and elicitation tasks, so only tool calls remain task-capable, and support is declared under extensions |
experimental | (free-form) | Declares support for non-standard experimental features |
A row marked Deprecated in 2026-07-28 still works. Under the deprecation policy the earliest revision that may remove it is the first one released on or after 2027-07-28, and new implementations SHOULD NOT adopt it.
What "roots", "sampling", and "elicitation" actually mean
All three are about the server asking the client for something, the reverse of the more familiar tool-call direction.
| Capability | What the server asks for | Who answers | What comes back |
|---|---|---|---|
roots (roots/list) | which directories and files it should work in | the client, from the user's configuration | a list of file:// roots, each with an optional name |
sampling (sampling/createMessage) | a model completion | the user approves, then the client's model generates | an assistant message, with a stopReason |
elicitation (elicitation/create) | a value from the person at the keyboard | the user, in the client's own UI | accept with content, or decline, or cancel |
roots: a filesystem MCP server does not know which directories the user wants it to work in. The client (Claude Desktop, for example) holds that list, and the server calls roots/list to ask for it. Each root uri MUST be a file:// URI, and it may name a single file as well as a directory:
{
"roots": [
{ "uri": "file:///home/user/Documents/work", "name": "Work" },
{ "uri": "file:///home/user/Projects", "name": "Projects" }
]
}
When the user adds or removes a root, the client emits a notifications/roots/list_changed so the server's view stays current. Roots are advisory: a server SHOULD respect them and validate every path it touches against the list. The specification puts the MUST on the client: expose only roots the user has permitted, validate root URIs to prevent path traversal, and implement access controls. A stdio server runs as a child process with the user's own filesystem permissions, so the protocol itself does not stop it reading elsewhere.
sampling: sometimes a server needs the model to generate something to finish its work: summarise a fetched page, classify an entry, decide what to do next. It can ask the client to make the LLM call on its behalf, then use the returned text to decide its next step. Four parties take part:
The client keeps the model handle and the API key: only the finished text travels back. The specification asks for a human on that path. There SHOULD always be someone able to deny a sampling request, and the client SHOULD let the user review and edit the prompt, and review the generated response before it goes back. Without sampling, a server must finish its work with its own logic, data and API calls.
elicitation: when a server needs more information from the user (not the model) mid-operation, it can ask the client to put the question to the user. A deployment tool might ask "Which environment should I deploy to?" before completing the action. The answer comes back as one of three actions:
A decline is a normal answer the server has to handle like any other result. Form mode sends a JSON Schema the client renders as a form. URL mode sends the user to an external page. It exists for values that must stay out of the client: servers MUST NOT ask for passwords, API keys, access tokens or payment credentials through a form.
Version Negotiation
MCP uses date-based versioning ("2025-11-25" style). Rules:
- The client declares the version it implements (per the spec, this SHOULD be the latest version the client supports)
- If the server supports the requested version, it MUST respond with the same version. Otherwise, it MUST respond with another protocol version it supports (typically its latest)
- If the client does not support the version in the server's response, it SHOULD disconnect
- After initialization, both parties use the negotiated version
- When using the HTTP transport, the client MUST also include the negotiated version in an
MCP-Protocol-Versionheader on every subsequent request to the server
Those five rules are one decision with two branches:
The disconnect is the client's decision, because the server has already answered with a version it can serve. Servers and clients therefore support several revisions at once, and how far back each side goes is an implementation choice.
Dynamic Capability Changes
Some capabilities support dynamic changes: the server's tool, resource or prompt lists can change during a session, while the declared capabilities themselves stay fixed after initialization. listChanged: true announces it.
When the tool list changes (e.g., a plugin was installed, a user was granted additional permissions), the server sends:
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
The notification does not carry any tool data. The client re-issues tools/list to get the updated list and re-exposes the new tools to the model.
This enables use cases like:
- Tools that become available after authentication
- Servers that load plugins on demand
- Permission-gated tool access
What Happens If Negotiation Fails?
If initialization cannot proceed at all, for example when the requested version is malformed like "1.0.0", the server returns an error. The spec illustrates this with -32602 (Invalid params), since the protocol version is a parameter of initialize:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Unsupported protocol version",
"data": {
"supported": ["2025-11-25"],
"requested": "1.0.0"
}
}
}
A client should show this error to the user, with the supported and requested values from data.
Key Takeaways
- Negotiation happens before any tool/resource/prompt operations
- Three messages:
initializerequest → response →initializednotification - Capabilities are declared by presence: the key means support, and sub-fields switch on optional behaviour
- Version negotiation lets the server answer with a version it supports, and the client disconnects if it cannot accept that
listChangedcapabilities enable dynamic updates during a live session
In the next class, we tackle the security model, the trust boundaries that make it safe to expose powerful tools to an AI.
Further Reading
- MCP specification 2025-11-25: Lifecycle: the rest of the connection lifecycle, including the operation phase, graceful shutdown on stdio and HTTP, and request timeouts.
- MCP specification 2026-07-28: Versioning and Compatibility: what replaced the handshake, including per-request
_meta, the-32022UnsupportedProtocolVersionError with its retry rule, and extension negotiation. - MCP specification 2025-11-25: Roots: the
roots/listexchange, thefile://URI rule, and which side is responsible for enforcing root boundaries. - MCP specification 2025-11-25: Sampling: the three sampling capability shapes side by side, the multi-turn tool loop with worked JSON, and the human-in-the-loop requirement.
- MCP specification 2025-11-25: Elicitation: form and URL modes, the restricted JSON Schema a form may use, and the three actions a user can take.
- MCP Tasks extension: where tasks live after 2026-07-28, with the reasons to hand back a durable handle instead of holding a connection open, the task lifecycle statuses, and which clients support it.
Sources
- MCP specification 2025-11-25: Lifecycle: the three-message handshake, the five version-negotiation rules, the
MCP-Protocol-Versionheader on HTTP, and the-32602"Unsupported protocol version" error example. - MCP schema, revision 2025-11-25: every sub-field in both capability tables, from
tools.listChangedtotasks.requests. - MCP schema, revision 2026-07-28:
extensionspresent on bothClientCapabilitiesandServerCapabilities,tasksabsent from both, androotsreduced to an empty object. - MCP specification 2026-07-28: Key Changes: the handshake removal and per-request
_meta, the removal oflogging/setLevelandnotifications/roots/list_changed(SEP-2575), tasks moving to an extension (SEP-2663), theextensionsfield, and the Roots, Sampling and Logging deprecation (SEP-2577). - MCP specification 2026-07-28: Deprecated Features: the migration path in each deprecated capability row, and the earliest removal of the first revision released on or after 2027-07-28.
- MCP specification 2025-11-25: Key Changes: SEP-1577 added tool calling to sampling, SEP-1036 added URL mode elicitation, and SEP-1686 added experimental tasks.
- MCP specification 2025-06-18: Key Changes: elicitation was introduced in this revision.
- MCP specification 2025-11-25: Sampling: the
contextandtoolssub-capabilities, the meaning of an emptysampling: {}, the soft-deprecatedincludeContextvalues, and the human-in-the-loop requirement. - MCP specification 2025-11-25: Elicitation: the
formandurlsub-capabilities, an emptyelicitation: {}meaning form mode only, and the ban on requesting credentials through form mode. - MCP specification 2025-11-25: Roots: the
roots/listrequest and response, thefile://URI requirement, and the split of enforcement duties between client and server. - MCP specification 2025-11-25: Tasks: the server and client
taskscapabilities, and the rule against declaringtasks.listwhen the receiver cannot identify who is asking. - MCP Tasks extension: tasks declared under
extensionsasio.modelcontextprotocol/tasks, polled withtasks/getand answered withtasks/update. - MCP Java SDK v2.0.0 release notes: the Java MCP SDK 2.0.0 tracks the 2025-11-25 specification.