Tool Approval

The consent prompt before a tool runs. The arguments are shown in full above the buttons, because “Allow issue_refund?” asks you to approve a function name — what matters is that it wants to move £3,400.

src/components/ui/tool-approval.tsx

About

From the component's own source, where it was written.

The consent prompt before a tool runs.

The most consequential component in an agent UI, and the one most often built wrong. Three rules it follows:

**The arguments are shown, in full, before the buttons.** A prompt that says "Allow issue_refund?" is asking you to approve a function name. What matters is that it wants to refund £3,400 to an account added this morning, and that is in the arguments.

**"Always allow" is scoped and labelled as a standing grant.** It is the option people click by reflex, so it says what it will do next time rather than reading as a slightly faster "yes".

**Deny is not styled as the dangerous choice.** Declining a tool call is always safe; running one may not be. A destructive tool puts the emphasis on approve, not on cancel, so the visual weight matches the actual risk.

There is no default focus on approve, and no timeout that auto-approves.

Install

npx astralyx-ui add tool-approval

Usage

import { ToolApproval } from '@/components/ui/tool-approval'

<ToolApproval
  tool="issue_refund"
  origin="billing-mcp"
  destructive
  args={call.arguments}
  onDecide={(decision) => resolve(decision)}
/>

Composer

Change the props and the source below follows.

issue_refundDestructivebilling-mcp

Moves money out of the merchant account.

Arguments

{
  "customer_id": "cus_8812",
  "amount": {
    "value": 340000,
    "currency": "GBP"
  },
  "reason": "requested_by_customer"
}
Props
tsx
<ToolApproval
  tool="issue_refund"
  origin="billing-mcp"
  destructive
  args={call.arguments}
  onDecide={resolve}
/>

Examples

A destructive call

An ordinary one

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

ToolApproval

Extends Omit<ComponentProps<'div'>, 'onSelect'>.

PropTypeDefaultDescription
toolstringrequiredThe tool the agent wants to call.
originstring—Which server or agent is asking.
descriptionReactNode——
argsunknown—Rendered in full, above the buttons. This is the thing actually being approved; the tool name is the label on the envelope.
destructivebooleanfalsePuts the weight on the approve action rather than on cancel, so the emphasis matches the actual risk.
onDecide(decision: ToolApprovalDecision) => voidrequiredThree outcomes, not two. “Always” is a standing grant and says what it will do next time rather than reading as a faster yes.
busybooleanfalseDisables the buttons while the decision is being applied.
onceLabelstring'Allow once'—
alwaysLabelstring'Always allow'—
denyLabelstring'Deny'—
alwaysHint(tool: string) => ReactNode(name) => `${name} will run without asking again in this project.`Explains the standing grant. Receives the tool name.
argsLabelstring'Arguments'—
destructiveLabelstring'Destructive'—

Types

TypeDefinitionDescription
ToolApprovalDecision
'once' | 'always' | 'deny'
The consent prompt before a tool runs.

Behaviour

TopicDetailDescription
deny stylingghost, never destructiveDeclining a tool call is always safe; running one may not be. Styling deny as the dangerous choice inverts that.
no auto-approveby designThere is no timeout and no default focus on approve.