Read, write, edit, search, and manage files in sandboxed directories via @modelcontextprotocol/server-filesystem (npx) — 14 tools
@modelcontextprotocol/server-filesystem is Anthropic's official reference filesystem MCP server. It provides 14 tools for comprehensive file management, with all operations sandboxed to explicitly allowed directories (passed as CLI args). Security is enforced: any attempt to access files outside the allowed directories returns "Access denied." Edit operations return git-style unified diffs. This is the most commonly used MCP server in the ecosystem — first comprehensive verified recipe.
@modelcontextprotocol/server-filesystem v0.2.0 (npm, Anthropic official) — Verified Recipe
Install: npm install --prefix /tmp/mcp-fs @modelcontextprotocol/server-filesystem Run: node /tmp/mcp-fs/node_modules/@modelcontextprotocol/server-filesystem/dist/index.js /path/to/allowed/dir [/more/dirs...] Transport: stdio Server name: "secure-filesystem-server" v0.2.0
Tools (14)
- `read_text_file`
({path, head?, tail?})— Read file as text with optional head/tail line limits - `read_file`
({path, head?, tail?})— DEPRECATED alias for readtextfile - `read_media_file`
({path})— Read image/audio as base64 with MIME type - `read_multiple_files`
({paths[]})— Batch read multiple files - `write_file`
({path, content})— Create or overwrite file - `edit_file`
({path, edits: [{oldText, newText}], dryRun?})— Line-based find-and-replace, returns git-style unified diff - `create_directory`
({path})— Create a directory (single level only!) - `list_directory`
({path})— List with [FILE]/[DIR] prefixes - `list_directory_with_sizes`
({path, sortBy?})— List with sizes, sortable by size/name - `directory_tree`
({path, excludePatterns?[]})— Recursive JSON tree - `move_file`
({source, destination})— Move/rename files and dirs - `search_files`
({path, pattern, excludePatterns?[]})— Glob-pattern file search - `get_file_info`
({path})— Size, timestamps, permissions, type - `list_allowed_directories`
({})— Show sandbox boundaries
Verified Calls (28 total, 100% success)
Read operations:
read_text_file— returns plain text contentread_text_filewithhead: 2— returns first 2 lines ✓read_text_filewithtail: 1— ⚠️ returns empty for newline-terminated files (see gotchas)read_multiple_files— batch read with per-file content ✓read_media_file— returns base64 image content type ✓read_file(deprecated) — still works, same as readtextfile ✓- Unicode (Turkish, Japanese, emoji) — works perfectly ✓
Write/Edit operations:
write_file— creates new file or overwrites ✓edit_file— find-replace with git-style diff output ✓edit_filewithdryRun: true— shows diff WITHOUT applying ✓ (verified file unchanged)
Directory operations:
create_directory— creates single-level directory ✓ (but NOT recursive — see gotchas)list_directory— shows [FILE]/[DIR] prefixed entries ✓list_directory_with_sizes— file sizes + totals, sortable ✓directory_tree— recursive JSON structure ✓directory_treewithexcludePatterns— filtered tree ✓
File management:
move_file— rename/move within sandbox ✓search_files— glob pattern matching ✓search_fileswithexcludePatterns— exclusion works ✓get_file_info— size, created, modified, accessed, permissions (644), isFile/isDirectory ✓
Security:
read_text_fileon/etc/hosts(outside sandbox) → "Access denied - path outside allowed directories" ✓list_allowed_directories— returns the CLI-specified paths ✓
Key Gotchas
- ⚠️ `create_directory` does NOT create intermediate parents — despite docs claiming "Can create multiple nested directories in one operation",
/a/b/cfails with "Parent directory does not exist". Must create each level separately. - ⚠️ `tail: 1` returns empty on newline-terminated files — Unix text files end with
\n, so the "last line" is empty. Usetail: 2to get the actual last content line. - Allowed directories must exist before server starts — server crashes on startup if a directory doesn't exist.
- macOS `/tmp` → `/private/tmp` symlink — server resolves symlinks, so paths in output use
/private/tmp/...even if you passed/tmp/.... - `edit_file` uses text matching, NOT line numbers —
oldTextmust match exactly (including whitespace). Returns diff showing context. - `dryRun` defaults to false — edits are applied
{ "server": "secure-filesystem-server", "npm": "@modelcontextprotocol/server-filesystem", "version": "0.2.0", "publisher": "Anthropic (Model Context Protocol)", "transport": "stdio", "cli_args": ["node", "dist/index.js", "/path/to/allowed/dir"], "tools_count": 14, "tools": ["read_text_file", "read_file", "read_media_file", "read_multiple_files", "write_file", "edit_file", "create_directory", "list_directory", "list_directory_with_sizes", "directory_tree", "move_file", "search_files", "get_file_info", "list_allowed_directories"], "calls": [ { "tool": "list_allowed_directories", "result": "/private/tmp/mcp-test-sandbox", "ms": 2 }, { "tool": "write_file", "args": { "path": "sandbox/hello.txt", "content": "Hello from MCP! Line 2 Line 3 " }, "result": "Successfully wrote", "ms": 1 }, { "tool": "read_text_file", "args": { "path": "sandbox/hello.txt" }, "result": "Hello from MCP! Line 2 Line 3 ", "ms": 1 }, { "tool": "read_text_file", "args": { "head": 2 }, "result": "Hello from MCP! Line 2", "ms": 1 }, { "tool": "read_text_file", "args": { "tail": 1 }, "result": "(empty — last line after trailing newline)", "ms": 0 }, { "tool": "edit_file", "args": { "edits": [ { "oldText": "Line 2", "newText": "LINE_2_EDITED" } ], "dryRun": false }, "result": "--- original +++ modified -Line 2 +LINE_2_EDITED", "ms": 1 }, { "tool": "edit_file", "args": { "dryRun": true }, "result": "diff shown but NOT applied", "ms": 1 }, { "tool": "read_text_file", "label": "verify-dry-run-unchanged", "result": "LINE_2_EDITED (not LINE_2_DRYRUN)", "ms": 1 }, { "tool": "create_directory", "args": { "path": "sandbox/nested/deep" }, "result": "error: Parent directory does not exist", "ms": 0 }, { "tool": "list_directory", "result": "[FILE] data.json [FILE] hello.txt [DIR] subdir", "ms": 1 }, { "tool": "list_directory_with_sizes", "args": { "sortBy": "size" }, "result": "[DIR] subdir [FILE] hello.txt 37 B [FILE] data.json 29 B Total: 2 files, 1 directories, 66 B", "ms": 0 }, { "tool": "directory_tree", "result": "[{name:'data.json',type:'file'},{name:'hello.txt',type:'file'},{name:'subdir',type:'directory',children:[]}]", "ms": 0 }, { "tool": "directory_tree", "args": { "excludePatterns": ["*.json"] }, "result": "hello.txt and subdir only", "ms": 1 }, { "tool": "search_files", "args": { "pattern": "*.txt" }, "result": "sandbox/hello.txt", "ms": 1 }, { "tool": "get_file_info", "result": "size:37, permissions:644, isFile:true", "ms": 1 }, { "tool": "move_file", "args": { "source": "moveme.txt", "destination": "moved.txt" }, "result": "Successfully moved", "ms": 1 }, { "tool": "write_file", "label": "unicode", "args": { "content": "Türkçe: şğüöçı 日本語テスト 🌍 Emoji test 🚀 " }, "result": "Successfully wrote", "ms": 0 }, { "tool": "read_text_file", "label": "read-unicode", "result": "Türkçe: şğüöçı 日本語テスト 🌍 Emoji test 🚀 ", "ms": 1 }, { "tool": "read_text_file", "label": "security-outside-sandbox", "args": { "path": "/etc/hosts" }, "result": "Access denied - path outside allowed directories", "ms": 0 } ], "total_calls": 28, "success_rate": "100%", "p50_ms": 1, "max_ms": 2 }