Human-in-the-loop for the Claude Agent SDK

Human-in-the-loop for the Claude Agent SDK, in one config block.

One mcpServers entry gives your Claude Agent SDK agent an ask-a-human tool. Nothing else to install.

The integration

Add it to your Claude Agent SDK agent

Add the hosted Pushary MCP server to the mcpServers option of query() and allow its tools. The agent can then put a question on a phone with ask_user and hold for the reply with wait_for_answer, with nothing else to install. For agents you ship to your own users, the Partner plan adds enroll_end_user and create_decision as MCP tools, or call decisions.ask from @pushary/server inside a custom tool so each user answers about their own action.

No install. One mcpServers entry.config
import { query } from "@anthropic-ai/claude-agent-sdk"

for await (const message of query({
  prompt: "Clean up stale feature flags. Ask before deleting anything.",
  options: {
    mcpServers: {
      pushary: {
        type: "http",
        url: "https://pushary.com/api/mcp/mcp",
        headers: { Authorization: `Bearer ${process.env.PUSHARY_API_KEY}` },
      },
    },
    allowedTools: [
      "mcp__pushary__ask_user",
      "mcp__pushary__wait_for_answer",
      "mcp__pushary__send_notification",
    ],
  },
})) {
  if (message.type === "result") console.log(message)
}

Why you need it

Claude Agent SDK pauses the agent. Pushary reaches the human.

The Claude Agent SDK runs the loop, the tools, and the permissions, and it has no channel to you once you close the laptop. Its permission callbacks decide whether a tool may run, and they still have to answer that question from whatever is already in the process. The Pushary server adds ask-a-human and notify as MCP tools the model can call mid run, which turns the missing half into a configuration entry rather than code. The Python SDK works the same way through ClaudeAgentOptions(mcp_servers={...}).

What Claude Agent SDK already gives you

The canUseTool callback

The Claude Agent SDK lets you intercept a tool call before it runs. Supply a callback, and every tool the agent wants to use passes through your code first, where you return allow or deny and can rewrite the input on the way through. As an interception point it is exactly right, and it is where an approval belongs.

native
const result = query({
  prompt,
  options: {
    canUseTool: async (toolName, input) => {
      // your decision goes here
      return { behavior: "allow", updatedInput: input }
    },
  },
})

Where it stops

  • The callback resolves in your process. It has no way to reach a person who is not at that machine.
  • Whatever you return has to come back before the SDK gives up waiting, so a long human wait needs handling outside the callback.
  • Allow and deny leave no trail. Nothing accumulates into a record you can query later.

Before you ship it

Things that bite on Claude Agent SDK

Denying without a reason confuses the model
A bare deny often makes the agent try a slightly different command to reach the same place. Return a reason with the denial so the model understands it was a policy decision rather than a malformed call, and stops rephrasing.
Match on the input, not just the tool name
Bash is one tool name covering git status and rm -rf. Gating on tool name alone either interrupts constantly or lets through the thing you cared about. The decision has to read the arguments.
Subagents call tools too
Work delegated to a subagent still runs tools. If your mental model is one callback for one linear session, the tool calls coming from delegated work can surprise you.

How it works

From ask to resume

Connect the phone once, then ask a human from the tool or step that guards the action. The waiting, the delivery, and the record are handled for you.

  1. 01

    Add the mcpServers entry

    Put the hosted Pushary server in the mcpServers option of query() with your API key as a Bearer token.

  2. 02

    Allow the tools

    Add mcp__pushary__ask_user, mcp__pushary__wait_for_answer, and mcp__pushary__send_notification to allowedTools.

  3. 03

    Tell the agent when to ask

    In the prompt or system prompt, say which actions need a human first. The model calls ask_user at that step and holds for the reply.

  4. 04

    Reach your own users when you ship

    When the agent serves your own product's users, allow the Partner-plan tools (mcp__pushary__enroll_end_user, mcp__pushary__create_decision, mcp__pushary__get_decision) so the decision goes durably to the right person, or call decisions.ask from @pushary/server inside a custom tool.

A decision is a yes or no confirm, a multiple-choice select, or a free-text input. Every one is stored with its outcome and kept in the audit log.

The Partner plan

Your agent asks your users, not just you

Ship the agent to real users and let each of them approve their own decisions from their phone, under your brand.

$99/ month, every framework included

Your users never see a Pushary login or bill.

Your users answer on their own phones

The externalId is your own id for the end-user. Connect their phone once with a keyless one-tap link that opens the Pushary app, and every approval lands with the right person. No account and no key on their side.

Durable, fail-closed waits

The agent stays open on a durable wait backed by a decisions ledger. If nobody answers in the window you set, the decision comes back denied, so a timeout or a restart never turns into an unapproved action.

Phone-first delivery

Pushary reaches the person on the device they carry: the app first, because it is the only channel that puts Approve and Deny on a lock screen, then PWA push notifications, then Slack if you route it there. You do not build a delivery pipeline.

Signed webhooks and an audit trail

Every decision is written to a durable ledger you can query and export, and resolved decisions post a signed webhook to your callback URL. You have the record of who approved what and when.

FAQ

Questions, answered

Do I need a Pushary npm package for the Claude Agent SDK?
No. The SDK's built-in MCP support is the integration: one mcpServers entry plus allowedTools. @pushary/server is there when you want the durable decisions API inside a custom tool.
Which tool names do I put in allowedTools?
mcp__pushary__ask_user, mcp__pushary__wait_for_answer, mcp__pushary__send_notification, and mcp__pushary__cancel_question. The prefix is mcp, then the server name, then the tool.
Does this work in the Python Claude Agent SDK?
Yes. Pass the same server entry in ClaudeAgentOptions(mcp_servers={...}) with the Bearer header, and allow the same tool names.
Who answers the question?
The phones connected to your Pushary workspace. For agents shipped to your own users, the Partner plan adds enroll_end_user and create_decision tools (and the decisions API in @pushary/server), so each end-user answers about their own action on their own phone.
What if nobody replies before the window closes?
The question expires and the agent is told there was no answer. Treat that as not approved, so silence never turns into consent.

Let your Claude Agent SDK agent ask a human.

Connect a user with one tap, ask a human with one call, and act on a fail-closed decision. Every decision is saved to a durable ledger you can audit.

Partner plan, $99 a month. Your users never see a Pushary login or bill.