Roots Primitive: Scoping What Files the Server Can Access
~10 min read
Roots lets the client define exactly what files a server can access, making interactions secured, sandboxed, and scoped — a boundary set by the client, not requested by the server.
Roots is the second client-side primitive, and its role is specifically about access control rather than generation or input-gathering. It allows the CLIENT to define what files the server can access, making interactions secured, sandboxed, and scoped — the direction of control here is important: unlike Sampling and Elicitation, where the SERVER initiates a request TO the client, Roots is the client proactively defining a boundary that constrains what the server is even allowed to touch.
This course's example: a server for booking travel might be given access to a specific directory, from which it can read a user's calendar — but critically, ONLY that specific directory, not arbitrary filesystem access. This is the client saying, in effect, 'you (the server) can read files within this specific scope, and nothing outside it,' rather than granting the server unrestricted filesystem access and trusting it to behave.
This connects directly to the same access-control logic covered for the Resources primitive earlier in this topic — Resources are what a server exposes as read-only data, while Roots is a complementary but distinct mechanism specifically for scoping FILESYSTEM access the client grants to a server. Where Resources define what a server CAN offer as readable content, Roots defines the boundary of what parts of the actual filesystem the server is permitted to reach into in the first place — a narrower, more fundamental safety boundary sitting underneath whatever the server chooses to expose as Resources.
This kind of explicit, client-defined scoping is exactly what makes MCP's tool/resource ecosystem meaningfully safer than simply handing a server broad filesystem permissions and hoping its implementation is careful — the client, not the server, holds and enforces the actual access boundary.
💻 Code example
# Client-side: defining Roots — the specific directories a server
# is permitted to access, set by the CLIENT, not requested by the server.
from mcp import ClientSession
from mcp.types import Root
async def configure_scoped_access(session: ClientSession) -> None:
# The client explicitly grants access to ONE specific directory —
# not the whole filesystem, even if the server would prefer more
roots = [
Root(uri="file:///home/user/travel-calendar/", name="Travel Calendar"),
]
await session.send_roots_list_changed(roots)
# The server can now only request files WITHIN this scoped root —
# e.g. file:///home/user/travel-calendar/upcoming.ics is allowed,
# file:///home/user/private-notes.txt is NOT, regardless of what
# the server's own code tries to request
💬 Deep Dive with AI
Key points
- •Roots lets the CLIENT define exactly what files a server can access — a client-set boundary, not a server request
- •This makes interactions secured, sandboxed, and scoped, rather than granting a server broad filesystem access
- •The book's example: a travel-booking server given access to one specific calendar directory, not the whole filesystem
- •Complements Resources: Resources define what a server offers as readable content; Roots defines what filesystem paths it's even allowed to reach
- •The client holds and enforces the actual access boundary — safer than trusting the server's own implementation to self-restrict