FS Context MCP Server
A secure, read-only MCP server for filesystem scanning, searching, and analysis with comprehensive security validation.
One-Click Install
Features
- Directory listing (immediate contents)
- Path search with glob patterns (files and directories)
- Content search with regex and context lines
- File reading with head previews (first N lines)
- Batch reads and metadata lookups in parallel
- Security: path validation, symlink escape protection, read-only operations
When to Use
| Task | Tool |
|---|---|
| Explore project structure | ls |
| Find files or directories | find |
| Search for code patterns/text | grep |
| Read source code | read |
| Batch read multiple files | read_many |
| Get file metadata (size, dates) | stat |
| Batch get file metadata | stat_many |
| Check available directories | roots |
Quick Start
NPX (recommended)
Allow the current working directory explicitly:
npx -y @j0hanz/fs-context-mcp@latest --allow-cwd
Or pass explicit directories:
npx -y @j0hanz/fs-context-mcp@latest /path/to/project /path/to/docs
If your MCP client supports the Roots protocol, you can omit directory arguments and let the client provide allowed directories. Otherwise, pass explicit directories or use --allow-cwd (if neither is provided, the server defaults to the current working directory).
VS Code (workspace folder)
Add to .vscode/mcp.json:
{
"servers": {
"fs-context": {
"command": "npx",
"args": ["-y", "@j0hanz/fs-context-mcp@latest", "${workspaceFolder}"]
}
}
}
Installation
NPX (no install)
npx -y @j0hanz/fs-context-mcp@latest /path/to/dir1 /path/to/dir2
Global installation
npm install -g @j0hanz/fs-context-mcp
fs-context-mcp /path/to/your/project
From source
git clone https://github.com/j0hanz/fs-context-mcp-server.git
cd fs-context-mcp-server
npm install
npm run build
node dist/index.js /path/to/your/project
Directory Access and Resolution
Access is always restricted to explicitly allowed directories.
- CLI directories are validated and added first (if provided).
--allow-cwdoptionally adds the current working directory.- MCP Roots from the client are used next:
- If CLI and/or
--allow-cwdare provided, only roots inside those baseline directories are accepted. - If no baseline is provided, roots become the allowed directories.
- If CLI and/or
- If nothing is configured and the client provides no roots, the server defaults to the current working directory and logs a warning.
Notes:
- Windows drive-relative paths like
C:pathare rejected. UseC:\pathorC:/path. - Reserved Windows device names (e.g.,
CON,NUL) are blocked.
Configuration
All configuration is optional. Sizes in bytes, timeouts in milliseconds.
Environment Variables
| Variable | Default | Description |
|---|---|---|
MAX_FILE_SIZE | 10MB | Max file size for read operations (range: 1MB-100MB) |
MAX_SEARCH_SIZE | 1MB | Max file size for content search (range: 100KB-10MB) |
DEFAULT_SEARCH_TIMEOUT | 30000 | Timeout for search/list operations (range: 100-3600000ms) |
FS_CONTEXT_SEARCH_WORKERS | min(cpu cores, 8) | Search worker threads (range: 1-16) |
See CONFIGURATION.md for examples and CLI usage.
Tools
All tools return both human-readable text and structured JSON. Structured
responses include ok, optional error (with code, message, path,
suggestion), plus the tool-specific fields documented below.
roots
List all directories that this server can access.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| (none) | - | - | - | - |
Returns: Allowed directory paths. Structured output includes ok and
directories.
ls
List the immediate contents of a directory (non-recursive). Omit path to use
the first allowed root.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | No | first root | Directory path to list (omit to use first root) |
includeHidden | boolean | No | false | Include hidden files and directories |
excludePatterns | string[] | No | [] | Glob patterns to exclude |
pattern | string | No | - | Glob pattern to include (relative, no ..) |
maxDepth | number | No | 10 | Maximum depth when using pattern (0-100) |
maxEntries | number | No | 10000 | Maximum entries to return (1-100000) |
timeoutMs | number | No | 30000 | Timeout in milliseconds |
sortBy | string | No | name | Sort by: name, size, modified, type |
includeSymlinkTargets | boolean | No | false | Include symlink target paths (symlinks are not followed) |
Returns: Entries with name, relativePath, type, size, and modified time.
Structured output includes ok, path, entries, and totalEntries.
find
Search for files using glob patterns. Automatically excludes common dependency/build
directories (node_modules, dist, .git, etc.). Omit path to search from the
first allowed root.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | No | first root | Base directory to search from (omit to use first root) |
pattern | string | Yes | - | Glob pattern (e.g., **/*.ts, src/**/*.js) |
maxResults | number | No | 100 | Maximum matches to return (1-10000) |
Returns: Matching paths (relative) with size and modified date. Structured
output includes ok, results, totalMatches, and truncated.
read
Read the contents of a text file.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | Yes | - | File path to read |
head | number | No | - | Read only first N lines |
Notes:
- Reads are UTF-8 text only; binary files are rejected.
- Max file size is capped by
MAX_FILE_SIZE(default 10MB).
Returns: File content plus structured metadata (ok, path, content,
truncated, totalLines).
read_many
Read multiple files in parallel.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
paths | string[] | Yes | - | Array of file paths (max 100) |
head | number | No | - | Read only first N lines of each file |
Notes:
- Reads files as UTF-8 text; binary files are not filtered. Max size per file
is capped by
MAX_FILE_SIZE(default 10MB). - Total read budget across all files is capped at 100MB.
- No binary detection is performed; use
readfor single-file safety checks.
Returns: Per-file content or error, plus structured summary (total,
succeeded, failed).
stat
Get detailed metadata about a file or directory.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | Yes | - | Path to file or directory |
Returns: name, path, type, size, timestamps (created/modified/accessed), permissions, hidden status, MIME type (for files), and symlink target (if applicable).
stat_many
Get metadata for multiple files/directories in parallel.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
paths | string[] | Yes | - | Array of paths to query (max 100) |
Returns: Array of file info with individual success/error status, plus summary (total, succeeded, failed).
grep
Search for text content within files using regular expressions. Omit path to
search from the first allowed root.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | No | first root | Base directory to search in (omit to use first root) |
pattern | string | Yes | - | Regex pattern to search for |
filePattern | string | No | **/* | Glob pattern to filter files |
excludePatterns | string[] | No | built-in exclude list | Glob patterns to exclude (overrides built-in list) |
caseSensitive | boolean | No | false | Case-sensitive search |
maxResults | number | No | 100 | Maximum number of results |
maxFileSize | number | No | 1MB | Maximum file size to scan (default from MAX_SEARCH_SIZE) |
maxFilesScanned | number | No | 20000 | Maximum files to scan before stopping |
timeoutMs | number | No | 30000 | Timeout in milliseconds |
skipBinary | boolean | No | true | Skip likely-binary files |
includeHidden | boolean | No | false | Include hidden files and directories |
includeIgnored | boolean | No | false | Include ignored dirs (node_modules, dist) and disable built-in excludes |
contextLines | number | No | 0 | Lines of context before/after match (0-10) |
wholeWord | boolean | No | false | Match whole words only |
isLiteral | boolean | No | false | Treat pattern as literal string (escape regex chars) |
baseNameMatch | boolean | No | false | Match file patterns without slashes against basenames |
caseSensitiveFileMatch | boolean | No | true | Case-sensitive filename matching |
Returns: Matching lines with file path, line number, content, and optional
context. Structured output includes ok, matches, totalMatches, and
truncated.
Matched line content is trimmed to 200 characters.
Built-in exclude list includes common dependency/build/output directories and
files: node_modules, dist, build, coverage, .git, .vscode, .idea,
.DS_Store, .next, .nuxt, .output, .svelte-kit, .cache, .yarn,
jspm_packages, bower_components, out, tmp, .temp,
npm-debug.log, yarn-debug.log, yarn-error.log, Thumbs.db. Pass
excludePatterns: [] or includeIgnored: true to disable it.
Error Codes
| Code | Meaning |
|---|---|
E_ACCESS_DENIED | Path outside allowed roots |
E_NOT_FOUND | Path does not exist |
E_NOT_FILE | Expected file, got directory |
E_NOT_DIRECTORY | Expected directory, got file |
E_TOO_LARGE | File exceeds size limits |
E_TIMEOUT | Operation timed out |
E_INVALID_PATTERN | Invalid glob/regex pattern |
E_INVALID_INPUT | Invalid argument(s) |
E_PERMISSION_DENIED | OS-level permission denied |
E_SYMLINK_NOT_ALLOWED | Symlink escapes allowed roots |
E_UNKNOWN | Unexpected error |
Client Configuration
VS Code
Add to .vscode/mcp.json (recommended) or .vscode/settings.json:
{
"servers": {
"fs-context": {
"command": "npx",
"args": ["-y", "@j0hanz/fs-context-mcp@latest", "${workspaceFolder}"]
}
}
}
Claude Desktop
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"fs-context": {
"command": "npx",
"args": ["-y", "@j0hanz/fs-context-mcp@latest", "C:\\path\\to\\project"]
}
}
}
If your client supports MCP Roots, you can omit the path. Otherwise, pass a path or --allow-cwd.
Cursor
Add to Cursor's MCP configuration:
{
"mcpServers": {
"fs-context": {
"command": "npx",
"args": ["-y", "@j0hanz/fs-context-mcp@latest", "${workspaceFolder}"]
}
}
}
Codex
Add to ~/.codex/config.toml:
[mcp_servers.fs-context]
command = "npx"
args = ["-y", "@j0hanz/fs-context-mcp@latest", "/path/to/your/project"]
If your client supports MCP Roots, you can omit the path. Otherwise, pass a path or --allow-cwd.
Windsurf
Add to Windsurf's MCP configuration:
{
"mcpServers": {
"fs-context": {
"command": "npx",
"args": ["-y", "@j0hanz/fs-context-mcp@latest", "${workspaceFolder}"]
}
}
}
Security
This server implements multiple layers of security:
| Protection | Description |
|---|---|
| Access control | Only explicitly allowed directories are accessible |
| Path validation | All paths are validated before any filesystem operation |
| Symlink protection | Symlinks that resolve outside allowed directories are blocked |
| Path traversal prevention | Attempts to escape via .. are detected and blocked |
| Read-only operations | No writes, deletes, or modifications |
| Safe regex | Regex validation with RE2 prevents ReDoS |
| Size limits | Configurable limits prevent resource exhaustion |
Development
Prerequisites
- Node.js >= 20.0.0
- npm
Scripts
| Command | Description |
|---|---|
npm run build | Compile TypeScript to JavaScript |
npm run dev | Watch mode with tsx |
npm run start | Run compiled server |
npm run test | Run tests (node --test with tsx/esm) |
npm run test:watch | Run tests in watch mode (node --test --watch) |
npm run test:coverage | Run tests with coverage (node --test --experimental-test-coverage) |
npm run test:node | Run node-tests (isolated checks) |
npm run lint | Run ESLint |
npm run format | Format code with Prettier |
npm run type-check | TypeScript type checking |
npm run inspector | Test with MCP Inspector |
Project Structure
src/
index.ts # CLI entry point
server.ts # MCP server wiring and roots handling
instructions.md # Tool usage instructions (bundled in dist)
config/ # Shared types and formatting helpers
lib/ # Core logic and filesystem operations
schemas/ # Zod input/output schemas
tools/ # MCP tool registration
__tests__/ # node:test + tsx tests
node-tests/ # Isolated Node.js checks
Troubleshooting
| Issue | Solution |
|---|---|
| "Access denied" error | Ensure the path is within an allowed directory. Use roots to check. |
| "Path does not exist" | Verify the path exists. Use ls to explore available files. |
| "File too large" | Use head or increase MAX_FILE_SIZE. |
| "Binary file" warning | read only supports UTF-8 text and rejects binary files. |
| No directories available | Pass explicit paths, use --allow-cwd, or ensure the client provides Roots. |
| Symlink blocked | Symlinks that resolve outside allowed directories are blocked. |
| Invalid regex/pattern | Simplify the regex or set isLiteral=true for exact matches. |
Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Run tests and linting (
npm run lint && npm run test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Code Style
- Use TypeScript with strict mode
- Follow ESLint configuration
- Use Prettier for formatting
- Write tests for new features