Skip to main content
Glama
vkmtx

veil-mcp

by vkmtx

veil-mcp

CI npm License: MIT Node 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

expect: { exit: 0, file_exists: "dist/index.js" } folds run → check → grep into a single call; effects come back typed, so "what changed?" needs no git status.

55% fewer round-trips (11 → 5) — a scenario model over 5 hand-picked common tasks, not a live measurement

♻️ Checkpoint & roll back

sh_checkpoint / sh_restore wrap a risky refactor in an undo — a copy-on-write clone on APFS.

clone ~1.5× faster, ~0 MB vs a 60 MB rsync copy

🔒 Kernel sandbox

sandbox: true confines writes to cwd + temp (optionally no network) — and refuses to run rather than go unconfined.

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

sh_run

Run a command → quiet structured result: exit, duration, files changed, token-aware stdout/stderr. background: true starts a long-running process (dev server, --watch) and returns { id, pid, status: "running" } immediately instead of blocking. The workhorse.

sh_logs

Poll a background run's output — incremental, per-stream byte cursor (stdout_cursor/stderr_cursor), plus status/exit/signal. Never re-dumps what was already tailed. Omit id for the newest live run.

sh_kill

Stop a background run. Signals the whole process group; SIGTERM escalates to SIGKILL after 2s. Killing an already-exited id is idempotent. Omit id for the newest live run.

sh_detail

Pull the full stored output of a past run — no re-run. Disk-backed, so it survives a server restart. match=<regex> greps the stored stream for a value condensing hid. Omit id for the most recent run.

sh_checkpoint / sh_restore

Snapshot a directory and roll back. Owner-only (0700) storage, published atomically. Restore refuses a target dir different from where the checkpoint was taken. Omit label to auto-number the checkpoint / restore the newest.

sh_checkpoints

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 the keys the caller's own Bash tool uses: cmd (→ command), timeout (→ timeout_ms, in milliseconds, string or number), run_in_background (→ background), and description (accepted, ignored). These are not conveniences — a 30-day audit of real agent sessions found argument shape, not execution, behind most failed calls, and an undeclared key is silently dropped before the handler runs: 131 calls asked for a timeout that was never applied, and 4 "background" runs blocked to completion instead.

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

command

The shell command (required).

cwd

Working directory (defaults to the server's cwd).

full

Return uncondensed stdout/stderr inline (escape hatch from condensing).

timeout_ms

Per-command timeout (default 120s). On expiry the whole process group is killed (SIGTERM→SIGKILL), so a compound command's grandchildren (sleep 5; …) are reaped too.

expect

Post-conditions verified in the same call: exit, stdout_contains, stdout_matches, stderr_empty, file_exists, file_absent, changed, max_ms. Failures surface in assert_ok + assertions_failed — no second ls/grep/git status.

retries / retry_on_exit / backoff_ms

Declarative retry; attempts is reported when > 1.

sandbox

Real OS sandbox. true confines file writes to cwd + temp; { network: false } also denies network; { writable: [...] } adds roots. { protect_secrets: true } or { deny_read: [...] } also blocks reads of configured secret dirs (~/.ssh, ~/.aws, …) — macOS deny file-read*, Linux --tmpfs mask; sets secrets_protected: <n>. Scoped: it blocks the listed paths, not a proof against all exfiltration. Refuses to run if unavailable — never executes unconfined. Sets sandboxed: true.

preview

Dry-run in a disposable CoW clone of cwd — the command runs inside the clone, you get the cwd-relative files_changed, and the real cwd is never touched (nothing is promoted). Honest scope: absolute-path / parent-dir / network effects are not captured and may happen for real — this is not a sandbox (combine with sandbox:true for containment). Refuses if the cwd can't be cloned. Sets preview: true + preview_warning; a diff too large to buffer reports preview_effects_incomplete rather than silently claiming nothing changed.

trace

Structured FS/syscall trace (Linux strace). Surfaces trace_summary (paths read/written + syscall count); full trace via sh_detail selector=trace. Best-effort: no tracer → command still runs, trace_unavailable: true. Bounded by VEIL_MAX_STREAM_BYTES; an overflowing trace sets trace_truncated: true.

scrub_env

Strip credential-shaped env vars (*_TOKEN/*_KEY/AWS_*/…) from the child's environment before spawn. Auto-on whenever sandbox.protect_secrets/deny_read is set. Reports secrets_env_scrubbed — a count, values are never echoed.

no_store

Keep this run memory-only: addressable via sh_detail for the session, but never written to disk. Sets stored: "memory-only".

background

Start a long-running process (dev server, --watch) — returns immediately with { id, pid, status: "running" } instead of blocking until exit. Poll with sh_logs id=<id>, stop with sh_kill id=<id>. No stdin/TTY. Refused together with options that need completion (expect, preview, trace, retries, full, timeout_ms); still honors cwd/sandbox/scrub_env/no_store. Capped by VEIL_MAX_BG_PROCS (default 16); live children are reaped on server shutdown.

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

VEIL_INLINE_MAX_LINES

45

stdout shorter than this (lines) is returned whole

VEIL_HEAD_LINES

20

lines kept from the top when condensing

VEIL_TAIL_LINES

20

lines kept from the bottom when condensing

VEIL_MAX_LINE_CHARS

1000

max chars of any single inline line (longer → capped with a pointer)

VEIL_STDERR_INLINE_ON_FAIL

60

on failure, show up to this many stderr lines inline

VEIL_TIMEOUT_MS

120000

default per-command timeout (0 = none)

VEIL_MAX_STREAM_BYTES

5000000

max bytes stored per stream (older dropped)

VEIL_MAX_RECORDS

500

max addressable run records (oldest evicted)

VEIL_MAX_STORE_BYTES

268435456

total disk-store byte budget (256MB), on top of VEIL_MAX_RECORDS — oldest evicted by mtime

VEIL_STATE_DIR

auto

record store base ($XDG_STATE_HOME/veil~/.local/state/veil$TMPDIR/veil). none/off/memory/0 = memory-only

VEIL_RECORD_TTL_MS

86400000

persisted records older than this are pruned on boot (0 = keep)

VEIL_EFFECTS

true

compute the git effect-diff (set 0 to skip in huge repos)

VEIL_MAX_BG_PROCS

16

max concurrent live background: true processes

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 more note, 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_lines are the true emitted count; binary output is base64-flagged, not mangled to mojibake.

  • advice never 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 macOS sandbox-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 (via landrun, 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 toward sh_run, but it is fail-open and VEIL_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 expect + effects + retry across 5 hand-picked common tasks (bench/metrics-data.ts) — counts calls, not bytes, so it holds as context windows grow

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 (sh_detail, match) · effect diff

✅ done

G / M

inline assertions (expect) · declarative retry/timeout

✅ done

B / K-lite

blast-radius classification (read-only → destructive) gating every sh_run

✅ done

C / C+

checkpoint / rollback · atomic CoW clone (same-volume APFS; cross-volume falls back to rsync, reported honestly)

✅ done

K

real sandbox (macOS sandbox-exec)

✅ done

J+

disk-backed record store (survives restart, TTL-pruned)

✅ done

K-read / P

secret read-confine (sandbox.protect_secrets) · dry-run preview (CoW clone, real cwd untouched)

✅ done

tool surface pruned to what agents actually call (sh_plan, sh_history removed in 0.8.0 — 0 calls across a 30-day audit of 3.5k real sessions)

✅ done

K+ / A

Linux sandbox (bubblewrap) · structured trace (strace)

🧪 experimental — validated on Linux CI

K++

namespace-free Linux sandbox (Landlock via landrun) — write-confine in containers/Codespaces where bwrap can't

🧪 experimental — arg-builder unit-tested

background / long-running processes (background: true, sh_logs, sh_kill) for dev servers / watchers

✅ 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:

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.

Available Tools

4 tools
sh_checkpointCheckpoint a directory (rollback point)A

Snapshot a working directory under a label so you can restore it later with sh_restore. Excludes .git and node_modules. Take one before a risky or irreversible change.

ParametersJSON Schema
NameRequiredDescriptionDefault
dirNoDirectory to snapshot. Defaults to server cwd.
labelYesCheckpoint name ([A-Za-z0-9._-]).

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses that .git and node_modules are excluded, which is helpful. However, with no annotations, the description could mention idempotency, storage location, or overwrite behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with purpose, no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Sufficient for a simple tool with few parameters and no output schema, though could mention overwrite behavior or error handling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema covers 100% of parameters with descriptions; the tool description adds minimal extra semantic info beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states it snapshots a directory under a label for later restoration, using specific verb 'Snapshot' and distinguishing from sibling sh_restore.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly advises to take a checkpoint before a risky or irreversible change, and mentions restoration via sh_restore, providing clear context for use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sh_detailPull stored detail for a previous runA

Retrieve full stored output for a previous sh_run by id WITHOUT re-running it (the addressable output store). Use after a condensed result hid lines you need.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe run id returned by sh_run (e.g. cmd3).
matchNoWith selector stdout/stderr: return ONLY lines matching this regex (with line numbers) — grep the stored stream for a value condensing hid, without dumping it all.
selectorNoWhich slice to return. 'trace' returns the full captured syscall trace.full

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and does disclose that the tool does not re-run the command. This is a key behavioral trait. However, it lacks details on side effects, authentication requirements, rate limits, or error behaviors, which would be helpful for an agent to fully understand the tool's operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences that front-load the primary purpose and then provide usage context. Every word is necessary, and there is no redundancy or filler. It is well-structured for quick comprehension.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has three well-documented parameters and no output schema, the description covers the essential functionality and usage scenario. It explains when to use the tool and what it returns at a high level. However, it does not elaborate on the return format or potential edge cases, which would raise it to a 5.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Since schema description coverage is 100%, the baseline is 3. The description adds no additional information about the parameters beyond what the schema already provides, such as the meaning of the 'id' or how to use 'match' and 'selector'. The schema alone is sufficient.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves full stored output for a previous sh_run by ID without re-running. It distinguishes itself from siblings like sh_run by emphasizing the 'without re-running' aspect and the 'addressable output store', making the purpose specific and unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly advises to use this tool after a condensed result has hidden needed lines, providing clear context for when it is appropriate. However, it does not mention alternatives or explicitly state when not to use it, leaving some room for ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sh_killStop a background runA

Stop a background run started with sh_run background:true, by id. Signals the whole process group (so a dev server's children die too). SIGTERM (the default) escalates to SIGKILL after 2s if the process ignores it. Returns status:"terminating" — the signal was sent but the OS hasn't confirmed the process is dead yet; poll sh_logs to see it settle to exited/killed. Killing an id whose process has ALREADY exited is not an error — it returns already_exited (idempotent).

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe background run id returned by sh_run (e.g. cmd7).
signalNoSignal to send. SIGTERM escalates to SIGKILL after 2s.SIGTERM

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description fully bears the load. Discloses key behavioral traits: whole process group signaling, SIGTERM→SIGKILL escalation after 2s, idempotency for already-exited runs, and specific return status.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Concise yet information-dense. Every sentence adds value: purpose, signaling behavior, return status, and idempotency. Front-loaded with the core action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers major aspects: purpose, parameters, behavior, return status, and edge case (already-exited). Lacks detail on error handling for invalid ids, but overall sufficient for the tool's complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, baseline 3. Description adds value by explaining id provenance (from sh_run) and signal behavior (escalation), which goes beyond schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb (stop) and resource (background run), and differentiates it from siblings like sh_logs and sh_detail by specifying it kills a run started with sh_run background:true.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides context on when to use (to stop a background run), behavior like signaling process group and signal escalation, and mentions polling sh_logs for confirmation. Lacks explicit alternatives or when-not-to-use instructions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sh_logsPoll a background run's outputA

Read the output of a background run started with sh_run background:true, by id. Returns a QUIET, condensed view of stdout/stderr plus status (running/exited/killed), exit code, and running_ms. Pass back stdout_cursor/stderr_cursor (the values returned by the previous call) to get ONLY new output since last poll — ideal for tailing a dev server. A live process reads from its in-memory buffer; once it exits the SAME id resolves to the durable record.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe background run id returned by sh_run (e.g. cmd7).
fullNoIf true, return full output inline (skip condensing).
streamNoWhich stream(s) to return.both
stderr_cursorNostderr byte cursor from a previous sh_logs call; returns only stderr since then.
stdout_cursorNostdout byte cursor from a previous sh_logs call; returns only stdout since then.

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It discloses key behaviors: condensed view, cursor usage, and difference between live and durable records. However, it lacks details on potential side effects, rate limits, or authentication requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise (about 3 sentences) and front-loaded with the primary purpose. Every sentence adds value, but could be slightly more structured with bullet points.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description adequately explains return values (condensed stdout/stderr, status, exit code, running_ms). It also covers the cursor mechanism and lifecycle behavior. Enough for an agent to understand what to expect.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description adds context beyond the schema by explaining the purpose of cursors (to get only new output) and the distinction between live and exited processes. This enhances understanding of parameter usage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it reads output of a background run by id, specifying the resource (background run) and action (read output). It also distinguishes from sibling tools like sh_run (starts runs) and sh_kill (kills runs), though not explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on using cursors for incremental polling ('ideal for tailing a dev server'). It also explains the behavior for live vs. finished processes, but does not explicitly state when not to use the tool or suggest alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.7.1
    • Removedsh_checkpoints
    • Removedsh_history
    • Removedsh_plan
    • Removedsh_restore
    • Removedsh_run
  2. 5 tool updatesv0.7.0
    • Changedsh_checkpoints1 field changed
      • addedInput schema / properties / dir
        Added value: +{
        +  "description": "Project directory whose checkpoints to list. Defaults to server cwd.",
        +  "type": "string"
        +}
    • Changedsh_detail1 field changed
      • changedInput schema / properties / selector / description
        Previous value: -"Which slice to return. 'trace' returns the full captured syscall trace (feature A)."New value: +"Which slice to return. 'trace' returns the full captured syscall trace."
    • Addedsh_kill
    • Addedsh_logs
    • Changedsh_run4 fields changed
      • addedInput schema / properties / background
        Added value: +{
        +  "description": "Run as a LONG-RUNNING background process (dev server, --watch build): returns IMMEDIATELY with { id, pid, status:\"running\" } instead of blocking until exit. Poll its output with sh_logs id=<id> (pass the returned cursor to tail only new lines); stop it with sh_kill id=<id>. No stdin/TTY. Incompatible with options that require completion (expect, preview, trace, retries, full, timeout_ms) — those are refused. Keeps cwd, sandbox, scrub_env, no_store.",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / no_store
        Added value: +{
        +  "description": "Keep this run MEMORY-ONLY: the record is cached for sh_detail this session but is NOT written to disk. Use for a sensitive run whose output should not persist. Result carries stored:\"memory-only\" so you know sh_detail works now but nothing was persisted.",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / scrub_env
        Added value: +{
        +  "description": "Strip credential-shaped vars (SECRET/TOKEN/PASSWORD/KEY/… see SECRET_ENV_PATTERNS) from the command's environment so a child can't read them. Auto-enabled whenever the sandbox requests protect_secrets or deny_read — masking ~/.ssh while leaving tokens in $env would be inconsistent. Surfaces secrets_env_scrubbed (a COUNT; values are never echoed).",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / trace / description
        Previous value: -"Capture a structured FS/syscall trace (feature A; Linux strace). Surfaces a read/write summary; full trace via sh_detail selector=trace. Best-effort: if no tracer is available the command still runs and trace_unavailable is set."New value: +"Capture a structured FS/syscall trace (Linux strace). Surfaces a read/write summary; full trace via sh_detail selector=trace. Best-effort: if no tracer is available the command still runs and trace_unavailable is set."
  3. 2 tool updatesv0.6.0
    • Addedsh_history
    • Changedsh_run3 fields changed
      • addedInput schema / properties / preview
        Added value: +{
        +  "description": "Dry-run in a disposable CoW clone of cwd: the command runs INSIDE the clone, you get the cwd-relative file diff, and the real cwd is never touched (nothing is promoted). Honest scope: absolute-path / parent-dir / network effects are NOT captured and may happen for real — this is NOT a sandbox (combine with sandbox:true for containment). Refuses if cwd can't be cloned.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / sandbox / anyOf
        Previous value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "properties": {
        -      "network": {
        -        "type": "boolean"
        -      },
        -      "writable": {
        -        "items": {
        -          "type": "string"
        -        },
        -        "type": "array"
        -      }
        -    },
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "type": "boolean"
        +  },
        +  {
        +    "properties": {
        +      "deny_read": {
        +        "description": "Extra existing DIRECTORIES whose reads are blocked under the sandbox (tilde ok).",
        +        "items": {
        +          "type": "string"
        +        },
        +        "type": "array"
        +      },
        +      "network": {
        +        "type": "boolean"
        +      },
        +      "protect_secrets": {
        +        "description": "Also block reads of a built-in secret-dir denylist (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gcloud|gh, ~/.kube, ~/.docker).",
        +        "type": "boolean"
        +      },
        +      "writable": {
        +        "items": {
        +          "type": "string"
        +        },
        +        "type": "array"
        +      }
        +    },
        +    "type": "object"
        +  }
        +]
      • changedInput schema / properties / sandbox / description
        Previous value: -"Run under a real OS sandbox (macOS sandbox-exec): file writes confined to cwd + temp. Pass {network:false} to also deny network, {writable:[...]} for extra writable paths. REFUSES to run (does not execute unconfined) if a sandbox is unavailable."New value: +"Run under a real OS sandbox (macOS sandbox-exec / Linux bubblewrap): file writes confined to cwd + temp. Pass {network:false} to deny network, {writable:[...]} for extra writable paths, {protect_secrets:true} or {deny_read:[...]} to BLOCK READS of configured secret dirs (scoped — blocks the listed paths, NOT a proof against all exfiltration). REFUSES to run (does not execute unconfined) if a sandbox is unavailable."
  4. 5 tool updatesv0.4.0
    • Changedsh_checkpoint1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedsh_detail1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedsh_plan1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedsh_restore1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedsh_run12 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / backoff_ms / maximum
        Added value: +9007199254740991
      • removedInput schema / properties / expect / additionalProperties
        Removed value: -false
      • addedInput schema / properties / expect / properties / exit / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / expect / properties / exit / minimum
        Added value: +-9007199254740991
      • removedInput schema / properties / expect / properties / file_absent / $ref
        Removed value: -"#/properties/expect/properties/file_exists"
      • addedInput schema / properties / expect / properties / file_absent / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "items": {
        +      "type": "string"
        +    },
        +    "type": "array"
        +  }
        +]
      • addedInput schema / properties / retries / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / retry_on_exit / items / maximum
        Added value: +9007199254740991
      • addedInput schema / properties / retry_on_exit / items / minimum
        Added value: +-9007199254740991
      • changedInput schema / properties / sandbox / anyOf
        Previous value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "network": {
        -        "type": "boolean"
        -      },
        -      "writable": {
        -        "items": {
        -          "type": "string"
        -        },
        -        "type": "array"
        -      }
        -    },
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "type": "boolean"
        +  },
        +  {
        +    "properties": {
        +      "network": {
        +        "type": "boolean"
        +      },
        +      "writable": {
        +        "items": {
        +          "type": "string"
        +        },
        +        "type": "array"
        +      }
        +    },
        +    "type": "object"
        +  }
        +]
      • addedInput schema / properties / timeout_ms / maximum
        Added value: +9007199254740991
  5. 6 tool updatesv0.3.0
    • First observedsh_checkpoint
    • First observedsh_checkpoints
    • First observedsh_detail
    • First observedsh_plan
    • First observedsh_restore
    • First observedsh_run

TDQS

A3.8/5.0

Scored across 4 tools

Disambiguation4/5

sh_detail and sh_logs both retrieve output for a run by id, so there is minor overlap after a background process exits. However, their primary purposes are clearly separated: full stored output vs. live/condensed tailing with status polling, and sh_checkpoint/sh_kill are unambiguous.

Naming Consistency3/5

All tool names consistently use the sh_ prefix and snake_case, which is readable. But the suffixes mix nouns (detail, checkpoint, logs) and a verb (kill), so the set does not follow a predictable verb_noun pattern.

Tool Count4/5

Four tools is a reasonable count for a focused helper set, and each tool has a distinct role. The count feels slightly low only because the set references core tools like sh_run and sh_restore that are not actually exposed here.

Completeness2/5

The descriptions repeatedly reference sh_run and sh_restore, but neither is available in this server, leaving obvious dead ends: an agent cannot create a run or restore a checkpoint. The exposed tools are useful helpers, but the surface is significantly incomplete for the domain it implies.

Maintenance

ActivitySlowing
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Agent-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.
    20
    2
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    A 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.
    4
    58
    316 npm
    1
    Apache 2.0