MCP Client–Server Capability Negotiation

Capability negotiation sets the per-session protocol contract: clients declare the client-side features they can perform, and servers declare the server-side features they expose. Each side may use only advertised capabilities. Nested flags such as `listChanged` record narrower commitments, so list access does not imply change notifications.
What capability negotiation establishes
Capability negotiation is the exchange, during initialization, that establishes which optional Model Context Protocol features each side supports for the session. Capabilities are declarations of supported protocol features, not requests to enable every possible feature.
The practical rule is strict: a client must not call a capability the server did not advertise, and a server must not request client behavior the client did not declare.
Capabilities are exchanged during initialization
The MCP lifecycle specification dated 2025-11-25 makes initialization the first client–server interaction. The client sends an initialize request containing its supported protocol version, its capabilities, and implementation information. The server returns an InitializeResult containing the protocol version it selected, its own capabilities, and its implementation information. The client then sends notifications/initialized before normal operation begins.
Protocol version and capability negotiation happen in the same phase but answer different questions. The version selects the protocol revision whose messages and schemas govern the connection. The capability objects declare which optional parts of that revision are available. A connection can therefore agree on a version while still omitting tools, resources, sampling, or another optional feature. When the version itself is in dispute, treat that separately as an MCP protocol version negotiation problem.
Before initialization completes, normal feature traffic is restricted. The client should not send requests other than pings before the server responds to initialize. The server should not send requests other than pings and logging before it receives notifications/initialized. Capability declarations do not waive that lifecycle boundary.
After initialization, both parties must respect the selected protocol version and use only capabilities successfully negotiated for that session. An implementation therefore needs to retain both capability objects with the connection state; checking only whether its own code contains a handler is insufficient.
A declaration is not an activation request
A capability object says, in effect, “I implement this part of the protocol.” It does not instruct the peer to turn the feature on, invoke it immediately, expose every possible item, or change product configuration.
For example, a server declaration of tools: {} says the server offers tools that the client may list and call. It does not call a tool, identify which tools exist, or require the client to present every tool to a model. The actual inventory comes from tools/list; individual execution happens through tools/call.
The same distinction applies to server capabilities such as prompts, resources, logging, and completions. Advertising prompts establishes that prompt templates are available through the protocol. It does not select a prompt or supply its arguments. The definitions and required values still come from prompt discovery and the individual prompt schema; MCP prompt argument validation remains a separate concern.
Client capabilities work the same way in the opposite direction. Declaring sampling says the client can receive supported sampling requests. It does not ask the server to sample, choose a model, or begin a generation. Declaring roots says the client can answer roots/list; it does not send the root list as part of initialization.
This distinction matters in product code. Capability parsing should populate a feature contract, while requests, notifications, subscriptions, and item discovery perform the actual work. Treating initialization as an activation command can create side effects before either party has sent the operation that authorizes them.
Client and server declarations have different directions
Capability negotiation is not one shared list in which both parties advertise identical features. Each side declares the optional behavior it can provide to the other.
The client capability object covers behavior the server may rely on from the client. In the 2025-11-25 specification, the named client capability families include:
roots, meaning the client can provide filesystem roots throughroots/list;sampling, meaning the client supports sampling requests, with narrower declarations for context inclusion and tool use;elicitation, meaning the client supports the declared form or URL elicitation modes;tasks, meaning the client supports the declared task operations or task-augmented request types;experimental, for named non-standard capabilities.
The server capability object covers behavior the client may rely on from the server. Its named families include:
prompts, meaning the server offers prompt templates;resources, meaning the server offers readable resources;tools, meaning the server offers callable tools;logging, meaning the server can send structured log messages;completions, meaning the server provides argument completion suggestions;tasks, meaning the server supports the declared task operations or task-augmented requests;experimental, for named non-standard capabilities.
The direction determines who may initiate the later protocol behavior. A client that receives tools: {} from a server may use the tool methods. A server that receives sampling: {} from a client may issue a supported sampling request. The server advertising tools says nothing about whether the client supports sampling, and the client advertising sampling says nothing about whether the server offers tools.
This is why the obligations are reciprocal but not symmetrical. A client must not call a capability the server did not advertise. A server must not request client behavior the client did not declare. Each sender checks the receiver’s relevant declaration before sending an optional operation.
The schema defines the exact declaration shape
The MCP 2025-11-25 schema reference defines InitializeRequestParams.capabilities as ClientCapabilities and InitializeResult.capabilities as ServerCapabilities. These are structured objects rather than arrays of feature names.
An empty child object can be a complete positive declaration. For example, "logging": {} is present when the server supports sending log messages, and "completions": {} is present when it supports argument-completion suggestions. The empty object is not missing data: the parent key itself carries the declaration.
Other capability families contain narrower fields. The client’s sampling object can contain context and tools. If sampling.tools is not declared, a server must not provide the sampling tools or toolChoice fields; the client must return an error if it receives them. If sampling.context is absent, the server should use includeContext: "none" or omit that parameter.
Tasks use deeper nesting because the declaration identifies both a task operation and, where applicable, the request type that may be augmented. Client task capabilities can declare task support for sampling/createMessage or elicitation/create. Server task capabilities can declare task support for tools/call. A broad tasks object does not erase those narrower boundaries.
The schema is deliberately open to additional capabilities. Both ClientCapabilities and ServerCapabilities may contain declarations beyond the known fields. experimental provides a named area for non-standard features, but an extension still needs a shared definition between implementations. An unknown key is not permission to guess at methods or payloads.
Nested flags represent separate commitments
Change-notification flags are nested capabilities because listing something and receiving changes to that list are separate commitments.
Consider server resources. The presence of resources means the server offers resources to read. Within that object, listChanged states whether the server supports notifications when the resource list changes, while subscribe states whether clients may subscribe to updates for individual resources. Those are three distinct behaviors:
- discovering the current resource list;
- learning that the membership or description of that list changed;
- receiving updates for a particular resource after subscribing to it.
A declaration of "resources": {} commits the server to the resource feature, but it does not commit the server to notifications/resources/list_changed or resource subscriptions. A client that needs a fresh list but sees no resources.listChanged support must not assume that a notification will arrive. Likewise, resources.listChanged does not imply resources.subscribe; list membership changes and updates to one resource are different event streams.
The same shape appears in server prompt and tool capabilities. prompts establishes that prompt templates can be listed and retrieved; prompts.listChanged separately declares support for prompt-list change notifications. tools establishes that tools can be listed and called; tools.listChanged separately declares support for tool-list change notifications.
The direction reverses for roots. roots is a client capability because the client supplies the roots. The parent object says the client supports roots/list; roots.listChanged says the client supports sending notifications when its root list changes. A server must not expect notifications/roots/list_changed merely because the client can answer roots/list.
The nesting prevents a common overstatement: “supports resources” is not equivalent to “keeps the client’s resource inventory live.” The first promises an on-demand operation. The second promises asynchronous change signaling. Supporting the second requires the sender to detect changes, emit notifications, and keep their ordering meaningful enough for the receiver to refresh its view. The protocol records that additional commitment explicitly instead of deriving it from list support.
Missing capability data is a boundary
Optional capability fields are not placeholders for optimistic probing. If the peer did not advertise a feature, the sender has no negotiated basis for using it.
For parent capability objects, absence means the feature was not declared. A client must not infer tools merely because it knows the server’s implementation name, has used another release of that server, or has configuration for a similarly named service. The current connection’s InitializeResult.capabilities is the applicable declaration.
For nested boolean flags such as listChanged and subscribe, support is established by the corresponding positive declaration. The parent capability alone does not supply the nested commitment. Code should therefore test the complete path—such as serverCapabilities.resources?.listChanged === true—before depending on the notification.
Empty objects and absent objects must also remain distinct when capabilities are deserialized, normalized, cached, or passed between application layers. Replacing every empty object with null, or applying defaults that create missing parents, changes the meaning of the negotiation. Preserve what the peer actually sent.
A known handler does not override an absent declaration. If a server implementation contains a tool router but omits tools from its initialization result, the client still must not call the tool methods for that session. Capability negotiation is the wire contract, not a remote inspection of dormant code.
Capabilities do not replace item discovery or authorization
The MCP base protocol overview dated 2025-11-25 defines requests, responses, and one-way notifications as separate JSON-RPC message forms. Capability negotiation determines which optional forms of protocol behavior may be used; later messages carry the operations and results.
A server capability also describes a feature family, not its inventory. tools: {} does not name available tools. prompts: {} does not provide prompt definitions. resources: {} does not provide resource URIs. Clients obtain those records through their list methods and then apply the returned item schemas.
Nor does a capability declaration replace authorization. The base protocol defines authorization separately for HTTP transports. A server may advertise a feature while a particular operation still depends on credentials, permissions, valid arguments, or current server state. Capability presence answers whether the protocol feature is supported, not whether every request using it must succeed.
The inverse is also important: possessing valid credentials does not create an unadvertised protocol capability. Authentication can establish who the caller is, but the initialization result still establishes which optional MCP server features the client may use.
Capability checks belong at message boundaries
Capability enforcement is most reliable where an implementation sends optional requests or notifications. A client sending tools/list, tools/call, resources/list, or prompts/get should consult the server capabilities retained for that connection. A server sending sampling, elicitation, or roots requests should consult the client capabilities from the corresponding initialize request.
Nested checks belong at the narrower boundary. Before a server sends a tool-list change notification, it should confirm that it declared tools.listChanged. Before a client sends a roots-list change notification, it should confirm that it declared roots.listChanged. Before a client requests a resource subscription, it should confirm that the server declared resources.subscribe.
UI gating is useful but not sufficient. Hiding a tool panel when tools is absent improves the interface, yet background jobs, model-driven dispatch, retries, and direct SDK calls still need the same capability check. The enforcement point should cover every outbound path.
Logs of initialization are correspondingly valuable. Retaining the selected protocol version, client capability object, server capability object, and implementation versions makes later traffic auditable. When a tool operation is rejected or never offered, debugging the MCP tool call should begin by comparing the recorded server declaration with the method the client attempted to send.
Capability data should be associated with one connection rather than treated as a permanent property of a product name. Different server releases, configurations, or endpoints may advertise different feature sets, and different clients may declare different client-side behavior. The initialization exchange is the evidence for the active session.
What to inspect next
When checking an integration, read the selected version’s lifecycle rules first, then its ClientCapabilities and ServerCapabilities schema definitions. Follow the nested path for the exact operation or notification rather than stopping at the parent feature name.
After the capability contract is clear, inspect the feature-specific request and result schemas, authorization state, and discovered item definitions. For deployment scope rather than wire behavior, choose MCP servers per project based on the operations the project actually needs; capability negotiation then records which of those operations each live connection supports.
Sources
- MCP lifecycle specification dated 2025-11-25modelcontextprotocol.io
- MCP 2025-11-25 schema referencemodelcontextprotocol.io
- MCP base protocol overview dated 2025-11-25modelcontextprotocol.io
See also
Separate Cloudinary MCP credentials by product environment and block writes when the runtime identifier does not match the intended target.
Build an MCP form request, validate its restricted schema, handle every outcome, and keep passwords, tokens, and other secrets out.
How MCP URL elicitation keeps OAuth credentials, payment details, and other secrets outside the client while preserving consent and completion tracking.
How MCP clients and servers advertise extensions, gate behavior on mutual support, and design versioned third-party contracts with a core fallback.