Client Roots

Purpose and Scope

This page explains how an MCP client can provide roots to a server. A root is a file:// URI supplied by the client as an advisory boundary for file-oriented server behavior. The important word is advisory: the SDK does not turn roots into an access-control system, and the server still accesses the filesystem with whatever permissions its process already has. Use roots only when interoperating with servers that still ask for them, and prefer explicit paths in tool arguments, resource URIs, or host configuration for new integrations.

Sources: docs/clients/roots.md

Roots are deprecated as of protocol revision 2026-07-28 under SEP-2577. The repository documentation makes the migration guidance explicit before describing the API: pass the path a call should act on as a tool argument, expose server-owned locations as resources, or configure fixed directories directly on the server. The roots API remains relevant for 2025-era connections and compatibility windows, so client implementations may still need to declare the capability, answer roots/list, and notify the server when the list changes.

Sources: docs/clients/roots.md

Relevant Source Files

  • docs/clients/roots.md — primary how-to for declaring roots capability, registering roots/list, root URI shape, deprecation guidance, 2026-07-28 behavior notes, and sendRootsListChanged().
  • docs/clients/connect.md — explains the connected-client lifecycle: construct a Client, choose a transport, call connect(), inspect server information, and close cleanly.
  • docs/clients/calling.md — provides the preferred migration path context for passing per-call paths through tool arguments and reading server-owned locations through resources.
  • docs/clients/caching.md — clarifies that ordinary client calls may be cached by response-cache rules, while roots list changes are explicit notifications rather than cache invalidation hints.
  • docs/clients/middleware.md — places roots in the HTTP client transport pipeline where middleware can observe requests, including initialization and other SDK-generated traffic.
  • docs/clients/machine-auth.md — explains that authorization context belongs to the transport and server permissions; roots do not grant access and should not be treated as credentials.

Lifecycle in a Connected Client

A client first constructs a Client, configures its capabilities, registers any handlers required by those capabilities, and then connects over a transport. The connection step runs the initialize handshake and leaves the client with negotiated protocol version, server capabilities, server version, and instructions. Roots fit into that lifecycle before connect() in practice because the server can only ask for roots if the client advertised the roots capability during initialization. Registering a roots handler without declaring the capability is documented as an error.

Sources: docs/clients/roots.md, docs/clients/connect.md

import { Client } from '@modelcontextprotocol/client';
 
const client = new Client(
  { name: 'workspace-client', version: '1.0.0' },
  { capabilities: { roots: { listChanged: true } } }
);

The roots capability tells the server that it can ask for the list. The nested listChanged: true flag is separate: it says this client may later send a notifications/roots/list_changed notification. Treat those as two related contracts. A client that can answer the initial list but never updates it can omit change notification support; a client that expects the list to move over time should opt in so the server knows a refresh signal may arrive.

Sources: docs/clients/roots.md

Answer roots/list

The request handler for roots is registered with setRequestHandler('roots/list', ...) and returns an object containing a roots array. Each root needs a uri beginning with file://; name is optional and is useful for display or host-side labeling. The server receives exactly the list returned by the handler, so keep the handler tied to the current workspace state rather than hard-coding stale paths in long-running hosts.

Sources: docs/clients/roots.md

const roots = [
  { uri: 'file:///home/user/projects/my-app', name: 'My App' },
  { uri: 'file:///home/user/data', name: 'Data' }
];
 
client.setRequestHandler('roots/list', async () => {
  return { roots };
});

Because roots are advisory, returning a directory does not authorize a server, sandbox it, or revoke ordinary operating-system access. Authorization and credential flow are handled separately by the transport and auth provider. For example, a machine client can use client credentials, a bearer token provider, private-key JWT, or cross-app access through transport authentication; none of those mechanisms are replaced by roots. When designing a secure client, use roots as compatibility metadata and enforce real access boundaries outside this list.

Sources: docs/clients/roots.md, docs/clients/machine-auth.md

Notify When the List Changes

When a client declared listChanged: true, it can call sendRootsListChanged() after mutating the list. The notification method is notifications/roots/list_changed and carries no payload. The server is expected to request roots/list again if it cares about the new state. This design avoids sending path data in the notification itself and keeps the current list behind the same handler used for the initial request.

Sources: docs/clients/roots.md

roots.push({
  uri: 'file:///home/user/projects/another-app',
  name: 'Another app'
});
 
await client.sendRootsListChanged();

Do not confuse roots changes with the response cache described for ordinary client operations such as listTools(), listPrompts(), listResources(), listResourceTemplates(), and readResource(). Those calls can be served locally when a server result includes freshness hints and the client cache mode allows it. A roots list update is instead an explicit client-to-server notification; it tells an interested server to ask again, and it is not a cache hint for resource reads or tool listings.

Sources: docs/clients/roots.md, docs/clients/caching.md

Protocol Revision and Migration Considerations

On 2025-era connections, roots are still a normal server-to-client request. The docs also note that on a 2026-07-28 connection there is no server-to-client request channel in the same shape; the same handler can fulfil a roots/list request embedded in an input_required result. That matters when you test both protocol eras: keep the application-level handler small, deterministic, and independent of transport assumptions so the SDK can route the request according to the negotiated version.

Sources: docs/clients/roots.md, docs/clients/connect.md

For new server APIs, avoid designing around roots. If the server needs a path for one operation, make it a tool argument and validate it as part of that tool call. If the server owns a location that clients should browse or read, expose it as a resource and let clients use listResources(), listResourceTemplates(), and readResource(). This aligns root migration with the regular client calling workflow and makes permissions and validation easier to reason about.

Sources: docs/clients/roots.md, docs/clients/calling.md

System-to-Code Mapping

ConcernPublic API or behaviorSource grounding
Declare roots supportnew Client(..., { capabilities: { roots: { listChanged: true } } })docs/clients/roots.md
Serve the listclient.setRequestHandler('roots/list', async () => ({ roots }))docs/clients/roots.md
Root shape{ uri: 'file://...', name?: string }docs/clients/roots.md
Notify changesawait client.sendRootsListChanged()docs/clients/roots.md
Connect lifecycleClient, transport, connect(), server introspection, closedocs/clients/connect.md
Migration pathTool arguments and resource URIs instead of rootsdocs/clients/roots.md, docs/clients/calling.md
Transport/auth boundaryMiddleware and auth providers wrap HTTP traffic; roots are not credentialsdocs/clients/middleware.md, docs/clients/machine-auth.md

Practical Checklist

Start by deciding whether you need roots at all. If you control both sides of a new integration, prefer tool arguments, resource URIs, or configuration. If you need compatibility with a 2025-era server, declare roots in the Client constructor before registering the handler, return only file:// URIs, and treat the list as descriptive metadata. If the workspace can change after connection, include listChanged: true, update your local list, and call sendRootsListChanged() so the server can request a fresh copy.

Sources: docs/clients/roots.md, docs/clients/calling.md, docs/clients/connect.md

For next steps, read the client connection guide to choose HTTP, stdio, SSE fallback, or in-memory testing transport. Then read the calling guide to migrate root-shaped workflows into tools and resources. If the client runs over HTTP in a production host, review middleware and machine authentication so request logging, headers, token refresh, and issuer checks are handled in the transport layer rather than being mixed into roots handling.