Skip to main content
Glama
Daniel221287

mcp-human-approval-gate

by Daniel221287

mcp-human-approval-gate

test

An AI agent prepares an action. Only a human can approve the exact preview. Then the agent runs it once.

A tiny, dependency-light MCP server, an Agent Skill and a reusable gate you can drop into your own agent tools. MIT licensed.

Why it matters

  • Agents with tools can now change real things: files, messages, orders, payments.

  • "Ask before acting" written in a prompt is not a control. The agent must be technically unable to approve itself.

  • Network calls time out. If an agent blindly retries an uncertain action, you get the email twice or the payment twice.

This gate turns those three points into code:

Rule

How it is enforced

The agent cannot approve

The MCP server has no approve tool. Approval exists only on a local human page protected by a per-start token

The human approves exactly what will run

Approval is bound to the SHA-256 of the preview. Any change means a new hash and a new approval

It runs once

The execution slot is reserved before the effect starts. A second call, a crash or a timeout is never replayed

It really happened

The effect must read back what it did. Unverified results are marked FAILED_NO_RETRY

Nothing waits forever

Requests and approvals expire (10 minutes by default). Max 20 pending requests

Related MCP server: Gatehouse

How it works

agent ── approval_prepare ──► PREVIEW_READY ──► shown on the human page
                                                   │
                         human: "Approve exact preview" (hash must match)
                                                   ▼
agent ── approval_execute ──► APPROVED_BY_HUMAN ──► effect + readback ──► SUCCEEDED_VERIFIED
agent ── approval_execute again ──► refused: ALREADY_EXECUTED_NO_REPLAY
agent ── approval_status ──► read-only answer: what happened, retry_allowed: false
human: "Revoke" ──► REVOKED_BEFORE_EFFECT (can never run)

The human page: an agent request waits until the human approves the exact preview

A real run: the agent's request waits on the human page. Before approval, approval_execute is refused with EXACT_PREVIEW_APPROVAL_REQUIRED. After one click on "Approve exact preview" it runs once (SUCCEEDED_VERIFIED), and a second call is refused with ALREADY_EXECUTED_NO_REPLAY.

Quick start

Requires Node.js 22 or newer.

npm install
npm start

The console prints two lines:

MCP endpoint: http://127.0.0.1:4800/mcp
Human page:   http://127.0.0.1:4800/?t=<random token for this start>
  1. Open the human page in your browser yourself. Do not give this link to the agent.

  2. Connect your MCP client (Streamable HTTP transport) to the MCP endpoint.

  3. Ask the agent to create a note. It prepares it, you approve the exact preview, it runs it once.

The example effect creates one new text file in ./sandbox (a server-generated name, never overwriting) and verifies it by reading it back. The agent never chooses a path.

MCP tools (for the agent)

Tool

What it does

approval_prepare

Prepares a note.create preview. Nothing runs yet

approval_status

Read-only: state and result of a request. Never runs anything

approval_execute

Runs a request once, only if a human approved this exact preview_sha256

Human endpoints (for the page, token required)

POST /human/pending, /human/approve, /human/revoke, /human/status, each with the X-Human-Token header. Requests without the right token get 401 HUMAN_TOKEN_REQUIRED.

Use the gate in your own code

import { ApprovalGate } from './src/gate.mjs';

const gate = new ApprovalGate({ ttlMs: 10 * 60 * 1000, maxPending: 20 });

// Agent side
const req = gate.prepare({ action: 'email.send', payload: { to: 'team@example.com', subject: 'Release' }, requestedBy: 'agent' });

// Human side (your UI, never an agent tool)
gate.approve({ id: req.id, previewSha256: req.preview_sha256 });

// Agent side: runs once, your effect must read back what it did
await gate.execute({ id: req.id, previewSha256: req.preview_sha256 }, async (preview) => {
  const sent = await sendEmail(preview.payload);       // your code
  return { verified: sent.accepted === true, detail: { messageId: sent.id } };
});

Verified with a real agent (Claude Code)

On 23 October 2026 the gate was tested with Claude Code CLI (model Sonnet, subscription login) as the MCP client. The agent had only this MCP server and the Agent Skill (copied into .claude/skills/human-approval-gate/ of its working folder). Bash, Edit, Write and web tools were disabled.

{ "mcpServers": { "gate": { "type": "http", "url": "http://127.0.0.1:4800/mcp" } } }
claude -p "Use the human-approval-gate skill. Save this short note: ..." --model sonnet \
  --mcp-config mcp.json --strict-mcp-config --setting-sources project \
  --allowedTools mcp__gate__approval_prepare mcp__gate__approval_status mcp__gate__approval_execute Skill \
  --disallowedTools Bash Edit Write WebFetch WebSearch

What happened in that run (the server ran on port 4810; server state is the ground truth):

  1. The agent loaded the skill and called approval_prepare → PREVIEW_READY.

  2. It stopped by itself and said: "This is waiting on your local approval page. Preview hash to match: 1ac57128b556. Please approve it there, then tell me and I'll execute it once."

  3. The exact preview was approved on the human page.

  4. Resumed with "I approved the exact preview on my approval page. Please continue.", the agent called approval_execute once → SUCCEEDED_VERIFIED (a 49-byte note, readback verified, exactly one file in the sandbox).

Agent Skill

skills/human-approval-gate/SKILL.md tells an agent how to behave around the gate: prepare, tell the human what is waiting, execute once after approval, use approval_status instead of retrying. Copy the folder into your agent's skills directory.

Security model and honest limits

  • Listens on 127.0.0.1 only and refuses any other host. Host and Origin headers are validated with the MCP SDK's localhost guards (DNS rebinding protection). The page is served with a strict Content-Security-Policy.

  • The human token separates the human channel from the MCP channel. It is not a sandbox against a local process that can read your console or browser.

  • State is in memory. Restarting the server clears all requests, which is the safe direction: nothing approved survives a restart.

  • The included effect is deliberately harmless. Real effects (email, payments) are yours to write, and they must return a real readback.

Tests

npm test

14 tests: 8 unit tests for the gate and the example effect, and 6 live tests that start the real server on a random local port and talk to it over MCP JSON-RPC and HTTP (no approve tool, token-protected human channel, exact-hash approval, single execution, revoke, Origin and Host guards, loopback-only binding).

Background

Built during the Amazon Build, Ship, Shape hackathon (October 2026) as the open-source, reusable core of the human-approval pattern behind my hackathon entry. Written from scratch for this repository.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI coding agents to request and perform registered deployment and login actions without exposing credentials, using local human approval and redacted verification receipts.
    1 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides a governed execution boundary for AI agents, enforcing deterministic policy, one-shot human approvals, and signed receipts for MCP tool effects.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables autonomous agents and multi-agent frameworks to route actions through deterministic human approval gates before execution. Enforces risk tiers, reversible checkpoints, and token approval bounds so that risky or costly operations can be reviewed, bounded, and rolled back without LLM round-trips.
    7
    -