Filesystem MCP Server
Overview
Filesystem-MCP is a Model Context Protocol server that lets AI assistants read and write files within explicitly allowed directories. Sensitive file patterns (.env, *.pem, *id_rsa*) are blocked by default. It exposes filesystem tools, resources, and prompts over stdio or Streamable HTTP transport.
| Aspect | Details |
|---|---|
| Status | Active (see npm badge for the current version) |
| Language | TypeScript (strict) |
| Runtime | Node.js >= 24 |
| Package | npm |
| License | MIT |
Features
| Feature | Description |
|---|---|
| Path guarding | Every path is validated against allowed roots; .env, *.pem, *id_rsa* and similar patterns are denied |
| Filesystem tools | Navigate, inspect, read, and write across all major file operations |
| Batch operations | Most tools accept path, paths[], or files[] for parallel execution |
| Dual transport | stdio by default; --port enables Streamable HTTP |
| File subscriptions | Resource subscriptions push change notifications when watched files update |
| Regex safety | RE2 in all search tools: linear-time matching, so no pattern can ReDoS the server |
Built with
| Layer | Technology |
|---|---|
| Protocol | MCP SDK v2 (@modelcontextprotocol/server) |
| Runtime | Node.js >= 24 · TypeScript 6 · ESM |
| Transport | stdio (default) · Streamable HTTP (--port) |
| Regex | RE2 (re2-wasm) — linear time, no lookahead/lookbehind/backreferences |
| Container | Docker alpine · multi-stage build · non-root user |
Table of Contents
Quick start
[!NOTE] Requires Node.js ≥ 24.
Prerequisites
| Requirement | Version / Notes |
|---|---|
| Node.js | ≥ 24 |
| npm | Bundled with Node.js |
| Docker | Optional — for container use |
Install via npx
npx -y @j0hanz/filesystem-mcp /path/to/allowed/dir
Or install globally:
npm install -g @j0hanz/filesystem-mcp
filesystem-mcp /path/to/allowed/dir
Install via Docker
docker run -i --rm \
-v /path/to/project:/workspace:ro \
ghcr.io/j0hanz/filesystem-mcp:latest \
--read-only /workspace
Configure in VS Code
Add to .vscode/mcp.json:
{
"servers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/project"]
}
}
}
Or install via CLI:
code --add-mcp '{"name":"filesystem","command":"npx","args":["-y","@j0hanz/filesystem-mcp@latest","/path/to/project"]}'
Configure in Visual Studio
Add to .vs\mcp.json in your solution directory, or %USERPROFILE%\.mcp.json for a global configuration:
{
"servers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/project"]
}
}
}
Configure in Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/project"]
}
}
}
Install in Cursor
Add to .cursor/mcp.json in your project root (project-scoped), or ~/.cursor/mcp.json for a global configuration:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/project"]
}
}
}
Docker configuration
VS Code (.vscode/mcp.json) and Visual Studio (.vs\mcp.json):
{
"servers": {
"filesystem": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-v",
"/path/to/project:/workspace",
"ghcr.io/j0hanz/filesystem-mcp:latest",
"/workspace"
]
}
}
}
Claude Desktop (claude_desktop_config.json) and Cursor (mcp.json):
{
"mcpServers": {
"filesystem": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-v",
"/path/to/project:/workspace",
"ghcr.io/j0hanz/filesystem-mcp:latest",
"/workspace"
]
}
}
}
[!NOTE] For least privilege, use both controls:
:romakes the container mount read-only at the operating-system boundary, while the server's--read-onlyflag removes mutating tools (create,edit,move,delete,patch,replace_text) fromtools/list.
Usage
Tools
All tools are scoped to the configured roots. Call list_roots first to discover what is allowed.
Navigate
| Tool | Description |
|---|---|
list_roots | List allowed workspace roots. Call this first — all other tools scope to these. |
list | List directory contents. Returns entries (dirs-first, alphabetical) and an ASCII tree. |
find_files | Find files by glob pattern (e.g. **/*.ts). Returns matching files with metadata. |
Inspect
| Tool | Description |
|---|---|
stat | Get file/directory metadata: size, modified time, permissions, MIME type, token estimate. |
search_text | Search file contents for text (grep-like). Returns matching lines with context. |
diff | Compare two files and return a unified diff with added/removed line counts. |
Read
| Tool | Description |
|---|---|
read | Read a text file. Supports head/tail and line ranges. Accepts paths[] for batches. |
Write
| Tool | Description |
|---|---|
create | Create one or more files, overwriting existing content and creating parent directories as needed. |
edit | Apply sequential literal string replacements to one or more files (max 5 per call). |
move | Move, rename, or copy (copy: true) one or more files/directories to explicit destinations. |
delete | Permanently delete one or more files or directories. This action is irreversible. |
replace_text | Bulk search-and-replace across files matching a glob pattern. |
patch | Apply a single-file unified diff and write the result. |
Resources
| URI | Description |
|---|---|
internal://instructions | Server navigation guide — tools overview, constraints, and error recovery. |
filesystem-mcp://file/{+path} | Read a workspace file. Subscribe to receive push notifications on change. |
filesystem-mcp://result/{id} | Ephemeral cached tool output. Expires after ~60 seconds, eviction, or server restart. |
Prompts
| Prompt | Description |
|---|---|
get-help | Return usage instructions, optionally filtered to a specific section. |
Project structure
filesystem-mcp/
├── __tests__/ Test suites
├── scripts/ Build and task utilities
├── src/
│ ├── core/ Path guarding, filesystem abstraction, concurrency, observability
│ ├── tools/ Tool definitions and registration
│ ├── index.ts Process entrypoint and transport selection
│ ├── server.ts Server factory and registrar composition
│ ├── transport/ stdio and Streamable HTTP transport setup
│ ├── prompts.ts Prompt definitions and registration
│ └── resources.ts Resource definitions and registration
└── Dockerfile Multi-stage alpine build, non-root user
Runtime composition flows from src/index.ts to src/transport.ts, then to
src/server.ts, the registrars, and finally src/core/. Each registrar owns
the narrow dependency contract it consumes.
| Path | Purpose |
|---|---|
src/core/path.ts | PathGuard — validates every path against allowed roots |
src/core/fs.ts | GuardedFileSystem — guarded filesystem facade |
src/tools/define.ts | Tool registration and execution framework |
src/tools/batch.ts | Batch helpers (runOverPaths, normalizeBatchItems) |
src/server.ts | Builds shared dependencies and invokes the three registrars |
src/transport.ts | Owns stdio and Streamable HTTP setup around the server factory |
Configuration
The server starts with allowed directories from explicit startup configuration:
- Positional directories passed to
filesystem-mcp. - Environment variable
FS_ALLOWED_DIRS(separated by:on POSIX or;on Windows). - Current working directory when
--allow-cwdis enabled.
Legacy MCP connections may additionally seed roots through the deprecated
roots/list flow. Modern 2026-07-28 connections do not automatically send
workspace roots. They can add access after startup by calling a tool with a
concrete path and approving the elicitation-backed grant. list_roots reports
the roots already configured or accepted; it cannot discover an unknown
workspace by itself.
Recommended global recipes
VS Code / Cursor / Claude Code (primary recipe)
Configure the project directory explicitly:
Add to your global or project-scoped configuration:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/project"]
}
}
}
Claude Desktop (fallback recipe via environment variable)
Claude Desktop and similar clients don't support the MCP Roots protocol. Use the FS_ALLOWED_DIRS environment variable to configure allowed folders.
Add to your claude_desktop_config.json:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@j0hanz/filesystem-mcp@latest"],
"env": {
"FS_ALLOWED_DIRS": "/path/to/project1:/path/to/project2"
}
}
}
}
(On Windows, separate directories with a semicolon ; instead of a colon :).
Advanced / per-project positional arguments
You can also restrict access to specific directories by passing positional arguments directly:
# Start with explicit positional paths
filesystem-mcp /path/to/project1 /path/to/project2
Configuration reference
CLI flags
| Flag | Default | Purpose |
|---|---|---|
[dirs...] | — | One or more allowed root directories (positional) |
--allow-cwd | false | Also allow the current working directory as a root |
--walk-cwd | false | Walk up from CWD to find a project root; implies --allow-cwd |
--allow-missing-roots | false | Start even if configured allowed directories do not exist |
--port <n> | — | Enable Streamable HTTP transport on the given port (env: FS_PORT) |
--http-host <host> | — | HTTP server bind address (env: FS_HTTP_HOST) |
--api-key <key> | — | Require this API key on HTTP requests (env: FS_API_KEY) |
--read-only | false | Disable write tools: create, edit, delete, move, patch, replace_text |
--safe | false | Alias for --read-only |
--deny <pattern> | — | Block paths matching this pattern; repeatable |
--allow-sensitive | false | Allow access to sensitive system paths (env: FS_ALLOW_SENSITIVE) |
--root-boundary <path> | — | Require all allowed roots to fall under this path (env: FS_ROOT_BOUNDARY) |
--max-file-size <bytes> | — | Maximum file size for reads in bytes (env: FS_MAX_FILE_SIZE) |
--log-level <level> | info | RFC 5424 log level, debug through emergency (env: FS_LOG_LEVEL) |
--print-config | false | Print the active configuration and exit (use --json for machine-readable output) |
--json | false | Output --print-config as JSON |
Environment variables
All boolean variables accept true or 1 to enable and false, 0, or
unset to disable; any other value logs a warning and reads as disabled.
Flags take precedence when both are set.
| Variable | Purpose |
|---|---|
FS_ALLOWED_DIRS | Colon-separated (POSIX) or semicolon-separated (Windows) list of directories to allow. |
FS_ROOT_BOUNDARY | Path prefix all allowed roots must fall under (mirrors --root-boundary). |
FS_ALLOW_CWD_WALK | Walk up from CWD to find a project root (mirrors --walk-cwd). |
FS_ALLOW_MISSING_ROOTS | Start even if configured directories do not exist (mirrors --allow-missing-roots). |
FS_ALLOW_SENSITIVE | Allow access to sensitive system paths (mirrors --allow-sensitive). |
FS_DENYLIST | Comma-separated list of paths or patterns to block (mirrors --deny). |
FS_MAX_FILE_SIZE | Maximum file size for reads in bytes (mirrors --max-file-size). |
FS_LOG_LEVEL | RFC 5424 log level: debug, info, notice, warn/warning, error, critical, alert, or emergency (mirrors --log-level). |
FS_PORT | Start the Streamable HTTP transport on this port; unset = stdio (mirrors --port). |
FS_HTTP_HOST | HTTP server bind address (mirrors --http-host). |
FS_API_KEY | API key required on HTTP requests (mirrors --api-key). |
FS_TRUST_PROXY | Express trust proxy setting: hop count or expression. Unset = do not trust X-Forwarded-*. |
FS_ALLOWED_HOSTS | Comma-separated Host header values to accept (HTTP transport). |
FS_ALLOWED_ORIGINS | Comma-separated origin hostnames for CORS. |
FS_ALLOW_UNRESTRICTED_HOSTS | Bind a wildcard host with no Host validation (accepts the risk). |
FS_PUBLIC_URL | Resource identifier URL for RFC 9728 discovery. |
FS_RATE_LIMIT_RPM | Per-client-IP requests/minute (default 120 with API-key authentication, 6,000 for keyless loopback; range 1–100000). |
FS_MAX_REQUEST_BYTES | Max HTTP request body bytes (default 4194304, 1024–268435456). |
FS_KEEPALIVE_TIMEOUT_MS | HTTP keep-alive timeout in ms; set above any fronting proxy's idle timeout (default 5000, 1000–600000). |
FS_MAX_WATCHERS | Max concurrent file watchers (default 256, 1–4096). |
FS_MAX_INLINE_MATCHES | Deprecated and ignored; maxResults sets the search_text page size. Logs a warning when set; removed in the next major. |
FS_MAX_READ_MANY_BYTES | Max total bytes across a batched read (default 524288, 10240–104857600). |
FS_SEARCH_TIMEOUT_MS | Search timeout in ms (default 5000, 100–60000). |
NO_COLOR | Any value disables ANSI color output. |
FS_REQUEST_STATE_KEY | HMAC key sealing input_required requestState across retry rounds. Optional (random per boot if unset); set it, at >=32 bytes UTF-8, to keep in-flight rounds alive across a restart. |
Examples
# Allow current working directory
filesystem-mcp --allow-cwd
# HTTP transport on port 3000
filesystem-mcp --port 3000
Scripts
| Mode | Command | Description |
|---|---|---|
| Full check | node scripts/tasks.mjs | Run build, type check, lint, format, knip, and tests |
| Auto-fix + check | node scripts/tasks.mjs fix | Auto-fix formatting/linting and run the full check |
| Static only | node scripts/tasks.mjs --quick | Run static analysis without tests |
| Tests only | node scripts/tasks.mjs test | Run tests; accepts native node --test options |
Security
[!IMPORTANT] Report vulnerabilities privately via GitHub Security Advisories. Do not open public issues for security reports.
| Topic | Detail |
|---|---|
| Path traversal | Every path is resolved and validated against allowed roots before any operation |
| Sensitive files | .env, *.pem, *id_rsa*, and similar patterns are denied by default |
| Regex safety | RE2 cannot backtrack, so a hostile pattern cannot hang the server (ReDoS) |
| Container | Runs as non-root mcp user; bind mounts control what is exposed |
Contributing
- Fork the repository.
- Create a feature branch:
git checkout -b feat/your-feature. - Commit your changes with a clear message.
- Run
node scripts/tasks.mjsto confirm tests, types, lint, formatting, and knip all pass. - Open a pull request.
License
Released under the MIT License. See LICENSE for details.