Metadata-Version: 2.4
Name: blastcheck-mcp
Version: 0.1.0
Summary: MCP server exposing blastcheck Impact Manifests to coding agents
Author-email: Kevin Proffitt <kproffitt@prococonsulting.com>
License: Apache-2.0
Project-URL: Homepage, https://blastcheck.dev
Project-URL: Source, https://github.com/prococonsulting/blastcheck-mcp
Keywords: mcp,terraform,impact-manifest,blastcheck,change-safety
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: mcp<3,>=2
Requires-Dist: blastcheck<0.8,>=0.7
Requires-Dist: jsonschema>=4.18
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Dynamic: license-file

# blastcheck-mcp

An [MCP](https://modelcontextprotocol.io) server that gives coding
agents (Claude Code, Claude Desktop, Cursor, and any other MCP client)
the ability to assess Terraform plans with
[blastcheck](https://blastcheck.dev): produce an
[Impact Manifest](https://github.com/prococonsulting/impact-manifest) -
a machine-readable change-safety assertion - and explain it in plain
language.

Read-only and propose-only. No tool applies Terraform, mutates a cloud
resource, or accepts a credential. This release performs no network I/O
at all: everything runs offline against the plan file.

## Quickstart

Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/). No install
step; the first run resolves everything:

```
uvx blastcheck-mcp
```

### Claude Code

```
claude mcp add blastcheck -- uvx blastcheck-mcp
```

### Claude Desktop

Add to `claude_desktop_config.json` (Settings > Developer > Edit
Config):

```json
{
  "mcpServers": {
    "blastcheck": {
      "command": "uvx",
      "args": ["blastcheck-mcp"]
    }
  }
}
```

### Cursor

Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):

```json
{
  "mcpServers": {
    "blastcheck": {
      "command": "uvx",
      "args": ["blastcheck-mcp"]
    }
  }
}
```

## Using it

Ask your agent to check a plan:

```
terraform plan -out plan.tfplan
terraform show -json plan.tfplan > plan.json
```

Then: "run blastcheck on plan.json and explain what could go wrong."
The agent will call impact_emit, get a manifest handle plus a verdict
summary, and follow up with impact_explain for the full picture.
(impact_emit also accepts the saved .tfplan directly and converts it.)

## Tools

### impact_emit

Runs blastcheck's plan-only analysis on a `terraform show -json` file
(or a saved .tfplan) and returns:

- `manifest_handle` - a short reference to the full Impact Manifest,
  written to a private temp file (0600, per-user 0700 directory, kept
  at most 24h). Pass it to impact_explain instead of shuttling the
  large document through model context.
- `summary` - the verdict, per-severity counts, which changes drove it,
  and the plan-only note.

A plan-only run can never emit `safe` - it never verified live state,
and blastcheck says so rather than guessing. That is the point of the
format.

### impact_explain

Turns a manifest into markdown a human can act on: the verdict and why,
each change's noteworthy findings with their rationales, a
"safe if ..." checklist of unmet preconditions, and exactly what could
not be verified. Deterministic local code; accepts a manifest_handle, a
manifest_path, or the manifest inline (exactly one).

Output is capped by a hard budget (BLASTCHECK_MCP_EXPLAIN_BUDGET env,
default 32000 characters, about 8K tokens) so the tool can never
consume a session's context in one call. Small plans render in full.
Large plans get a compact view: dimensions identical across every
change are stated once, changes with identical assessments are grouped
with numeric indexes compressed to ranges (app[0..11]) - for_each
string keys are never wildcarded, so which tenants or keys are
affected stays visible - and blocking changes always keep their
complete address list, dropping only per-change repetition. Whenever
anything is grouped or cut, an explicit partial-view notice says so
and shows the exact follow-up calls; all lists are lexically sorted
before any cut, so the same plan always shows the same subset. Never
a silent drop.

Drill-down filters (optional, combined with AND):

- `severity` - e.g. `["blocking"]` or `["blocking", "caution"]`
- `module_prefix` - address prefix, e.g. `"module.tenants[\"contoso-prod\"]"`
- `resource_type` - exact type, e.g. `"azurerm_managed_disk"`
- `addresses` - exact addresses, always rendered in full detail; the
  guaranteed path to any specific change

## Compatibility

Verified against real clients and across MCP protocol eras
(v0.1.0-rc1, mcp SDK 2.x, macOS, Python 3.13):

| Check | Result |
|---|---|
| Protocol 2024-11-05 (oldest era) | negotiated, tools list + calls work |
| Protocol 2025-03-26 | negotiated, tools list + calls work |
| Protocol 2025-06-18 | negotiated, tools list + calls work |
| Unknown version offered | server answers with its latest, per spec |
| Claude Code 2.0.76 (stdio) | connects; both tools exercised end to end in three separate headless runs. Investigated note: when driving headless `claude -p` sessions from INSIDE another Claude Code session (a nested test rig), intermittent multi-minute client startup stalls were observed; process inspection showed they occur before the MCP server process is even spawned, and the identical command succeeds on retry with no changes, so they are attributed to the nested test environment rather than this server. An ordinary interactive session has not yet been driven by a human; recommended as a final confirmation. |
| Claude Desktop | config block above is the documented uvx form; not yet exercised on this machine |
| Cursor | config block above is the documented uvx form; not yet exercised (Cursor not installed on the test machine) |
| Large-plan compression | verified against a synthetic 288-change fixture ONLY (nested modules, for_each string keys, count indexes; 372KB full render compressed to a 12.5KB overview). NOT yet verified against a large real-world plan - if real changes fingerprint less uniformly, overviews will be larger; the hard output budget still caps them and the partial-view notice still applies. |

## Errors

Failures come back as `CODE: one clear sentence` - for example
`FILE_NOT_FOUND: no such file: plan.json` or
`MANIFEST_UNSUPPORTED_VERSION: ...` - never a stack trace.

## Data boundary

This release sends nothing anywhere: no telemetry, no API calls, no
network I/O. Raw plan JSON never leaves the machine.

A future release adds an optional CloudRift tier (live cost, dependency
and tenant enrichment; see `docs/remote-tier.md`). Its boundary is
already implemented and tested in this codebase, before any network
code exists: if a manifest is ever scored remotely, what leaves the
machine is an identity-only projection - per change: address, resource
type, name, Terraform provider address, and actions; plus the
manifest's schema_version, generated_at, producer name/version, and
source type. Evidence, rationales, verdicts, attribute values (ports,
CIDRs, sizes), and the plan itself are stripped by an explicit
allowlist, so a future manifest field is excluded by default rather
than leaked by default. That tier will require an explicit
CLOUDRIFT_API_TOKEN; without one, the server registers the local tools
only.

## Environment variables

| Variable | Effect |
|---|---|
| BLASTCHECK_MCP_EXPLAIN_BUDGET | Hard cap, in characters, on impact_explain output (default 32000, floor 4000). Over the cap, a compact grouped view with an explicit partial-view notice is returned instead - never a silent cut. |
| CLOUDRIFT_API_TOKEN | Reserved for the future remote tier. If set today, the server notes on stderr that the remote tier is not in this release and runs the local tools only. Credentials are NEVER accepted as tool arguments. |

## Development

```
python3 -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/pytest
```

The suite is fully offline. `tests/test_project.py` holds the
data-boundary tests; treat a failure there as a stop-ship.

## Relationship to blastcheck and the spec

blastcheck-mcp is a thin MCP layer over the
[blastcheck](https://github.com/prococonsulting/blastcheck) library
(pinned `>=0.7,<0.8`) and implements a consumer of the
[Impact Manifest specification](https://github.com/prococonsulting/impact-manifest)
(schema major version 0). The manifest schema is read from blastcheck's
own vendored copy, so there is no second copy to drift.

## License

Apache-2.0. See [LICENSE](./LICENSE).
