Metadata-Version: 2.4
Name: sandbroker-mcp
Version: 0.1.0
Summary: MCP server for the sandbroker typed-return secret broker: USE secrets, never SEE their values.
Author: Grayson Adams
License: MIT
Project-URL: Homepage, https://github.com/312-dev/sandbroker
Keywords: mcp,sandbroker,secrets,claude,broker
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp<2,>=1.12

# sandbroker MCP server

A stdio [Model Context Protocol](https://modelcontextprotocol.io) server that
lets any MCP host (Claude Code and others) natively drive the **sandbroker**
broker. The model can **USE** secrets through pinned verbs but can **NEVER SEE**
their values.

It is a thin MCP wrapper over the exact same daemon protocol `bin/sandbroker`
speaks. It adds no new daemon-side surface: it consumes the existing `catalog`
and `invoke` actions only.

## What it exposes

| Tool | Wraps | Purpose |
|------|-------|---------|
| `secret_catalog` | `catalog` action | List the item references available in a vault. Returns item ids + titles only, never a value. |
| `secret_invoke` | `invoke` action | Run a pinned verb that USES a secret and return the typed OUTCOME (`ok`/`error`), never the value or the operation's output. |

The human-only, direct-socket actions (`lock`, `status`, `set_retention`) are
**not** exposed. Those are operator actions, not model actions.

## Security guarantee (values never cross the tool boundary)

The product promise holds **structurally**, not by filtering:

- sandbroker's response schema has no field a secret value can occupy. A verb
  returns the OUTCOME of an operation (`{ok}` / `{error}`), never its output.
- This server passes through only the daemon's own typed keys
  (`ok, error, verb, items, status, retry_after`) and re-filters them on ingress
  as defense in depth, so even a tampered daemon reply cannot smuggle an extra
  field to the model.
- This server never reads a secret from anywhere; it holds no tokens.

The **approval code** surfaced on a confirm-tier request is **not** a secret: it
is a one-time code the human types into the Windows approval window. Only its
`sha256(sid:code)` hash is ever sent to the daemon; the plaintext lives only in
the chat and the human's typing, exactly as with `sandbroker`.

## Transports

Two transports, tried in `sandbroker`'s order, so this works sandboxed or not:

1. **Direct UNIX socket** to sandbroker (`SANDBROKER_SOCKET`, default
   `/opt/sandbroker/run/sandbroker.sock`). This is the normal path: the MCP server
   runs UNSANDBOXED, launched over stdio by the host.
2. **File-queue bridge** (`SANDBROKER_BRIDGE_DIR`, default `/tmp/sandbroker-bridge`)
   for parity with sandboxed contexts whose seccomp filter blocks `AF_UNIX`
   connect. The per-session queue carries only the operation in and `{ok,error}`
   out, never a value.

### Environment variables

| Var | Default | Meaning |
|-----|---------|---------|
| `SANDBROKER_SOCKET` | `/opt/sandbroker/run/sandbroker.sock` | Daemon UNIX socket path. |
| `SANDBROKER_BRIDGE_DIR` | `/tmp/sandbroker-bridge` | File-queue base for the bridge transport. |
| `SANDBROKER_SID` | (unset -> daemon default `_nosid`) | Session id, normally injected by the launcher. |
| `SANDBROKER_NO_BRIDGE` | (unset) | `1` = socket only, never fall back to the bridge. |
| `SANDBROKER_FORCE_BRIDGE` | (unset) | `1` = bridge only, skip the socket (sandbox parity testing). |

## Install

```bash
pipx install sandbroker-mcp     # puts the `sandbroker-mcp` command on PATH
```

From a checkout instead: `cd mcp && python3 -m venv .venv && .venv/bin/pip install .`
(provides the `sandbroker-mcp` console script and `python -m sandbroker_mcp`).

## Register in Claude Code

After `pipx install sandbroker-mcp`, the command is on PATH:

```bash
claude mcp add sandbroker --scope user -- sandbroker-mcp
```

From a checkout venv instead, point at its absolute path, e.g.
`/path/to/mcp/.venv/bin/sandbroker-mcp`.

Pass a non-default socket or force a transport with `--env`:

```bash
claude mcp add sandbroker --scope user \
  --env SANDBROKER_SOCKET=/opt/sandbroker/run/sandbroker.sock \
  -- sandbroker-mcp
```

### settings.json snippet (equivalent)

```json
{
  "mcpServers": {
    "sandbroker": {
      "command": "/absolute/path/to/mcp/.venv/bin/sandbroker-mcp",
      "args": [],
      "env": {
        "SANDBROKER_SOCKET": "/opt/sandbroker/run/sandbroker.sock"
      }
    }
  }
}
```

## Usage flow

1. `secret_catalog(vault="Dev")` -> discover item ids/titles. Build a ref like
   `op://Dev/<id-or-title>/password`.
2. `secret_invoke(verb="canary.probe", ref="file://canary")` -> auto-tier verbs
   return `{ok:true}` immediately.
3. Confirm-tier vaults (e.g. `Production`, `Infra`) require one human approval:
   - First call returns `{ok:false, error:"hello_pending"}` plus a 6-digit
     `approval_code` in `guidance`.
   - Relay the code to the human. They type it into the Windows approval window
     and approve with Windows Hello.
   - Call `secret_invoke` **again with the same `verb`/`ref`/`justification`** to
     poll. You will see `hello_waiting` until they confirm, then `ok`.
   - An auditor-enabled vault may instead return `auditor_reviewing`; just call
     again with the same arguments. The daemon fail-closes to a human by its own
     deadline.

`secret_invoke` requires a `justification` for confirm-tier vaults (a short,
human-readable reason). Never put a secret in `args`; secrets come only from the
`ref`.

## Relationship to sandbroker

This server mirrors `bin/sandbroker` exactly: same request shape, same
per-invocation code + `code_hash` derivation, same auditor retry loop (sleep
`retry_after`, re-send the same request, up to 6 times). The difference is
surface only: `sandbroker` writes human banners to stderr; this server returns
that guidance in the tool result so the model relays it to the human.
