zshield-mcp
<p align="center"><img src="docs/images/logo.png" alt="zshield-mcp logo" width="160"></p>

# zshield-mcp
Open-source **MCP server** that exposes one tool, `zcash_pay`, for agent-initiated **shielded ZEC** payments under human policy:
- **Hard per-call spending cap**
- **Recipient allow-list**
- **Append-only viewing-key fingerprinted audit log**
Spend keys never belong in the model. Mock chain by default. A live adapter sends real shielded payments on Zcash **testnet only** through zingo-cli.

## Purpose
Agents need a payment tool they can call safely. This server enforces policy *before* any payment attempt, records an audit row for every call (success or reject), and fingerprints viewing-key material so auditors can correlate receipts without storing raw keys in the log.
Built as a concrete, minimal artifact for design-partner review of shielded agent-payment MCP surfaces (see Zcash Community Grants discussions around agent/MCP payment tooling).
## Demo
Real output of `npm run demo:mock` (mock chain, no coins spent): one payment allowed, one blocked by the spending cap, one blocked by the allow-list, then the audit log.

## Quickstart
```bash
git clone <this-repo> zshield-mcp && cd zshield-mcp
cp .env.example .env
npm install
npm test
npm run build
npm start # stdio MCP server
```
Point your MCP client (Cursor, Claude Desktop, etc.) at:
```json
{
"mcpServers": {
"zshield": {
"command": "node",
"args": ["/absolute/path/to/zshield-mcp/dist/index.js"],
"env": {
"SPENDING_CAP_ZEC": "0.1",
"ALLOWLIST_PATH": "/absolute/path/to/zshield-mcp/allowlists/recipients.example.json",
"CHAIN_MODE": "mock",
"AUDIT_DIR": "/absolute/path/to/zshield-mcp/audits"
}
}
}
}
```
Or during development: `"command": "npx", "args": ["tsx", "src/index.ts"]` with `cwd` set to the repo.
## Tool: `zcash_pay`
| Arg | Type | Required | Description |
|-----|------|----------|-------------|
| `amount_zec` | number | yes | Must be `> 0` and `<= SPENDING_CAP_ZEC` |
| `recipient` | string | yes | Must exactly match an allow-list `address` |
| `memo` | string | no | Optional memo (mock) |
Resource `zshield://policy` returns the active cap, allow-list path, and chain mode.
## Config: caps & allow-list
Precedence: **environment variables** override `config/default.yaml`.
| Variable | Default | Meaning |
|----------|---------|---------|
| `SPENDING_CAP_ZEC` | `0.1` | Hard max ZEC **per tool call** |
| `ALLOWLIST_PATH` | `./allowlists/recipients.example.json` | JSON allow-list |
| `CHAIN_MODE` | `mock` | `mock` or `testnet` (any other live value exits) |
| `AUDIT_DIR` | `./audits` | JSONL audit directory |
| `VIEW_KEY_FINGERPRINT_SALT` | from yaml | Salt for view-key fingerprints |
Allow-list shape:
```json
{
"recipients": [
{ "address": "u1…", "label": "Demo merchant" }
]
}
```
## Audit log format
JSONL per day: `audits/YYYY-MM-DD.jsonl`. Example success line:
```json
{"ts":"2026-10-05T19:00:00.000Z","tool":"zcash_pay","amount_zec":0.05,"recipient":"u1…","allowlist_hit":true,"cap_zec":0.1,"status":"ok","mock_txid":"mock_…","view_key_fingerprint":"a1b2c3d4e5f60718","memo":"test"}
```
Raw viewing-key material is **never** written, only a short fingerprint. See [docs/AUDIT.md](docs/AUDIT.md).
## Layout
```
src/policy/ caps + allow-list
src/chain/ mock adapter, testnet adapter, address checks
src/audit/ JSONL writer + fingerprint
src/tools/ zcash_pay
src/server.ts MCP registration
tests/ happy path + rejections
```
## Testnet mode
What it does: with `CHAIN_MODE=testnet` the server checks cap, allow-list, and address network first, writes the audit row, and only then calls `zingo-cli quicksend` against a public testnet lightwalletd (default `https://testnet.zec.rocks:443`). Mainnet addresses (`u1`, `zs1`, `t1`) are rejected; testnet ones (`utest`, `ztestsapling`, `tm`) are accepted. `CHAIN_MODE=live` alone is refused.
Setup:
```bash
# build zingo-cli (Rust)
git clone https://github.com/zingolabs/zingolib && cd zingolib
cargo build --release -p zingo-cli
# create a fresh testnet wallet outside the repo and print its address
zingo-cli --chain testnet --server https://testnet.zec.rocks:443 \
--data-dir ~/.zshield-testnet/wallet addresses
```
Fund that address with TAZ from a testnet faucet such as https://faucet.testnet.valargroup.dev (API claim) or https://zcashfaucet.jinolabs.xyz and wait for it to confirm. Keep `~/.zshield-testnet/` private; it holds the wallet seed and is never inside the repo.
Run the MCP server in testnet mode:
```bash
CHAIN_MODE=testnet ZINGO_BIN=$(which zingo-cli) \
ZINGO_DATA_DIR=~/.zshield-testnet/wallet \
ALLOWLIST_PATH=./allowlists/testnet.json npm start
```
Optional variables: `ZINGO_BIN`, `ZINGO_DATA_DIR`, `LIGHTWALLETD_URL`.
Run the demo:
```bash
npm run demo:testnet
```
The demo creates a second fresh testnet wallet as the recipient, writes a one-entry allow-list for it, and sends 0.001 TAZ through `executePay`, the same policy path the `zcash_pay` tool uses. It prints the txid and a link on https://blockexplorer.one/zcash/testnet. A confirmed txid proves the full path works end to end: policy checks, audit write, and a real shielded transaction on the Zcash testnet.
## Proof: live testnet payment
One real shielded 0.001 TAZ payment through `zcash_pay`, plus a blocked over-cap attempt: [tx 59d9cb1b5a47... on blockexplorer.one](https://blockexplorer.one/zcash/testnet/tx/59d9cb1b5a473a84602c4812a7809217f6e9d359174deb178e7fd52c151ae8fc)

## Status / non-goals
- ✅ Mock payments, policy enforcement, audit
- ✅ Testnet shielded sends via zingo-cli
- ❌ Mainnet (refused by design)
- ❌ Custody of user spend keys
## License
MIT
TDQS
Scored across 1 tool
With only one tool, there is no possibility of overlap or misselection between tools. The single tool's purpose—paying shielded ZEC under a spending cap—is singular and unambiguous.
zcash_pay follows a clear snake_case resource_action pattern. There are no competing conventions to create inconsistency, so the naming is fully predictable for this server.
A single tool is very thin for a server named zshield-mcp, which implies broader policy, allow-list, and audit functionality. The pay tool itself is well-scoped, but the overall surface feels under-provisioned.
The description implies allow-listed recipients, spending caps, and audit logging, yet only the payment action is exposed. There are no tools to inspect the audit log, manage the allow-list, check balances, or query transaction status, leaving significant operational gaps.