← Discover MCPs and Agents
s
MCPAI & MLGitHub

smoke

SMOKE sits between the agent deciding to write code and the file actually being written. Every .js, .ts, .tsx, and .py

Links

README

From the repo.

SMOKE logo

SMOKE

Write. Run. Know.

Rust 1.81+ Apache 2.0 Status Sandbox


SMOKE sits between the agent deciding to write code and the file actually being written. Every .js, .ts, .tsx, .py, and .rs file is:

  1. Syntax-checked by tree-sitter (< 1 ms)
  2. Validated in an isolated environment — V8 for JS/TS, subprocess + seccomp for Python, cargo check/rustc for Rust
  3. Optionally followed by auto-running co-located test files

The agent finds out about bugs the same second it introduces them, and SMOKE tracks repeated failures so an agent stuck retrying the same broken fix gets nudged to change strategy instead of looping forever.

Works as a Claude Code hook (PreToolUse / PostToolUse), an MCP server for Claude Desktop, Windsurf, Cursor, Cline, and Roo Code, and as a standalone CLI.


Install

# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/senapati484/smoke/main/install.sh | sh
# Windows — PowerShell, no Admin needed
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/senapati484/smoke/main/install.ps1'))

This builds SMOKE from source, installs the binary to ~/.smoke/bin, adds it to your PATH, and asks which tools to register it with:

1) All supported tools        [default]
2) Claude Code only (hooks)
3) Claude Desktop (MCP server)
4) Windsurf (MCP server)
5) Cursor (MCP server)
6) Cline / Roo Code (MCP server)
7) Custom — enter tool keys manually
8) Skip registration

Piped through curl | sh (non-interactive), it registers all tools automatically.

Prerequisites: Rust (curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh). Python ≥ 3.6 on PATH only if you want Python sandboxing.

Verify

source ~/.zshrc   # or ~/.bashrc — reload your shell first
smoke status
smoke test --code 'console.log("hello")' --lang js
smoke test --code 'print("hello")' --lang py

Manage registration later

smoke install --tools cursor              # add a tool
smoke uninstall --tools claude-desktop    # remove a tool
smoke status                              # show what's registered where
Manual registration (skip the installer, edit config files directly)

Claude Code hooks (~/.claude/settings.json):

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{ "type": "command", "command": "smoke hook", "timeout": 10 }]
    }],
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{ "type": "command", "command": "smoke post-hook", "timeout": 30 }]
    }]
  }
}

MCP server (Claude Desktop, Windsurf, Cursor, Cline/Roo Code — config file path varies by client):

{
  "mcpServers": {
    "smoke": { "command": "/home/you/.smoke/bin/smoke", "args": ["server"] }
  }
}

Run smoke status to see the exact config path SMOKE targets for each tool on your OS.


How it works

Write/Edit tool call
      │
      ▼
  tree-sitter syntax check (<1ms)  ──fail──▶ block, exact line/col error
      │ pass
      ▼
  large file? extract enclosing function/class (>200 lines)
      │
      ▼
  run in sandbox                    ──fail──▶ block, real stdout/stderr
  (V8 for JS/TS · seccomp for Python · cargo check/rustc for Rust)
      │ pass
      ▼
  PostToolUse: run co-located tests ──fail──▶ block, test output
      │ pass
      ▼
  write completes ✓
ModeCommandUse case
Hooksmoke hookPreToolUse — blocks/warns before bad code reaches disk
Post-hooksmoke post-hookPostToolUse — runs co-located tests after a successful write
MCP serversmoke serversmoke_verify tool, usable from any MCP client

Hook modes & loop detection

Set via [hook].mode in config:

  • advisor (default) — never blocks. Errors go to the terminal and into Claude's context via additionalContext so the agent can self-correct.
  • strict — blocks (exit 2) on syntax/sandbox errors, but only for standalone-runnable scripts (has fn main in Rust, or no import/export/require in JS/TS). Module files fall back to warnings.
  • silent — verification skipped, everything allowed.

Loop detection watches for an agent retrying the same broken fix. On each failure, SMOKE normalizes the error (strips line numbers, whitespace, quoted values) into a fingerprint and records it per Claude Code session in ~/.smoke/state/<session_id>.json:

  • Same fingerprint again → a warning note is prepended.
  • Same fingerprint a 3rd time (configurable) → the message is replaced with a forced strategy-change prompt telling the agent to stop retrying variations, re-read the error, state its hypothesis, or ask the user.
  • A successful write clears the fingerprint history for that file.

CLI commands

CommandDescription
smoke hookPreToolUse handler (reads Claude Code's hook JSON from stdin)
smoke post-hookPostToolUse handler (discovers and runs co-located tests)
smoke serverMCP server over stdio
smoke test --code '...' --lang js|ts|py|rustRun a snippet directly in the sandbox
smoke install [--tools all|claude-code,cursor,...]Register SMOKE with one or more AI tools
smoke uninstall [--tools ...]Remove SMOKE registration, leaves other config untouched
smoke statusShow registration status for every supported tool
smoke config initWrite a commented .smoke.toml to the current directory
smoke config showPrint the fully-merged active configuration
$ smoke test --code 'const x: number = 42; console.log(x)' --lang ts
{ "passed": true, "stdout": "42", "stderr": "", "language": "typescript", "execution_time_ms": 38 }

$ smoke test --code 'print(1/0)' --lang py
{ "passed": false, "stdout": "", "stderr": "ZeroDivisionError: division by zero", "language": "python", "execution_time_ms": 14 }

Configuration

Four layers, each overriding the last: built-in defaults → ~/.config/smoke/smoke.toml.smoke.toml (project) → --config <path>.

smoke config init   # writes .smoke.toml with defaults + inline comments
[limits]
timeout_ms = 2000              # sandbox execution timeout
max_file_lines = 200           # above this, Edit runs snippet-only (enclosing function/class)
memory_limit_mb = 256          # Python subprocess memory cap (MB)
max_file_lines_absolute = 1000 # above this, verification is skipped entirely

[languages]
js_enabled = true
ts_enabled = true
python_enabled = true
rust_enabled = true

[python]
interpreter = "python3"

[hook]
mode = "advisor"               # advisor | strict | silent

[prompts]
deletion_lines_threshold   = 50   # soft-warn when an Edit removes ≥N lines
deletion_percent_threshold = 30   # ...or ≥N% of the file
writing_size_threshold     = 100  # soft-warn when new code re-implements a stdlib pattern
clean_file_line_threshold  = 50   # praise small, clean edits under N lines
clean_max_added_lines      = 30   # ...that add at most N lines

[loop_detection]
enabled = true
warn_threshold = 2             # repeat count that triggers a warning note
escalate_threshold = 3         # repeat count that forces a strategy-change prompt
fingerprint_window_minutes = 30
state_retention_hours = 24     # how long session state is kept before cleanup

Every field is optional — only set what you need to change.


Benchmark & Performance

SMOKE has a built-in benchmark subcommand to measure local execution overhead:

smoke benchmark

Key performance metrics measured on a standard developer machine:

ComponentAverage LatencyImpact
Tree-sitter Parser~13.8 µsInstantaneous syntax check
JS V8 Sandbox (Warm)~30.0 µsZero sandbox escape execution
Python Sandbox~15.0 msSubprocess spawn overhead limits
Loop Tracking (FNV-1a)~8.18 µsScoped fingerprint check
  • Feedback loop latency is reduced by ~99.5% (15-50ms vs 10s+ baseline).
  • Token consumption is reduced by ~85% per syntax/execution bug by catching errors inside the same tool turn.

Security model

JS/TypeScript — runs in V8 via deno_core. No filesystem or network access; that's a V8 property, not a SMOKE setting.

Python — process-isolated: rlimit caps CPU/memory (256 MB)/file descriptors; seccomp (Linux) blocks fork/exec/raw sockets; timeouts get SIGTERM → 500ms → SIGKILL on the whole process group. Not container-grade — logic-based escapes aren't prevented. SMOKE catches bugs in agent-generated code, it doesn't sandbox adversarial code. Use E2B or Modal for that.

Rust — not executed, only checked for compile-correctness: cargo check --tests if a workspace Cargo.toml is found (temp-writes the change, restores original after), otherwise rustc --emit=metadata on the standalone snippet. post-hook mode runs cargo test -- <file-stem> for matching tests.


Design notes

  • Fail-open — internal errors, unknown extensions, disabled languages all exit 0. Only confirmed syntax/runtime errors block.
  • V8 runs on a dedicated OS thread (~20–50ms cold start); a watchdog thread kills JS/TS infinite loops by polling every 10ms.
  • Large files (>200 lines) get snippet-only verification: tree-sitter walks the AST to the enclosing function/class.
  • .tsx files use the TSX dialect grammar to avoid false positives on JSX.
  • Loop detection is session-scoped and fingerprint-based (see above) — a real fix clears the history for that file.

FAQ

Does it work with agents other than Claude Code?

PreToolUse/PostToolUse hooks are Claude Code–specific. smoke server works with any MCP client.

What about import/require in JS/TS snippets?

No filesystem/network access in the V8 sandbox, so external module resolution fails. SMOKE tests snippets in isolation, not a full Node environment.

Can I disable a language or the loop detector?

Yes — rust_enabled = false (or js_enabled/ts_enabled/python_enabled = false) in [languages], or enabled = false in [loop_detection].

Will it block my agent when there are no tests yet?

No. smoke post-hook only runs tests that already exist alongside the edited file.

Why Rust?

~5ms JS startup, embedded V8, kernel-level seccomp, tree-sitter at compile time.


Building from source

git clone https://github.com/senapati484/smoke.git
cd smoke
cargo build --release        # binary at ./target/release/smoke
cargo test

Docs

DocumentCovers
Getting StartedStep-by-step tutorial
ArchitectureModules, data flow, security model
ConfigurationFull config reference
DevelopmentBuild commands, adding languages
TestingSandbox testing, known limitations

License

Apache 2.0

Collected info

  • 1 stars
  • Language: Rust
  • Source updated: 7/15/2026

Config for your environment

Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.

Tool

OS

Config file: ~/.cursor/mcp.json

{
  "mcpServers": {
    "mcp-server": {
      "url": "{MCP_ENDPOINT_URL}"
    }
  }
}

Paste into mcpServers in the config file. Restart Cursor after saving.

If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.