pendnt
FreeNot checkedDurable human approvals, notifications, inbound webhooks, and wake-ups for headless agents.
About
Durable human approvals, notifications, inbound webhooks, and wake-ups for headless agents.
README
Try it in 10 seconds — no account:
curl https://api.pendnt.dev/try?plain=1returns a 72-hour trial key (20 requests) with a quickstart.
Wires a Claude Code session — interactive or headless (claude -p) — up
to a pendnt workspace: durable human approvals, notifications, and a "run finished" ping,
without the agent needing a terminal.
"pendnt" is a placeholder product name. It's kept in exactly one place — PRODUCT_NAME in
lib/config.mjs — plus the name fields in
plugin.json, marketplace.json and
.mcp.json, and the matching MCP server name registered in
/app/src/routes/mcp.ts. Rename all of those together and it's renamed
everywhere that matters.
This plugin ships two independent ways to answer permission prompts headlessly — pick one (or both;
--permission-prompt-tool takes priority when both are wired up, see §1a below):
- A
PermissionRequesthook (hooks/permission-request.mjs) — works with plainclaude -p, no extra flags. Runs automatically before every tool-use prompt. - The
permission_promptMCP tool, via--permission-prompt-tool mcp__pendnt__permission_prompt— Claude Code's own dedicated mechanism for headless permission prompts (see INTEGRATIONS.md §1a).
Plus a Notification hook and a Stop hook that both relay to POST /v1/notify — see
What's included below.
Install
Local marketplace (for trying this out before publishing anywhere)
This repo doubles as its own local marketplace — .claude-plugin/marketplace.json
lists this plugin with source: "./", pointing at the same directory plugin.json lives in. From
Claude Code:
/plugin marketplace add Pendnt/pendnt-plugin
/plugin install pendnt@pendnt
(or non-interactively: claude plugin marketplace add Pendnt/pendnt-plugin && claude plugin install pendnt@pendnt)
To publish for real later, split marketplace.json out into its own repo with source pointing at
this plugin's repo (see INTEGRATIONS.md §1d for the marketplace
manifest shape and github/git-subdir/npm/archive source types), then
claude plugin marketplace add <owner>/<repo> from anywhere.
Env vars
| Variable | Required | Default | Meaning |
|---|---|---|---|
PENDNT_API_KEY |
yes | — | Authorization: Bearer <key> for every call to the API (aio_..., from POST /dev/bootstrap locally, or your real workspace's API key in production). Without it, the PermissionRequest/Notification/Stop hooks all fail closed (deny / no-op) rather than erroring the session. |
PENDNT_URL |
no | https://api.pendnt.dev |
Base URL of the pendnt REST API — not including /mcp or /v1. Point this at http://localhost:8787 for local dev against wrangler dev (see below). |
PENDNT_APPROVAL_TIMEOUT_S |
no | 600 (10 min) |
Total wall-clock budget the PermissionRequest hook spends re-polling before it gives up and denies. Each individual poll is capped server-side at 25s (wait_s), so the hook loops GET /v1/requests/:id?wait_s=25 until this budget runs out. |
Set them in your shell, or in .claude/settings.json under env — see
settings.json docs. .mcp.json's ${PENDNT_URL:-...} /
${PENDNT_API_KEY} syntax is Claude Code's own env-var expansion for .mcp.json values (headers
and URLs) — see INTEGRATIONS.md §1e.
What's included
.mcp.json— registers thependntremote MCP server (${PENDNT_URL:-https://api.pendnt.dev}/mcp,Authorization: Bearer ${PENDNT_API_KEY}) so all 14 tools (request_approval,check_request,permission_prompt,notify,create_endpoint,list_endpoints,wait_for_event,schedule_wakeup,list_wakeups,cancel_wakeup,kv_get,kv_set,kv_delete,whoami) are available to the model directly, undermcp__pendnt__<tool>— exceptpermission_prompt, which Claude Code removes from its own visible tool list once named by--permission-prompt-tool(see below), since it's meant to be called by the CLI itself, not by the model.hooks/hooks.json— wires three hooks (shape matchessettings.json'shookskey, see INTEGRATIONS.md §1d):PermissionRequest→hooks/permission-request.mjs. Fires before every tool-use prompt. POSTs a summary of the tool call toPOST /v1/requests(kind: "approval",wait_s: 25,timeout_s: $PENDNT_APPROVAL_TIMEOUT_S), then re-pollsGET /v1/requests/:id?wait_s=25until it's answered or the totalPENDNT_APPROVAL_TIMEOUT_Sbudget elapses. Prints exactly one JSON object per INTEGRATIONS.md §1b'sPermissionRequestcontract:
or{ "hookSpecificOutput": { "hookEventName": "PermissionRequest", "decision": { "behavior": "allow", "updatedInput": {} } } }{"behavior": "deny", "message": "..."}. Fails closed on every error path (missing API key, network failure, operator denies, request expires, timeout) — the only way to getallowis an explicit approval. An operator using the answer page's free-text box (always shown, even forkind: "approval"requests) is treated as approval only if the text starts with yes/y/allow/approve/ok — anything else denies with that text as the reason.Notification→hooks/notify.mjs. Relaysnotification_type/messagetoPOST /v1/notify. Cannot block Claude Code (per the hook's own semantics) and never tries to — always exits 0.Stop→hooks/stop.mjs. Relays a"run finished"message toPOST /v1/notifyat the end of every turn. Never returns{"decision":"block"}— it only notifies, it doesn't keep the agent going.
lib/— the three hook scripts' shared bits:config.mjs(env vars + thePRODUCT_NAMEconstant),client.mjs(thinfetchwrappers for/v1/requestsand/v1/notify),stdin.mjs(hook stdin JSON parsing). No npm dependencies — plain Node 18+ (fetch,AbortController) is enough.
The --permission-prompt-tool path (§1a)
Instead of (or in addition to — Claude Code prefers --permission-prompt-tool over hooks when both
apply) the PermissionRequest hook, you can point Claude Code's dedicated permission-prompt mechanism
directly at the permission_prompt MCP tool this plugin's .mcp.json registers — the one tool in this
server purpose-built for this flag (see /app/README.md's permission_prompt section for the full
contract):
export PENDNT_API_KEY=aio_...
export PENDNT_URL=https://api.pendnt.dev # or http://localhost:8787 for local dev
export MCP_TOOL_TIMEOUT=600000 # ms — must exceed PERMISSION_PROMPT_WAIT_S (default 540s)
claude -p --permission-prompt-tool mcp__pendnt__permission_prompt "deploy to prod"
Three things worth knowing before you rely on this path:
- Claude Code blocks the first turn until the
pendntMCP server connects, capped byMCP_TIMEOUT(default 30s, env var, milliseconds) — bump it if your network is slow:export MCP_TIMEOUT=60000(from INTEGRATIONS.md §1a). - As of CLI ≥2.1.199, an
allowfrom a--permission-prompt-toolis converted todenyfor any MCP tool call flagged_meta["anthropic/requiresUserInteraction"]— this prompt tool can't rubber-stamp those regardless of what the operator answers (also fromINTEGRATIONS.md§1a). permission_promptholds the MCP tool call open server-side for up toPERMISSION_PROMPT_WAIT_S(env var on/app, default 540s = 9 min) waiting for the operator, unlike every other tool's ≤25swait_s. Claude Code's ownMCP_TOOL_TIMEOUT(its per-tool-call timeout, ms, default 30000) must be set higher than that or the CLI gives up first — henceexport MCP_TOOL_TIMEOUT=600000above.
Unlike request_approval, permission_prompt does return Claude Code's exact {behavior, updatedInput, updatedPermissions, interrupt} / {behavior:"deny", message, interrupt} contract natively — an allow
echoes the original input back as updatedInput, a deny (or an unanswered timeout) carries a
human-readable message. That's the same translation the PermissionRequest hook below does by hand for
its own hook-shaped contract; permission_prompt does it as the MCP tool result directly, which is what
--permission-prompt-tool actually reads.
Headless example
export PENDNT_API_KEY=aio_...
export PENDNT_URL=https://api.pendnt.dev
export MCP_TOOL_TIMEOUT=600000
claude -p --permission-prompt-tool mcp__pendnt__permission_prompt \
"review open PRs and merge the ones that pass CI"
Or, with the PermissionRequest hook doing the work instead (no extra flag needed once the plugin is
installed):
export PENDNT_API_KEY=aio_...
export PENDNT_URL=https://api.pendnt.dev
claude -p "review open PRs and merge the ones that pass CI"
Either way, every tool-use prompt now creates a durable approval request instead of blocking on a
terminal that doesn't exist — check your configured channels (email/web inbox — see /app/README.md's
"Channels" section for what's actually implemented today), answer from your phone, and the agent
resumes.
Running the hook locally against wrangler dev
From /app:
cd ../app
npm install
npm run db:migrate:local
npm run dev # http://localhost:8787
In another terminal, bootstrap a workspace + API key (see /app/README.md) and point the plugin's env
vars at it:
BOOT=$(curl -s -X POST http://localhost:8787/dev/bootstrap -d '{}')
export PENDNT_API_KEY=$(echo "$BOOT" | jq -r .api_key)
export PENDNT_URL=http://localhost:8787
Then either run a hook script directly, feeding it the JSON a real Claude Code hook invocation would send on stdin (useful for iterating on the hook without a full Claude Code session):
echo '{"session_id":"s1","cwd":"'"$PWD"'","tool_name":"Bash","tool_use_id":"toolu_1","tool_input":{"command":"deploy prod"}}' \
| PENDNT_APPROVAL_TIMEOUT_S=60 node hooks/permission-request.mjs
...or install the plugin locally (/plugin marketplace add Pendnt/pendnt-plugin from Claude Code, see
Install) and drive a real session — with PENDNT_URL/PENDNT_API_KEY exported in the
shell Claude Code runs in, every tool-use prompt round-trips through your local wrangler dev
instance. Answer the request the same way /app/README.md's curl walkthrough does: compute the HMAC
signature yourself (it's a pure function of the request id and SIGNING_SECRET) and POST /a/<id>/<sig>, or watch the log delivery channel's console output from wrangler dev for the
answer link once a real channel is wired up.
Caveats
- No npm dependencies by design (
fetch/AbortControlleronly) — every hook script runs under plainnode hooks/*.mjs, nonpm installstep inside the plugin itself. - The
PermissionRequesthook's fail-closed defaults (missing key, network error, timeout → deny) are a deliberate safety choice, not a limitation to work around — a headless agent with nobody to ask should not proceed by default. hooks/hooks.json'sNotificationmatcher is not narrowed to specificnotification_types — every notification (including ones that aren't permission-prompt-related) gets relayed. Narrow the matcher or filter insidehooks/notify.mjsif that's noisier than you want.
Guides
Practical writeups on running agents unattended, at pendnt.dev/guides: --dangerously-skip-permissions alternatives · claude -p on cron · the --permission-prompt-tool contract · hooks for unattended runs · exit codes: retry vs stay dead
Installing pendnt
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/Pendnt/pendnt-pluginFAQ
Is pendnt MCP free?
Yes, pendnt MCP is free — one-click install via Unyly at no cost.
Does pendnt need an API key?
No, pendnt runs without API keys or environment variables.
Is pendnt hosted or self-hosted?
A hosted option is available: Unyly runs the server in the cloud, no local setup required.
How do I install pendnt in Claude Desktop, Claude Code or Cursor?
Open pendnt on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare pendnt with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
