When an MCP client connects to a server that touches a filesystem — a code editor talking to a local dev-tools server, say — how does the server know which folder it’s actually supposed to work in? The answer the spec defines is roots: a list of URIs the client hands the server, marking out the boundaries it’s relevant to operate within. It’s one of the quieter parts of MCP, and, like the tool annotations we covered separately, it’s an advisory signal rather than an enforced wall.
What a root actually is
A root is a URI — almost always a file:// path in practice, though the spec doesn’t restrict it to that scheme — plus an optional human-readable name. A client that supports the roots capability exposes a roots/list request the server can call to get the current set, and can send a notifications/roots/list_changed notification if that set changes mid-session, say because the user opened a different project folder. That’s the entire mechanism: a list of “here’s what’s relevant right now,” kept up to date as the user’s context changes.
Who decides, who reads
Roots follow the same control model we laid out in tools, resources, and prompts: each MCP primitive has one party that’s in charge of it. Roots are client-controlled — the client application decides what counts as a relevant root (typically the folder or workspace the user has open) and declares it upfront as a capability. The server is on the receiving end: it can ask what the current roots are, and it’s expected to treat that list as the scope of what it should be looking at. The model itself isn’t in this loop at all — roots are negotiated between client and server before the model ever sees a tool call.
What roots are actually for
The clearest use case is a local, filesystem-facing server: an editor extension or an IDE integration that exposes tools like “read file” or “search this codebase.” Without roots, that server has no protocol-level way to know which directory the current session is even about — it would have to guess, or rely on whatever working directory it happened to be launched from. Roots give it an explicit, client-supplied answer, and let that answer update if the user switches projects without restarting the connection.
It’s a narrower idea than it might sound. Roots don’t grant access, describe permissions, or authenticate anything — they’re a statement of relevance, not a credential. A server still needs its own way to actually read files (usually just the OS-level permissions of the process it’s running as), and the client still decides what tools that server is allowed to call at all.
The catch: it’s a hint, not a fence
This is the part worth sitting with. The MCP spec describes roots as guidance the server should respect — not a boundary the protocol enforces. Nothing stops a server from calling roots/list, getting back a single project folder, and then reading a file well outside it anyway. The same caveat we wrote about for tool annotations applies here almost word for word: a well-behaved server treats roots as real constraints, and a careless or malicious one can ignore them completely, with nothing in the handshake to stop it.
In practice that means roots protect you from an honest server operating outside the scope you intended — a helpful guardrail against sloppy defaults — but they do nothing against a server that was never going to respect the boundary in the first place. A local MCP server already runs as a process on your machine with your own user permissions, which is the deeper risk we covered in MCP server supply chain risk: roots can’t retroactively fix what a process is already capable of doing once it’s running.
Why you rarely hear about them
Roots are an optional capability on both sides. A client has to declare it supports roots, and a server has to actually call roots/list and act on the result — neither is required to implement it at all. Plenty of MCP servers, especially remote, OAuth-based ones that don’t touch a local filesystem in the first place, have no reason to use roots and simply skip the capability. It matters most for the local, stdio-style servers we described in remote vs. local MCP servers, which is a narrower slice of the ecosystem than remote servers with their own hosted APIs.
What this means if you’re building a server
If your server reads or writes files, implement roots and actually honor them — check the current root list before an operation, not just once at startup, since it can change. But don’t stop there: validate paths server-side regardless, the same way you’d never trust client-supplied input for anything else. Roots are a courtesy signal from a cooperative client, not an access-control layer, and treating them as one is the mistake. If you’re generating a server from an existing API with gate’s MCP server builder, this mostly won’t apply unless your API itself exposes filesystem-shaped operations — but it’s worth checking for if it does.
The bottom line
Roots solve a real coordination problem — telling a filesystem-facing server which directory is actually in scope, and keeping that current as the user’s context changes. They’re useful exactly because most servers that implement them are trying to behave. They don’t solve the harder problem of a server that isn’t trying to behave, and nothing about the mechanism pretends otherwise once you read the spec closely. Know the difference before you treat “this server supports roots” as a safety property instead of a courtesy.