Durable human approvals, notifications, inbound webhooks, and wake-ups for headless agents.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
One-click editor setup isnβt available for this listing yet β we donβt have a confirmed install command, and weβd rather show nothing than point your editor at the wrong package or host. Follow the projectβs own setup instructions, linked above.
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):
PermissionRequest hook (hooks/permission-request.mjs) β works with plain claude -p, no
extra flags. Runs automatically before every tool-use prompt.permission_prompt MCP 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.
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:
(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.
| 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.
.mcp.json β registers the pendnt remote 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, under mcp__pendnt__<tool> β except permission_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 matches settings.json's hooks key, see
INTEGRATIONS.md Β§1d):
PermissionRequest β hooks/permission-request.mjs. Fires before every tool-use prompt. POSTs
a summary of the tool call to POST /v1/requests (kind: "approval", wait_s: 25,
timeout_s: $PENDNT_APPROVAL_TIMEOUT_S), then re-polls GET /v1/requests/:id?wait_s=25 until it's
answered or the total PENDNT_APPROVAL_TIMEOUT_S budget elapses. Prints exactly one JSON object per
INTEGRATIONS.md Β§1b's PermissionRequest contract:
or {"behavior": "deny", "message": "..."}. Fails closed on every error path (missing API key,
network failure, operator denies, request expires, timeout) β the only way to get allow is an
explicit approval. An operator using the answer page's free-text box (always shown, even for
kind: "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. Relays notification_type/message to POST /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 to POST /v1/notify at 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 + the PRODUCT_NAME
constant), client.mjs (thin fetch wrappers for /v1/requests and /v1/notify), stdin.mjs (hook
stdin JSON parsing). No npm dependencies β plain Node 18+ (fetch, AbortController) is enough.--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):
Three things worth knowing before you rely on this path:
pendnt MCP server connects, capped by MCP_TIMEOUT
(default 30s, env var, milliseconds) β bump it if your network is slow: export MCP_TIMEOUT=60000
(from INTEGRATIONS.md Β§1a).allow from a --permission-prompt-tool is converted to deny for any MCP
tool call flagged _meta["anthropic/requiresUserInteraction"] β this prompt tool can't rubber-stamp
those regardless of what the operator answers (also from INTEGRATIONS.md Β§1a).permission_prompt holds the MCP tool call open server-side for up to PERMISSION_PROMPT_WAIT_S
(env var on /app, default 540s = 9 min) waiting for the operator, unlike every other tool's β€25s
wait_s. Claude Code's own MCP_TOOL_TIMEOUT (its per-tool-call timeout, ms, default 30000) must be
set higher than that or the CLI gives up first β hence export MCP_TOOL_TIMEOUT=600000 above.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.
Or, with the PermissionRequest hook doing the work instead (no extra flag needed once the plugin is
installed):
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.
wrangler devFrom /app:
In another terminal, bootstrap a workspace + API key (see /app/README.md) and point the plugin's env
vars at it:
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/pendnt)<a href="https://allmcps.com/mcp/pendnt"><img src="https://allmcps.com/api/badge/pendnt?style=directory" alt="Pendnt on AllMCPs" /></a>