confirm_hand
Approve and execute a pending Hands action using its single-use approval token. Only call after user authorizes the action, as side effects can be destructive.
Instructions
Approve and EXECUTE a previously-issued Hands invocation by its single-use approval token. The token is returned by any confirm-required Hands tool; tokens expire after 60 seconds and CANNOT be reused. Side effect equals whatever the underlying Hand does — this can be highly destructive (running arbitrary shell commands, modifying files, etc.), so only call when the user has authorised the pending action. The token IS the auth (no external auth, no rate limits). Invalid, expired, or already-consumed tokens return an inert text response, NOT an error.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| token | Yes | The approval token from the pending response |
Implementation Reference
- apps/mcp/src/index.ts:2143-2159 (handler)The actual MCP tool handler for 'confirm_hand'. It consumes a token from the ConfirmStore, executes the stored pending hand, and returns the result.
server.tool( "confirm_hand", "Approve and EXECUTE a previously-issued Hands invocation by its single-use approval token. The token is returned by any confirm-required Hands tool; tokens expire after 60 seconds and CANNOT be reused. Side effect equals whatever the underlying Hand does — this can be highly destructive (running arbitrary shell commands, modifying files, etc.), so only call when the user has authorised the pending action. The token IS the auth (no external auth, no rate limits). Invalid, expired, or already-consumed tokens return an inert text response, NOT an error.", { token: z.string().describe("The approval token from the pending response") }, async ({ token }) => { const pending = handsRegistry.getConfirmStore().consume(token); if (!pending) { return { content: [{ type: "text", text: "Token invalid, expired, or already consumed." }] }; } try { const result = await pending.execute(); return { content: [{ type: "text", text: formatExecResult(pending.toolName, result) }] }; } catch (e: any) { return { content: [{ type: "text", text: `Execution failed after approval: ${e?.message ?? e}` }] }; } } ); - apps/mcp/src/index.ts:2143-2159 (registration)Tool registered via server.tool() with name 'confirm_hand', description, Zod schema {token: string}, and handler function.
server.tool( "confirm_hand", "Approve and EXECUTE a previously-issued Hands invocation by its single-use approval token. The token is returned by any confirm-required Hands tool; tokens expire after 60 seconds and CANNOT be reused. Side effect equals whatever the underlying Hand does — this can be highly destructive (running arbitrary shell commands, modifying files, etc.), so only call when the user has authorised the pending action. The token IS the auth (no external auth, no rate limits). Invalid, expired, or already-consumed tokens return an inert text response, NOT an error.", { token: z.string().describe("The approval token from the pending response") }, async ({ token }) => { const pending = handsRegistry.getConfirmStore().consume(token); if (!pending) { return { content: [{ type: "text", text: "Token invalid, expired, or already consumed." }] }; } try { const result = await pending.execute(); return { content: [{ type: "text", text: formatExecResult(pending.toolName, result) }] }; } catch (e: any) { return { content: [{ type: "text", text: `Execution failed after approval: ${e?.message ?? e}` }] }; } } ); - apps/mcp/src/index.ts:2144-2147 (schema)The input schema for confirm_hand: a single required 'token' string parameter.
"confirm_hand", "Approve and EXECUTE a previously-issued Hands invocation by its single-use approval token. The token is returned by any confirm-required Hands tool; tokens expire after 60 seconds and CANNOT be reused. Side effect equals whatever the underlying Hand does — this can be highly destructive (running arbitrary shell commands, modifying files, etc.), so only call when the user has authorised the pending action. The token IS the auth (no external auth, no rate limits). Invalid, expired, or already-consumed tokens return an inert text response, NOT an error.", { token: z.string().describe("The approval token from the pending response") }, async ({ token }) => { - apps/mcp/src/hands/confirm.ts:1-54 (helper)ConfirmStore class: manages single-use approval tokens with TTL (60s). stash() creates a token with CSPRNG, consume() returns the pending execution if valid, clearProject() and clearAll() for cleanup.
import { randomBytes } from "node:crypto"; import type { ExecResult } from "./types.js"; interface PendingEntry { toolName: string; projectName: string; resolvedArgv: string[]; workingDir: string; env: Record<string, string>; expiresAt: number; execute: () => Promise<ExecResult>; } export interface StashInput { toolName: string; projectName: string; resolvedArgv: string[]; workingDir: string; env: Record<string, string>; execute: () => Promise<ExecResult>; } export class ConfirmStore { private map = new Map<string, PendingEntry>(); private ttlMs: number; constructor(opts: { ttlMs?: number } = {}) { this.ttlMs = opts.ttlMs ?? 60_000; } stash(input: StashInput): string { const token = randomBytes(32).toString("hex"); this.map.set(token, { ...input, expiresAt: Date.now() + this.ttlMs }); return token; } consume(token: string): { execute: () => Promise<ExecResult>; toolName: string } | null { const entry = this.map.get(token); if (!entry) return null; this.map.delete(token); if (entry.expiresAt < Date.now()) return null; return { execute: entry.execute, toolName: entry.toolName }; } clearProject(projectName: string): void { for (const [token, entry] of this.map) { if (entry.projectName === projectName) this.map.delete(token); } } clearAll(): void { this.map.clear(); } } - formatPendingConfirm() generates the instruction message telling the agent to call confirm_hand({ token: '...' }) to approve a hand execution.
export function formatPendingConfirm(p: PendingFormatInput): string { return [ `This tool requires human approval before running.`, ``, ` Tool: ${p.toolName}`, ` Project: ${p.projectName}`, ` Command (resolved):`, ...p.resolvedArgv.map((a, i) => ` [${i}] ${a}`), ` Working dir: ${p.workingDir}`, ``, `To approve, call: confirm_hand({ token: "${p.token}" })`, `This token expires in 60 seconds.`, ].join("\n"); }