Files MCP Server
Stdio MCP server for sandboxed file access — explore directories, read files, search content, and safely edit with checksum verification.
Author: overment
WARNING
This server provides filesystem access to an AI agent. While it's sandboxed to specific directories, always:
Review tool outputs before confirming changes
Use dryRun=true to preview destructive operations
Keep backups of important files
Set FS_ROOTS to only the directories you want the agent to access
Motivation
Traditional file operations require precise paths and exact content — things LLMs struggle with. This server is designed so AI agents can:
Explore first — understand directory structure before acting
Find by name or content — locate files without knowing exact paths
Edit safely — checksum verification prevents stale overwrites
Preview changes — dry-run mode shows diffs before applying
Recover from errors — hints guide the agent to correct mistakes
The result: an agent that can reliably manage your Obsidian vault, documentation, notes, or any text-based file collection.
Features
✅ Directory Exploration — tree view with file counts, sizes, timestamps
✅ File Reading — line-numbered content with checksums for safe editing
✅ Find Files — recursive search by filename patterns (*.md, config.json)
✅ Content Search — literal, regex, fuzzy, and smart pattern matching
✅ Preset Patterns — built-in Obsidian patterns (wikilinks, tags, tasks, headings)
✅ Safe Editing — checksum verification, dry-run preview, unified diffs
✅ Batch Operations — replaceAll for bulk renames across a file
✅ Multi-Mount Support — access multiple directories as virtual mount points
✅ Sandboxed — cannot access paths outside configured mounts
Design Principles
Explore before edit: Agent must read a file before modifying it (gets checksum + line numbers)
Preview before apply: dryRun=true shows exactly what would change
Clear feedback: Every response includes hints for next steps and error recovery
Compact by default: File details (size, modified) only shown when details=true
Single mount optimization: When one mount is configured, fs_read(".") shows contents directly
Quick Start
1. Install
2. Configure
Create .env:
# Directories the agent can access (comma-separated)
FS_ROOTS=/path/to/vault,/path/to/docs
# Or for a single directory:
# FS_ROOT=/path/to/vault
# Optional
LOG_LEVEL=info
MAX_FILE_SIZE=1048576
3. Run
4. Connect to Client
Claude Desktop / Cursor:
{
"mcpServers": {
"filesystem": {
"command": "bun",
"args": ["run", "/absolute/path/to/files-mcp/src/index.ts"],
"env": {
"FS_ROOTS": "/Users/you/vault,/Users/you/docs"
}
}
}
}
MCP Bundle (MCPB)
This server is also available as an MCP Bundle (.mcpb) for one-click installation in supported apps like Claude Desktop, Alice, and other MCPB-compatible applications.
What is MCPB?
MCP Bundles are zip archives containing a local MCP server and a manifest.json that describes the server and its capabilities. The format enables end users to install local MCP servers with a single click — no manual configuration required.
Installing from MCPB
Download the files-mcp.mcpb file
Open it with a compatible app (Claude Desktop, Alice, etc.)
Configure the Root Directory when prompted — this is the directory the agent will have access to
Done! The server is installed and ready to use
manifest.json
The manifest defines:
Server configuration — command, args, environment variables
Tools — fs_read and fs_write with descriptions
User config — prompts for FS_ROOT directory during installation
{
"manifest_version": "0.2",
"name": "files-mcp",
"version": "1.0.0",
"server": {
"type": "node",
"entry_point": "dist/index.js",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/dist/index.js"],
"env": {
"FS_ROOT": "${user_config.FS_ROOT}"
}
}
},
"user_config": {
"FS_ROOT": {
"type": "directory",
"title": "Root Directory",
"description": "The directory the agent will have access to.",
"required": true
}
}
}
The ${user_config.FS_ROOT} syntax injects the user-selected directory into the server's environment at runtime.
Server Instructions (What the Model Sees)
🔒 SANDBOXED FILESYSTEM — This tool can ONLY access specific mounted directories.
You CANNOT access arbitrary system paths like /Users or C:\.
Always start with fs_read(".") to see available mounts.
⚠️ ALWAYS read a file BEFORE answering questions about its content.
⚠️ ALWAYS read a file BEFORE modifying it (you need the checksum).
MANDATORY WORKFLOW:
1. fs_read(".") → see available mounts
2. fs_read("path/file.md") → get content + checksum
3. fs_write with dryRun=true → preview diff
4. fs_write with dryRun=false + checksum → apply change
5. Verify diff in response matches your intent
Tools
fs_read
Explore directories, read files, find files by name, or search content.
Input:
{
path: string; // "." for root, "docs/", "notes/todo.md"
// Finding files by name
find?: string; // "*.md", "config.json"
// Searching content
pattern?: string; // Text to search for
patternMode?: "literal" | "regex" | "fuzzy" | "smart";
preset?: "wikilinks" | "tags" | "tasks" | "tasks_open" | "tasks_done"
| "headings" | "codeblocks" | "frontmatter";
// Options
depth?: number; // Directory traversal depth (default 3)
details?: boolean; // Include size/modified (default false)
lines?: string; // "10-50" for partial read
context?: number; // Lines around matches (default 3)
maxFiles?: number; // Max files with matches to return (default: no limit)
maxMatches?: number; // Limit matches (default 100)
}
Output:
{
success: boolean;
path: string;
type: "directory" | "file" | "search";
// For directories
tree?: {
entries: Array<{ path, kind, children?, size?, modified? }>;
summary: string;
};
// For files
content?: {
text: string; // With line numbers
checksum: string; // Pass to fs_write
totalLines: number;
};
// For searches
matches?: Array<{ file, line, column, text, context }>;
matchCount?: number;
hint: string; // Next action suggestion
}
fs_write
Create, modify, or delete files with safety features.
Input:
{
path: string;
operation: "create" | "update" | "delete";
// For create
content?: string;
// For update — target by lines OR pattern
lines?: string; // "10-15" — PREFERRED
pattern?: string; // Text to find
patternMode?: "literal" | "regex" | "fuzzy" | "smart";
// For update — action
action?: "replace" | "insert_before" | "insert_after" | "delete_lines";
content?: string; // New content
// Batch replace
replaceAll?: boolean; // Replace ALL occurrences
// Safety
checksum?: string; // From fs_read — RECOMMENDED
dryRun?: boolean; // Preview only (default false)
}
Output:
{
success: boolean;
path: string;
operation: "create" | "update" | "delete";
applied: boolean;
result?: {
action: string;
linesAffected?: number;
newChecksum?: string;
diff?: string; // Unified diff
};
error?: {
code: string;
message: string;
recoveryHint?: string;
};
hint: string;
}
Preset Patterns (Obsidian/Markdown)
Built-in patterns for common searches:
Preset | Finds |
wikilinks
| [[Note]]
and [[Note|Display]]
|
tags
| #tag
and #nested/tag
|
tasks
| - [ ]
and - [x]
(all tasks) |
tasks_open
| - [ ]
only (incomplete) |
tasks_done
| - [x]
only (completed) |
headings
| #
through ######
|
codeblocks
| ```
code blocks |
frontmatter
| YAML ---
blocks |
Example:
{ "path": ".", "preset": "tasks_open" }
Examples
1. Explore the vault
Response:
18 items (15 files, 3 directories)
- Core/
- Projects/
- Books/
- map.md
- inbox.md
...
hint: "Showing contents of 'vault'. Use fs_read on any path to explore deeper."
2. Find a file
{ "path": ".", "find": "*.md" }
Response:
Found 42 item(s) matching "*.md"
- Core/Values.md
- Core/Process.md
- Projects/Alice.md
...
hint: "Found 42 matching files. Use fs_read on a specific path to see its content."
3. Read a file
{ "path": "Core/Values.md" }
Response:
File read complete. Checksum: a1b2c3d4e5f6.
1| # Values
2|
3| ## Integrity
4| Be honest, even when it's hard.
5|
6| ## Growth
7| Learn something new every day.
...
hint: "To edit this file, use fs_write with checksum a1b2c3d4e5f6."
4. Find all incomplete tasks
{ "path": ".", "preset": "tasks_open" }
Response:
Found 7 matches in 42 files across all mounts.
- Projects/Alice.md:12 — "- [ ] Implement search"
- Projects/Alice.md:15 — "- [ ] Add tests"
- inbox.md:3 — "- [ ] Review PR"
...
5. Replace text (preview first)
{
"path": "Core/Values.md",
"operation": "update",
"pattern": "Be honest",
"action": "replace",
"content": "Act with integrity",
"checksum": "a1b2c3d4e5f6",
"dryRun": true
}
Response:
DRY RUN — no changes applied.
--- a/Core/Values.md
+++ b/Core/Values.md
@@ -3,1 +3,1 @@
-Be honest, even when it's hard.
+Act with integrity, even when it's hard.
hint: "Review the diff above. Run with dryRun=false to apply."
6. Bulk rename wikilinks
{
"path": "Projects/Alice.md",
"operation": "update",
"pattern": "[[Old Name]]",
"action": "replace",
"content": "[[New Name]]",
"replaceAll": true,
"dryRun": true
}
Response:
DRY RUN — would replace 3 occurrence(s) at lines 5, 12, 28.
7. Mark task as complete
{
"path": "inbox.md",
"operation": "update",
"pattern": "- [ ] Review PR",
"action": "replace",
"content": "- [x] Review PR",
"checksum": "xyz789"
}
Response:
replaced 1 line(s). New checksum: abc123.
hint: "The diff above shows what changed."
Configuration
Variable | Default | Description |
FS_ROOTS
| .
| Comma-separated paths the agent can access |
FS_ROOT
| .
| Single path (backward compatibility) |
MCP_NAME
| files-mcp
| Server name |
MCP_VERSION
| 1.0.0
| Server version |
LOG_LEVEL
| info
| Log level: debug, info, warning, error |
MAX_FILE_SIZE
| 1048576
| Max file size in bytes (1MB) |
Multi-Mount Setup
Access multiple directories:
FS_ROOTS=/Users/me/vault,/Users/me/projects,/Users/me/notes
Each path becomes a mount named after its folder:
Client Configuration
Claude Desktop:
{
"mcpServers": {
"filesystem": {
"command": "bun",
"args": ["run", "/path/to/files-mcp/src/index.ts"],
"env": {
"FS_ROOTS": "/Users/me/vault"
}
}
}
}
Cursor:
{
"filesystem": {
"command": "bun",
"args": ["run", "/path/to/files-mcp/src/index.ts"],
"env": {
"FS_ROOTS": "/Users/me/vault"
}
}
}
Development
bun dev # Start with hot reload
bun test # Run tests
bun run typecheck # TypeScript check
bun run lint # Lint code
bun run build # Production build
bun run inspector # Test with MCP Inspector
Architecture
src/
├── index.ts # Entry point: stdio transport
├── config/
│ ├── env.ts # Environment config & mount parsing
│ └── metadata.ts # Tool descriptions
├── core/
│ ├── capabilities.ts # Server capabilities
│ └── mcp.ts # McpServer builder
├── tools/
│ ├── index.ts # Tool registration
│ ├── fs-read.tool.ts # Read, explore, search
│ └── fs-write.tool.ts # Create, update, delete
├── lib/
│ ├── checksum.ts # SHA256 checksums
│ ├── diff.ts # Unified diff generation
│ ├── filetypes.ts # Text/binary detection
│ ├── ignore.ts # .gitignore support
│ ├── lines.ts # Line manipulation
│ ├── paths.ts # Multi-mount path resolution
│ └── patterns.ts # Pattern matching & presets
└── utils/
├── errors.ts # Error utilities
└── logger.ts # Logging
Troubleshooting
Issue | Solution |
"SANDBOXED FILESYSTEM: Absolute paths not allowed" | Use relative paths within mounts. Start with fs_read(".")
to see available mounts. |
"Path does not match any mount" | Check FS_ROOTS
is set correctly. Paths must start with a mount name (e.g., vault/notes.md
). |
"CHECKSUM_MISMATCH" | File changed since you read it. Re-read with fs_read
to get fresh content. |
"PATTERN_NOT_FOUND" | Pattern doesn't exist in file. Try patternMode="fuzzy"
or read file first. |
"MULTIPLE_MATCHES" | Pattern matches multiple times. Use replaceAll=true
or be more specific. |
Binary file errors | Only text files can be read/written. Check file extension. |
Single mount still shows "docs" | Restart the MCP server after changing FS_ROOTS
. |
License
MIT