MCP server by portertech
Filesystem MCP Server
A secure, high-performance Model Context Protocol (MCP) server that gives AI coding agents like Crush and OpenCode safe access to your local filesystem. Built in Go with directory sandboxing, symlink attack prevention, and atomic writes—so you can confidently let AI agents read, write, and manage files within boundaries you control.
Features
- 17 filesystem tools for comprehensive file operations
- Secure by default: Only operates within explicitly allowed directories
- Symlink attack prevention: Resolves symlinks and validates targets
- Streaming support: Memory-efficient handling of large files
- Atomic writes: Uses temp files with rename for safe file operations
- Cross-platform: Works on Linux, macOS, and Windows
- AI agent optimized: Line range support and dynamic formatting for efficient code navigation
Installation
Docker (Recommended)
The easiest way to run the server is with Docker:
docker pull portertech/filesystem-mcp-server
See the Docker Usage section for configuration examples.
Go Install
go install github.com/portertech/filesystem-mcp-server/cmd/filesystem@latest
Build from Source
git clone https://github.com/portertech/filesystem-mcp-server.git
cd filesystem-mcp-server
make build
Usage
# Start server with allowed directories
filesystem /path/to/allowed/dir1 /path/to/allowed/dir2
# Enable verbose logging
filesystem -verbose /path/to/dir
# Show version
filesystem -version
# List allowed directories
filesystem -list /path/to/dir
Available Tools
read_text_file
Read the contents of a text file with optional partial read and line numbers.
Parameters:
path(required): Path to the file to readhead(optional): Number of lines to read from the starttail(optional): Number of lines to read from the endstart_line(optional): Starting line number (1-based, inclusive)end_line(optional): Ending line number (1-based, inclusive)line_numbers(optional): Include line numbers in output (default: false)
Notes:
start_line/end_linecannot be combined withhead/tail- Using
start_line/end_linealways includes line numbers (optimized for AI agent use) - Line number width dynamically adjusts based on total lines
Examples:
// Read specific line range (lines 50-100)
{"path": "/path/to/file.go", "start_line": 50, "end_line": 100}
// Read from line 200 to end of file
{"path": "/path/to/file.go", "start_line": 200}
// Read first 20 lines with line numbers
{"path": "/path/to/file.go", "head": 20, "line_numbers": true}
// Read last 10 lines with line numbers
{"path": "/path/to/file.go", "tail": 10, "line_numbers": true}
Returns: File contents as text, optionally with line numbers prefixed
read_file
Read the complete contents of a file. Deprecated: use read_text_file instead.
Parameters:
path(required): Path to the file to read
Returns: File contents as text
read_multiple_files
Read multiple files concurrently. Failed reads for individual files won't stop the entire operation.
Parameters:
paths(required): Array of file paths to readformat(optional): Output format -textorjson(default: text)
Returns: Array of file contents with their paths
read_media_file
Read a media file and return its contents as base64-encoded data.
Parameters:
path(required): Path to the media file
Returns: Base64-encoded file data with MIME type
write_file
Create or overwrite a file with new content using atomic writes (temp file + rename).
Parameters:
path(required): Path to the file to writecontent(required): Content to write to the file
Returns: Success confirmation
edit_file
Apply find/replace edits to a text file with git-style diff output. Supports exact matching and whitespace-normalized line matching.
Parameters:
path(required): Path to the file to editedits(required): Array of edit operations, each with:oldText: Text to search for (exact or whitespace-normalized match)newText: Text to replace withrequireUnique(optional): Require exactly one match (default: true)occurrence(optional): Which occurrence to replace when multiple exist (1-indexed)
dryRun(optional): Preview changes without applying (default: false)
Returns: Git-style diff showing changes made
copy_file
Copy a file to a new location. Uses streaming for memory-efficient handling of large files.
Parameters:
source(required): Path to the source filedestination(required): Path to the destination fileoverwrite(optional): Overwrite existing destination file (default: false)
Returns: Success confirmation
move_file
Move or rename a file or directory.
Parameters:
source(required): Current pathdestination(required): New path
Returns: Success confirmation
delete_file
Delete a file.
Parameters:
path(required): Path to the file to delete
Returns: Success confirmation
delete_directory
Delete a directory. Cannot delete allowed root directories.
Parameters:
path(required): Path to the directory to deleterecursive(optional): Delete contents recursively (default: false)
Returns: Success confirmation
create_directory
Create a directory, including any necessary parent directories.
Parameters:
path(required): Path to the directory to create
Returns: Success confirmation
list_directory
List the contents of a directory with [FILE] and [DIR] prefixes.
Parameters:
path(required): Path to the directory to listformat(optional): Output format -textorjson(default: text)
Returns: Array of directory entries with type indicators
list_directory_with_sizes
List directory contents with file sizes and optional sorting.
Parameters:
path(required): Path to the directory to listsortBy(optional): Sort field -name,size, ormodified(default: name)order(optional): Sort order -ascordesc(default: asc)format(optional): Output format -textorjson(default: text)
Returns: Array of entries with name, type, size, and modification time
directory_tree
Get a recursive tree view of files and directories as JSON.
Parameters:
path(required): Path to the root directoryexcludePatterns(optional): Array of glob patterns to exclude
Returns: JSON structure with name, type, and children for each entry
search_files
Recursively search for files matching a glob pattern.
Parameters:
path(required): Starting directory for the searchpattern(required): Glob pattern to match (e.g.,*.go,**/*.json)excludePatterns(optional): Array of patterns to excludeformat(optional): Output format -textorjson(default: text)
Returns: Array of matching file paths
get_file_info
Get detailed metadata about a file or directory.
Parameters:
path(required): Path to the file or directory
Returns: Metadata including:
size: Size in bytescreated: Creation timestampmodified: Last modification timestampaccessed: Last access timestampisDirectory: Whether path is a directoryisFile: Whether path is a filepermissions: Unix permission string
list_allowed_directories
List all directories the server is allowed to access.
Parameters: None
Returns: Array of allowed directory paths
Tool Annotations
This server sets MCP Tool Annotations on each tool to help clients understand tool behavior:
| Tool | readOnlyHint | idempotentHint | destructiveHint | Notes |
|-----------------------------|--------------|----------------|-----------------|---------------------------------------------|
| read_text_file | true | – | – | Pure read |
| read_file | true | – | – | Pure read (deprecated) |
| read_multiple_files | true | – | – | Pure read |
| read_media_file | true | – | – | Pure read |
| list_directory | true | – | – | Pure read |
| list_directory_with_sizes | true | – | – | Pure read |
| directory_tree | true | – | – | Pure read |
| search_files | true | – | – | Pure read |
| get_file_info | true | – | – | Pure read |
| list_allowed_directories | true | – | – | Pure read |
| create_directory | – | true | – | Re-creating existing dir is a no-op |
| write_file | – | true | true | Overwrites existing files |
| edit_file | – | – | true | Re-applying edits can fail or double-apply |
| copy_file | – | – | true | May overwrite destination |
| move_file | – | – | true | Source is removed |
| delete_file | – | – | true | Permanently removes file |
| delete_directory | – | – | true | Permanently removes directory |
Note:
–indicates the hint is not set (treated as unknown/unspecified by clients).
Configuration
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
Using Docker (Recommended)
{
"mcpServers": {
"filesystem": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/Users/username/Documents:/Users/username/Documents",
"-v", "/Users/username/Projects:/Users/username/Projects",
"portertech/filesystem-mcp-server",
"/Users/username/Documents", "/Users/username/Projects"
]
}
}
}
Using Binary
{
"mcpServers": {
"filesystem": {
"command": "/path/to/filesystem",
"args": [
"/Users/username/Documents",
"/Users/username/Projects"
]
}
}
}
Docker Usage
Running the server in Docker provides an additional layer of security through container isolation, limiting the agent's access to only the mounted directories. Mirror host paths in your volume mounts (e.g., -v /path:/path instead of -v /path:/data) so the AI agent sees the same paths you reference in conversation.
# Run with mounted directories
docker run -i \
-v /path/to/dir:/path/to/dir \
portertech/filesystem-mcp-server /path/to/dir
# Multiple directories
docker run -i \
-v /home/user/projects:/home/user/projects \
-v /home/user/documents:/home/user/documents \
portertech/filesystem-mcp-server /home/user/projects /home/user/documents
# Build locally (optional)
docker build -t filesystem-mcp-server .
Security
- Path validation: All paths are validated against allowed directories
- Symlink resolution: Symlinks are resolved and validated
- Null byte rejection: Paths with null bytes are rejected
- Parent traversal prevention:
..sequences cannot escape allowed directories - Atomic writes: File writes use temp files to prevent corruption
- Delete protection: Cannot delete allowed root directories
Symlink Handling
Symlinks are handled consistently across all tools to balance usability with security. The server supports symlinks when they resolve to paths within allowed directories, while protecting against symlink-based attacks.
Behavior by Operation Type
| Operation | Symlink Behavior | Rationale | |-----------|------------------|----------| | Read | Follow if target is within allowed directories | Safe - target path is validated before access | | Write/Edit | Reject symlinks | Prevents TOCTOU (time-of-check-time-of-use) attacks | | Delete | Reject symlinks | Prevents unintended deletion of symlink targets | | Create directory | Reject symlinks in path | Prevents creating directories through symlinked paths | | Traversal (search, tree) | Skip symlinks during recursion | Prevents infinite loops and directory escape |
Tool-Specific Behavior
| Tool | Root Path | During Traversal |
|------|-----------|------------------|
| read_text_file | Follows symlinks | N/A |
| read_file | Follows symlinks | N/A |
| read_multiple_files | Follows symlinks | N/A |
| read_media_file | Follows symlinks | N/A |
| write_file | Rejects symlinks | N/A |
| edit_file | Rejects symlinks | N/A |
| copy_file | Source: follows, Destination: rejects | N/A |
| move_file | Source: follows, Destination: rejects | N/A |
| delete_file | Rejects symlinks | N/A |
| delete_directory | Rejects symlinks | Rejects if directory contains symlinks |
| create_directory | Rejects symlinks in path | N/A |
| list_directory | Follows symlinks | Shows symlinks as entries |
| list_directory_with_sizes | Follows symlinks | Shows symlinks as entries |
| directory_tree | Follows symlinks | Skips symlinked entries |
| search_files | Follows symlinks | Skips symlinked files/directories |
| get_file_info | Follows symlinks | N/A |
Security Considerations
-
TOCTOU Prevention: Write operations reject symlinks to prevent race conditions where a symlink could be swapped between validation and the actual write.
-
Atomic Writes: File writes use a temp file + rename pattern with
O_EXCLflag to prevent symlink attacks on the temp file. -
Traversal Safety: During recursive operations, symlinks are skipped to prevent:
- Infinite loops from circular symlinks
- Escaping allowed directories via symlinks to parent paths
- Accessing files outside allowed directories
-
Allowed Directory Resolution: Allowed directories themselves are resolved via
filepath.EvalSymlinksat startup, so symlinked allowed directories work correctly.
Development
# Run tests
make test
# Run linter
make lint
# Format code
make fmt
# Build binary
make build
License
MIT