Chisel

Chisel

Reduce context usage on file use. Send only unified diffs instead of full files (up to 20-100ร— fewer tokens), and read large files with targeted grep/sed instead of full reads (up to 500ร—). Kernel-enforced path confinement hard-locks the agent to a configured root: no accidental reads or writes outside scope. Standalone for your file access or embed in any MCP server (Rust, Node.js, Python via

Category
Visit Server

README

Chisel

CI codecov MCP server with tools

<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.md teaches 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

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_exec exposes 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-core is 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:## Errors

Task 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 section

Task 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 untouched

Task 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 performed

Every 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


Standalone usage

Binary

Option A โ€” Download pre-built binary (recommended)

Go to the latest release and download the binary for your platform:

Platform File
macOS Apple Silicon chisel-macos-arm64
macOS Intel chisel-macos-x86_64
Linux x86-64 chisel-linux-x86_64
Linux ARM64 chisel-linux-arm64
# Make executable and run
chmod +x chisel-macos-arm64          # adjust filename for your platform
MCP_APP_SECRET=mysecret ./chisel-macos-arm64 --root /path/to/data

Option B โ€” Build from source

cargo build --release -p chisel

Running

# Secret via env var (preferred)
MCP_APP_SECRET=mysecret ./chisel --root /path/to/data

# Or via --secret flag (env var takes precedence if both set)
./chisel --root /path/to/data --secret mysecret

# Read-only mode (shell_exec still works; writes are blocked)
MCP_APP_SECRET=mysecret ./chisel --root /path/to/data --read-only

The server binds to 127.0.0.1:3000 by default. Use --port to change the port.

Docker

Three ways to run Chisel in Docker โ€” pick the one that fits your workflow.

Option 1 โ€” Docker Compose (recommended)

# 1. Create your .env
echo "MCP_APP_SECRET=changeme" > .env

# 2. Create the data directory that will be exposed to the LLM
mkdir -p data

# 3. Start (builds the image on first run, then stays running)
docker compose up -d

# Tail logs
docker compose logs -f

# Stop
docker compose down

docker-compose.yml mounts ./data โ†’ /data inside the container and binds 127.0.0.1:3000:3000 โ€” the port is never exposed beyond localhost.

Option 2 โ€” docker run

# Build
docker build -t chisel:latest .

# Run (replace /absolute/path/to/data with your actual data directory)
docker run -d \
  --name chisel \
  -e MCP_APP_SECRET=changeme \
  -v /absolute/path/to/data:/data \
  -p 127.0.0.1:3000:3000 \
  --restart unless-stopped \
  chisel:latest

# Read-only mode (writes blocked, shell_exec still works)
docker run -d \
  --name chisel \
  -e MCP_APP_SECRET=changeme \
  -v /absolute/path/to/data:/data:ro \
  -p 127.0.0.1:3000:3000 \
  chisel:latest chisel --root /data --read-only

Option 3 โ€” Custom port

docker run -d \
  --name chisel \
  -e MCP_APP_SECRET=changeme \
  -v /absolute/path/to/data:/data \
  -p 127.0.0.1:8080:3000 \
  chisel:latest

Host port 8080 maps to container port 3000. Update your MCP client URL to http://127.0.0.1:8080/mcp accordingly.

The container runs as a non-root user (chisel) and never binds on 0.0.0.0. For remote access, place Caddy or nginx in front โ€” see Behind a reverse proxy.

Configure your MCP client

{
  "mcpServers": {
    "chisel": {
      "url": "http://127.0.0.1:3000/mcp",
      "headers": {
        "Authorization": "Bearer <your-secret>"
      }
    }
  }
}

Behind a reverse proxy (Caddy)

Chisel deliberately refuses to bind on 0.0.0.0. Remote or multi-client access goes through a reverse proxy that handles TLS.

Caddy โ€” automatic HTTPS

mcp.yourdomain.com {
    reverse_proxy 127.0.0.1:3000
}

Caddy auto-provisions a Let's Encrypt certificate. The bearer token still authenticates every request end-to-end.

# Install Caddy (macOS)
brew install caddy

# Run
caddy run --config Caddyfile

Your MCP client URL becomes https://mcp.yourdomain.com/mcp.

nginx

server {
    listen 443 ssl;
    server_name mcp.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }
}

Extending chisel

See chisel-core/README.md for the full integration guide covering:

  • Rust API reference with code examples
  • spawn_blocking pattern for async handlers
  • Node.js (โ‰ฅ 22) WASM / WASI integration
  • Python (wasmtime-py) WASM integration
  • Error types and security properties

Workspace layout

chisel/
โ”œโ”€โ”€ chisel/          # HTTP server binary (MCP Streamable HTTP transport)
โ”œโ”€โ”€ chisel-core/     # Portable sync library โ€” no HTTP, no async, no MCP protocol
โ””โ”€โ”€ chisel-wasm/     # wasm32-wasip1 build of chisel-core for Node.js / Python / Deno
graph TD
    A[LLM / MCP client] -->|HTTP + Bearer| B[chisel binary]
    B --> C[chisel-core]
    D[Your Rust MCP server] --> C
    E[Node.js MCP server] -->|WASI| F[chisel-wasm .wasm]
    G[Python MCP server] -->|WASI| F
    F --> C

Security model

Security properties are split across two layers. Each row is verified by the test suite referenced at the bottom of this section.

chisel โ€” MCP server / HTTP layer

# Property Mechanism Default
1 Bearer-token auth โ€” every request authenticated subtle::ConstantTimeEq; timing-safe comparison; missing or empty secret โ†’ process exits at startup required
2 Rate limiting โ€” brute-force and runaway-agent protection governor token-bucket; excess requests โ†’ HTTP 429 before auth is checked 100 req/s
3 Request body cap โ€” memory exhaustion prevention axum::DefaultBodyLimit; oversized body โ†’ HTTP 413 before any parsing 4 MiB
4 Audit log โ€” every tool op traceable tracing::info on success, tracing::warn on failure; records op name, path, error info
5 Loopback-only binding โ€” no accidental public exposure TcpListener::bind("127.0.0.1:<port>"); 0.0.0.0 is never used โ€”

chisel-core โ€” portable library (enforced on every op, including embedded use)

# Property Mechanism
6 Kernel-enforced root confinement โ€” directory traversal, symlink escape, TOCTOU all blocked cap_std::fs::Dir; every path component traversed via openat(fd, component, O_NOFOLLOW); confinement is enforced at the kernel level during I/O, not in userspace before it
7 Atomic writes โ€” failed patch never corrupts the target file Dir::create(".name.PID.tmp") + Dir::rename(tmp โ†’ target); both ops confined inside the same root fd; on any failure the tmp file is discarded and the original is untouched
8 Read-only mode โ€” blanket write protection check_writable(read_only) runs before any I/O inside every write op; no code path bypasses it
9 Shell whitelist + direct execve โ€” no injection, no arbitrary commands Fixed compile-time whitelist; std::process::Command spawns directly (no sh -c); path-like args validated via validate_path before spawn
graph LR
    req[Incoming request] --> body{Body โ‰ค limit?\nDefaultBodyLimit}
    body -- too large --> 413[HTTP 413]
    body -- ok --> rate{Rate limit\ntoken-bucket}
    rate -- exceeded --> 429[HTTP 429]
    rate -- ok --> auth{Bearer token\nconstant-time compare}
    auth -- reject --> 401[HTTP 401]
    auth -- pass --> path{Path confinement\ncap-std openat + O_NOFOLLOW}
    path -- outside root --> err[OutsideRoot error]
    path -- inside root --> ro{Read-only mode?}
    ro -- write op --> readonly[ReadOnly error]
    ro -- read / shell --> exec[Execute tool]
    exec --> audit[Audit log\ntracing::info/warn]
    exec --> atomic[Atomic write\nDir::create + Dir::rename]

Attack and misuse prevention

Each layer targets a specific class of failure โ€” accidental or deliberate.

1. Authentication โ€” unauthorised access

Every HTTP request must carry Authorization: Bearer <secret>. The comparison uses subtle::ConstantTimeEq, which takes the same number of CPU cycles regardless of how many characters match. A timing-based brute-force attack that probes the token character-by-character cannot succeed because no timing signal leaks.

The server refuses to start if the secret is empty or absent โ€” misconfiguration is a hard error, not a silent fallback to no auth.

2. Path confinement โ€” escape from root directory

This is the core guarantee. All filesystem tools (patch_apply, append, write_file, create_directory, move_file) operate exclusively through a cap_std::fs::Dir handle rooted at the configured root directory.

input string
  โ†’ strip root prefix โ†’ relative path
  โ†’ cap_std::fs::Dir::open / write / rename โ€ฆ
       โ””โ”€ kernel: openat(root_fd, "sub/file", O_NOFOLLOW)
                  openat(sub_fd,  "file",     O_NOFOLLOW)

Every component of the path is traversed via openat with O_NOFOLLOW. The kernel enforces confinement โ€” no userspace prefix check can be bypassed:

Attack Example input What happens
Directory traversal /data/sub/../../etc/passwd .. components tracked per-fd; escape above root blocked by kernel โ†’ error
Absolute path bypass /etc/hosts Root prefix stripped; remaining path confined to root fd โ†’ OutsideRoot
Symlink in component /data/link/file where link โ†’ /etc O_NOFOLLOW on link open โ†’ kernel rejects โ†’ error
TOCTOU swap Valid path at check time, swapped to symlink before I/O No separate check/use window โ€” confinement is enforced during the I/O itself

validate_path (the userspace canonicalize + prefix check from earlier versions) is still used exclusively for shell_exec path arguments, where we pass strings to spawned processes that cap-std cannot confine.

3. Shell injection โ€” arbitrary command execution

shell_exec does not invoke a shell interpreter. It calls std::process::Command directly with the command and arguments as separate OS-level strings:

shell_exec("grep", ["-r", "foo", "/data"])
  โ†’ execve("/usr/bin/grep", ["-r", "foo", "/data"])   โ† no sh -c wrapper

Shell metacharacters (;, &&, |, $(), backticks, etc.) in any argument are passed as literal bytes to the target process. There is no shell to interpret them.

A fixed compile-time whitelist (grep sed awk find cat head tail wc sort uniq cut tr diff file stat ls du rg) is checked before the process is spawned. Any command not on the list returns CommandNotAllowed immediately โ€” the process is never started.

Path-like arguments (starting with / or containing ..) are validated against root via validate_path before the process starts.

4. Partial write โ€” file corruption on failed patch

patch_apply never writes directly to the target file. The flow is entirely confined within the cap-std Dir:

1. Parse and validate the diff
2. Dir::create(".filename.PID.tmp")   โ† confined temp file, same directory
3. On success  โ†’ Dir::rename(tmp, target)   โ† single syscall, cannot be interrupted mid-write
4. On failure  โ†’ return PatchFailed, tmp is dropped and cleaned up

If the hunk context does not match the current file (the file has drifted since the diff was generated), the operation aborts at step 1 and the original file is never touched. A process crash between steps 2 and 3 leaves the .tmp file โ€” the original is still intact.

5. Read-only mode โ€” blanket write protection

Starting with --read-only causes all five write tools (patch_apply, append, write_file, create_directory, move_file) to return ReadOnly immediately without performing any I/O. The check happens inside chisel-core before any disk access โ€” there is no code path that bypasses it.

shell_exec remains available in read-only mode because it only reads (whitelisted commands are all inspection tools; mkdir and mv are explicitly excluded from the whitelist).

6. Rate limiting โ€” brute-force and runaway-agent protection

Every request passes through a token-bucket rate limiter (governor) before authentication. When the configured rate (default: 100 req/s) is exceeded, the server immediately returns HTTP 429 Too Many Requests without doing any work.

This prevents two threat classes:

  • Token brute-force: an attacker cannot enumerate tokens faster than the configured rate, even with a leaked secret in partial form.
  • Runaway agent loops: a malfunctioning LLM client flooding the server is throttled before it can cause resource exhaustion.

Configure with --rate-limit <N> (or set to 0 to disable). The limiter is the outermost layer โ€” requests that exceed the rate never reach the auth check.

7. Request body cap โ€” memory exhaustion prevention

All incoming request bodies are capped at 4 MiB by default (DefaultBodyLimit). An oversized body is rejected before any parsing, authentication, or tool dispatch occurs.

Configure with --body-limit <bytes>. For typical LLM usage (unified diffs and file content), 4 MiB is generous โ€” increase only if you intentionally send large file writes.

8. Audit logging โ€” operation traceability

Every tool invocation emits a structured tracing log line recording the operation name, the target path (or command), and whether it succeeded or failed:

INFO chisel::tools::filesystem op=patch_apply path=/data/foo.txt
WARN chisel::tools::filesystem op=write_file  path=/etc/passwd error=OutsideRoot { ... }
INFO chisel::tools::shell       op=shell_exec cmd=grep exit_code=0

Log verbosity is controlled via the RUST_LOG environment variable (e.g. RUST_LOG=chisel=debug). The default level is info, which captures every tool call result without flooding output with framework internals.

9. Network exposure โ€” no accidental public binding

The server calls TcpListener::bind("127.0.0.1:<port>") โ€” not 0.0.0.0. It is impossible for the process itself to accept connections from outside the machine. Remote access must be deliberately routed through a reverse proxy (Caddy, nginx), which is where TLS termination and any additional access controls live.

The Docker image runs as a non-root user (mcp) and the docker-compose.yml maps the port as 127.0.0.1:3000:3000, preserving the loopback restriction even inside a container.

Security test coverage

Every property above is verified by a dedicated test suite at [chisel/tests/security.rs](chisel/tests/security.rs). Run with cargo test --test security -p chisel.

Property Tests
ยง1 Authentication auth_missing_secret_is_hard_error ยท auth_empty_secret_is_hard_error ยท auth_missing_header_returns_401 ยท auth_wrong_token_returns_401 ยท auth_basic_scheme_returns_401 ยท auth_prefix_of_secret_returns_401 ยท auth_valid_token_passes
ยง2 Path confinement path_directory_traversal_is_blocked ยท path_absolute_outside_root_returns_outside_root_error ยท path_symlink_in_component_is_blocked ยท path_toctou_symlink_swap_is_blocked ยท path_deeply_nested_outside_root_is_blocked
ยง3 Shell injection shell_dangerous_commands_blocked_before_spawn ยท shell_metacharacters_are_literal ยท shell_path_arg_outside_root_blocked_before_spawn ยท shell_traversal_in_arg_blocked_before_spawn
ยง4 Partial write partial_write_failed_patch_leaves_file_intact ยท partial_write_no_tmp_artefact_on_failure
ยง5 Read-only mode readonly_all_write_tools_are_blocked ยท readonly_shell_exec_remains_available ยท readonly_no_disk_mutation_occurs
ยง6 Network binding network_bind_address_is_loopback_only ยท network_sse_endpoint_returns_404

All 23 tests pass on every CI run. A failure means a documented security guarantee has regressed.

$ cargo test --test security -p chisel
running 23 tests
test auth_empty_secret_is_hard_error ... ok
test auth_missing_secret_is_hard_error ... ok
test network_bind_address_is_loopback_only ... ok
test path_absolute_outside_root_returns_outside_root_error ... ok
test path_deeply_nested_outside_root_is_blocked ... ok
test readonly_all_write_tools_are_blocked ... ok
test readonly_no_disk_mutation_occurs ... ok
test path_directory_traversal_is_blocked ... ok
test shell_dangerous_commands_blocked_before_spawn ... ok
test shell_path_arg_outside_root_blocked_before_spawn ... ok
test path_symlink_in_component_is_blocked ... ok
test shell_traversal_in_arg_blocked_before_spawn ... ok
test partial_write_failed_patch_leaves_file_intact ... ok
test partial_write_no_tmp_artefact_on_failure ... ok
test path_toctou_symlink_swap_is_blocked ... ok
test readonly_shell_exec_remains_available ... ok
test shell_metacharacters_are_literal ... ok
test auth_missing_header_returns_401 ... ok
test auth_valid_token_passes ... ok
test auth_basic_scheme_returns_401 ... ok
test auth_prefix_of_secret_returns_401 ... ok
test auth_wrong_token_returns_401 ... ok
test network_sse_endpoint_returns_404 ... ok

test result: ok. 23 passed; 0 failed

Development

# Run all tests
cargo test --workspace

# Run only the server tests
cargo test -p chisel

# Build the WASM target
rustup target add wasm32-wasip1
cargo build --target wasm32-wasip1 -p chisel-wasm

# Build Docker image
docker build -t chisel:dev .

Platform support

Platform Server binary (chisel) chisel-core (lib, no shell) chisel-wasm
Linux (x86_64, arm64) โœ… Full support โœ… โœ…
macOS (Apple Silicon, Intel) โœ… Full support โœ… โœ…
Windows โŒ Not supported โš ๏ธ Compiles, untested โœ…

Linux / macOS โ€” primary targets. All nine security properties hold. Docker image is Debian-based. shell_exec whitelist (grep, sed, awk, find, cat, ls, โ€ฆ) assumes a standard Unix environment.

Windows โ€” not supported for the server binary for three reasons:

  1. shell_exec whitelist is entirely Unix tools; most do not exist natively on Windows
  2. cap-std kernel confinement uses POSIX openat + O_NOFOLLOW semantics; Win32 reparse-point / junction handling differs
  3. Symlink creation requires elevated privileges (SeCreateSymbolicLinkPrivilege) โ€” security tests cannot run without Administrator or Developer Mode

**chisel-wasm** โ€” fully portable. WASM has no OS dependency; runs identically on any WASI-capable runtime (Node.js โ‰ฅ 22, Deno, Wasmtime) regardless of host OS. shell_exec is excluded from the WASM build.


Contributing

Contributions are welcome. A few guidelines:

  • Bug fixes and small improvements โ€” open a PR directly.
  • New tools or behaviour changes โ€” open an issue first to agree on scope before writing code.
  • Security issues โ€” do not open a public issue. File a private report and include reproduction steps.

Before submitting:

cargo test --workspace          # all tests must pass
cargo test --test security -p chisel   # security suite must be green
cargo clippy --workspace -- -D warnings
cargo fmt --check

New security-relevant code should come with a matching test in chisel/tests/security.rs that names the exact attack vector it covers.


Future considerations

S3 / R2 object storage backend

A potential extension is a thin sync layer that hydrates S3 or R2 objects into a local scratch directory (RAM-backed or tmpfs), runs all Chisel operations against that directory through the normal chisel-core path, then flushes changed objects back on completion.

The agent would interact exclusively with Chisel โ€” path confinement, atomic patching, and shell tooling all behave identically. S3/R2 would serve purely as the persistence layer, transparent to the model.

Open problems before this is viable: concurrent writes to the same object need optimistic locking (ETag-based check-and-swap on flush), and a native cap-std equivalent does not exist for object storage, so confinement must be enforced at the scratch directory level rather than the storage layer. The core shell_exec whitelist also assumes a Unix filesystem and would require the object to be fully materialized locally before any command runs.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured