Roots
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. Servers can request the list of roots from supporting clients and receive notifications when that list changes.
Configuring Roots
You can specify one or more roots that will be exposed to the server. Roots may be added before or after connecting the client.
- Roots added before
connect()are sent during the initial handshake. - Roots added after
connect()require the roots.listChanged capability.
Adding Roots
use neva::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Error> {
let mut client = Client::new()
.with_options(|opt| opt
.with_stdio(
"cargo",
["run", "--manifest-path", "./neva-mcp-server/Cargo.toml"]));
// Add roots that should be available during the initial handshake
client.add_root("file:///home/user/projects/my_project", "My Project");
client.connect().await?;
// Add additional roots dynamically after connection
client.add_roots([
("file:///home/user/projects/another_project", "My Another Project"),
("file:///home/user/projects/one_more_project", "One More Project"),
]);
// Call a tool, read a resource ....
client.disconnect().await
}
Notifying the Server When the Root List Changes
If roots are added or removed after the client has connected, enable the
roots.listChanged capability so the server can be notified of updates.
let mut client = Client::new()
.with_options(|opt| opt
.with_roots(|roots| roots.with_list_changed())
.with_stdio(
"cargo",
["run", "--manifest-path", "./neva-mcp-server/Cargo.toml"]));
Enable roots.listChanged only if:
- Roots are modified after
connect() - The server relies on receiving root updates dynamically
If all roots are known upfront, this capability is not required.
Accessing Roots on the Server
On the server side, roots provided by the client are available through the
request Context. Roots may be received during the initial handshake or
updated dynamically via the roots.listChanged capability.
To access the current list of roots, inject Context
into your tool handler:
#[tool]
async fn roots_request(mut ctx: Context) -> Result<(), Error> {
let list = ctx.list_roots().await?;
// Each root contains a URI and a human-readable name
for root in list.roots {
tracing::info!(uri = %root.uri, name = %root.name);
}
Ok(())
}
Learn By Example
Here you may find the full example.