Roots
MCP 2026-07-28 removed roots/list as a capability-driven server→client
request and re-homed it onto MRTR as an
input-request kind
— already deprecated. Client::add_root / add_roots carry
#[deprecated] and need #[allow(deprecated)].
New tools should take the paths they need as explicit arguments instead.
The Model Context Protocol (MCP) provides a standardized way for clients to expose filesystem “roots” to servers. Roots define the boundaries of where servers can operate within the filesystem, allowing them to understand which directories and files they have access to.
Roots are Configured Data
Roots are not a handler. The client answers the server's roots/list input
request from the list it was built with, and a non-empty list is what makes
it declare clientCapabilities.roots on every request — a server may only
ask for a kind the client declared.
Because there is no notifications/roots/list_changed any more, the list a
server sees is whatever the client holds when the request arrives. There is
no push to subscribe to and no roots.listChanged capability to enable.
Adding Roots
use neva::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Error> {
let mut client = Client::new()
.with_options(|opt| opt
.with_http(|http| http.bind("127.0.0.1:3001").with_endpoint("/mcp")));
// Deprecated on arrival, like the whole roots kind.
#[allow(deprecated)]
client
.add_root("file:///home/user/projects/my_project", "My Project")
.add_root("file:///home/user/projects/my_another_project", "My Another Project");
client.connect().await?;
// The MRTR round-trip happens inside this one call.
let result = client.call_tool("scan_workspace", ()).await?;
tracing::info!("Result: {:?}", result.content);
client.disconnect().await
}
Accessing Roots on the Server
Inject Context
into your tool handler and ask for the list with a stable replay key:
#[tool]
async fn scan_workspace(mut ctx: Context) -> Result<String, Error> {
// Round 1 unwinds the handler with `input_required` and a `roots/list`
// envelope; round 2 replays the answer from `requestState`.
#[allow(deprecated)]
let roots = ctx.list_roots("dirs").await?;
// Each root contains a URI and a human-readable name
for root in &roots.roots {
tracing::info!(uri = %root.uri, name = %root.name);
}
Ok(format!("{} root(s)", roots.roots.len()))
}
Everything above the list_roots point re-runs on the second round, so
guard side effects with ctx.memo / ctx.once / ctx.on_commit — the same
primitives that cover elicitation.
legacy-specRoots are a push request: ctx.list_roots() takes no key, roots can be added
after connect(), and the roots.listChanged capability
(with_roots(|r| r.with_list_changed())) notifies the server of updates. See
Legacy spec.
Learn By Example
Here you may find the full example.