Metadata-Version: 2.4
Name: mcpjail
Version: 0.1.0
Summary: The flight recorder & firewall for MCP tool calls.
Project-URL: Homepage, https://github.com/tonypius/mcpjail
Project-URL: Repository, https://github.com/tonypius/mcpjail
Project-URL: Issues, https://github.com/tonypius/mcpjail/issues
Author-email: Tony Alapatt <tonypiusalapatt@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: audit,mcp,model-context-protocol,proxy,security
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: System :: Networking :: Monitoring
Requires-Python: >=3.11
Requires-Dist: cryptography>=42
Requires-Dist: pydantic>=2
Requires-Dist: pyyaml>=6
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Description-Content-Type: text/markdown

<div align="center">

```
   ███╗   ███╗ ██████╗██████╗      ██╗ █████╗ ██╗██╗
   ████╗ ████║██╔════╝██╔══██╗     ██║██╔══██╗██║██║
   ██╔████╔██║██║     ██████╔╝     ██║███████║██║██║
   ██║╚██╔╝██║██║     ██╔═══╝ ██   ██║██╔══██║██║██║
   ██║ ╚═╝ ██║╚██████╗██║     ╚█████╔╝██║  ██║██║███████╗
   ╚═╝     ╚═╝ ╚═════╝╚═╝      ╚════╝ ╚═╝  ╚═╝╚═╝╚══════╝
        the flight recorder & firewall for MCP tool calls
```

**Every tool call your agent makes — recorded, redacted, and (optionally) blocked. Local-first. One command. No SaaS.**

[Quickstart](#quickstart-60-seconds) · [Proof](#proof) · [How it works](#how-it-works) · [Policy](#policy) · [Audit log](#the-audit-log) · [Threat model](#what-the-audit-log-does-and-doesnt-prove)

</div>

---

> **Status: pre-1.0, building in the open.** Shadow mode (observe + log) is the default and safe to try. Enforcement is opt-in. See [the backlog](docs/BACKLOG.md).

## The one-minute version

Your AI agent talks to MCP servers — GitHub, your filesystem, a database — through a stream of JSON-RPC tool calls. Today there is **no portable record of which tool it called, with what arguments, or whether it should have been allowed to.** Your provider logs show `HTTP 200`, not `agent invoked delete_repo(name="prod")`.

mcpjail sits in the middle. It records every tool call to a **tamper-evident audit log** (hash-chained, signed, verifiable offline), **redacts secrets** before anything hits disk, and — if you turn it on — **blocks calls that violate a policy you write in plain YAML.**

```bash
# Wrap any MCP server. Zero code changes. Logs everything, blocks nothing (shadow mode).
uvx mcpjail wrap -- npx -y @modelcontextprotocol/server-filesystem /tmp
```

> **Not on PyPI yet.** Until it's published, `uvx`/`pipx mcpjail` won't resolve — install from a clone (`uv tool install .`) and use `mcpjail …`. See [Quickstart](#quickstart-60-seconds).

## Proof

A coding agent, wrapped by mcpjail in **enforce** mode with a three-line policy that forbids repo deletion:

```
$ mcpjail log --since 5m --tool delete_repo --deny

  2026-07-03T10:15:30Z  github  tools/call  delete_repo
    args:     { "owner": "acme", "repo": "prod", "token": "[REDACTED:github_pat]" }
    decision: DENY  (rule: block_repo_deletion)
    → agent received a clean JSON-RPC error; the call never reached GitHub.

$ mcpjail verify
  ✓ 1,204 records, chain intact, 12 checkpoints, all signatures valid.
  ✓ audit-2026-07-03.jsonl has not been modified since it was written.
```

The destructive call was **stopped**, the token was **never written to disk**, and the log is **provably unaltered**. That's the whole product in one screen.

## Quickstart (60 seconds)

```bash
# Run with no install (uv handles it):
uvx mcpjail wrap -- npx -y @modelcontextprotocol/server-everything

# Or install it (not yet on PyPI — install from a clone):
uv tool install .           # or: pipx install .

# Point your agent at it automatically (backs up your config first):
mcpjail install claude      # also: cursor | desktop | zcode
# Any other client: point at its MCP config directly.
mcpjail install --config /path/to/its/mcp.json
#   ... use your agent normally ...
mcpjail uninstall claude    # restores your original config exactly (byte-for-byte)

# See what happened, and prove the log is untampered:
mcpjail log --since 1h
mcpjail verify
```

Requires Python 3.11+. No Docker, no daemon, no account.

## Commands

| Command | What it does |
|---------|--------------|
| `mcpjail wrap -- <cmd>` | Run a **local (stdio)** MCP server under the jail |
| `mcpjail wrap-http --url <remote> --listen <host:port>` | Proxy a **remote (HTTP)** MCP server |
| `mcpjail install <claude\|cursor\|desktop\|zcode>` | Route a client's MCP servers through mcpjail (`--config PATH` for any other client) |
| `mcpjail uninstall <client>` | Restore the client's config byte-for-byte from backup |
| `mcpjail log [--since 1h] [--tool T] [--deny] [--json]` | Query the audit log |
| `mcpjail verify` | Recompute the hash chain + checkpoint signatures |
| `mcpjail policy test <file>` · `policy explain …` | Validate a policy file · show what it would decide |
| `mcpjail version` | Version and environment info |

## What it does

- **Audit** — every `tools/call`, `resources/read`, `tools/list`, and `initialize`, with redacted arguments, to append-only JSONL.
- **Redact** — API keys, tokens, passwords, AWS keys, and private keys are replaced with `[REDACTED:<pattern>]` **before** the record is hashed or written. Add your own patterns.
- **Tamper-evidence** — a SHA-256 hash chain plus periodic Ed25519 signatures. `mcpjail verify` recomputes the chain offline and names the first altered record if there is one.
- **Policy** — first-match-wins YAML rules: allow/deny by server, tool, resource URI, or argument value. Default is **shadow** (log only); enforcement is one flag.
- **Local & remote** — `wrap` proxies local stdio servers (opens no sockets); `wrap-http` proxies remote HTTP servers.
- **Zero-friction adoption** — `mcpjail install claude|cursor|desktop|zcode` rewrites your agent's MCP config for you — top-level *and* project-scoped servers — and `uninstall` puts it back byte-for-byte.

## How it works

```
   your agent                     mcpjail (local)                     MCP server
  (Claude Code,     JSON-RPC   ┌───────────────────────┐   JSON-RPC   (github, fs,
   Cursor, …)  ───────────────▶│ frame → redact → policy│─────────────▶  database, …)
               ◀───────────────│         → audit        │◀─────────────
                               └──────────┬─────────────┘
                                          ▼
                              ~/.mcpjail/audit/*.jsonl  (+ .sig checkpoints)
```

mcpjail is a **protocol-agnostic** JSON-RPC proxy. It matches on method names and forwards the exact bytes it received for every allowed message, so it stays correct across MCP spec revisions and never corrupts a payload. It does **not** reimplement MCP.

### Local (stdio) vs remote (HTTP)

`wrap` handles **local stdio** servers and opens no sockets. For a **remote (HTTP)** server, point your client at a local listener and let mcpjail forward upstream:

```bash
mcpjail wrap-http --url https://remote.example.com/mcp --listen 127.0.0.1:8722
#   then set the client's server URL to http://127.0.0.1:8722/
```

`wrap-http` audits and enforces on request bodies exactly like `wrap`; it is a separate, network-facing mode (the stdio path stays socket-free). It also correlates each call's outcome (`ok`/`error`) from the upstream response — JSON or SSE — without ever persisting the response body (ADR-004), and denies batch (JSON array) request bodies fail-closed rather than forwarding them ungoverned. Remaining gaps: `install` doesn't yet rewrite `url`-based entries to a local listener, and the GET server-push stream is unaudited — see [docs/BACKLOG.md](docs/BACKLOG.md).

To see it observe an allowed call and block a denied one end-to-end (self-contained, no real remote needed): `scripts/verify-http.sh`.

## Policy

```yaml
version: 1
mode: enforce             # start in `shadow` to observe first
rules:
  - id: block_repo_deletion
    match: { server: github, tool: "delete_repo" }
    action: deny
    reason: "Repo deletion is never allowed via an agent."
  - id: block_secret_reads
    match: { server: "*", resource_uri: "file://**/.env*" }
    action: deny
  - id: block_writes_outside_home
    match:
      tool: ["write_file", "edit_file"]
      args: { path: "/etc/**" }   # match on (redacted) argument values
    action: deny
  - id: flag_writes
    match: { server: github, tool: ["create_issue", "create_pr"] }
    action: allow
    annotate: "write-operation"
```

Rules match first-match-wins on `server`, `tool`, `resource_uri` (globs; `tool`
may be a list), and `args` (each `{name: pattern}` must match the stringified,
redacted argument value).

```bash
mcpjail policy test mcpjail.yaml
mcpjail policy explain --server github --tool delete_repo   # → "would DENY (rule: block_repo_deletion)"

# Enforce it when wrapping a server:
mcpjail wrap --policy mcpjail.yaml -- npx -y @modelcontextprotocol/server-github
```

The policy loaded by `wrap` defaults to **`~/.mcpjail/policy.yaml`** (override with
`--policy PATH`); with no file present, mcpjail runs observe-only. A ready-to-copy
starting point lives at [`docs/policy.example.yaml`](docs/policy.example.yaml).

## The audit log

One JSON object per line, one file per day. Every record carries the hash of the previous one, so any edit, insertion, or deletion breaks the chain:

```json
{"schema_version":1,"seq":42,"ts":"2026-07-03T10:15:30.123Z","method":"tools/call",
 "server_label":"github","tool":"delete_repo",
 "args_redacted":{"owner":"acme","repo":"prod","token":"[REDACTED:github_pat]"},
 "decision":"deny","matched_rule":"block_repo_deletion",
 "prev_hash":"sha256:…","record_hash":"sha256:…"}
```

### EU AI Act note
For high-risk AI systems, Article 12 requires automatic, tamper-resistant logging of system
operation. mcpjail's audit log is designed to be usable as part of that evidence for the
agent's tool use. It is a building block, not legal advice or a complete compliance solution.

## What the audit log does — and doesn't — prove

Being honest about the guarantees, because a trust tool that overclaims is worse than useless:

- **It proves** the log wasn't altered after the fact *by anyone who doesn't hold the signing key*, and that it was produced by this installation's key.
- **It does not prove** anything against an attacker who has already compromised the machine and stolen the signing key — they could rewrite and re-sign. Local-first tamper-evidence has this inherent limit; optional external anchoring may come later.
- **It records tool calls, not intent.** mcpjail does not detect prompt injection or judge whether a call was "meant" maliciously. It records and, if you ask, enforces your rules.

Protect `~/.mcpjail/key.ed25519` (created `0600`) like any private key.

## Comparison

|                | Scope | Deploy | Local-first | Tamper-evident log | Audit-first |
|----------------|-------|--------|-------------|--------------------|-------------|
| **mcpjail**    | Any MCP server's tool calls (stdio + HTTP) | clone + `uv tool install .`, or config-rewrite | Yes | Yes | Yes |
| Enterprise MCP gateways | Full gateway (routing, auth, rate-limit, …) | Server / Kubernetes | Sometimes | Varies | No (audit is one feature) |
| Pre-install scanners | Static scan of a server *before* use | CLI | Yes | N/A | No (not runtime) |

mcpjail's bet is to be the small, single-purpose, runtime **audit + policy** layer, not another heavyweight gateway.

## Design docs

- [SPEC.md](docs/SPEC.md) — governed methods, audit schema, redaction, tamper-evidence, policy, CLI, success criteria
- [ARCHITECTURE.md](docs/ARCHITECTURE.md) — components and data flow (stdio + HTTP)
- [docs/policy.example.yaml](docs/policy.example.yaml) — a ready-to-copy policy
- [ADR-004](docs/adr/ADR-004-outcome-recording.md) — why the log records outcome metadata, never response bodies
- [BACKLOG.md](docs/BACKLOG.md) — what's next (stdio-path outcome correlation, `install` integration for remote servers, GET-stream auditing)

## Contributing

```bash
uv sync
uv run pytest
uv run ruff check .
```

Issues and PRs welcome. No telemetry lives in this project and none ever will.

## License

[Apache-2.0](LICENSE).
