FlowplaneDocs

#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:

json
{  "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

VariableRequiredDefaultPurpose
RATIFIA_API_KEYOrg-scoped worker API key (from the dashboard).
RATIFIA_API_URLhttps://api.ratifia.comAPI base — point at a different environment.
RATIFIA_APP_URLhttps://app.ratifia.comDashboard base for the decision links returned to the agent.
RATIFIA_APPROVER_EMAILWho the decision is assigned to / notified.
RATIFIA_POLICYA 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

ToolWhat it does
request_decisionOpen a decision, notify the human, return a decision_id.
await_decisionBlock until the verdict lands (or a max wait elapses).
check_decisionPoll a decision's status once.
list_pending_decisionsThe agent's inbox — what's outstanding.
cancel_decisionWithdraw 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_typeThe human…The agent reads
approval (default)approves or rejectsverdict (+ note)
selectpicks from options you supply (set allow_multiple for many)response_value.selected (ids → labels via response_spec.options)
texttypes a valueresponse_value.text
ts
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.

ts
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):

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).

VariablePurpose
RATIFIA_GATED_TOOLSOptional 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_SECHow long to block waiting for a human (default 240). Keep the hook timeout a bit higher.
RATIFIA_GATE_FAIL_OPEN1 = 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:

json
{  "mcpServers": {    "ratifia": {      "command": "node",      "args": ["/absolute/path/to/ratifia-mcp/dist/index.js"],      "env": { "RATIFIA_API_KEY": "your-org-worker-api-key" }    }  }}