MCP Spotlight: Filesystem MCP Server — Anthropic's Reference Implementation for Sandboxed, Allowed-Directory-Scoped File Access
Server: @modelcontextprotocol/server-filesystem by Anthropic
License: MIT · Tools: ~14 (read, write, edit, search, directory, metadata) · Transport: stdio or Docker
Security: Explicit allowed-directories allow-list · read/write/edit/move/delete modes · Roots-aware dynamic updates
GitHub: github.com/modelcontextprotocol/servers/tree/main/src/filesystem
NPM: @modelcontextprotocol/server-filesystem
Docker: mcp/filesystem · MCP Tracker: glama.ai/mcp/servers/modelcontextprotocol/filesystem
Every agent that does real work eventually needs the filesystem. The "give the agent unrestricted filesystem access" approach is a security incident waiting to happen — the agent reads ~/.ssh/id_rsa, writes to /etc/passwd, deletes the user's home directory. The "give the agent nothing" approach blocks every workflow that benefits from local file processing — code review, document editing, log analysis, project bootstrapping. The naive "wrap fs.readFile / fs.writeFile" approach gives the agent full filesystem access through a thin wrapper.
The official Filesystem MCP Server by Anthropic is the bridge that resolves this. ~14 tools covering the file operations an agent actually needs — read, write, edit, search, directory navigation, metadata — all gated behind an explicit allowed-directories allow-list. The agent can ONLY access directories the operator explicitly authorized. Out-of-bounds paths are blocked at the server level. MIT-licensed, official Anthropic-maintained, available via NPX or Docker.
This is the local-file-access default for AI agents in 2026. Small surface, hard sandbox boundary, ubiquitous use.
The Architecture: Allowed-Directories Sandboxing
The Filesystem MCP Server's defining design choice: the server refuses any access to a directory not in its allowed list. The configuration is explicit:
# Option A: NPX with explicit allowed directories
npx -y @modelcontextprotocol/server-filesystem /home/user/projects /tmp/scratch
# Option B: Docker with explicit allowed directories
docker run -i --rm \
-v /home/user/projects:/projects:rw \
-v /tmp/scratch:/scratch:rw \
mcp/filesystem /projects /scratch
The directories passed at startup are the complete set the agent can access. There's no "current working directory" trick, no relative path escape, no symlink attack — the server validates every operation against the allow-list before executing.
If the agent tries read_file("/etc/passwd"), the server returns an error: path not in allowed directories. If the agent tries read_file("/home/user/projects/../../../etc/passwd"), the server resolves the path and rejects it. No path traversal, no escape, no exceptions.
The allow-list is the security boundary. Set it correctly, and the agent can only touch what you want it to touch.
The Roots Pattern: Dynamic Allowed Directories
For MCP clients that support the Roots protocol extension, the allowed directories can be updated dynamically:
Client → Server: "I now allow these directories: /projects/repo-a, /projects/repo-b"
Server: "Updated. Allowed directories: repo-a, repo-b. Cache invalidated."
The agent's working set changes as the user navigates between projects. The Roots mechanism is the dynamic scope mechanism — the operator grants per-project access, the agent operates within it, the operator moves to the next project.
This is the right pattern for IDE-integrated agents (Cursor, Windsurf, VS Code) where the user is constantly switching contexts. The filesystem scope matches the user's current focus.
The Tool Surface: ~14 Tools
The MCP surface covers the file operations an agent actually uses:
Read
| Tool | What It Does |
|---|---|
read_file | Read a file (full content) |
read_text_file | Read with explicit UTF-8 encoding |
read_image_file | Read an image file (returns base64 + mime) |
read_multiple_files | Batch read (efficient for multi-file context) |
Write
| Tool | What It Does |
|---|---|
write_file | Write a file (overwrites if exists) |
edit_file | Surgical edit (find-and-replace with uniqueness check) |
create_directory | Create a directory |
Search
| Tool | What It Does |
|---|---|
search_files | Find files matching a glob pattern |
search_code | Search file contents with regex |
list_directory | List a directory's contents |
list_allowed_directories | Show the current allow-list (transparency) |
Metadata
| Tool | What It Does |
|---|---|
get_file_info | Get size, mtime, type, permissions |
move_file | Move/rename a file |
list_directory_with_sizes | List with file sizes |
The tools are well-named, well-scoped, and map directly to filesystem operations. The agent doesn't have to guess which library function to call.
The Read Patterns: How Agents Use the Filesystem
Single file read
read_file(path="/projects/repo/src/index.ts")
→ Returns the file content
Batch read for context
read_multiple_files(paths=[
"/projects/repo/src/index.ts",
"/projects/repo/src/config.ts",
"/projects/repo/src/types.ts"
])
→ Returns the file contents in one call
For agents that need multi-file context (e.g., understanding a module), batch read is the efficient pattern — one round-trip, multiple files.
Search across files
search_code(
path="/projects/repo",
pattern="TODO|FIXME|XXX",
file_pattern="*.{ts,js,tsx,jsx}"
)
→ Returns matching files with line numbers and context
The search returns structured results — file paths, line numbers, matched content. The agent can reason about where to focus.
Tree walking
list_directory(path="/projects/repo")
→ Returns:
[
{"name": "src", "type": "directory"},
{"name": "tests", "type": "directory"},
{"name": "package.json", "type": "file"},
{"name": "README.md", "type": "file"}
]
list_directory(path="/projects/repo/src")
→ Returns the next level down
The agent builds the project's file tree iteratively.
The Write Patterns: Safe Mutations
Direct write (new file or full overwrite)
write_file(
path="/projects/repo/src/utils.ts",
content="// ... full file content ..."
)
→ Writes the file
For new files or full rewrites, write_file is the right primitive. The agent composes the full content and writes it.
Surgical edit (find and replace)
edit_file(
path="/projects/repo/src/config.ts",
edits=[{
"oldText": "const PORT = 3000;",
"newText": "const PORT = process.env.PORT || 3000;"
}]
)
→ Surgically replaces the matched text
For targeted edits (refactor a constant, fix a typo, update an import), edit_file is the right primitive. The oldText must match exactly (whitespace included), the server enforces uniqueness, the edit is atomic.
Multi-edit in one call
edit_file(
path="/projects/repo/src/api.ts",
edits=[
{"oldText": "import moment from 'moment';", "newText": "import { format } from 'date-fns';"},
{"oldText": "moment(", "newText": "format(new Date(), "},
{"oldText": "moment(", "newText": "format(new Date(), "} // second occurrence
]
)
For multi-edit refactors, batch the edits in one call. The server validates uniqueness and applies atomically.
The Search Patterns: Discovery + Grep
The Filesystem server exposes two search tools — search_files (filename matching) and search_code (content matching):
Filename search
search_files(
path="/projects/repo",
pattern="*.test.ts"
)
→ Returns all TypeScript test files
Content search
search_code(
path="/projects/repo/src",
pattern="deprecated",
context=3,
file_pattern="*.ts",
ignore_case=true
)
→ Returns matches with 3 lines of context
The agent can grep the codebase for patterns, identifiers, TODOs, deprecated APIs. The results are structured (file paths + line numbers + context), so the agent can follow up with read_file on specific matches.
The Image Support: Multimodal Files
The Filesystem MCP server handles image files too:
read_image_file(path="/projects/repo/docs/architecture.png")
→ Returns:
{
"type": "image",
"mime_type": "image/png",
"data": "<base64 encoded PNG data>"
}
For agents that do visual reasoning (UI review, design feedback, image analysis), the image read primitive is invaluable. The base64 data flows into the agent's vision-capable context.
The Security Boundary: What Cannot Happen
The Filesystem MCP server's security model is deny-by-default:
/etc/passwd— blocked (not in allow-list)~/.ssh/id_rsa— blocked (not in allow-list)~/.aws/credentials— blocked (not in allow-list)/var/log/— blocked (not in allow-list)- Symlink traversal — resolved before allow-list check, then blocked
- Path traversal —
../../etc/passwdresolved, blocked - Hidden files — only blocked if the parent directory isn't allowed (no special-casing)
- Network paths — not supported (the filesystem is local-only)
The server's single job is enforcing the allow-list. Every operation goes through the check. There's no "developer override" or "sudo mode." If the path isn't allowed, the operation fails.
Facio Integration
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y", "@modelcontextprotocol/server-filesystem",
"/home/user/projects",
"/tmp/scratch",
"/data/agent-workspace"
]
}
}
}
Or via Docker with explicit mounts:
{
"mcpServers": {
"filesystem": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/home/user/projects:/projects:rw",
"-v", "/tmp/scratch:/scratch:rw",
"mcp/filesystem", "/projects", "/scratch"
]
}
}
}
Facio's audit trail captures every Filesystem MCP call with the tool, the path, the operation, the result, and the diff (for edits). For a regulated team (SOC2, ISO 27001, financial compliance), this is the complete file-access record: "Agent at 14:32 UTC edited /projects/repo/src/config.ts, replaced 1 occurrence of 'const PORT = 3000;' with 'const PORT = process.env.PORT || 3000;'."
For HITL workflows, the Filesystem MCP server's destructive surface is meaningful — the agent can modify files:
| Tool | Severity | Suggested Gate |
|---|---|---|
read_*, search_*, list_*, get_file_info | Read | None — autonomous |
list_allowed_directories | Read (transparency) | None — autonomous |
create_directory | Write, contextual | Soft confirm |
write_file (existing file) | Write, destructive in effect | Hard confirm if path matches sensitive patterns |
write_file (new file) | Write, contextual | Soft confirm |
edit_file | Write, surgical | Soft confirm (review the diff) |
move_file | Write, destructive in effect | Hard confirm if destination exists |
read_image_file | Read | None — autonomous |
The write_file and move_file tools deserve special attention — overwriting an existing file or moving to an existing destination can lose data. Facio's parameter-aware policies can add extra gates:
path matches *.env, *.key, *.pem, id_rsa*→ hard confirm (secrets-adjacent files)path matches /etc/*, /var/*, ~/.ssh/*→ hard confirm (system files; these should never be in the allow-list anyway)path matches node_modules/*, .git/*→ block (noise, not the user's work)
For multi-project setups (one agent working across many repos), the pattern is one allow-list per active project. The agent's filesystem scope updates as the user switches projects (via Roots).
For multi-tenant SaaS setups (one agent per customer), each tenant has their own Filesystem MCP server with their own allow-list. No cross-tenant file access.
Quickstart
# Option 1: NPX with explicit allowed directories
npx -y @modelcontextprotocol/server-filesystem \
/home/user/projects \
/tmp/agent-scratch
# Option 2: Docker with mounted volumes
docker run -i --rm \
-v /home/user/projects:/projects:rw \
-v /tmp/agent-scratch:/scratch:rw \
mcp/filesystem /projects /scratch
Configuration:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y", "@modelcontextprotocol/server-filesystem",
"/home/user/projects",
"/tmp/agent-scratch"
]
}
}
}
First prompts:
"Read all .ts files in /projects/repo/src and summarize the architecture"
"Find every TODO in /projects/repo"
"Edit /projects/repo/src/config.ts to use the PORT environment variable"
"Create a new directory /projects/repo/src/auth with an empty index.ts"
"Search for usage of the deprecated 'moment' library across all .ts files"
Use Cases
Code review: "Read the 5 changed files in this PR, summarize the changes, identify issues." Multi-file read → analysis → structured review.
Refactoring: "Find every usage of the deprecated 'moment' library and propose replacements." Search → multi-file read → migration plan → edits.
Project bootstrapping: "Create a new TypeScript project skeleton: package.json, tsconfig.json, src/index.ts, README.md, .gitignore." Multi-file create with templated content.
Documentation generation: "Read the source code, generate API documentation, write to docs/api.md." Code analysis → doc generation → file write.
Log analysis: "Read the last 100 lines of /var/log/app.log, summarize the errors, suggest fixes." File read → analysis → structured summary.
Image review: "Read the architecture diagram and explain the system's components." Image read → visual reasoning → structured explanation.
Codebase exploration: "Walk the project tree, summarize the structure, identify the main modules." Recursive listing → tree construction → summary.
Migration scripts: "Find every file that imports 'moment', for each, replace with 'date-fns' equivalents." Code search → multi-file edit → verify.
Configuration management: "Update the .env.example with the new feature flags, ensuring no actual secrets are committed." Templated file write with safety checks.
Test generation: "For each .ts file in src/, generate a corresponding .test.ts file in tests/." Multi-file creation with consistent patterns.
Code cleanup: "Find all unused exports and remove them." Code analysis → multi-file edit.
Project audit: "Find all files larger than 1MB, list them with sizes, flag for review." Directory walk with size analysis.
Git workflow: "Read the diff in src/, suggest a commit message, write it to COMMIT_MSG." Diff read → message generation → file write.
Documentation sync: "Update the README to reflect the new API endpoints from src/api/routes.ts." Code reading → doc regeneration → file write.
Backup automation: "Copy every .ts file in src/ to backup/src/ with today's date suffix." Recursive copy.
Dependency analysis: "Read package.json, identify outdated dependencies, suggest updates." JSON parsing → version analysis → recommendations.
Schema migration: "For each .sql file in migrations/, generate a corresponding rollback script." SQL parsing → reverse-script generation.
Codebase metrics: "Count lines of code per file, group by directory, generate a metrics report." Recursive analysis → aggregation → structured report.
Setup automation: "Create a new project from the template at /templates/nextjs-app, configure for our domain." Template copy → configuration → verification.
Workspace organization: "Move all .log files older than 30 days to /archive/." File metadata → age check → move operation.
The Sandboxing Pattern
The Filesystem MCP server's defining innovation — explicit allowed-directories at startup — is the design lesson every "system-resource" MCP server should copy.
Why explicit allow-lists win:
- Deny-by-default — no resource is accessible unless explicitly granted
- Configuration-level safety — the operator defines the scope at setup, not per-call
- Inspectable —
list_allowed_directoriesshows the agent what it can touch - No escape — path traversal, symlink tricks, relative path games all fail
- Per-tenant isolation — one Filesystem server per tenant, each with their own allow-list
- Container-friendly — the Docker pattern uses volume mounts, so the operator controls filesystem isolation at the OS level
For any MCP server that touches a system resource — filesystem, network, processes, environment variables — the same pattern applies: explicit allow-list at startup, deny-by-default, no escape paths. The agent's capabilities are bounded by the operator's configuration, not by tool naming conventions or hope.
Bottom Line
The Filesystem MCP Server is the local-file-access default for AI agents in 2026. ~14 focused tools (read, write, edit, search, directory, metadata), explicit allowed-directories sandboxing, Roots-aware dynamic updates, image support, MIT-licensed, official Anthropic-maintained. Available via NPX or Docker.
For any agent that does real work — code review, refactoring, project bootstrapping, log analysis, documentation generation, file migration — this is the bridge. The agent operates on files within the allow-list, can't escape the boundary, every operation is audited.
For the broader MCP ecosystem, the Filesystem pattern is the design lesson every "system-resource" MCP server should copy. Explicit allow-lists at startup. Deny-by-default. No escape paths. When the security boundary is configuration-level, the agent's blast radius is bounded.
npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/dir (or docker run -i --rm -v /path:/mount:rw mcp/filesystem /mount) and your agent has bounded file access.
MCP Spotlight is a series covering servers that give AI agents real capabilities. Every server is evaluated for design clarity, ecosystem impact, and integration fit with Facio's HITL-first agent runtime.