#Ratifia MCP (for agents)
Give an AI agent a human-in-the-loop. The Ratifia MCP server lets an agent — Claude Code, Claude Desktop, Cursor, or anything that speaks MCP — pause on a human decision mid-task and resume once it's answered. The agent is the "engine"; Ratifia owns the decision, the notification, and the verdict.
Use it when the agent is about to do something consequential (deploy, delete, spend, send an email externally), needs a human to pick between options, or wants a person to review and refine a draft before it goes out.
#Configure it
The MCP runs as a local process your client launches over stdio. Add it to your MCP client's config with one required env var — your Ratifia API key:
{ "mcpServers": { "ratifia": { "command": "npx", "args": ["-y", "@ratifia/mcp"], "env": { "RATIFIA_API_KEY": "your-org-worker-api-key", "RATIFIA_APPROVER_EMAIL": "you@example.com" } } }}
That's it — restart your client and the agent gains the Ratifia tools.
Where the API key comes from
RATIFIA_API_KEY is an org-scoped worker API key. Create one in the Ratifia dashboard under API Keys — it's shown once at creation (store it somewhere safe; it's hashed afterward and can't be retrieved). It scopes every decision the agent opens to your org.
#Environment variables
| Variable | Required | Default | Purpose |
|---|---|---|---|
RATIFIA_API_KEY | ✅ | — | Org-scoped worker API key (from the dashboard). |
RATIFIA_API_URL | https://api.ratifia.com | API base — point at a different environment. | |
RATIFIA_APP_URL | https://app.ratifia.com | Dashboard base for the decision links returned to the agent. | |
RATIFIA_APPROVER_EMAIL | — | Who the decision is assigned to / notified. | |
RATIFIA_POLICY | — | A named policy whose route decides how you're reached (e.g. Slack, or your phone). |
How an approver is reached (email / Slack / SMS / escalation) is decided inside Ratifia by your org's policy — never by the client. The MCP is a thin request/verdict bridge.
#Multiple environments
Point separate registrations at different environments by giving each its own URL + key — e.g. a ratifia-stg and a ratifia-prod, each with its own RATIFIA_API_URL and RATIFIA_API_KEY.
#The tools
| Tool | What it does |
|---|---|
request_decision | Open a decision, notify the human, return a decision_id. |
await_decision | Block until the verdict lands (or a max wait elapses). |
check_decision | Poll a decision's status once. |
list_pending_decisions | The agent's inbox — what's outstanding. |
cancel_decision | Withdraw a decision the agent no longer needs. |
Typical loop: the agent calls request_decision before the risky step, await_decisions for the verdict, and proceeds only on approved — on rejected/expired it adapts.
#Decision shapes
request_decision isn't only yes/no. Shape the answer with response_type:
response_type | The human… | The agent reads |
|---|---|---|
approval (default) | approves or rejects | verdict (+ note) |
select | picks from options you supply (set allow_multiple for many) | response_value.selected (ids → labels via response_spec.options) |
text | types a value | response_value.text |
request_decision( title: "Which environment?", question: "Where should I deploy?", response_type: "select", options: [{ label: "Staging" }, { label: "Production" }])
#Refining a draft — Conversational Approvals
Pass proposed_output to attach an editable draft (e.g. an email). The human refines it in conversation — in the dashboard or by replying in the Slack thread — and the agent gets their final version back on output.content (with a refined flag). Act on output.content, not the draft you sent. See Conversational Approvals.
request_decision( title: "Send welcome email", question: "Review this before I send it.", proposed_output: { title: "Welcome email", content: "<your draft>" })
#Enforce approval on specific tools (hook)
The tools above are model-elected — the agent chooses to call request_decision. To make approval non-bypassable for a set of tools, wire the gate command as a Claude Code PreToolUse hook. The hook's matcher is your list of gated tools: any matching call is intercepted, Ratifia opens a decision (as a Tool call panel showing exactly what would run), and the tool is blocked until a human approves — deny otherwise.
In ~/.claude/settings.json (or a project .claude/settings.json):
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|deploy_.*|delete_.*", "hooks": [ { "type": "command", "command": "npx -y @ratifia/mcp gate", "timeout": 300 } ] } ] }}
The hook command inherits your environment, so export the same config the MCP uses (RATIFIA_API_KEY, optionally RATIFIA_API_URL / RATIFIA_APPROVER_EMAIL / RATIFIA_POLICY).
| Variable | Purpose |
|---|---|
RATIFIA_GATED_TOOLS | Optional comma-separated allow-list checked inside the gate — lets you use a broad matcher and narrow here (e.g. deploy_prod,delete_database). Unset = gate everything the matcher sends. |
RATIFIA_GATE_TIMEOUT_SEC | How long to block waiting for a human (default 240). Keep the hook timeout a bit higher. |
RATIFIA_GATE_FAIL_OPEN | 1 = allow the tool if Ratifia is unreachable or the wait times out. Default is fail-closed (deny) — a gate that fails open is no gate. |
On approval the tool runs; on rejection it's blocked and the reviewer's note is returned to the agent as the reason. This is the enforcing complement to request_decision: the harness runs the hook whether or not the model chose to ask.
#Install options
Published on npm as @ratifia/mcp — the npx -y @ratifia/mcp config shown above fetches and runs it, no clone required.
For development, run from a local clone instead — npm install && npm run build, then point the entry at the built file:
{ "mcpServers": { "ratifia": { "command": "node", "args": ["/absolute/path/to/ratifia-mcp/dist/index.js"], "env": { "RATIFIA_API_KEY": "your-org-worker-api-key" } } }}