--- title: Approval sidebarTitle: Approval description: Human-in-the-loop approval gates for agent actions icon: shield-check tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' `Approval` adds a human-in-the-loop confirmation step to any server. The LLM presents what it's about to do, the user approves or rejects via buttons, and the decision flows back into the conversation as a message. The Approval provider shown in Goose, with a payment confirmation card and Approve/Cancel buttons ```python from fastmcp import FastMCP from fastmcp.apps.approval import Approval mcp = FastMCP("My Server") mcp.add_provider(Approval()) ``` This registers a single tool: | Tool | Visibility | Purpose | |------|-----------|---------| | `request_approval` | Model | Shows an approval card, sends the user's decision back as a message | The LLM calls `request_approval` with a summary (and optional details) whenever it's about to take a significant action. The user sees a card with Approve and Reject buttons. Clicking either sends a message back into the conversation via `SendMessage`, which triggers the LLM's next turn. The message looks like it came from the user: ``` "Deploy v3.2 to production" — I selected: Approve ``` Approval is an advisory gate, not an enforcement mechanism. The conversation isn't blocked while the card is open — the user can keep typing, and a determined LLM could proceed without waiting. Think of it as a strong UX signal that encourages confirmation, not a security boundary. For hard enforcement, implement approval logic server-side in your tool implementations. ## Configuration The constructor sets defaults; the LLM can override all of these per-call via tool arguments. ```python Approval( name="Approval", # App name title="Approval Required", # Card heading approve_text="Approve", # Approve button label reject_text="Reject", # Reject button label approve_variant="default", # "default", "destructive", "success", "info" reject_variant="outline", # same options plus "outline" ) ``` The LLM can customize each invocation: ```python request_approval( summary="Delete 47 files from /tmp", details="This cannot be undone.", title="Destructive Action", approve_text="Delete", approve_variant="destructive", reject_text="Keep files", ) ``` ## How It Works When the user clicks a button, two things happen: 1. `SendMessage` pushes the decision into the conversation as a user message 2. `SetState("decided", True)` replaces the buttons with "Response sent." The tool description instructs the LLM to stop and wait for the "I selected:" message before proceeding. If approved, it continues. If rejected, it acknowledges and asks how to proceed.