What is ClientCapabilities?
ClientCapabilities is the client-side declaration of optional MCP features, transmitted once inside the initialize request before any other protocol traffic. It functions as a capability contract: the server reads this object and limits its behavior strictly to what the client has advertised. If a client omits a capability field entirely, the server must treat the feature as unsupported and must never invoke it — there is no fallback negotiation round-trip after initialization completes. In the message flow, ClientCapabilities travels as the capabilities property of InitializeParams. The server inspects it, then responds with its own ServerCapabilities object, completing the symmetric handshake. This means the capabilities you declare here directly gate which server-side features become active for the session lifetime: omitting roots means the server will not call roots/list, omitting sampling means the server will not issue createMessage requests back to the client, and omitting elicitation means the server will skip any form or URL-based elicitation flows. The type has grown incrementally across spec versions. The roots field acquired a listChanged sub-flag to signal support for roots/listChanged notifications separately from basic roots access. The elicitation field is newer still, with form and url sub-flags tracking two distinct elicitation delivery modes. Experimental is an open-ended escape hatch for pre-spec extensions; treat any key under it as unstable across server implementations.
When to use
Always — every initialize request carries a ClientCapabilities object.
When NOT to use
Never lie about capabilities — declare only what you actually support.
Notes
Omission equals explicit denial
A missing capability field is not ambiguous — servers that follow the spec treat absence the same as { }. Never rely on server-side defaulting to fill in omitted flags. If your client builds ClientCapabilities dynamically, audit every conditional branch to ensure features the user has enabled are actually included in the outgoing object.
roots.listChanged is a separate contract
Declaring roots without listChanged means the server knows you can respond to roots/list but will not send you roots/listChanged notifications. If your client updates its workspace roots at runtime, you must set listChanged: true or the server will cache the initial list forever. Forgetting this flag is a common source of stale-context bugs in long-running sessions.
sampling and elicitation carry security weight
Advertising sampling grants the server permission to call createMessage back on your client, which executes an LLM completion on behalf of server logic. Advertising elicitation permits the server to surface forms or URLs to your end user. Both capabilities should be considered consent grants, not just protocol hints — only declare them if your client has the UX and authorization model to handle incoming requests safely.
experimental keys are opaque to the spec
The experimental field has type Record<string, object>, so both key names and value shapes are entirely implementation-defined. Two servers using the same key may interpret its value differently. Validate experimental payloads defensively on both sides and version-gate any behavior you build on top of them, since they can disappear or change shape without a spec revision.
Compare with ServerCapabilities symmetry
ServerCapabilities mirrors this type but covers server-side features like tools, resources, prompts, and logging. The two types are intentionally asymmetric: clients advertise consumer capabilities (what they can receive), servers advertise producer capabilities (what they can serve). Understanding the distinction avoids the mistake of looking for tools or resources inside ClientCapabilities when inspecting a captured initialize exchange.
Fields
| Field | Type | Required | Purpose |
|---|---|---|---|
| experimental | object? | no | Map of experimental capability names → settings. |
| roots | { listChanged?: boolean }? | no | Whether the client provides filesystem roots. |
| sampling | object? | no | Whether the client can fulfil sampling/createMessage requests. |
| elicitation | { form?, url? }? | no | Whether the client can render elicitation forms or URL flows. |
| tasks | object? | no | Whether the client supports task-augmented requests. |
Examples
Full capabilities declaration
{
"roots": { "listChanged": true },
"sampling": {},
"elicitation": { "form": {} }
}
Common mistakes
❌ Declaring sampling:{} without implementing the handler
✅ Only declare features that are wired up — servers will call them.