Skip to main content
Glama
im-amir

holdgate-proxy

by im-amir
README.md
# HoldGate

**Human approval for AI agent actions — self-hosted.**

An agent wants to wire $12,000, delete a table, or send a customer email. HoldGate
holds the call, pushes it to a person's phone, and runs it only if they say yes.

```
[ AI agent ]
     ↓ calls a tool
[ holdgate-proxy ] ── holds it ──→ [ backend ] ── push ──→ [ a person's phone ]
     ↓ only once approved                                        ↓
[ the real tool runs ]  ←───────────── approve / deny ───────────┘
```

MIT licensed, no hosted service, no account. You run all of it.

## Two ways to use it

**As an MCP proxy — no code changes.** Wrap any stdio MCP server in your client
config and every tool call it exposes becomes gated:

```jsonc
"stripe": {
  "command": "holdgate-proxy",
  "args": ["--key", "hg_live_…", "--upstream", "npx -y @stripe/mcp --api-key=sk_live_…"]
}
```

Nothing changes in anyone's repository. Remove the wrapper and the gate is gone.

**As an SDK — for code an agent never touches.** A server-side function calling
Stripe directly never goes near MCP, so it needs a line:

```ts
const receipt = await holdGate.gatekeeper(
  "refundCustomer",
  [customerId, amountUsd],
  refundCustomer,
);
```

`gatekeeper` blocks, then calls your function only if a human approved. The
arguments are frozen server-side when the ticket opens, so an agent cannot get a
tap on $10 and then run $999,999.

## What's in here

| Package | |
|---|---|
| **holdgate-proxy** | stdio MCP proxy. The main event. Published to npm. |
| **holdgate-sdk** | `@holdgate/sdk` — for direct, non-MCP calls |
| **holdgate-backend** | Next.js API + Postgres schema and migrations |
| **holdgate-dashboard** | Next.js admin UI — rules, keys, audit log |
| **holdgate-mobile** | Expo app — where approvals are answered |

Rules live in the backend: `ALWAYS_ASK`, `AUTO_APPROVE`, or `THRESHOLD` (ask only
above a number). **A tool with no rule asks** — the default is fail-closed, so a
newly wrapped tool is never silently permitted. If the backend is unreachable the
proxy refuses rather than allows.

## Honest comparison

Several projects do this, and some will suit you better:

- **Anthropic's Remote Control**, inside the Claude iOS and Android apps, is free,
  built in, and needs no setup. If you only use Claude Code, start there.
- **Pushary**, **ClawMetry**, **Agent Approve**, **Forge Remote** — hosted, polished
  phone approval, nothing to run.
- **HumanLayer** — approvals into Slack and email, where teams already decide.

HoldGate's reason to exist is that **you host it**. Your Postgres, your audit
trail, no third party holding a log of what your agents tried to do. If that
doesn't matter to you, one of the above is less work.

## Running it yourself

Needs a Supabase project (or any Postgres) and an Expo account for push.

```bash
cd holdgate-backend
cp .env.example .env.local     # SUPABASE_URL, keys
supabase db push               # migrations 0001–0007
yarn install && yarn dev
```

Then the dashboard for rules and API keys, and the mobile app for whoever
approves. Each directory has its own README.

## Status

Working and tested end to end: an MCP tool call held, pushed to a physical phone,
approved, and forwarded to the real server — plus the refusal paths (denied,
expired, backend unreachable), each verified to leave the tool unrun.

Rough edges worth knowing: only **stdio** MCP servers are proxied (not HTTP/SSE),
and the config lives in a file the developer controls, so this is a **guardrail,
not a sandbox** — anyone who can edit the MCP config can remove the wrapper. The
way to make it enforceable is to let the proxy hold the upstream's credentials,
so bypassing it loses access to the tools rather than just skipping approval.

## License

MIT.