Chisel
<img src="chisel.jpeg" alt="Chisel" width="300" />🪛 Rust powered precision file operations for agents. Unix-native tools, minimal context footprint, strict path confinement: use directly with Chisel MCP or bring your own MCP, embeddable in any MCP server in Rust, Python, Nodejs.
https://github.com/user-attachments/assets/af84f1af-db47-4e42-808b-00861504cd34
Install — download a pre-built .mcpb bundle (one-click, no build step) or a raw binary from the Releases page — see Standalone usage below.
Agent skill included —
skills/chisel/SKILL.mdteaches agents how to use Chisel at maximum efficiency. Install with:npx skills add ckanthony/Chisel
Security hardened — Verified properties across two layers: the MCP server (
chisel) and the portable core library (chisel-core). See the Security model section for the full breakdown.
Contents
- Motivation
- Tools
- Standalone usage
- Behind a reverse proxy
- Bring your own MCP
- Extending chisel
- Workspace layout
- Security model
- Platform support
- Development
- Contributing
- Future considerations
Motivation
Most MCP file tools hand an LLM a blank canvas: read anything, write anything, make any mistake. Chisel takes the opposite approach:
- Reduce context overhead — every tool call is compact. File edits go through
patch_apply: the model sends only a unified diff instead of rewriting an entire file, so large-file edits cost a fraction of the tokens - Familiar command patterns —
shell_execexposes the same Unix tools (grep,sed,awk,find,cat, …) LLMs already know well from training data, so prompts stay short and outputs are predictable - Precision over flexibility — a fixed whitelist and strict path confinement mean the model cannot accidentally escape scope or run arbitrary commands
- Safety first — bearer-token auth,
127.0.0.1-only binding, symlink-aware root confinement, atomic writes, and a read-only mode are all on by default - Reusable core —
chisel-coreis a plain synchronous Rust library; any MCP server (Rust, Node.js via WASM, Python via WASM) can embed it without running a second process
Real-world demo
Six tasks on the same markdown file. Left: typical MCP file server. Right: chisel.
File: docs/api.md — 300 lines, 6 headers, one section of ~20 lines.
Task 1 — find all headers
# Typical # Chisel
tool: read_file tool: shell_exec
path: /data/docs/api.md command: grep
args: ["-n", "^#", "/data/docs/api.md"]
← 300 lines returned (~3 000 tokens) ← 6 lines returned (~30 tokens)
model must scan the whole file 1:# API Reference
45:## Authentication
89:## Endpoints
134:## Request Format
178:## Response Format
234:## ErrorsTask 2 — read the content under ## Endpoints
# Typical # Chisel
tool: read_file (again, or re-use above) tool: shell_exec
path: /data/docs/api.md command: sed
args: ["-n", "/^## Endpoints/,/^## /p",
← 300 lines returned again (~3 000 tokens) "/data/docs/api.md"]
model must locate the section in context
← 44 lines returned (~440 tokens)
only the Endpoints sectionTask 3 — edit one line in that section
# Typical # Chisel
tool: write_file tool: patch_apply
path: /data/docs/api.md path: /data/docs/api.md
content: <entire 300-line file patch: |
with one line changed> --- a/docs/api.md
+++ b/docs/api.md
← 300 lines uploaded (~3 000 tokens) @@ -91,1 +91,1 @@
any hallucination corrupts the file -GET /v1/items
+GET /v2/items
← 7 lines uploaded (~50 tokens)
hunk mismatch → PatchFailed, file untouchedTask 4 — replace all - with : in that section
# Typical # Chisel
tool: read_file tool: shell_exec
path: /data/docs/api.md command: sed
args: ["-i", "s/-/:/g",
← 300 lines returned (~3 000 tokens) "/data/docs/api.md"]
tool: write_file
content: <300 lines with replacement> ← 1 line call, 0 file content transmitted
← 300 lines uploaded (~3 000 tokens) sed runs the replacement in-place
Total: ~12 000 tokens Total: ~520 tokens (23× less)Task 5 — model tries to run rm -rf ~
# Typical # Chisel
(no shell tool exposed) tool: shell_exec
command: rm
args: ["-rf", "~"]
not applicable — typical MCP file
servers have no shell tool, so the ← CommandNotAllowed
model would need a separate shell "rm" is not in the whitelist.
MCP or use write_file to script it Permitted: grep sed awk find cat
head tail wc sort uniq cut tr
diff file stat ls du rg
process is never spawned
rm,bash,sh,curl,chmod,sudo— none of these are in the whitelist. The list is fixed at compile time; it cannot be extended at runtime by the model.
Task 6 — model tries to edit /Users/home/.ssh/config directly
# Typical # Chisel
tool: write_file tool: patch_apply
path: /Users/home/jor/.ssh/config path: /Users/home/jor/.ssh/config
content: <malicious key appended> patch: <adds authorized_keys entry>
← succeeds if the server process ← OutsideRoot
has filesystem access — no path resolved path /Users/home/.ssh/config
confinement in a naive file server does not start with root /data
I/O is never performedEvery path — including those passed to
shell_exec— is validated against the configured root before any I/O or process spawn. An absolute path outside root is always rejected, regardless of which tool is called.
Context cost comparison
Estimates use Claude Sonnet 4.6 tokenisation: typical source code averages ~10 tokens/line (identifiers, punctuation, and whitespace each count as tokens under BPE).
Single edit — 500-line file, 5 lines changed:
Naive (read_file → write_file) | Chisel (patch_apply) | Reduction | |
|---|---|---|---|
| Tokens in (upload) | ~5 000 (full file) | ~120 (11 diff lines + headers) | 42× |
| Tokens out (model output) | ~5 000 (full file rewrite) | ~15 (success ack) | 333× |
| Round-trip total | ~10 000 | ~135 | ~74× |
| Failure mode | Silent hallucination corrupts entire file | PatchFailed — original untouched | — |
Read / search — 2 000-line file:
| Task | Naive (read_file full) | Chisel (shell_exec) | Reduction |
|---|---|---|---|
| Find a symbol | ~20 000 tokens (full read) | ~40 tokens (grep matched lines) | 500× |
| Count occurrences | ~20 000 tokens | ~5 tokens (grep -c integer) | 4 000× |
| Extract lines 40–60 | ~20 000 tokens | ~210 tokens (sed -n '40,60p') | 95× |
| Directory tree | ~20 000 tokens | ~300 tokens (find / ls -R) | 67× |
Savings scale linearly with file size. A 2 000-line file costs 4× more than the 500-line baseline above.
Tools
Every path argument is canonicalized and confined to the configured root before any I/O — .. traversal and symlink escapes are rejected. This confinement is enforced inside chisel-core and applies equally when the library is embedded directly.
When using the MCP server (chisel), all tools additionally require Authorization: Bearer <secret>.
| Tool | Description |
|---|---|
patch_apply | Apply a unified diff atomically; accepts raw or ````diff` fenced patches — primary edit tool; sends only changed lines, not the full file |
append | Append content to an existing file |
write_file | Write (create or overwrite) a file; creates parent dirs automatically |
create_directory | Create a directory tree (mkdir -p semantics) |
move_file | Move or rename a file within root |
shell_exec | Run a whitelisted command — grep sed awk find cat head tail wc sort uniq cut tr diff file stat ls du rg |
Full reference: docs/tools.md
Bring your own MCP
Chisel ships two embeddable libraries alongside the standalone server. Neither carries any HTTP, MCP protocol, or async runtime dependency — drop them into your own server and own the transport entirely.
| Package | What it is | Use it when |
|---|---|---|
chisel-core | Pure Rust sync library — path confinement, all file ops, shell exec | Writing a Rust MCP server |
chisel-wasm | chisel-core compiled to wasm32-wasip1 | Writing an MCP server in Node.js, Python, Deno, or any WASI runtime |
Full integration guide → **[chisel-core/README.md]
…