This article is part of a 6-part series. To check out the other articles, see:
Primitives: Tools | Resources | Prompts | Sampling | Roots | Elicitation

Imagine you open a project in your editor and an AI assistant offers to refactor files. You want it to touch only the current project — not your private notes, SSH keys, or system config, right? 😬

Roots solves that: the client tells the server, “You may operate only inside /Users/alex/work/my-project,” and the server asks permission before it ever tries to read or write a file. That small, explicit boundary turns a risky free‑for‑all into a clearly limited sandbox: the AI can do powerful work, but only where you’ve agreed it may.

In the sections ahead we’ll cover why Roots matters (Part I), how the initialize/roots/list handshake works at the protocol level (Part II), copy‑pasteable code patterns for safe implementations (Part III), and the production guardrails you must follow so a misconfigured session never becomes a data breach (Part IV).

Specification Note: This guide is based on the official & latest MCP Roots specification (Protocol Revision: 2025-06-18). Where this guide extends beyond the official spec with patterns or recommendations, it is clearly marked.

Part I: Foundation - The "Why & What"

This section establishes the conceptual foundation. If a reader only reads this part, they will understand what the primitive is, why it's critically important, and where it fits in the ecosystem.

1. Core Concept

1.1 One-Sentence Definition

Roots is a standardized MCP primitive that allows clients to expose filesystem "roots" to servers, defining the boundaries of where servers can operate within the filesystem and enabling them to understand which directories and files they have access to.

1.2 The Core Analogy

The most effective analogy for Roots is that of a highly-secured government facility with a visitor's pass.

Imagine an AI agent is a contractor hired to renovate a single, specific office (Room 101) inside a secure building.

  • Without Roots, the contractor is given a master key to the entire building. They could just go to Room 101, but nothing stops them from wandering into the server room, the executive offices, or the archives. A single mistake or malicious act could compromise the entire facility. This is the "digital burglar with a master key" problem.
  • With Roots, the contractor is issued a visitor's pass that is electronically keyed only to the main entrance and the door to Room 101. Every other door they try will remain locked. The pass defines their boundary. Roots functions as this electronic pass, ensuring the AI agent can only operate within its explicitly designated and approved workspace.

For a more technical analogy, Roots is to MCP what workspaceFolders is to the Language Server Protocol (LSP). It provides the essential, scoped context that prevents the server from needing to dangerously index or access the entire system.

1.3 Architectural Position & Control Model

Roots sits at the intersection of all three tiers of the MCP architecture, serving as the contract that defines the server's operational scope.

Control Model: Client/Host-Controlled

This is the most critical aspect of Roots. The boundaries are defined and controlled by the Client (the user's application, like an IDE or web app) and the Host (the underlying infrastructure). The Server (the AI agent) is a guest; it receives the boundaries and is expected to operate within them. It cannot grant itself more access. This choice is fundamental to the protocol's security, placing ultimate authority with the user and their trusted environment, not the potentially untrusted AI service.

1.4 What It Is NOT
  • It is NOT an OS-level sandbox. Roots is a protocol-level agreement, a form of "guidance." It does not, by itself, create a kernel-level jail or container. True security relies on the server's cooperation and the client's implementation of hard boundaries (like Docker volumes).
  • It is NOT a replacement for authentication. Roots defines what an authenticated agent can access, not who the agent is. It works in concert with authentication protocols like OAuth.
1.5 User Interaction Model

While MCP standardizes the protocol for communicating roots, it does not mandate any specific user interaction pattern. Implementations are free to expose roots through various interfaces:

  • Workspace/Project Pickers: Allow users to select directories the server should access
  • Automatic Detection: Detect workspaces from version control systems or project files
  • Manual Configuration: Let users explicitly define roots in configuration files
  • Hybrid Approaches: Combine automatic detection with user confirmation

The protocol is intentionally flexible to accommodate different application needs and user experiences.

2. The Problem It Solves

2.1 The LLM-Era Catastrophe

Roots was created to prevent the Uncontrolled AI Agent Access Problem, where autonomous agents with broad, undifferentiated permissions become catastrophic vectors for data breaches and system damage.

The "before" state is a landscape of extreme risk:

  • Implementation Gap: Many MCP servers currently lack proper boundary validation, creating a significant security gap in the ecosystem.
  • Security Impact: Proper Roots implementation dramatically reduces the risk of data breaches by enforcing the principle of least privilege at the protocol level.
  • Prevention vs Recovery: A simple, preventive configuration of Roots can avoid lengthy and expensive incident recovery processes that often follow a boundary violation.
2.2 Why Traditional Approaches Fail
  1. Monolithic Permissions: Traditional systems grant an AI agent a single user role (e.g., "service-account-user"). This role might have access to everything a human user does, with no intermediate checkpoints. If the agent is compromised, its entire permission set is compromised.
  2. Custom In-App Logic: Developers might try to build custom path-checking logic inside their AI tools. This approach is brittle, inconsistent across different tools, prone to bypass through techniques like directory traversal (../../), and creates a maintenance nightmare.
  3. Relying on LLM "Instructions": Simply telling an LLM in its system prompt "Only access files in the /project directory" is a massive security vulnerability. Prompt injection can easily trick the model into ignoring these instructions. Roots provides a secure, out-of-band mechanism that cannot be manipulated by the LLM's context.

Part II: Technical Architecture - The "How"

This is the technical deep dive, detailing the protocol mechanics, message structures, and the end-to-end lifecycle. This section is the source of truth for implementers.

3. Protocol Specification

3.1 Request & Response Schemas

The Roots primitive is primarily managed through the initialize handshake and a roots/list request/response flow.

1. initialize Request (Client → Server)
The client must declare its ability to handle Roots.

{
  "jsonrpc": "2.0",
  "method": "initialize",
  "id": "init-request-001",
  "params": {
    "protocolVersion": "2025-06-18",
    "clientInfo": {
      "name": "SecureIDE",
      "version": "1.2.3"
    },
    "capabilities": {
      // This object signals to the server that the client
      // supports and will provide Roots.
      "roots": {
        // 'listChanged: true' is critical. It means the client
        // will proactively notify the server if boundaries change.
        "listChanged": true
      }
    }
  }
}

2. roots/list Request (Server → Client)
A well-behaved server asks for its boundaries immediately after initialization.

{
  "jsonrpc": "2.0",
  "method": "roots/list",
  "id": "get-roots-request-002"
}

3. roots/list Response (Client → Server)
The client responds with the authoritative list of approved boundaries.

{
  "jsonrpc": "2.0",
  "id": "get-roots-request-002",
  "result": {
    "roots": [
      {
        // A mandatory RFC 3986 URI. The current specification requires this
        // MUST be a file:// URI. Future versions may support additional schemes.
        "uri": "file:///Users/dev/project-alpha",
        // An optional, human-readable name for UI purposes.
        "name": "Project Alpha Code"
      }
    ]
  }
}
3.2 Official Examples

Single Project Directory:

{
  "uri": "file:///home/user/projects/myproject",
  "name": "My Project"
}

Multiple Repositories:

[
  {
    "uri": "file:///home/user/repos/frontend",
    "name": "Frontend Repository"
  },
  {
    "uri": "file:///home/user/repos/backend",
    "name": "Backend Repository"
  }
]

4. notifications/roots/list_changed Notification (Client → Server)
When the user opens a new project or closes an old one, the client sends this notification.

{
  "jsonrpc": "2.0",
  "method": "notifications/roots/list_changed",
  // Notifications intentionally have no "id" field as they don't expect responses.
  // This is per JSON-RPC 2.0 specification for notification messages.
  "params": {}
}

⚠️ SECURITY CRITICAL: Upon receiving this notification, the server MUST discard its cached roots and send a new roots/list request to get the updated boundaries. Continuing to operate on stale roots is a major security flaw.

3.3 Error Handling

Clients SHOULD return standard JSON-RPC errors for failure cases.

Example error response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32601,
    "message": "Roots not supported",
    "data": {
      "reason": "Client does not have roots capability"
    }
  }
}

See the Quick Reference section for a complete error code table.

4. The 4-Step Lifecycle

The Roots lifecycle is a clear, stateful process that establishes and maintains security boundaries throughout a session.

4.1 Step-by-Step Communication Flow

The official specification provides this canonical flow:

Our detailed sequence expands on this foundation with more context:

4.2 State Management & Dynamic Behavior

Statefulness is managed via the listChanged capability.

  • If a client declares listChanged: true, the server knows it can rely on the client to push notifications. This enables dynamic, real-time security that adapts as the user's context (e.g., active project) changes.
  • If a client sets this to false or doesn't include it, the server's boundaries are considered static for the session's duration. This is less secure and less flexible, as it can't adapt to user actions without a full session restart.

A robust server implementation must include a state manager that listens for the list_changed notification, immediately flags its current roots as stale, and queues all incoming requests until the new roots have been fetched and validated.

Part III: Implementation Patterns - The "Show Me"

This section makes concepts concrete through a progression of code-heavy examples, from a simple "hello world" to a complex, production-ready workflow.

5. Pattern 1: The Canonical "Golden Path"

5.1 Goal

To establish a basic, secure workspace for an AI agent that needs to read a file from a user's project directory.

5.2 Implementation

This example uses TypeScript-like pseudocode for clarity.

Client-Side Logic (e.g., in an IDE extension)

// Assume 'mcpConnection' is an established JSON-RPC connection to the server
class SecureClient {
  private currentRoots: Root[] = [];

  constructor(private mcpConnection: Connection) {
    this.setupListeners();
  }

  // When the user opens a folder, update and notify.
  public onWorkspaceChanged(folderPath: string): void {
    this.currentRoots = [{ uri: `file://${folderPath}` }];
    this.mcpConnection.sendNotification('notifications/roots/list_changed', {});
    console.log(`[CLIENT] Notified server of new root: ${this.currentRoots[0].uri}`);
  }

  // Listen for the server's request for its boundaries.
  private setupListeners(): void {
    this.mcpConnection.onRequest('roots/list', () => {
      console.log('[CLIENT] Server requested roots. Responding.');
      return { roots: this.currentRoots };
    });
  }
}

Server-Side Logic (e.g., in an AI agent)

// Assume 'mcpConnection' is an established JSON-RPC connection to the client
class SecureServer {
  private allowedRoots: string[] = [];
  private isInitialized = false;

  constructor(private mcpConnection: Connection) {
    this.mcpConnection.onNotification('notifications/roots/list_changed', () => {
      console.log('[SERVER] Received roots changed notification. Refetching.');
      this.fetchRoots();
    });
  }

  public async initialize(): Promise<void> {
    // On init, fetch the initial set of roots.
    await this.fetchRoots();
    this.isInitialized = true;
  }

  private async fetchRoots(): Promise<void> {
    const response = await this.mcpConnection.sendRequest('roots/list', {});
    this.allowedRoots = response.roots.map(r => r.uri.replace('file://', ''));
    console.log('[SERVER] Updated allowed roots:', this.allowedRoots);
  }

  // ⚠️ SECURITY CRITICAL: This is the core validation logic.
  private isPathAllowed(filePath: string): boolean {
    // In a real implementation, this would use robust path canonicalization.
    const absolutePath = path.resolve(filePath);
    return this.allowedRoots.some(root => absolutePath.startsWith(root));
  }

  public async readFile(filePath: string): Promise<string> {
    if (!this.isInitialized) throw new Error("Server not ready.");
    if (!this.isPathAllowed(filePath)) {
      console.error(`[SERVER] FORBIDDEN ACCESS: ${filePath}`);
      throw new Error("Access denied: Path is outside the defined root boundaries.");
    }
    console.log(`[SERVER] Access granted: Reading ${filePath}`);
    return fs.promises.readFile(filePath, 'utf-8');
  }
}```

##### **5.3 Key Takeaway**
The core mechanism is a **cooperative handshake**: the client offers boundaries, the server requests them, and then the server takes responsibility for checking every single one of its own operations against those boundaries.

#### **6. Pattern 2: The Compositional Workflow**

##### **6.1 Goal**
To demonstrate how `Roots` provides a critical security layer for a workflow involving multiple primitives. Here, a user `Prompt` triggers a `Tool`, which operates safely within the boundaries defined by `Roots`.

**Scenario:** A financial analyst uses a slash command (`/analyze_quarterly_report`) to run an analysis tool on a specific company's report.

##### **6.2 Implementation**

**The Flow:**
1.  **Client:** The user opens the project folder `~/work/acme_corp_q3_report/`. The client sets the `Root` to this directory.
2.  **User:** The user types the `Prompt`: `/analyze_quarterly_report ticker="ACME"`.
3.  **Host:** The Host interprets the prompt and decides to call the `runFinancialAnalysis` `Tool`.
4.  **Server (Tool):** The tool receives the request. It needs to read `report.pdf`. It first checks if `~/work/acme_corp_q3_report/report.pdf` is within its allowed `Roots`. Since it is, the operation proceeds. The tool then returns a `Resource` pointer to the generated `analysis_summary.csv`.

**Server-Side Tool Implementation**
```typescript
class FinancialAnalysisServer {
  private allowedRoots: string[] = [];
  // ... (constructor and fetchRoots logic from Pattern 1) ...

  // This method is registered as the 'runFinancialAnalysis' Tool.
  public async runFinancialAnalysis(params: { ticker: string }): Promise<{ summaryResource: Resource }> {
    const reportDir = this.allowedRoots[0]; // Assuming one root for simplicity
    const sourceReportPath = path.join(reportDir, 'report.pdf');
    const outputPath = path.join(reportDir, 'analysis_summary.csv');

    // 1. Boundary Check (Composition with Roots)
    if (!this.isPathAllowed(sourceReportPath) || !this.isPathAllowed(outputPath)) {
        throw new Error("Cannot operate outside the designated 'ACME' project folder.");
    }

    // 2. Perform the action (Tool logic)
    console.log(`[TOOL] Analyzing ${sourceReportPath}...`);
    const summaryData = await performAnalysis(sourceReportPath);
    await fs.promises.writeFile(outputPath, summaryData);

    // 3. Return a pointer to the result (Composition with Resource)
    return {
      summaryResource: {
        uri: `file://${outputPath}`,
        // Other resource metadata...
      }
    };
  }
}
6.3 Key Takeaway

Roots does not work in isolation. It is the foundational security context that makes other primitives, like Tools, safe to use. By enforcing boundaries, Roots allows an organization to confidently expose powerful tools to AI agents, knowing their blast radius is contained. The composition of Prompt (intent) → Roots (boundary) → Tool (action) → Resource (result) is a core pattern for building safe, agentic systems.

7. Pattern 3: The Domain-Specific Solution (Healthcare)

7.1 Goal

[Illustrative Example - Not from Official Spec]
To solve a real-world business problem under strict regulatory constraints by demonstrating how Roots can enable a HIPAA-compliant AI assistant that helps clinicians review patient data. The system must enforce the "Minimum Necessary" principle and create a verifiable audit trail for all data access.

7.2 Implementation

The key is to dynamically set Roots to only the specific patient record being discussed and for the Client to perform compliance checks before setting the boundary.

Host/Client-Side Compliance Logic

// This logic runs *before* setting and sending the Roots to the server.
class HipaaCompliantHost {
  private auditLogger: AuditLogger;

  // Called when a clinician opens a new patient chart.
  public async openPatientChart(clinician: User, patientId: string): Promise<void> {
    // Step 1: Verify clinician's authorization for this specific patient.
    const hasAccess = await this.verifyAuthorization(clinician.id, patientId);
    if (!hasAccess) {
      this.auditLogger.logAccessDenial(clinician.id, patientId, "Authorization failed");
      throw new Error("User not authorized to access this patient record.");
    }

    // Step 2: Enforce "Minimum Necessary" Principle. Determine the narrowest
    // possible data scope needed for the clinician's role.
    const patientRecordPath = this.getMinimumNecessaryPath(clinician.role, patientId);
    
    // Step 3: Create a specific, time-limited audit entry for this session.
    const sessionId = this.auditLogger.startSession(clinician.id, patientId, patientRecordPath);
    
    // Step 4: Only after all checks pass, set the root and notify the AI server.
    const root: Root = { uri: `file://${patientRecordPath}` };
    this.mcpConnection.setRoots([root]);
    this.mcpConnection.sendNotification('notifications/roots/list_changed', {});

    console.log(`[HOST] HIPAA Check Passed. Root set for session ${sessionId}.`);
  }
  
  // ... (verifyAuthorization and getMinimumNecessaryPath logic) ...
}

// Simplified Audit Trail Implementation
class AuditLogger {
    public startSession(clinicianId: string, patientId: string, resourcePath: string): string {
        const sessionId = crypto.randomUUID();
        const logEntry = {
            timestamp: new Date().toISOString(),
            event: "ACCESS_SESSION_START",
            actor: { type: "clinician", id: clinicianId },
            target: { type: "patient_record", id: patientId },
            details: {
                sessionId: sessionId,
                granted_scope: resourcePath,
                justification: "Clinical review"
            }
        };
        // In a real system, this would write to a secure, immutable log store.
        console.log("AUDIT_LOG:", JSON.stringify(logEntry));
        return sessionId;
    }
}
7.3 Key Takeaway

In regulated industries, Roots is more than a security feature; it is a compliance primitive. It provides the technical mechanism to enforce legal and regulatory requirements like HIPAA's "Minimum Necessary" rule. The dynamic, programmatic nature of Roots allows developers to build systems where compliance is not an afterthought but is baked into the operational logic of every user interaction.

Part IV: Production Guide - The "Build & Deploy"

8. Server-Side Implementation

8.1 Core Logic & Handlers

A production server must robustly implement the Roots lifecycle.

Minimal Server Setup (Node.js/Express-like pseudocode)

// server.ts
const server = new MCP.Server();

// Register a capability indicating this server needs and understands Roots.
server.capabilities.register('roots', {
    // The server needs a robust path validation utility.
    pathValidator: new PathValidator()
});

server.onInitialize(async (session) => {
    // On every new connection, immediately fetch the boundaries.
    session.roots = await session.connection.sendRequest('roots/list');
    
    session.connection.onNotification('notifications/roots/list_changed', async () => {
        // Handle dynamic updates.
        session.roots = await session.connection.sendRequest('roots/list');
    });
});

// Example tool implementation that uses the validator.
server.tools.register('readFile', async (params, context) => {
    const { path } = params;
    const { session } = context;

    // Use the validator, which checks against session.roots
    if (!session.capabilities.pathValidator.isAllowed(path, session.roots)) {
        throw new MCP.JsonRpcError(-32000, "Path is outside of allowed roots.");
    }
    return fs.promises.readFile(path);
});

server.listen();
8.2 Production Hardening
  • Security: Your PathValidator is your most critical security component. It MUST perform robust path canonicalization to prevent directory traversal (../../), resolve all symbolic links to their final target and re-validate, and block access to sensitive system paths (/etc, C:\Windows).
  • Performance: For high-volume servers, the roots/list request can add latency. Cache the resolved and validated root paths within the session. The list_changed notification is your cache invalidation signal.
  • Observability:
    • Logging: Log every instance where a path check fails. This is a critical security signal. Your logs should be structured: {"event": "boundary_violation", "path": "/etc/passwd", "roots": ["/home/user/project"]}.
    • Metrics: Track mcp_roots_violations_total (a counter for denied requests) and mcp_roots_cache_invalidations_total (a counter for list_changed notifications). Spikes in violations can indicate a misconfiguration or an attack.
8.3 Official Implementation Guidelines

Client Guidelines (SHOULD):

  1. Prompt users for consent before exposing roots to servers
  2. Provide clear user interfaces for root management
  3. Validate root accessibility before exposing
  4. Monitor for root changes and emit notifications

Server Guidelines (SHOULD):

  1. Check for roots capability before usage
  2. Handle root list changes gracefully
  3. Respect root boundaries in operations
  4. Cache root information appropriately

9. Client-Side Implementation

9.1 Core Logic & State Management

The client is the guardian of the boundaries. Its primary job is to maintain an accurate list of roots based on user context and consent.

Robust Client-Side State Management

class WorkspaceManager {
    private activeRoots: Root[] = [];
    
    constructor(private mcpConnection: Connection) {
        // Respond to the server's request for roots.
        this.mcpConnection.onRequest('roots/list', () => ({ roots: this.activeRoots }));
    }

    // This method is the single source of truth for changing boundaries.
    public async setActiveWorkspace(newPath: string | null): Promise<void> {
        // 1. Get explicit user consent if the path is sensitive.
        if (newPath && this.isSensitive(newPath)) {
            const consented = await this.showConsentDialog(newPath);
            if (!consented) return;
        }

        // 2. Update the internal state.
        this.activeRoots = newPath ? [{ uri: `file://${newPath}` }] : [];
        
        // 3. Notify the server of the change.
        this.mcpConnection.sendNotification('notifications/roots/list_changed', {});
    }
    
    private async showConsentDialog(path: string): Promise<boolean> {
        // ... (UI logic) ...
        return true;
    }
}```

##### **9.2 User Experience (UX) Patterns**
The user must always be in control. Do not set roots without their knowledge.

**Mandatory UX Pattern: The Explicit Consent Dialog**

When an AI agent needs access to a new folder for the first time, you **MUST** show a consent dialog.



**Key Elements:**
1.  **Clear Ask:** "AI Assistant wants to access the folder..."
2.  **Specific Scope:** Show the full path (`/Users/dev/project-alpha`).
3.  **Clear Actions:** "Allow" and "Deny." "Allow" should not be the default.
4.  **Persistence Option:** "Remember for this workspace" to avoid annoying the user, but this choice must be easily reversible in the settings.

### **Part V: Guardrails & Reality Check - The "Don't Fail"**

#### **10. Decision Framework**

##### **10.1 When to Use This Primitive**

| If you need to... | Then use `Roots` because... | An alternative would be... |
| :--- | :--- | :--- |
| Give an AI agent access to a local filesystem. | It is the **only** MCP-native, standardized way to define secure file boundaries. | Manually passing file contents in-prompt (expensive, insecure, doesn't scale). |
| Enforce data locality for compliance (e.g., GDPR). | `Roots` can be set to specific, compliant server paths (e.g., `file:///data/eu-cluster/`). | Building complex, custom IAM policies (less portable, not integrated with MCP). |
| Isolate the "blast radius" of a potentially buggy or untrusted AI tool. | It provides a clear, auditable sandbox that contains the tool's actions. | Running every tool in a heavyweight, fully virtualized machine (high overhead). |
| Allow a user to control which data an AI can "see" in their IDE. | It directly maps the user's context (the open project folder) to a security boundary. | Hard-coding paths in the server (inflexible and insecure). |

##### **10.2 Critical Anti-Patterns (The "BAD vs. GOOD")**

**Anti-Pattern 1: The "Root of All Evil"**
Giving the server access to the entire filesystem.

**BAD 👎**
```typescript
// Client sets the root to the filesystem root. This completely defeats the purpose of Roots.
client.onWorkspaceChanged('/'); // or 'C:\' on Windows

Why it's wrong: This gives the server carte blanche to read SSH keys (~/.ssh), system passwords (/etc/shadow), browser cookies, and all other user data. It is the single worst practice.

GOOD 👍

// Client sets the root to the *specific* project directory the user has opened.
client.onWorkspaceChanged('/Users/dev/project-alpha');

Anti-Pattern 2: The "Stale Read"
Failing to update roots when the user's context changes.

BAD 👎

// Server fetches roots once on initialize and never again.
class BadServer {
    private allowedRoots = [];
    async initialize() {
        // Fetches once, but never listens for 'list_changed'.
        this.allowedRoots = await mcpConnection.sendRequest('roots/list');
    }
    // ... continues to use stale roots ...
}

Why it's wrong: If the user closes project-a and opens project-b, the agent will still be operating on project-a, leading to incorrect actions and potential data leakage.

GOOD 👍

// Server correctly implements the notification handler to invalidate its cache.
class GoodServer {
    constructor() {
        mcpConnection.onNotification('notifications/roots/list_changed', () => this.fetchRoots());
    }
    async initialize() { await this.fetchRoots(); }
    async fetchRoots() { /* ... fetches and updates roots ... */ }
}

Anti-Pattern 3: The "Trusting Server"
Failing to perform path validation on the server side.

BAD 👎

// A server tool that just concatenates a path without checking it.
server.tools.register('deleteFile', async (params) => {
    // This allows a request like `params.file = '../../../../etc/hosts'` to succeed.
    const filePath = path.join(session.roots[0], params.file);
    fs.promises.unlink(filePath); // DANGEROUS!
});

Why it's wrong: The server is trusting the client's input. A malicious request or even a simple bug could allow an attacker to perform directory traversal and delete files anywhere on the system the server process has permission to.

GOOD 👍

server.tools.register('deleteFile', async (params) => {
    const requestedPath = path.resolve(session.roots[0], params.file);

    // Canonicalize the path and verify it's still inside the root.
    if (!requestedPath.startsWith(session.roots[0])) {
        throw new Error("Path traversal attempt detected.");
    }
    fs.promises.unlink(requestedPath);
});```

#### **11. The Reality Check**

##### **11.1 Known Limitations & Constraints**
*   **URI Scheme Restriction:** The current specification (2025-06-18) mandates that all root URIs **MUST** use the `file://` scheme. While the protocol architecture could theoretically support other schemes like `https://` or `db://`, these are not currently specified or supported.
*   **Performance Overhead:** The initial `roots/list` call adds a round-trip latency to session startup.

##### **11.2 The Trust Model: Enforced vs. Cooperative**
⚠️ **SECURITY CRITICAL: This is the most important concept to understand about `Roots`.**

The MCP specification uses the IETF keyword **"SHOULD"** when describing a server's obligation to respect roots, not **"MUST."** This is an intentional design choice that has profound security implications.

*   **Cooperative (The Protocol's Guarantee):** `Roots` provides a **standardized language for communicating boundaries**. This flexible, cooperative model allows MCP to be implemented across many environments without requiring kernel-level changes.
*   **Enforced (Your Responsibility):** Because the protocol itself does not enforce the boundary, true security is **your responsibility**. The client **MUST** enforce the boundary using OS-level controls. The safest way to do this is to run the server process in a container (e.g., Docker) and use volume mounts to expose *only* the directories defined in the roots.

**Never trust a server to be well-behaved. Always build a containerized fence around it.**

   Client's Hard Boundary (Docker Volume Mount)

+-------------------------------------------------------------+
| |
| Server's Soft Boundary (Respects Roots) |
| +---------------------------------------------------------+ |
| | | |
| | 🤖 AI Agent operates here safely. | |
| | | |
| +---------------------------------------------------------+ |
| |
| ^--- If a rogue server tries to escape, the container |
| wall stops it. |
+-------------------------------------------------------------+


##### **11.3 Security Requirements Matrix**

**Client Requirements (MUST):**
| Requirement | Rationale |
|:------------|:----------|
| Only expose roots with appropriate permissions | Prevents unauthorized access to sensitive data |
| Validate all root URIs to prevent path traversal | Blocks directory traversal attacks |
| Implement proper access controls | Ensures only authorized servers get access |
| Monitor root accessibility | Detects when roots become unavailable |

**Server Recommendations (SHOULD):**
| Recommendation | Rationale |
|:---------------|:----------|
| Handle cases where roots become unavailable | Prevents crashes and data loss |
| Respect root boundaries during operations | Core security cooperation |
| Validate all paths against provided roots | Prevents unauthorized access |

### **Part VI: Quick Reference - The "Cheat Sheet"**

#### **12. Common Operations & Snippets**

**Client: Initialize Session with `Roots` Capability**
```json
{
  "jsonrpc": "2.0",
  "method": "initialize",
  "id": "init-1",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "roots": { "listChanged": true }
    }
  }
}

Server: Request Roots from Client

{
  "jsonrpc": "2.0",
  "method": "roots/list",
  "id": "req-roots-1"
}

Client: Respond with Project Folder Root

{
  "jsonrpc": "2.0",
  "id": "req-roots-1",
  "result": {
    "roots": [{ "uri": "file:///Users/dev/project-alpha", "name": "Project Alpha" }]
  }
}

Client: Notify Server of Context Change

{
  "jsonrpc": "2.0",
  "method": "notifications/roots/list_changed",
  "params": {}
}

13. Configuration & Error Code Tables

Root Object Configuration Reference

Parameter Type Mandatory Description
uri string Yes An RFC 3986 compliant URI. MUST use the file:// scheme per the current specification.
name string No A human-readable label for the root, used for display in UIs and logs.

Standard Error Code Reference

Code Meaning Context Recommended Action
-32601 Method Not Found The peer does not support the requested method. If roots are required, treat the peer as non-compliant and terminate the session.
-32602 Invalid Params The parameters sent in a request were malformed. Log the error and check your message schema against the specification.
-32603 Internal Error An internal error occurred on the peer's side. Log the error and consider retrying the operation with an exponential backoff strategy.
-32000 Server Error Custom server-side error, often used to signal a tool-specific failure. Display a user-friendly error message; e.g., "Access denied: Path is outside allowed boundaries."