
A lightweight stdio-based MCP server for local file system operations — read, write, edit, search, exec for AI assistants. Specially optimized for Chatbox: bat-bypass for exec (CVE-2026-6130), b64 encoding to eliminate escaping issues, and multi-pattern regex for precise code block targeting.
Zero-dependency MCP server for local file operations. 13 filesystem tools + 3 meta-tools for progressive discovery — no SDKs, no frameworks, no npm install needed.
MCP Protocol: 2024-11-05 · Transport: stdio + Streamable HTTP · Runtime: Node.js ≥ 22.0.0
local-mcp.mjs — 595 lines, 13 tools, entry point
lib/mcp-core.mjs — 229 lines, stdio + HTTP transport, 9 MCP methods
lib/config.mjs — 31 lines, MCP_WORKSPACE/DATA env config with validation
Total: ~855 lines, zero runtime dependencies.
| Tool | Description | Annotation |
|---|---|---|
read | Read file with line numbers, optional head/tail truncation | readOnlyHint |
search | File search by name (glob) then content (grep) | readOnlyHint |
ls | Compact directory listing with lazy stat | readOnlyHint |
exec | Streaming command execution with stdin support and timeout | destructiveHint |
diff | Diff two files or text strings (Myers O(ND)) | readOnlyHint |
copy | Copy file or directory | destructiveHint |
move | Move or rename file/directory | destructiveHint |
batch | Run multiple ops sequentially; atomic rollback, $prev refs | destructiveHint |
file | Unified: read, write, edit, append, delete, info, mkdir, move | — |
block | Read/replace/insert/delete code blocks by range or function name | — |
bookmark | Persistent path aliases (add/get/list/delete) | — |
grep | Compact file:line:content format with adaptive concurrency | readOnlyHint |
watch | Watch file/directory for changes; max 20 concurrent watchers | — |
| Tool | Description |
|---|---|
search_tools | Search available tools by keyword — saves ~90% tokens vs listing all |
describe_tool | Get full input schema for a specific tool (loaded on demand) |
call_tool | Execute any tool by name with arguments |
Instead of sending all 13 tool schemas (~3,000 tokens) in every request, progressive discovery with these 3 meta-tools reduces it to ~50 tokens — ~90% token savings.
| # | Optimization | Impact |
|---|---|---|
| A | Stream head/tail read | streamHead() avoids reading entire files. 500MB logs: 3s → 5ms, memory: 500MB → few KB |
| B | Lazy stat in ls | Only calls statSync when sort=size. 1000-file dir: 50ms → 2ms |
| C | Adaptive grep concurrency | os.availableParallelism() (max 16, min 4) instead of hardcoded 16 workers |
| D | LRU cache eviction | Map insertion-order LRU — hot small files no longer evicted by cold large files |
| E | Grep byte protection | MAX_GREP_TOTAL_MB=100 + MAX_GREP_FILES=1000 guards prevent OOM |
| F | Progress notification | _meta.progressToken passthrough for MCP 2025 spec (TODO: events for long exec) |
| Area | Detail |
|---|---|
| Read cache | Size-aware eviction (max 50 items, 10 MB) + 5s TTL |
| Myers diff | O(ND) algorithm, used by edit, block, and diff |
| Search scoring | Name-match first (no I/O), then stat only top 50 candidates |
| Output format | grep: file:line:content, ls: compact columns, read: line numbers + truncation hint |
| Protocol | O(1) Map dispatch, sync handler short-circuit |
# Zero install — no dependencies
node local-mcp.mjs
# With configuration
MCP_WORKSPACE=D:/projects node local-mcp.mjs
{
"mcpServers": {
"local-mcp": {
"command": "node",
"args": ["D:/path/to/local-mcp.mjs"],
"env": {
"MCP_WORKSPACE": "D:/projects"
}
}
}
}
node local-mcp.mjs --http
node local-mcp.mjs --http --port 3456
Supports JSON-RPC 2.0 POST, SSE streaming (Accept: text/event-stream), CORS, and GET /tools.
node local-mcp.mjs --help # Show usage + env vars
node local-mcp.mjs --list-tools # Print available tools and exit
node local-mcp.mjs --http # Start HTTP mode
node local-mcp.mjs --http --port 3456
| Variable | Default | Description |
|---|---|---|
MCP_WORKSPACE | process.cwd() | Working directory root (security boundary) |
MCP_DATA | {WORKSPACE}/.mcp-data | Data directory (bookmarks, temp files) |
MCP_DIR | {WORKSPACE} | Default directory for tree/ls commands |
MCP_PORT | 3100 | HTTP server port (when using --http) |
MCP_READONLY | false | Set to true to block all write operations |
MCP_EXCLUDE | — | Comma-separated extra dirs to exclude from search |
MCP_WORKSPACE and subdirectories__proto__/constructor/prototype injection).gitignore and common exclude dirs (node_modules, .git, etc.) respectedZero runtime dependencies. Uses only Node.js built-ins:
| Module | Purpose |
|---|---|
fs | File system + glob (Node 22) |
child_process | Streaming shell execution |
http | HTTP transport (no Express needed) |
path | Path resolution |
os | availableParallelism() for adaptive concurrency |
readline | Streaming line-by-line processing |
availableParallelism()streamHead scope bug — done defined outside Promise callback# Run tests
node --test test/*.test.mjs
# Adding a tool
# 1. Define schema + handler in local-mcp.mjs
# 2. Register with server.tool()
# 3. Add tests
MIT