Skip to main content
Glama
Atharva-Jayappa

blast-scope-mcp

Blast Scope

A consequence engine for shell commands. Blast Scope scores what a command would actually do — before an AI agent (or you) runs it. It doesn't pattern-match syntax into a blocklist; it figures out the command's real target, observes that target with a safe, read-only probe, and returns a structured risk score with evidence.

The whole point is contextual blast radius. The same command gets a completely different score depending on what it would actually hit:

COMMAND                            SEVERITY   WHY                                          ADVICE
─────────────────────────────────  ────────   ──────────────────────────────────────────  ───────
rm -rf ./logs                      LOW        0 importers · regenerable · outside src      proceed
rm -rf ./config                    CRITICAL   8 modules import it · high PageRank hub      block
git reset --hard   (clean tree)    LOW        nothing uncommitted to discard               proceed
git reset --hard   (4 dirty files) HIGH       would throw away 4 files of uncommitted work confirm
git push --force   (protected)     CRITICAL   would orphan commits on a protected branch   block
docker volume rm cache  (absent)   LOW        volume doesn't exist — nothing to remove     proceed
docker volume rm pgdata (in use)   CRITICAL   holds data · in use · no image to rebuild    block
pip uninstall flask     (uv.lock)  LOW        regenerable — exact version pinned in lock   proceed
DROP TABLE users        (42 rows)  CRITICAL   schema + 42 rows · irreversible              block
DELETE FROM logs        (in txn)   HIGH       no WHERE — but inside a txn, ROLLBACK-able    confirm

Two commands can be byte-identical and score four bands apart. That gap is the product.

Not a blocklist. Not a replacement for Shellfirm. Not a syscall monitor. It scores structural consequence — advisory, never blocking — and for the rare critical command it captures an undo snapshot first.


How it works

A command flows through a cheap funnel: almost everything is recognized as non-destructive in microseconds and exits silent. Only a flagged destructive candidate pays for a probe.

  shell command
      │   split chains (&& || ; |) · de-alias PowerShell · parse flags/targets
      ▼
  ┌──────────────────────────────────────────────────────────────────────┐
  │  STAGE 1 · triage  (near-free regex — runs on every command)          │
  │     which class?   git · docker · pip/uv · sql · else filesystem       │
  │     destructive?   `git status` → no.   `git reset --hard` → yes ↓     │
  └───────────────────────────────┬──────────────────────────────────────┘
                    destructive candidate │   (everything else exits here, silent)
                                          ▼
  ┌──────────────────────────────────────────────────────────────────────┐
  │  ELIGIBILITY FILTER   safe read-only probe?   AND   undo authorable?   │
  └──────────────┬──────────────────────────────────────┬─────────────────┘
         yes, probe it │                       no probe here / now │
                       ▼                                           ▼
   STAGE 2 · safe probe (read-only)                    heuristic estimate
     git  status · reflog · rev-list                   from a static per-class
     docker  inspect · ps · ls                          table — and LABELED
     sqlite  SELECT count(*)  [mode=ro]                  "(estimated)" so you
     pip/uv  read lockfiles                              know it wasn't probed
                       │                                           │
                       └─────────────────────┬─────────────────────┘
                                              ▼
        blast radius  ×  reversibility   (combined PER CLASS — no global formula)
        filesystem also folds in: dependency-graph centrality + recoverability
                                              ▼
              score 0.0–1.0  →  severity (low / medium / high / critical)
                                              ▼
        PreToolUse hook:  silent (low/med) · advise (high) · advise + snapshot (critical)

The eligibility filter is the design boundary. A command class earns a live probe only when both hold: (1) its impact is observable by a strictly side-effect-free read (HTTP-GET sense — never mutate state to assess state), and (2) its undo story is well-known enough to encode in a static table. When a probe can't run here and now (no docker daemon, no DB driver, no creds), the tool degrades to a labeled estimate — it never guesses silently, and it never blocks.

See docs/heuristics.md for the per-class tables, the exact filesystem formula, and calibration.

The five command classes

Class

Destructive ops it scores

Safe (read-only) probe

Reversibility signal

Filesystem

rm -rf, mv, > truncate

dependency graph + git status

git-tracked? regenerable? secret? precious?

Git

reset --hard, push --force, branch -D, clean -fdx

status · reflog · rev-list · rev-parse @{u}

reflog window · remote ahead · protected branch

Docker

volume rm, system prune -a, rm -f

volume inspect · ps -a · volume ls

volume → none · container → recreatable from image

pip / uv

pip uninstall, uv pip uninstall

read lockfile / manifest (no subprocess)

lockfile present → fully regenerable

SQL

DROP, TRUNCATE, DELETE without WHERE

SQLite: SELECT count(*) mode=ro; transaction check

inside a transaction? backup posture?

New classes drop in behind one protocol (triage / assess) in src/blast_scope/classes/; each class confines assess to strictly side-effect-free reads.


Related MCP server: runtime-guard

Status

Calibrated multi-class guardrail with command resolution and a precise dependency graph.

Capability

Module

Flag/operand-sensitive command model (POSIX and PowerShell)

command_effects.py, command_parser.py

Command resolution — env/tilde/brace/glob expansion, unset-var hazards, script transparency (sh -c, npm run + pre/post hooks, script files, Makefile targets), read-only $(...) substitution

resolution.py

Dry-run oraclesgit clean -n exact lists, reset divergence, checkout clobber preview, find -delete-print rewrite, sqlite scoped-DELETE counts, rsync --dry-run; oracle targets feed the undo snapshot

classes/git.py, classes/find.py, classes/rsync.py, classes/sql.py

Recoverability classification (git state, secrets, regenerable, precious data)

recoverability.py

Dependency graph + weighted PageRank centrality, incremental indexing

graph_resolver.py, centrality.py

Two-axis, evidence-based filesystem scoring

risk_scorer.py

Command-class probes — git / docker / pip·uv / SQL, behind one protocol

classes/

Out-of-graph path analyzers (infra / config-by-path) + git base

consequences.py, vcs.py, infra.py, config_refs.py

PreToolUse hook + tarball snapshot/undo

hook.py, snapshot.py

Eval harness + labeled corpus + calibration

eval.py, tests/fixtures/eval_corpus.jsonl

Calibration. Two harnesses, both run-it-yourself:

  • In-repo corpus (tests/fixtures/eval_corpus.jsonl, 58 cases spanning every recoverability category, git working-tree state, infra/config, rm -rf .git, a graph-indexed central module, the git/docker/pip/SQL classes, and the resolution layer — unset-var collapses, glob/env-var targets, sh -c payloads, npm pre-hooks, opaque wrappers, mass destruction of tracked source) — 58/58 exact severity, gate F1 1.00, pinned by tests/test_eval.py with headroom so changes can't silently regress.

  • SABER — 716 real coding-agent workspaces. Against ~1725 safe commands, blast-scope's false-positive rate is 0.58%; on its core competency (data_destruction) it catches 82.4% of injected attacks on realistic workspaces — on the fast hook path, no graph required, thanks to command resolution (env/glob binding + script transparency). Wrapper transparency also lifts code_tampering from ~0% to 50%. The per-category recall is deliberately uneven, and the table says so: blast-scope scores destructive consequence — filesystem/data loss plus git/docker/pip/SQL state. Network exfiltration and persistence are a different threat model, out of scope by design — not an unfinished corner. That's the boundary, drawn on purpose. See bench/.

uv run python -m blast_scope.eval                 # in-repo corpus
python bench/saber_eval.py --tasks <saber>/dataset/data/tasks.jsonl   # SABER

Installation

The fastest path for any MCP client is zero-install via uvx (no clone, no venv):

uvx blast-scope        # runs the MCP server on stdio

Claude Code users — one line wires up both the MCP tools and the advisory hook:

/plugin marketplace add Atharva-Jayappa/blast-scope
/plugin install blast-scope

For development, or to pin a checkout:

git clone https://github.com/Atharva-Jayappa/blast-scope.git
cd blast-scope && uv sync --all-extras

Usage

As an MCP server

Add to your MCP client config (e.g. Claude Code settings.json):

{
  "mcpServers": {
    "blast-scope": { "command": "uvx", "args": ["blast-scope"], "type": "stdio" }
  }
}

Tools exposed:

Tool

Purpose

assess_command(command, cwd?, project_root?)

Score a (possibly chained) command. Returns score, severity, rationale, evidence, recoverability, affected nodes, and a per-segment chain breakdown.

index_project(project_root)

Force a dependency-graph rebuild (auto-built on first use otherwise).

list_snapshots(project_root)

List undo snapshots, newest first.

restore_snapshot(snapshot_id, project_root)

Undo a risky command by restoring its snapshot.

As a PreToolUse hook (tiered advice + auto-snapshot)

Intercept Bash commands before they run — advisory, never blocking. Volume scales with stakes: silent on low/medium, advise on high, advise + snapshot on critical. The snapshot skips what's already recoverable (git-clean, regenerable) and warns rather than tars anything over a hard size cap, so the undo net stays fast and trustworthy. Add to .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "python -m blast_scope.hook" }] }
    ]
  }
}

Full details and the undo flow: docs/hook.md.


Example output

A filesystem command, scored against the dependency graph:

// assess_command("rm -rf ./config", project_root="/proj")
{
  "score": 0.93,
  "severity": "critical",
  "recommendation": "block",
  "recoverability": "untracked",
  "rationale": "rm targets config. 8 direct importer(s), 14 total affected. not git-tracked. recursive deletion. CRITICAL risk.",
  "evidence": [
    "8 importer(s), 14 affected node(s)",
    "high centrality (PageRank 0.91) — a hub other code routes through",
    "untracked — not in git history",
    "recursive — applies to every file underneath"
  ],
  "affected_nodes": [ /* ... */ ],
  "chain": [ /* per-segment breakdown */ ]
}

A command class that couldn't probe — note the labeled estimate (no Postgres driver, server possibly remote, so the tool refuses to guess silently):

// assess_command('psql -c "DROP TABLE users"')
{
  "score": 0.9,
  "severity": "critical",
  "recommendation": "block",
  "evidence": [
    "drops users — its schema and all rows, irreversible (estimated — no read-only probe for postgres)"
  ]
}
// the same DROP against a local SQLite file probes for real:
//   "drops users — its schema and 42 row(s), irreversible"   (estimated: false)

Development

uv sync --all-extras
uv run pytest -q              # full suite
uv run python -m blast_scope.eval   # scoring accuracy report

Project structure

blast-scope/
├── src/blast_scope/
│   ├── server.py            # MCP server + tools (assess, index, snapshots)
│   ├── command_parser.py    # shell → structured intent (POSIX + PowerShell)
│   ├── command_effects.py   # command/flag/operand → intent + weight
│   ├── recoverability.py    # path → how recoverable if destroyed
│   ├── graph_resolver.py    # paths → dependency-graph impact (+ PageRank)
│   ├── centrality.py        # pure-Python weighted PageRank
│   ├── risk_scorer.py       # signals → score + severity + evidence
│   ├── classes/             # command-class probes behind one protocol
│   │   ├── __init__.py      #   Candidate · ConsequenceClass · registry
│   │   ├── git.py           #   reflog / upstream-divergence / protected branch
│   │   ├── docker.py        #   volume / container / system-prune probes
│   │   ├── packages.py      #   pip·uv uninstall vs. lockfile presence
│   │   └── sql.py           #   DROP/TRUNCATE/DELETE — SQLite probe + estimates
│   ├── consequences.py      # coordinator: class probes + path analyzers
│   ├── vcs.py / infra.py / config_refs.py   # git base + path analyzers
│   ├── hook.py              # PreToolUse advisory hook
│   ├── snapshot.py          # tarball snapshot / restore / list
│   ├── eval.py              # evaluation harness + metrics
│   └── vendor/crg/          # vendored from code-review-graph (MIT)
├── tests/                   # 298 tests incl. eval regression guard
│   └── fixtures/eval_corpus.jsonl   # labeled calibration corpus
└── docs/
    ├── heuristics.md        # scoring model + per-class tables + calibration
    └── hook.md              # hook registration + undo

Roadmap

  • Lift recall on the destruction classes (glob targets over tracked files, find-based deletion variants) — the SABER per-category table is the worklist.

  • Optional live probes for Postgres/MySQL (in-process, read-only) once a driver policy is settled — today those engines degrade to labeled estimates.

  • PowerShell-shell awareness in the hook path (the MCP tool already supports it).

  • Optional richer interception modes beyond advisory.

See CLAUDE.md for the full spec, contracts, and design rules.


License

Apache 2.0 (versions ≤ 0.3.1 were MIT). The vendored code-review-graph sources remain MIT under their upstream notice — see NOTICE.

A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
3Releases (12mo)
Commit activity

Latest Blog Posts

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/Atharva-Jayappa/blast-scope'

If you have feedback or need assistance with the MCP directory API, please join our Discord server