veil-mcp
veil-mcp is an AI-agent-native shell MCP server that turns shell command execution into structured, safe, and addressable data.
sh_run— Execute a shell command and receive a structured result (exit code, duration, files changed, condensed stdout/stderr). Supports:Post-condition verification via
expect(exit code, file existence, stdout content, timing) — fold run + check + grep into one callKernel-level sandboxing (
sandbox: true) — confine file writes to cwd + temp, deny network access, block reads of secret dirs (~/.ssh,~/.aws); refuses to run unconfined if sandboxing is unavailableDry-run preview (
preview: true) — run in a disposable copy-on-write clone of cwd without touching the real filesystemDeclarative retry with
retries,retry_on_exit, andbackoff_msBackground execution (
background: true) — start long-running processes (dev servers, watchers) and return immediately with a process IDCredential scrubbing — strip secret-shaped env vars from child processes
Memory-only runs (
no_store) — keep sensitive output off diskSyscall/FS tracing (
trace) — structured read/write summaries via strace on Linux
sh_detail— Retrieve full stdout, stderr, metadata, or trace for any previous run by ID without re-running; supports regex filtering (match) to grep stored outputsh_logs— Incrementally tail stdout/stderr of a background run using byte cursors so only new output is returned each pollsh_kill— Signal a background run's entire process group (SIGTERM escalating to SIGKILL); idempotent if already exitedsh_checkpoint/sh_restore/sh_checkpoints— Snapshot a working directory (excluding.git/node_modules) to a named label and roll back to it later; list all available checkpointssh_plan— Statically predict a command's blast radius (read-only, mutating, destructive, network, complex, unknown) and reversibility without executing itsh_history— Aggregate pastsh_runrecords for a command showing observed exit codes, retry counts, durations, and file churn — read-only, runs nothing
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@veil-mcpcheck the git status of this project"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
veil-mcp
A shell built for AI agents, not humans. veil is an MCP server that gives a coding agent (Claude Code, Cursor, …) a shell whose results come back as structured data — typed effects, one-call verification, addressable output, and a real undo — instead of a wall of scrollback text.
A normal terminal dumps everything and the agent re-greps fragile text, round-trips for state, and can't undo a mistake. veil turns each command into a quiet, structured result — and adds three things a plain shell simply can't.
quiet-by-default · effects-as-data · lazy detail · real safety net
Why it's good — in three numbers
You can approximate most of veil with Bash + truncation + careful prompting. The
reason to actually adopt it is the three things a shell genuinely cannot do — each
quantified, each reproducible with npm run metrics:
What you get | The number | |
✅ Verify in one call |
| 55% fewer round-trips (11 → 5) — a scenario model over 5 hand-picked common tasks, not a live measurement |
♻️ Checkpoint & roll back |
| clone ~1.5× faster, ~0 MB vs a 60 MB rsync copy |
🔒 Kernel sandbox |
| 5 / 5 escape attempts blocked (in-cwd write still lands) |
And one honesty number — because quiet must never mean dishonest: a failure buried in
the hidden middle of a long log is still surfaced, at 100% recall on a labeled
corpus (SIGSEGV, CONFLICT, ! [rejected], timed out, …, none of which contain
the word "error").
Everything else — quieter output, addressable detail, retry, blast-radius classification — is genuine convenience on top, not the moat.
Related MCP server: daimonos
Quickstart
No clone, no build — runs via npx:
claude mcp add veil -- npx -y veil-mcp
npx -y veil-mcp init # adds the "prefer sh_run" nudge to this project's CLAUDE.md// MCP server config for any MCP-speaking agent
{ "mcpServers": { "veil": { "command": "npx", "args": ["-y", "veil-mcp"] } } }# from source
git clone https://github.com/vkmtx/veil-mcp && cd veil-mcp
npm install # builds dist/ via the prepare script
claude mcp add veil -- node "$(pwd)/dist/index.js"
# dev, no build step: npm run dev (tsx src/index.ts)npx -y github:vkmtx/veil-mcp runs straight from GitHub. veil init is idempotent and
touches only CLAUDE.md — see Adoption.
The tools
Tool | What it does |
| Run a command → quiet structured result: exit, duration, files changed, token-aware stdout/stderr. |
| Poll a background run's output — incremental, per-stream byte cursor ( |
| Stop a background run. Signals the whole process group; SIGTERM escalates to SIGKILL after 2s. Killing an already-exited id is idempotent. Omit |
| Pull the full stored output of a past run — no re-run. Disk-backed, so it survives a server restart. |
| Snapshot a directory and roll back. Owner-only ( |
| List checkpoint labels. |
Every id/label is optional and defaults to the run or checkpoint you almost certainly
mean; a wrong one answers with the values that are addressable. sh_run also accepts
cmd as an alias for command. These are not conveniences — a 30-day audit of real agent
sessions found argument shape, not execution, behind most failed calls.
See it
// build AND verify the artifact exists — one call, no follow-up ls
sh_run { "command": "npm run build", "expect": { "exit": 0, "file_exists": "dist/index.js" } }
// confine a risky script to cwd, deny network, block reads of secret dirs
sh_run { "command": "./untrusted.sh", "sandbox": { "network": false, "protect_secrets": true } }
// dry-run in a CoW clone — see the cwd-relative diff, real cwd untouched
sh_run { "command": "rm -rf build && npm run generate", "preview": true }
// start a dev server detached, tail its output incrementally, stop it when done
sh_run { "command": "npm run dev", "background": true } // → { id: "cmd12", pid, status: "running" }
sh_logs { "id": "cmd12", "stdout_cursor": 0 } // poll again with the returned cursor for only NEW output
sh_kill { } // no id = the newest live run → { status: "terminating" }
// undo a refactor — label optional both ways (auto-N, then newest-first)
sh_checkpoint { "label": "pre-refactor" }
sh_restore { "label": "pre-refactor" }
// find a value a condensed 50k-line log hid — no re-run, no full dump
sh_detail { "id": "cmd9", "selector": "stdout", "match": "ERROR|version=" }Option | Effect |
| The shell command (required). |
| Working directory (defaults to the server's cwd). |
| Return uncondensed stdout/stderr inline (escape hatch from condensing). |
| Per-command timeout (default 120s). On expiry the whole process group is killed (SIGTERM→SIGKILL), so a compound command's grandchildren ( |
| Post-conditions verified in the same call: |
| Declarative retry; |
| Real OS sandbox. |
| Dry-run in a disposable CoW clone of cwd — the command runs inside the clone, you get the cwd-relative |
| Structured FS/syscall trace (Linux |
| Strip credential-shaped env vars ( |
| Keep this run memory-only: addressable via |
| Start a long-running process (dev server, |
id, exit, ok, ms; then attempts, stdout_lines/stderr_lines (TRUE emitted
counts), files_changed, timed_out, stdout_truncated/stderr_truncated,
stdout_binary/stderr_binary, sandboxed, secrets_protected/secrets_unprotected,
secrets_env_scrubbed, stored ("memory-only" under no_store), preview/
preview_method/preview_warning/preview_effects_incomplete, trace_summary/
trace_unavailable/trace_truncated, assert_ok/assertions_failed, advice, hint,
and the condensed stdout/stderr. A background: true run instead returns id, pid,
status: "running", and a hint pointing at sh_logs/sh_kill.
Env var | Default | Meaning |
| 45 | stdout shorter than this (lines) is returned whole |
| 20 | lines kept from the top when condensing |
| 20 | lines kept from the bottom when condensing |
| 1000 | max chars of any single inline line (longer → capped with a pointer) |
| 60 | on failure, show up to this many stderr lines inline |
| 120000 | default per-command timeout (0 = none) |
| 5000000 | max bytes stored per stream (older dropped) |
| 500 | max addressable run records (oldest evicted) |
| 268435456 | total disk-store byte budget (256MB), on top of |
| auto | record store base ( |
| 86400000 | persisted records older than this are pruned on boot (0 = keep) |
| true | compute the git effect-diff (set |
| 16 | max concurrent live |
Output honesty
Condensing saves tokens, but it must never hide signal. So:
A failure buried mid-stream is surfaced — including crash idioms with no error/fail keyword (
Segmentation fault,SIGSEGV,CONFLICT,! [rejected],undefined reference,timed out). More distinct signals than fit inline? The marker reports the true total with a+N morenote, never a silent cap. Best-effort, but measured: 100% recall on a labeled corpus (see below).A byte-capped stream is labeled and never shows its tail as the head.
stdout_lines/stderr_linesare the true emitted count; binary output is base64-flagged, not mangled to mojibake.advicenever blocks — it nudges on the highest-signal issue (widen a sandbox denial, checkpoint before an unconfined destructive command, use raw Bash for an interactive tool).
Safety
sh_run runs arbitrary shell commands with your privileges, and exposes the
server's full environment (secrets included) to them. It's a shell — run it in trusted
contexts. Two opt-in layers harden the risky cases:
Kernel sandbox (
sandbox: true) — the real boundary. Confines writes to cwd + temp via macOSsandbox-exec(Linux bubblewrap / Landlock, experimental), optionally denies network (Linux bwrap also masks/run//var/run, so a Docker/Podman socket isn't a bypass), blocks reads of secret dirs, and refuses to run rather than go unconfined. Honest scope: solid on macOS; Linux bwrap needs unprivileged user namespaces, which containers / Codespaces / Ubuntu 24.04+ often restrict — there veil falls back to a namespace-free Landlock backend (vialandrun, kernel 5.13+) that write-confines where bwrap can't, and still reports unavailable (refusing) if neither works. The Landlock path is write-confine only: it refuses network-deny / secret-read-confine rather than fake them. The default non-sandboxed path works everywhere.Guard hook (
hooks/veil-guard.sh) — a routing nudge, not a security boundary. It steers verbose/dangerous Bash towardsh_run, but it is fail-open andVEIL_BYPASS-able and never stops a command from running. Real containment is the sandbox above.
A PreToolUse guard that hard-blocks only verbose (installs / builds / test
runners — npm/pnpm/yarn/bun/deno/uv/pip/cargo/go/…, plus
docker build/buildx/compose build) or dangerous (rm -rf, dd, mkfs,
shred, find -delete, raw-device writes) Bash, steering it to sh_run. Commands
sh_run can't help with are explicitly allowed through to raw Bash: long-running
dev/watch/start servers (incl. bun run dev, docker compose up),
backgrounded jobs (trailing &), process management (kill/pkill), and
interactive/TTY tools (vim/less/top/tail -f). It is fail-open (any parse
error → allow, so a bug can never block all Bash), with an escape hatch: prefix a
command with VEIL_BYPASS=1 to force raw Bash.
It classifies what the shell will execute, not what the command string contains:
heredoc bodies and quoted strings are stripped before matching (so a commit message
mentioning "build", or grep -E '"(tsc|build)"' package.json, is not a build), and
every tool name must sit at executable position — start of command, after an operator,
or behind a runner like sudo/timeout/npx — so grep -rn "HttpApiGroup.make" src
passes while npx vitest run still blocks. Deleting a regenerable build artifact
(rm -rf .next|dist|build|out|coverage|.turbo|node_modules/.cache, relative or under an
absolute project path) is not treated as dangerous, which keeps the dev-server
restart idiom (pkill …; rm -rf .next; nohup next dev …) on the allow path; anything
unresolvable — a glob, .., ~, $VAR, a root-level path, or one non-build target in
the list — still blocks. Enable globally in ~/.claude/settings.json:
{ "hooks": { "PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command",
"command": "/bin/sh '/ABSOLUTE/PATH/veil-mcp/hooks/veil-guard.sh'" }] }
] } }Takes effect on the next Claude Code restart. Remove the entry to disable.
Adoption
veil is opt-in and complements Bash — its value lands only when the agent actually
reaches for sh_run, and an agent left to itself often defaults to raw Bash. Two levers
close that gap: the nudge (veil init writes a short CLAUDE.md block — soft,
zero-friction) and the guard hook (stronger, per-machine). There's no native
integration yet, so one must be configured; or skip both and call sh_run directly.
Reproduce every number
Don't take the numbers on trust — no account, all local:
git clone https://github.com/vkmtx/veil-mcp && cd veil-mcp && npm install
npm test # 429+ smoke assertions over a live stdio server (prints its tally; some platform-gated)
npm run metrics # the value numbers below
npm run backtest # byte-savings regression (bulk-condense ratio + per-command overhead floor)
npm run bench # detailed 5-dimension benchmark (economy, latency, per-feature, condense, session)Metric | Result | What it measures |
Agent turns saved | 55% fewer round-trips (11 → 5) — a scenario model, not a live measurement | MCP calls collapsed by |
Sandbox escapes blocked | 5 / 5 | adversarial outside-cwd / spawned-child / symlink / network writes denied by the kernel; a legitimate in-cwd write still lands (selective, not deny-all) |
Signal recall | 100% on 10 fixtures | buried failures surfaced from the elided middle, incl. non-keyword crash idioms |
Checkpoint cost | clone ~1.5× faster, ~0 MB vs rsync 60 MB | CoW clone latency + disk vs the rsync mirror (macOS / same-volume APFS) |
The deterministic rows (turns, recall) are asserted in the smoke suite from the same fixtures, so the published figures can't silently drift. Timing rows are machine-dependent. CI runs the whole suite on macOS and Linux (with bubblewrap + strace), so the Linux-only sandbox and trace paths are exercised too.
Feature | Status | |
I / J / H | token-aware output · addressable detail ( | ✅ done |
G / M | inline assertions ( | ✅ done |
B / K-lite | blast-radius classification (read-only → destructive) gating every | ✅ done |
C / C+ | checkpoint / rollback · atomic CoW clone (same-volume APFS; cross-volume falls back to rsync, reported honestly) | ✅ done |
K | real sandbox (macOS | ✅ done |
J+ | disk-backed record store (survives restart, TTL-pruned) | ✅ done |
K-read / P | secret read-confine ( | ✅ done |
— | tool surface pruned to what agents actually call ( | ✅ done |
K+ / A | Linux sandbox (bubblewrap) · structured trace ( | 🧪 experimental — validated on Linux CI |
K++ | namespace-free Linux sandbox (Landlock via | 🧪 experimental — arg-builder unit-tested |
— | background / long-running processes ( | ✅ done |
— | streaming / PTY (interactive processes) | 🔭 planned |
See CHANGELOG.md for version history and ARCHITECTURE.md for the module/feature map. (Why an MCP server and not a shell fork? Most of the value is a presentation/orchestration layer that ships natively to how an LLM already consumes tools — in weeks, not a 200k-line C fork — and the kernel/FS bits, veil drives rather than reimplements.)
Community
Early project, good time to shape it:
💬 Discussions — questions, ideas, show-and-tell
🐛 Issues — bugs & feature requests (templates provided)
🌱 Good first issues — scoped first PRs
License
MIT — see LICENSE.
v0.7.1 — experimental, single-author. Adds background/long-running processes (sh_logs /
sh_kill), env-secret scrubbing (scrub_env), memory-only runs (no_store), and two
correctness/security audit passes (CHANGELOG): 429+ smoke assertions +
backtest + value metrics, green on macOS and Linux CI. Judge it by the reproducible suite
above, not its age.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Tools
Related MCP Servers
- AlicenseAqualityBmaintenanceMCP server that lets AI models invoke CLI agents (Gemini, Codex, Claude, OpenCode) as tools — with parallel execution, retries, and structured output parsing.Last updated54MIT
- AlicenseBqualityAmaintenanceAgent-optimized MCP server that replaces built-in file, search, exec, and git tools with compact, structured JSON equivalents. Benchmarked 20–45% token savings for AI coding agents.Last updated202MIT
- AlicenseAqualityBmaintenanceA local-first MCP server that gives AI coding agents persistent memory and controlled commands. Features a git-backed markdown knowledge vault with FTS5 search, surgical section edits, token-aware context budgeting, and a sandboxed command engine with human approval gates. Works with Claude Code, Cursor, Copilot, Gemini, and more.Last updated531Apache 2.0
- Flicense-qualityBmaintenanceA production-ready MCP server enabling Claude and other LLMs to perform intelligent file operations with minimal token usage.Last updated
Related MCP Connectors
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Local-first RAG engine with MCP server for AI agent integration.
User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/vkmtx/veil-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server