Filesystem MCP

Secure filesystem MCP server for reading, writing, searching, diffing, and patching files.

Documentation

Filesystem MCP Server

License npm version Build GitHub stars

Install in VS Code Install in VS Code Insiders Install in Visual Studio Install in Cursor

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.

AspectDetails
StatusActive (see npm badge for the current version)
LanguageTypeScript (strict)
RuntimeNode.js >= 24
Packagenpm
LicenseMIT

Features

FeatureDescription
Path guardingEvery path is validated against allowed roots; .env, *.pem, *id_rsa* and similar patterns are denied
Filesystem toolsNavigate, inspect, read, and write across all major file operations
Batch operationsMost tools accept path, paths[], or files[] for parallel execution
Dual transportstdio by default; --port enables Streamable HTTP for both 2025-era and 2026-07-28 clients
File subscriptionsResource subscriptions push change notifications when watched files update
Regex safetyRE2 in all search tools: linear-time matching, so no pattern can ReDoS the server

Compared with the reference server

How this server differs from @modelcontextprotocol/server-filesystem, checked against its README and source on 2026-09-24:

Capabilityfilesystem-mcpReference server
Search inside filessearch_text: RE2 regex or literal, linear timeNone; search_files matches names only
Secret files.env, *.pem, *id_rsa* denied by defaultNot blocked
Read-only mode--read-only removes every mutating toolDocker ro mounts only
Apply a unified diffpatchNone
Compare two filesdiffNone
Replace across many filesreplace_text over a globNone
Watch filesResource subscriptions push change notificationsNo resources
Transportstdio, or Streamable HTTP with --portstdio

Built with

Node.js TypeScript Docker

LayerTechnology
ProtocolMCP SDK v2 (@modelcontextprotocol/server)
RuntimeNode.js >= 24 · TypeScript 6 · ESM
Transportstdio (default) · Streamable HTTP (--port)
RegexRE2 (re2-wasm) — linear time, no lookahead/lookbehind/backreferences
ContainerDocker alpine · multi-stage build · non-root user

Table of Contents

Quick start

[!NOTE] Requires Node.js ≥ 24.

Prerequisites

RequirementVersion / Notes
Node.js≥ 24
npmBundled with Node.js
DockerOptional — 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

One click: download filesystem-mcp.mcpb, open it with Claude Desktop, and pick the directories to allow. Claude Desktop's built-in Node.js runs it.

Or configure it by hand. 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"]
    }
  }
}

Install as a plugin

The filesystem-mcp plugin wires the server up with project-scoped defaults:

ClientInstall
Claude Code/plugin marketplace add j0hanz/j0hanz-marketplace, then /plugin install filesystem-mcp@j0hanz-marketplace
Copilot CLIcopilot plugin marketplace add j0hanz/j0hanz-marketplace, then copilot plugin install filesystem-mcp@j0hanz-marketplace
Antigravity CLIgit clone https://github.com/j0hanz/j0hanz-marketplace, then agy plugin install ./j0hanz-marketplace/plugins/filesystem-mcp

The plugin README covers the defaults and how each client picks the project directory.

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: :ro makes the container mount read-only at the operating-system boundary, while the server's --read-only flag removes mutating tools (create, edit, move, delete, patch, replace_text) from tools/list.

Usage

Tools

All tools are scoped to the configured roots. Call list_roots first to discover what is allowed.

Navigate

ToolDescription
list_rootsList allowed workspace roots. Call this first — all other tools scope to these.
listList directory contents. Returns entries (dirs-first, alphabetical) and an ASCII tree.
find_filesFind files by glob pattern (e.g. **/*.ts). Returns matching files with metadata.

Inspect

ToolDescription
statGet file/directory metadata: size, modified time, permissions, MIME type, token estimate.
search_textSearch file contents for text (grep-like). Returns matching lines with context.
diffCompare two files and return a unified diff with added/removed line counts.

Read

ToolDescription
readRead a text file. Supports head/tail and line ranges. Accepts paths[] for batches.

Write

ToolDescription
createCreate one or more files, creating parent directories as needed. An existing file prompts the user to confirm the overwrite; overwrite: true on an entry skips the prompt, append: true adds to the end instead.
editApply sequential literal string replacements to one or more files (up to 5 files per call, 100 edits per file).
moveMove, rename, or copy (copy: true) one or more files/directories to explicit destinations.
deletePermanently delete one or more files or directories. This action is irreversible.
replace_textBulk search-and-replace across files matching a glob pattern.
patchApply a single-file unified diff and write the result.

Resources

URIDescription
internal://instructionsServer 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

PromptDescription
get-helpReturn usage instructions, optionally filtered to a specific section.

Project structure

filesystem-mcp/
├── __tests__/          Test suites (node --test) and shared helpers
├── docs/adr/           Architecture decision records
├── mcpb/manifest.json  Claude Desktop extension manifest
├── scripts/            Release-path scripts (MCPB pack, Smithery publish)
├── src/
│   ├── core/           Path guarding, filesystem facade, search, stores, watchers
│   ├── tools/          One file per tool, plus define.ts (registration) and batch.ts
│   ├── transport/      stdio.ts, http.ts, http-policy.ts (auth, Origin, rate limit), shared.ts
│   ├── cli.ts          Argument parsing and --print-config
│   ├── cli-help.ts     --help / --version text
│   ├── index.ts        Process entrypoint, shutdown, transport selection
│   ├── instructions.ts Server instructions sent to every client
│   ├── prompts.ts      Prompt definitions and registration
│   ├── resources.ts    Resource definitions, subscriptions, completion
│   ├── server.ts       Server factory and registrar composition
│   └── transport.ts    Facade re-exporting startServer / startHttpServer
└── Dockerfile          Multi-stage alpine build, non-root user

Runtime composition flows from src/index.ts to src/transport/ (stdio or HTTP), then to src/server.ts, the registrars, and finally src/core/. Each registrar owns the narrow dependency contract it consumes.

PathPurpose
src/core/path.tsPathGuard — validates every path against allowed roots
src/core/fs.tsGuardedFileSystem — guarded filesystem facade
src/tools/define.tsTool registration and execution framework
src/tools/batch.tsBatch helpers (runOverPaths, isTotalFailure)
src/server.tsBuilds shared dependencies and invokes the three registrars
src/transport/stdio (stdio.ts), Streamable HTTP (http.ts), HTTP policy (http-policy.ts)

Configuration

The server starts with allowed directories from explicit startup configuration:

  1. Positional directories passed to filesystem-mcp.
  2. Environment variable FS_ALLOWED_DIRS (separated by : on POSIX or ; on Windows).
  3. Current working directory when --allow-cwd is 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.

Over HTTP, 2025-era clients are served statelessly: tools, resources and prompts work. Confirmations (recursive delete, overwrite, access grants) need a 2026-07-28 client or stdio and answer with a tool error saying so; file subscriptions are not advertised on that leg, and a resources/subscribe sent anyway is refused with method-not-found.

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

FlagDefaultPurpose
[dirs...]—One or more allowed root directories (positional). A whole argument ${NAME} is read from the environment and dropped when unset
--allow-cwdfalseAlso allow the current working directory as a root
--walk-cwdfalseWalk up from CWD to find a project root; implies --allow-cwd
--allow-missing-rootsfalseStart 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-onlyfalseDisable write tools: create, edit, delete, move, patch, replace_text
--deny <pattern>—Block paths matching this pattern; repeatable
--allow <pattern>—Exempt a pattern from the built-in sensitive denylist; repeatable (env: FS_ALLOWLIST). Does not lift --deny/FS_DENYLIST entries
--allow-sensitivefalseAllow 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>infoRFC 5424 log level, debug through emergency (env: FS_LOG_LEVEL)
--print-configfalsePrint the active configuration as JSON and exit

--deny and --allow patterns support * (any run within a segment), ** (any run of segments), ?, [...] classes, and {a,b} alternation. Dot-leading (hidden) names match like any other — secrets/** denies secrets/.env, *id_rsa* denies .id_rsa.

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.

VariablePurpose
FS_ALLOWED_DIRSColon-separated (POSIX) or semicolon-separated (Windows) list of directories to allow.
FS_ROOT_BOUNDARYPath prefix all allowed roots must fall under (mirrors --root-boundary).
FS_ALLOW_CWD_WALKWalk up from CWD to find a project root (mirrors --walk-cwd).
FS_ALLOW_MISSING_ROOTSStart even if configured directories do not exist (mirrors --allow-missing-roots).
FS_ALLOW_SENSITIVEAllow access to sensitive system paths (mirrors --allow-sensitive).
FS_DENYLISTComma-separated list of paths or patterns to block (mirrors --deny).
FS_ALLOWLISTComma-separated patterns exempted from the built-in sensitive denylist (mirrors --allow). Never lifts FS_DENYLIST/--deny entries.
FS_MAX_FILE_SIZEMaximum file size for reads in bytes (mirrors --max-file-size).
FS_LOG_LEVELRFC 5424 log level: debug, info, notice, warn/warning, error, critical, alert, or emergency (mirrors --log-level).
FS_PORTStart the Streamable HTTP transport on this port; unset = stdio (mirrors --port).
FS_HTTP_HOSTHTTP server bind address (mirrors --http-host).
FS_API_KEYAPI key required on HTTP requests (mirrors --api-key).
FS_TRUST_PROXYExpress trust proxy setting: hop count or expression. Unset = do not trust X-Forwarded-*.
FS_ALLOWED_HOSTSComma-separated Host header values to accept (HTTP transport).
FS_ALLOWED_ORIGINSComma-separated origin hostnames allowed to call /mcp from a browser. Replaces the localhost default, so also list localhost, 127.0.0.1 or [::1] if local browser clients still need access.
FS_ALLOW_UNRESTRICTED_HOSTSBind a wildcard host with no Host validation (accepts the risk).
FS_PUBLIC_URLResource identifier URL for RFC 9728 discovery.
FS_RATE_LIMIT_RPMPer-client-IP requests/minute (default 120 with API-key authentication, 6,000 for keyless loopback; range 1–100000).
FS_MAX_WATCHERSMax concurrent file watchers (default 256, 1–4096).
NO_COLORAny value disables ANSI color output.
FS_REQUEST_STATE_KEYHMAC 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

ModeCommandDescription
Full checknpm run checkRun build, type check, lint, format, knip, and tests
Auto-fix + checknpm run fixAuto-fix formatting/linting and run the full check
Static onlynpm run check:staticRun static analysis without tests
Tests onlynpm testRun tests; accepts native node --test options

Security

[!IMPORTANT] Report vulnerabilities privately via GitHub Security Advisories. Do not open public issues for security reports.

TopicDetail
Path traversalEvery 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 safetyRE2 cannot backtrack, so a hostile pattern cannot hang the server (ReDoS)
ContainerRuns as non-root mcp user; bind mounts control what is exposed

Contributing

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feat/your-feature.
  3. Commit your changes with a clear message.
  4. Run npm run check to confirm tests, types, lint, formatting, and knip all pass.
  5. Open a pull request.

Contributors

Privacy Policy

filesystem-mcp runs entirely on your machine. This policy covers the npm package, the Docker image, and the .mcpb desktop extension.

  • Data collection: none. The server has no telemetry, analytics, or crash reporting, and makes no outbound network requests.
  • Use and storage: files are read and written only inside the directories you allow, and only when your MCP client calls a tool. Tool results go to that client and nowhere else. Short-lived result caches live in memory and disappear when the server exits.
  • Third-party sharing: none by this server. Your MCP client may send tool results to its model provider under that client's own privacy policy.
  • Retention: nothing is kept after the process exits. Diagnostic logs go to stderr on your machine; your MCP client may save them in its own log files.
  • Contact: open an issue at https://github.com/j0hanz/filesystem-mcp/issues, or report security problems privately through GitHub Security Advisories.

License

Released under the MIT License. See LICENSE for details.