Claude Agent SDK
For a long-running Agent SDK daemon that needs to pause on a real decision instead
of auto-approving or auto-denying — wire pendnt's MCP server in as an MCP server, then call
request_approval from your canUseTool callback.
1. Register the MCP server
Both SDKs accept an MCP server config identical in shape to .mcp.json — point it
at the same remote endpoint:
{
"type": "http",
"url": "https://api.pendnt.dev/mcp",
"headers": { "Authorization": "Bearer YOUR_PENDNT_API_KEY" }
}
TypeScript
import { query } from "@anthropic-ai/claude-agent-sdk";
const PENDNT_MCP = {
type: "http" as const,
url: "https://api.pendnt.dev/mcp",
headers: { Authorization: `Bearer ${process.env.PENDNT_API_KEY}` },
};
async function canUseTool(toolName: string, input: Record<string, unknown>) {
// Only gate the decisions that matter — let routine, read-only tools through.
if (!["Bash", "deploy", "send_email"].includes(toolName)) {
return { behavior: "allow" as const, updatedInput: input };
}
const res = await fetch("https://api.pendnt.dev/v1/requests", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PENDNT_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
kind: "approval",
title: `Allow ${toolName}?`,
details: JSON.stringify(input, null, 2),
timeout_s: 1800,
wait_s: 25,
}),
});
const { id, status, answer } = await res.json();
if (status === "pending") {
// Still no answer after 25s — keep long-polling the same durable id.
const poll = await fetch(
`https://api.pendnt.dev/v1/requests/${id}?wait_s=25`,
{ headers: { Authorization: `Bearer ${process.env.PENDNT_API_KEY}` } },
);
const row = await poll.json();
return row.status === "approved"
? { behavior: "allow" as const, updatedInput: input }
: { behavior: "deny" as const, message: "Not approved" };
}
return status === "approved"
? { behavior: "allow" as const, updatedInput: input }
: { behavior: "deny" as const, message: answer?.text ?? "Not approved" };
}
for await (const msg of query({
prompt: "Run the weekly report job and deploy if it passes",
options: {
mcpServers: { pendnt: PENDNT_MCP },
canUseTool,
},
})) {
console.log(msg);
}
Python
import os
import json
import httpx
from claude_agent_sdk import ClaudeAgentOptions, query
PENDNT_API_KEY = os.environ["PENDNT_API_KEY"]
PENDNT_MCP = {
"type": "http",
"url": "https://api.pendnt.dev/mcp",
"headers": {"Authorization": f"Bearer {PENDNT_API_KEY}"},
}
async def can_use_tool(tool_name: str, tool_input: dict) -> dict:
if tool_name not in ("Bash", "deploy", "send_email"):
return {"behavior": "allow", "updatedInput": tool_input}
async with httpx.AsyncClient() as client:
resp = await client.post(
"https://api.pendnt.dev/v1/requests",
headers={"Authorization": f"Bearer {PENDNT_API_KEY}"},
json={
"kind": "approval",
"title": f"Allow {tool_name}?",
"details": json.dumps(tool_input, indent=2),
"timeout_s": 1800,
"wait_s": 25,
},
)
row = resp.json()
while row["status"] == "pending":
poll = await client.get(
f"https://api.pendnt.dev/v1/requests/{row['id']}",
params={"wait_s": 25},
headers={"Authorization": f"Bearer {PENDNT_API_KEY}"},
)
row = poll.json()
if row["status"] == "approved":
return {"behavior": "allow", "updatedInput": tool_input}
return {"behavior": "deny", "message": "Not approved"}
async def main():
async for msg in query(
prompt="Run the weekly report job and deploy if it passes",
options=ClaudeAgentOptions(
mcp_servers={"pendnt": PENDNT_MCP},
can_use_tool=can_use_tool,
),
):
print(msg)
Notes
- Gate selectively. Routing every single tool call through
request_approvalwould make an unattended daemon useless — only call it for the decisions that actually need a human (deploys, spend, anything irreversible). - The 25-second
wait_scap applies per call; both snippets above loop, re-issuing the long-poll against the same durablerequest_iduntil it resolves ortimeout_sis reached — the request itself stays open the whole time, so the process can also exit and pick it back up later with the same id. - Pair this with
schedule_wakeupif your daemon needs to exit while waiting rather than stay resident — see the MCP reference.
Early access — launched August 2026.