# How the MCP Roots Protocol Enables Dynamic Directory Access

> Discover how the MCP roots protocol allows servers to dynamically access authorized directories at runtime. Learn more about this innovative modelcontextprotocol/servers feature.

- Repository: [Model Context Protocol/servers](https://github.com/modelcontextprotocol/servers)
- Tags: deep-dive
- Published: 2026-03-01

---

**The MCP roots protocol enables servers to discover, validate, and update authorized filesystem directories at runtime without requiring restarts or pre-declared command-line arguments.**

The `modelcontextprotocol/servers` repository implements the MCP roots protocol to eliminate static directory configurations. Instead of launching with hardcoded paths, servers dynamically request authorized roots from the client, validate them, and maintain a live whitelist that updates throughout the session.

## Server Initialization and Root Discovery

When the filesystem server starts in [`src/filesystem/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/index.ts), it checks for client capabilities during the `oninitialized` lifecycle hook. If the client advertises roots support, the server immediately requests the authorized directory list.

```typescript
// src/filesystem/index.ts
server.server.oninitialized = async () => {
  const clientCapabilities = server.server.getClientCapabilities();

  if (clientCapabilities?.roots) {
    // Client supports MCP Roots → request the list
    const response = await server.server.listRoots();
    // Process validated roots...
  }
};

```

The `listRoots()` method returns an array of `Root` objects, each containing a `uri` (e.g., `file:///home/user/project`) and an optional `name`. This request happens automatically during connection setup, allowing the server to bootstrap with zero pre-configured directories.

## Validating Root Directories with getValidRootDirectories

Raw URIs from the client undergo strict validation before becoming active access points. The `getValidRootDirectories` function in [`src/filesystem/roots-utils.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/roots-utils.ts) handles this sanitization:

```typescript
// src/filesystem/roots-utils.ts
export async function getValidRootDirectories(
  requestedRoots: readonly Root[]
): Promise<string[]> {
  // ...
  const resolvedPath = await parseRootUri(requestedRoot.uri);
  // ...
  const stats = await fs.stat(resolvedPath);
  if (stats.isDirectory()) validatedDirectories.push(resolvedPath);
}

```

The validation pipeline performs several critical steps:

- **`parseRootUri`** expands `file://` schemes, resolves tilde (`~`) home directories, follows symlinks, and normalizes paths
- Each path must exist on the filesystem and be a directory (not a file)
- Invalid or inaccessible entries are filtered out with diagnostic logging

Only directories passing all validation checks enter the active whitelist.

## Runtime Directory Updates via roots/list_changed

The protocol supports live directory changes without server restarts. After initialization, the server registers a notification handler for `roots/list_changed` events:

```typescript
// src/filesystem/index.ts
server.server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
  const response = await server.server.listRoots();   // re‑fetch current roots
  if (response && 'roots' in response) {
    await updateAllowedDirectoriesFromRoots(response.roots);
  }
});

```

When the client sends a `roots/list_changed` notification, the server executes `updateAllowedDirectoriesFromRoots`. This function validates the new root set and calls `setAllowedDirectories` from [`src/filesystem/lib.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/lib.ts) to atomically replace the global whitelist. All subsequent tool calls immediately respect the new boundaries.

## Session-Based Root Tracking in the Everything Server

The Everything server package extends this pattern to support multi-session environments. In [`src/everything/server/roots.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/server/roots.ts), roots are stored per session ID:

```typescript
// src/everything/server/roots.ts
export const roots: Map<string | undefined, Root[]> = new Map();

export async function syncRoots(sessionId?: string) {
  if (!clientCapabilities?.roots) return;
  const response = await server.listRoots();
  if (response?.roots) roots.set(sessionId, response.roots);
  // ...
}

```

The `syncRoots` function maintains isolated root sets for different client connections, enabling concurrent sessions with distinct directory access rights.

## Querying Current Roots with Diagnostic Tools

Servers expose their current root configuration back to clients through dedicated tools. The Everything server implements `get-roots-list` in [`src/everything/tools/get-roots-list.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/tools/get-roots-list.ts) to return a formatted view of active roots. This bidirectional visibility allows clients to verify that the server correctly interpreted the root list before executing sensitive filesystem operations.

## Practical Implementation Examples

### Starting Without Pre-Declared Directories

Launch the filesystem server without command-line arguments:

```bash
$ mcp-server-filesystem

# Server prints:

# "Started without allowed directories - waiting for client to provide roots via MCP protocol"

```

The server remains operational but blocks filesystem access until the client provides valid roots through the protocol.

### Client-Side Root Updates

A client implementation sends new directories and triggers updates:

```typescript
// Client sends initial roots during capability negotiation
const myRoots = [
  { uri: 'file:///home/alice/workspace', name: 'workspace' },
  { uri: 'file:///tmp/shared', name: 'shared' }
];

// Later, after adding /var/log to the list:
await client.notify('roots/list_changed');   // Server refreshes automatically

```

The server receives the notification, calls `listRoots()`, validates the expanded set including `/var/log`, and updates `allowedDirectories` instantly.

## Summary

- **Dynamic discovery**: The `modelcontextprotocol/servers` implementation requests authorized directories via `listRoots()` during the `oninitialized` phase rather than parsing static command-line arguments.
- **Strict validation**: The `getValidRootDirectories` function in [`src/filesystem/roots-utils.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/roots-utils.ts) sanitizes URIs, resolves symlinks, and verifies directory existence before granting access.
- **Live updates**: The `roots/list_changed` notification handler enables runtime root replacement without server restarts through `updateAllowedDirectoriesFromRoots`.
- **Session isolation**: The Everything server tracks roots per session in [`src/everything/server/roots.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/server/roots.ts), supporting concurrent clients with different access permissions.
- **Bidirectional protocol**: Servers expose current root states through tools like `get-roots-list`, allowing clients to verify access boundaries before executing operations.

## Frequently Asked Questions

### What is the MCP roots protocol?

The MCP roots protocol is a capability that allows MCP servers to request a list of authorized filesystem directories (roots) from the client rather than requiring those paths to be specified at server startup. This enables dynamic, per-session access control where the client determines which directories the server may touch.

### How does the server validate root directories?

The server validates roots through `getValidRootDirectories` in [`src/filesystem/roots-utils.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/roots-utils.ts), which calls `parseRootUri` to convert `file://` URIs to absolute paths, expand tildes, resolve symlinks, and verify that each entry exists and is a directory. Invalid entries are silently skipped with diagnostic logging.

### Can root directories change after the server starts?

Yes. Clients send a `roots/list_changed` notification whenever they modify their root list. The server's notification handler re-fetches the current roots via `listRoots()`, validates them, and updates the internal whitelist immediately. No server restart is required for these changes to take effect.

### Which files implement dynamic root management in modelcontextprotocol/servers?

Key implementations include [`src/filesystem/index.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/index.ts) for the main initialization and update logic, [`src/filesystem/roots-utils.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/roots-utils.ts) for URI parsing and validation, [`src/filesystem/lib.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/lib.ts) for the `setAllowedDirectories` state management, and [`src/everything/server/roots.ts`](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/server/roots.ts) for multi-session root tracking.