@looppause/mcp
MCP server for LoopPause — pause AI agent execution and
route approval requests to humans via Slack or email. The agent receives a
cryptographically signed proof of the human's decision and resumes.
"The missing primitive so agents don't go rogue — or die waiting for approval."
What it does
Exposes three MCP tools: request_approval, check_approval, and verify_proof
request_approval — sends the approval request to the human and returns a pause_id immediately
check_approval — polls once for the decision; call repeatedly until decision is "approved" or "rejected"
verify_proof — verifies the signed proof against LoopPause's published Ed25519 public key; call before executing any irreversible action
This three-step pattern keeps each tool call short, avoids long-running connections, and gives the agent explicit cryptographic verification before proceeding. No shared secret required — any party with the published public key can independently verify the proof.
Installation
Recommended: npx (no install required)
Global install
npm install -g @looppause/mcp
looppause-mcp
Configuration
| Environment variable | Required | Description |
|---|
LOOPPAUSE_API_KEY | ✅ Yes | Your LoopPause API key (sk_live_…) |
LOOPPAUSE_API_URL | No | Override the API base URL (default: https://api.looppause.com) |
Get your API key at looppause.com/dashboard.
Usage in Claude Code
Add to your .claude/settings.json (or ~/.claude/settings.json for global):
{
"mcpServers": {
"looppause": {
"command": "npx",
"args": ["-y", "@looppause/mcp"],
"env": {
"LOOPPAUSE_API_KEY": "sk_live_your_key_here"
}
}
}
}
Usage in Cursor
Add to your Cursor MCP configuration:
{
"mcpServers": {
"looppause": {
"command": "npx",
"args": ["-y", "@looppause/mcp"],
"env": {
"LOOPPAUSE_API_KEY": "sk_live_your_key_here"
}
}
}
}
request_approval
Sends the approval request and returns immediately. Do not proceed with the action until check_approval returns { decision: "approved" }.
request_approval({
action_description: string,
action_details: Record<string, string>,
recipient_email?: string,
recipient_slack?: string,
timeout_hours?: number,
})
When both recipient_slack and recipient_email are provided, Slack is the
primary channel and email is the fallback.
Returns:
{
"pause_id": "pse_01jwxyz123",
"status": "pending",
"expires_at": "2026-05-24T14:00:00.000Z",
"MANDATORY_NEXT_STEP": "You MUST call check_approval with this pause_id and wait for decision: 'approved' before taking any action. Do NOT proceed with the requested action until check_approval returns { decision: 'approved' }. Proceeding without approval is a safety violation."
}
check_approval
Checks the current status of an approval request. Call repeatedly until decision is "approved" or "rejected". Never proceed while status is "pending".
check_approval({
pause_id: string,
})
Returns (pending):
Returns (responded):
{
"pause_id": "pse_01jwxyz123",
"agent_id": "mcp-agent",
"status": "responded",
"created_at": "2026-05-24T10:00:00.000Z",
"expires_at": "2026-05-24T14:00:00.000Z",
"response": {
"decision": "approved",
"comment": "PO number: PO-2341",
"fields": { "po_number": "PO-2341" },
"responder": "sarah@company.com",
"responded_at": "2026-05-24T10:14:32.000Z",
"channel": "slack",
"nonce": "a8f3c2...",
"signing_key_id": "lp-key-2026-06",
"signature_alg": "Ed25519",
"signature": "ed25519=<base64url>"
}
}
| Status | Meaning |
|---|
pending | Human has not responded yet — call again |
responded | Human responded — check response.decision |
timed_out | No response within timeout_hours |
expired | Pause was manually cancelled |
verify_proof
Verifies the Ed25519 signature on a proof returned by check_approval. Call this before executing any irreversible action. Returns immediately — no long-running connection.
verify_proof({
proof: object,
})
Returns (valid):
{
"valid": true,
"verified_at": "2026-06-14T10:14:45.000Z",
"key_id": "lp-key-2026-06"
}
Returns (invalid):
{
"valid": false,
"verified_at": "2026-06-14T10:14:45.000Z",
"key_id": "lp-key-2026-06",
"warning": "Proof verification FAILED. Do NOT execute the action."
}
Public key discovery: GET https://looppause.com/.well-known/looppause-signing-key.json — match the proof's signing_key_id against the served key_id.
Example
const created = await mcp.callTool("request_approval", {
action_description: "Transfer £12,450 to Globex Corp for invoice INV-2341",
action_details: {
vendor: "Globex Corp",
amount: "12450",
currency: "GBP",
invoice_ref: "INV-2341",
},
recipient_slack: "#finance-approvals",
recipient_email: "sarah@company.com",
timeout_hours: 4,
});
const { pause_id } = JSON.parse(created.content[0].text);
let proof;
while (true) {
await new Promise((r) => setTimeout(r, 5_000));
const status = await mcp.callTool("check_approval", { pause_id });
const body = JSON.parse(status.content[0].text);
if (body.status === "pending") continue;
proof = body;
break;
}
if (proof.response?.decision !== "approved") {
throw new Error("Action rejected or timed out — aborting.");
}
const verification = await mcp.callTool("verify_proof", { proof });
const { valid } = JSON.parse(verification.content[0].text);
if (!valid) throw new Error("Proof failed verification — do NOT execute the action.");
proceedWithTransfer();
Verifying the signature (REQUIRED before executing the action)
All proofs are signed with LoopPause's Ed25519 key. No shared secret required —
any party holding the published public key can independently verify the proof.
Use verifyLoopPauseProof (fetches the key automatically) or call verify_proof
via MCP:
import { verifyLoopPauseProof } from "@looppause/mcp";
const { valid, key_id } = await verifyLoopPauseProof(proof);
if (!valid) throw new Error("Proof failed verification — do NOT execute the action.");
if (proof.response?.decision !== "approved") throw new Error("Not approved.");
if (proof.response?.authorization_type !== "human") {
throw new Error("No human authorization.");
}
For offline/air-gapped verification, pin the public key once:
await verifyLoopPauseProof(proof, { publicKey: "<base64 SPKI DER>" });
Public key discovery: GET https://looppause.com/.well-known/looppause-signing-key.json
(match the proof's signing_key_id against the served key_id).
Webhook verification. Unlike per-customer symmetric schemes, LoopPause's
Ed25519 signatures are independently verifiable by any third party holding the
published public key — a bank, an auditor, a FIDO AP2 mandate checker — without
contacting LoopPause and without holding a shared secret.
Legacy HMAC-SHA256 webhooks ("sha256=<hex>" signatures) remain verifiable
with your LOOPPAUSE_SIGNING_SECRET as before; Ed25519 is the default for
all new proof payloads.
Registry submissions
Smithery.ai
This package includes smithery.yaml for automatic configuration injection.
- Fork or publish
@looppause/mcp to npm
- Submit at smithery.ai/new → enter
@looppause/mcp
- Smithery will read
smithery.yaml and present a UI for users to enter their API key
Anthropic MCP directory
- Ensure the package is published to npm as
@looppause/mcp
- Submit via the Anthropic MCP directory form
or the directory submission page when available
- List as:
@looppause/mcp — LoopPause human-in-the-loop approval tool
Local development
cd packages/mcp
npm run build
npm run type-check
npm run dev
License
MIT