Skip to main content
Glama

Ask User a Question

ask_user
Destructive

Ask the user a question as a push notification on their phone and block until they answer. Reach for this whenever you need the user's decision and they may be away from the current client: approving a risky or irreversible step (deleting files, force pushing, spending money, sending external messages), picking between implementation options, or supplying missing input. The user answers from the lock screen or a decision page; you do not need a separate wait_for_answer call because this tool waits by default. Three question types: "confirm" (yes/no), "select" (2 to 6 fixed choices), "input" (free text). A single call blocks for at most 55 seconds. If a live question times out, nextAction is "wait_for_answer": poll once with timeoutMs 55000. If that poll is also unanswered, cancel the phone question before asking in the current chat or client. If cancellation returns handoffAction "stop", stop. Otherwise, if cancellation returns false, poll once for 1 second and honor the answer that won the race. Cancelled, expired, and missing questions are reported as terminal states rather than as timeouts. Every response carries answerUrl, the signed-in dashboard page where this question is waiting. When you report that you are waiting, print that URL to the user so they can answer from a browser instead of hunting for it. Works from Claude Code, Codex, Cursor, Hermes, or any MCP client; no Claude subscription is required. SIDE EFFECT: sends a real push notification.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
envNoSet to "test" from a test suite. The question is stored and returned as pending, and nothing is delivered to a phone, browser or Slack. The X-Pushary-Env: test header does the same for every call on the connection.
tagsNoDeliver only to subscribers that have any of these tags.
typeNoQuestion type: confirm renders yes/no buttons, select renders the options list, input renders a free-text field.confirm
waitNotrue (default) blocks until the user answers or the timeout fires. Set false to return immediately with a pending correlationId and poll it yourself via wait_for_answer.
actionNoThe concrete operation about to happen, one line. Shown as the Action line.
intentNoThe user's stated task (from their last prompt), one line. Shown as the Intent line so the user can see why the agent stopped.
blockerNoThe single gating reason the agent stopped, one line. Shown as the Blocker line.
contextNoOne or two sentences about what the agent is working on, shown above the question so the user can decide without opening the terminal.
optionsNoThe 2 to 6 choices for a select question. Required when type is "select", ignored otherwise. The answered value is the chosen option string.
repoKeyNoStable repository identity for the working directory, e.g. "github.com/acme/api". Lets an approval routing rule scoped to one repository avoid governing another. Optional; omit it and only workspace-wide routing rules apply.
questionYesThe question shown on the user's lock screen (max 500 chars). Phrase it so it is answerable at a glance; put background in context instead.
toolNameNoThe tool this approval is for (e.g. "Bash"), so the user can choose to always-allow it.
agentNameNoName of the agent asking, format "{Agent} - {project}" (e.g. "Claude Code - myproject"). Shown in the notification title so the user knows which session needs them. Falls back to the MCP client name if omitted.
machineIdNoStable machine id of the asking agent, so two machines never collapse into one session.
questionsNoONE question, in the richer Claude-compatible shape: a header, per-option descriptions, multiSelect, and an optional write-in. Exactly one keeps already-installed clients answerable; asking several means several calls. Runtime-populated; ordinary callers should omit it and use question/type/options.
requestIdNoMACHINE-POPULATED. The CALLER's own identifier for one logical invocation, used only when the runtime supplies no toolUseId. Mint it once, outside your retry loop, and send the same value on every attempt, so three retries of one ask become one decision. Do NOT derive it from the question text or reuse it across two deliberate asks: both collapse a real second question into the first one's answer. If you are a model deciding to call this tool, omit this field.
scopePathNoSet ONLY when this approval exists because the path falls outside the scope the user ratified via propose_scope. Approving then widens the run scope to include this exact path, so the user is not asked again for the same area.
sessionIdNoOpaque per-session id of the asking agent, so parallel sessions are attributed separately.
timeoutMsNoHow long this call blocks, in milliseconds (max 55000). Defaults to the site policy timeout. The question stays open for 10 minutes regardless, so a timeout here is not a refusal; follow up with wait_for_answer.
toolUseIdNoMACHINE-POPULATED. The agent RUNTIME's own identifier for the tool call this approval gates, forwarded verbatim by a hook that received it. Do NOT invent, guess, derive, or reuse a value: two different questions sent under the same id collapse into one, and the second one never reaches a human. If you are a model deciding to call this tool, omit this field.
actionBodyNoThe diff (Edit/Write) or full command (Bash/apply_patch), secret-redacted and size-capped. Rendered as a collapsible detail block; never used as the push body.
toolTargetNoCompact target of the tool call (e.g. the command head "git push" for Bash, or a file extension like ".ts" for Edit/Write). Used to mine policy suggestions.
waitEndsAtNoMACHINE-POPULATED. When the agent hook stops waiting live and hands control back to the terminal. The question may remain answerable after this time. Ordinary callers should omit it.
callbackUrlNoWebhook URL that receives a POST with the answer when the user responds, signed with the X-Pushary-Signature header. Useful when the agent process may exit before the answer arrives.
externalIdsNoDeliver only to subscribers matching these external IDs.
placeholderNoHint text shown inside the free-text field for input questions
subscriberIdsNoDeliver only to these subscriber IDs. Omit all targeting fields to reach every connected device.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
envNoEchoed when the call was test traffic.
heldNoPresent when the question was stored but deliberately not delivered: test traffic, or a permission ask with no toolName and no sessionId.
hintNoWhat to do next, when there is a next step.
modeNoThe site delivery mode that stopped this call from waiting.
noteNoFree text the user added alongside their answer.
typeYesThe question type that was rendered.
valueNoThe user's answer: "yes" or "no" for confirm, the chosen option for select, the typed text for input.
statusNoThe question state. Only pending is a live unanswered wait; cancelled, expired, missing, and unavailable must not be described as timeouts.
warningNoPresent only when no channel is connected, naming what the user has to connect.
answeredNoTrue once the user responded. Absent on the wait:false path, where nothing was awaited.
deliveryNoPer-channel reach for the push carrying this question.
questionYesThe question exactly as the user saw it.
timedOutNoTrue when the initial wait ended while the question was still live. Poll once with wait_for_answer, then follow handoffAction when present, otherwise nextAction.
answerUrlNoThe signed-in dashboard page where this question is waiting. Print it when you tell the user you are waiting, so they can answer from a browser.
noDevicesNoTrue when no phone, browser, or Slack channel could receive the question. Do not wait; follow handoffAction immediately.
nextActionNoBackward-compatible next step: poll once or ask in the current client. Follow handoffAction first when present.
suppressedNoTrue when the PHONE push was deliberately held because a terminal on this machine is active. It says nothing about the notch, the dashboard or Slack, which are unaffected and may still be showing this question. A caller with no screen of its own should treat it as the terminal's to answer; a caller that can render the question itself should keep waiting.
waitEndsAtNoWhen the agent hook stops waiting live. On an idempotent replay this is the original question's deadline, which the hook must reuse.
correlationIdYesId of the question that was created. Pass it to wait_for_answer to keep waiting, or to cancel_question to retract it.
handoffActionNoRace-safe directive for updated clients. Takes precedence over nextAction: cancel before asking in the current client, or stop the handoff.
expiresInSecondsNoHow long the question stays answerable.

Schema Changelog

Changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. Changed3 schema fields changed
    • addedInput schema / properties / env
      Added value: +{
      +  "description": "Set to \"test\" from a test suite. The question is stored and returned as pending, and nothing is delivered to a phone, browser or Slack. The X-Pushary-Env: test header does the same for every call on the connection.",
      +  "enum": [
      +    "test"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / env
      Added value: +{
      +  "description": "Echoed when the call was test traffic.",
      +  "enum": [
      +    "test"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / held
      Added value: +{
      +  "description": "Present when the question was stored but deliberately not delivered: test traffic, or a permission ask with no toolName and no sessionId.",
      +  "enum": [
      +    "test_traffic",
      +    "unattributed"
      +  ],
      +  "type": "string"
      +}
  2. Changed2 schema fields changed
    • addedInput schema / properties / waitEndsAt
      Added value: +{
      +  "description": "MACHINE-POPULATED. When the agent hook stops waiting live and hands control back to the terminal. The question may remain answerable after this time. Ordinary callers should omit it.",
      +  "format": "date-time",
      +  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
      +  "type": "string"
      +}
    • addedOutput schema / properties / waitEndsAt
      Added value: +{
      +  "description": "When the agent hook stops waiting live. On an idempotent replay this is the original question's deadline, which the hook must reuse.",
      +  "format": "date-time",
      +  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
      +  "type": "string"
      +}
  3. Changed1 schema field changed
    • changedOutput schema / properties / suppressed / description
      Previous value: -"True when the push was deliberately held because the user is at the keyboard and will see the terminal prompt instead."New value: +"True when the PHONE push was deliberately held because a terminal on this machine is active. It says nothing about the notch, the dashboard or Slack, which are unaffected and may still be showing this question. A caller with no screen of its own should treat it as the terminal's to answer; a caller that can render the question itself should keep waiting."
  4. Changed2 schema fields changed
    • changedInput schema / properties / questions / description
      Previous value: -"ONE question, in the richer Claude-compatible shape: a header, per-option descriptions, and multiSelect. Exactly one, because an answer carries no way to say which of several questions it belongs to, so a combined card cannot be answered from a phone, the decision page, or Slack. Asking several means several calls. Runtime-populated; ordinary callers should omit it and use question/type/options."New value: +"ONE question, in the richer Claude-compatible shape: a header, per-option descriptions, multiSelect, and an optional write-in. Exactly one keeps already-installed clients answerable; asking several means several calls. Runtime-populated; ordinary callers should omit it and use question/type/options."
    • addedInput schema / properties / questions / items / properties / allowOther
      Added value: +{
      +  "type": "boolean"
      +}
  5. Changed2 schema fields changed
    • changedInput schema / properties / questions / description
      Previous value: -"A bounded Claude-compatible question set rendered and submitted as one phone task. Runtime-populated; ordinary single-question callers should omit it."New value: +"ONE question, in the richer Claude-compatible shape: a header, per-option descriptions, and multiSelect. Exactly one, because an answer carries no way to say which of several questions it belongs to, so a combined card cannot be answered from a phone, the decision page, or Slack. Asking several means several calls. Runtime-populated; ordinary callers should omit it and use question/type/options."
    • changedInput schema / properties / questions / maxItems
      Previous value: -4New value: +1
  6. Changed6 schema fields changed
    • addedOutput schema / properties / handoffAction
      Added value: +{
      +  "description": "Race-safe directive for updated clients. Takes precedence over nextAction: cancel before asking in the current client, or stop the handoff.",
      +  "enum": [
      +    "cancel_then_ask_in_current_client",
      +    "stop"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / nextAction
      Added value: +{
      +  "description": "Backward-compatible next step: poll once or ask in the current client. Follow handoffAction first when present.",
      +  "enum": [
      +    "wait_for_answer",
      +    "ask_in_current_client"
      +  ],
      +  "type": "string"
      +}
    • changedOutput schema / properties / noDevices / description
      Previous value: -"True when no phone, browser, or Slack channel could receive the question at all. Stop waiting and fall back to the terminal."New value: +"True when no phone, browser, or Slack channel could receive the question. Do not wait; follow handoffAction immediately."
    • changedOutput schema / properties / status / description
      Previous value: -"Set only when no answer was awaited. pending: wait was false. notified: the site policy is notify_only. terminal: the user routed decisions to the terminal, so no push was sent."New value: +"The question state. Only pending is a live unanswered wait; cancelled, expired, missing, and unavailable must not be described as timeouts."
    • changedOutput schema / properties / status / enum
      Previous value: -[
      -  "pending",
      -  "notified",
      -  "terminal"
      -]New value: +[
      +  "answered",
      +  "pending",
      +  "cancelled",
      +  "expired",
      +  "missing",
      +  "unavailable",
      +  "notified",
      +  "terminal",
      +  "stopped"
      +]
    • changedOutput schema / properties / timedOut / description
      Previous value: -"True when this call returned before the user answered. Not a refusal: the question stays open for 10 minutes, so keep polling with wait_for_answer."New value: +"True when the initial wait ended while the question was still live. Poll once with wait_for_answer, then follow handoffAction when present, otherwise nextAction."
  7. Changed1 schema field changed
    • addedInput schema / properties / requestId
      Added value: +{
      +  "description": "MACHINE-POPULATED. The CALLER's own identifier for one logical invocation, used only when the runtime supplies no toolUseId. Mint it once, outside your retry loop, and send the same value on every attempt, so three retries of one ask become one decision. Do NOT derive it from the question text or reuse it across two deliberate asks: both collapse a real second question into the first one's answer. If you are a model deciding to call this tool, omit this field.",
      +  "maxLength": 200,
      +  "type": "string"
      +}
  8. Changed1 schema field changed
    • addedInput schema / properties / questions
      Added value: +{
      +  "description": "A bounded Claude-compatible question set rendered and submitted as one phone task. Runtime-populated; ordinary single-question callers should omit it.",
      +  "items": {
      +    "properties": {
      +      "header": {
      +        "maxLength": 40,
      +        "type": "string"
      +      },
      +      "multiSelect": {
      +        "type": "boolean"
      +      },
      +      "options": {
      +        "items": {
      +          "properties": {
      +            "description": {
      +              "maxLength": 500,
      +              "type": "string"
      +            },
      +            "label": {
      +              "maxLength": 100,
      +              "minLength": 1,
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "label"
      +          ],
      +          "type": "object"
      +        },
      +        "maxItems": 4,
      +        "minItems": 2,
      +        "type": "array"
      +      },
      +      "question": {
      +        "maxLength": 500,
      +        "minLength": 1,
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "question",
      +      "multiSelect",
      +      "options"
      +    ],
      +    "type": "object"
      +  },
      +  "maxItems": 4,
      +  "minItems": 1,
      +  "type": "array"
      +}
  9. Changed1 schema field changed
    • addedInput schema / properties / toolUseId
      Added value: +{
      +  "description": "MACHINE-POPULATED. The agent RUNTIME's own identifier for the tool call this approval gates, forwarded verbatim by a hook that received it. Do NOT invent, guess, derive, or reuse a value: two different questions sent under the same id collapse into one, and the second one never reaches a human. If you are a model deciding to call this tool, omit this field.",
      +  "maxLength": 200,
      +  "type": "string"
      +}
  10. Changed1 schema field changed
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "$schema": "http://json-schema.org/draft-07/schema#",
      +  "additionalProperties": false,
      +  "properties": {
      +    "answerUrl": {
      +      "description": "The signed-in dashboard page where this question is waiting. Print it when you tell the user you are waiting, so they can answer from a browser.",
      +      "type": "string"
      +    },
      +    "answered": {
      +      "description": "True once the user responded. Absent on the wait:false path, where nothing was awaited.",
      +      "type": "boolean"
      +    },
      +    "correlationId": {
      +      "description": "Id of the question that was created. Pass it to wait_for_answer to keep waiting, or to cancel_question to retract it.",
      +      "type": "string"
      +    },
      +    "delivery": {
      +      "additionalProperties": false,
      +      "description": "Per-channel reach for the push carrying this question.",
      +      "properties": {
      +        "mobile": {
      +          "description": "Phones the question reached.",
      +          "type": "number"
      +        },
      +        "pending": {
      +          "description": "True when delivery was still in flight when this returned, so the counts above are not final.",
      +          "type": "boolean"
      +        },
      +        "web": {
      +          "description": "Browsers the question reached.",
      +          "type": "number"
      +        }
      +      },
      +      "required": [
      +        "web",
      +        "mobile"
      +      ],
      +      "type": "object"
      +    },
      +    "expiresInSeconds": {
      +      "description": "How long the question stays answerable.",
      +      "type": "number"
      +    },
      +    "hint": {
      +      "description": "What to do next, when there is a next step.",
      +      "type": "string"
      +    },
      +    "mode": {
      +      "description": "The site delivery mode that stopped this call from waiting.",
      +      "enum": [
      +        "notify_only",
      +        "terminal_only"
      +      ],
      +      "type": "string"
      +    },
      +    "noDevices": {
      +      "description": "True when no phone, browser, or Slack channel could receive the question at all. Stop waiting and fall back to the terminal.",
      +      "type": "boolean"
      +    },
      +    "note": {
      +      "description": "Free text the user added alongside their answer.",
      +      "type": "string"
      +    },
      +    "question": {
      +      "description": "The question exactly as the user saw it.",
      +      "type": "string"
      +    },
      +    "status": {
      +      "description": "Set only when no answer was awaited. pending: wait was false. notified: the site policy is notify_only. terminal: the user routed decisions to the terminal, so no push was sent.",
      +      "enum": [
      +        "pending",
      +        "notified",
      +        "terminal"
      +      ],
      +      "type": "string"
      +    },
      +    "suppressed": {
      +      "description": "True when the push was deliberately held because the user is at the keyboard and will see the terminal prompt instead.",
      +      "type": "boolean"
      +    },
      +    "timedOut": {
      +      "description": "True when this call returned before the user answered. Not a refusal: the question stays open for 10 minutes, so keep polling with wait_for_answer.",
      +      "type": "boolean"
      +    },
      +    "type": {
      +      "description": "The question type that was rendered.",
      +      "enum": [
      +        "confirm",
      +        "select",
      +        "input"
      +      ],
      +      "type": "string"
      +    },
      +    "value": {
      +      "description": "The user's answer: \"yes\" or \"no\" for confirm, the chosen option for select, the typed text for input.",
      +      "type": "string"
      +    },
      +    "warning": {
      +      "description": "Present only when no channel is connected, naming what the user has to connect.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "correlationId",
      +    "question",
      +    "type"
      +  ],
      +  "type": "object"
      +}
  11. First observed

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds substantial behavioral context beyond the annotations: the explicit 'SIDE EFFECT: sends a real push notification' confirms and explains destructiveHint=true, the 55-second blocking ceiling, timeout-to-wait_for_answer handoff, terminal states for cancelled/expired/missing questions, and the cancel/poll race with handoffAction 'stop'. It discloses the full lifecycle behavior an agent must understand. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but appropriately so for a 27-parameter tool with a complex timeout/cancel state machine. It is front-loaded: purpose first, then when-to-use, then mechanics, then side effect. Nearly every sentence earns its place; the only near-expendable content is the client-compatibility sentence ('Works from Claude Code, Codex, Cursor...'), which is minor filler rather than a structural problem.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 27 parameters, a 55-second block, a poll/cancel follow-up protocol, and terminal-state semantics, the description covers the entire decision lifecycle: ask, timeout, poll with timeoutMs 55000, cancel, race handling, and the answerUrl disclosure instruction. An output schema exists, so return values need not be described. Nothing an agent needs to invoke this correctly across the tool's edge cases is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the schema descriptions are unusually rich (e.g., requestId explains retry collapse, wait explains block-vs-return, timeoutMs explains the 10-minute open window). The tool description adds only marginal param-level value — the three question types and default-wait behavior echo what the type/wait schema fields already say. Baseline 3 is correct when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Ask the user a question as a push notification on their phone and block until they answer.' It explicitly differentiates from the sibling wait_for_answer ('you do not need a separate wait_for_answer call'), and the question types (confirm/select/input) sharpen the scope. An agent can tell exactly what this tool does without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives an explicit trigger condition ('Reach for this whenever you need the user's decision and they may be away from the current client') followed by concrete examples: approving risky or irreversible steps, picking between implementation options, supplying missing input. It also explains the follow-up path via wait_for_answer and the cancellation state machine, effectively telling the agent when to use this tool vs. its siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

TDQS

A4.6/5.0
Disambiguation5/5

Each tool has a clearly distinct responsibility: ask_user blocks for an answer, wait_for_answer polls an existing question, cancel_question retracts a pending one, send_notification is one-way, and propose_scope handles a specific scope-ratification workflow. Even though propose_scope is a form of asking, its contract-enforcement semantics are clearly separated from a general ask_user call.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern: ask_user, cancel_question, propose_scope, send_notification, wait_for_answer. The naming is predictable and makes the action and target of each tool immediately clear.

Tool Count5/5

Five tools is well-scoped for a push-notification and user-question server. Each tool covers a distinct part of the interaction lifecycle without redundant or filler operations.

Completeness4/5

The core lifecycle is well covered: create a question, wait for an answer, cancel a stale question, send a one-way notification, and propose a scope contract. A minor gap is the lack of a way to list or inspect all pending questions, though wait_for_answer and cancel_question handle the primary workflows adequately.

Resources