Who this is for
This is for developers building MCP servers, or file-handling tools, who run into roots in older examples. After reading, you should be able to say what roots are and are not, and choose a replacement for a new project.
What roots are
Roots are a way for an MCP client to tell a server which filesystem locations matter for the current request. Each root has a file:// URI and an optional display name [1]. A client returns them in this shape:
{
"roots": [
{
"uri": "file:///home/user/projects/weather-service",
"name": "Weather Service"
}
]
}Roots are not access control
This is the point most often misunderstood. Roots are hints. They are not a store of conversation history, not a protocol session, and not a permission system. The protocol does not force a server to stay inside the listed roots [1]. Clients should expose only locations the user has approved. Servers should treat roots as a guide to which files are probably relevant, not as proof that they may read them [1].
Deprecated in MCP 2026-07-28
Roots are deprecated in MCP revision 2026-07-28. They stay in that revision for compatibility, and they are eligible for removal in the first specification revision released on or after July 28, 2027 [1]. New implementations should not build on them.
How the 2026-07-28 flow works
A client that supports roots declares the capability in every request, under _meta [1]:
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"roots": {}
}
}
}When a server needs the roots while handling a request, it can return an InputRequiredResult that asks for roots/list [1]:
{
"resultType": "input_required",
"inputRequests": {
"workspaceRoots": {
"method": "roots/list"
}
},
"requestState": "opaque-state-from-server"
}The client then collects the approved roots and sends the request again, sending back the matching inputResponses and the requestState unchanged [1]. The round trip is what keeps the protocol stateless. There is no initialize handshake and no session at the protocol level [1].
What changed from 2025-11-25
Under MCP 2025-11-25, clients advertised roots during initialization. A server could send a direct roots/list request, and a client could send notifications/roots/list_changed when its roots changed [1]. This is legacy behavior. Do not mix those initialization and notification examples into a 2026-07-28 implementation [1].
What to use instead
There are three replacements [1].
- Tool parameters. Put the directory or file you need into the tool's input schema as an explicit, required argument:
{
"name": "analyze_project",
"inputSchema": {
"type": "object",
"properties": {
"projectDirectory": {
"type": "string",
"description": "Approved project directory to analyze"
}
},
"required": ["projectDirectory"]
}
}- Resource URIs. If the server can expose the relevant files under stable URIs, use MCP Resources. Discovery and retrieval are then both explicit.
- Server configuration. For a fixed deployment, set the allowed directories when the server starts. This is often clearer than discovering them during a tool call.
Security checks to keep
Whichever replacement you pick, these requirements apply to every option [1]:
- Get user consent before exposing filesystem locations.
- Canonicalize and validate paths to block traversal.
- Enforce authorization and sandboxing on their own, without relying on root values.
- Recheck permissions when a file is accessed, not only when it is listed.
- Do not put sensitive paths in logs or error messages.
How to tell which version an old example targets
Under 2025-11-25, clients declare roots during initialization, a server can send roots/list directly, and clients can send notifications/roots/list_changed when roots change [1]. Under 2026-07-28, the capability is declared in _meta on every request, and the server asks for roots with an InputRequiredResult [1]. Code that mixes an initialization handshake with InputRequiredResult combines two versions, which is not recommended [1].
Why roots are not a permission system
Clients should expose only the locations the user has approved [1]. Servers should treat roots as a guide to relevant files, and still canonicalize and validate paths to block traversal [1]. Permission checks belong at access time, because a file allowed when it was listed may not be allowed when it is opened [1]. Keep sensitive paths out of logs and error messages [1].
Choosing among the replacements
Tool parameters are the most direct option: the directory or file arrives as a required argument and is visible in the schema [1]. Resource URIs suit servers that can expose files under stable URIs, which keeps discovery and retrieval explicit [1]. Server configuration suits fixed deployments, where the allowed directories are set at startup rather than discovered during a tool call [1].
Common questions
Can I rely on roots to limit what a server can read? No. Roots are not an access-control mechanism, and the protocol does not enforce them [1]. Put the limit in the server's own code, configuration, and permission checks.
Why check permissions again at read time? A file that was listed earlier may no longer be allowed when it is actually opened. Checks have to happen at access time, not only at listing time [1].
Adapted from the MCP Roots section of Microsoft's open-source course *MCP for Beginners*[1], released under the MIT License.
Sources
- 1MCP Roots (Legacy Feature)Microsoft mcp-for-beginners (MIT)
Cite this page: MCP Roots: Telling Servers Which Files Are Relevant. TellWorld, https://www.tellworld.net/practice/practice-mcp-root-contexts. Updated 2026-11-14