Tool approvals allow you to configure specific tools to require explicit user confirmation before execution. This provides enhanced control over potentially sensitive or destructive operations.
When a tool is configured to require approval:
Agent pauses execution when the tool is called
Approval request emitted in the data stream with tool details
User sees approval UI with tool name, arguments, and approve/deny options
Execution continues only after user approval
Tool executes normally with the original arguments
Configure tool approvals using the McpToolSelection format when defining agent tools:
import { agent , tool } from "@inkeep/agents-sdk" ;
// Configure tools with approval requirements
const weatherAgent = agent ( "weather-forecast" )
. prompt ( "You help users get weather information." )
. canUse (
tool ( "weather-mcp" ). with ({
selectedTools : [
"get_current_weather" , // No approval required
{
name : "get_weather_forecast" ,
needsApproval : true , // Requires user approval
},
{
name : "delete_weather_data" ,
needsApproval : true , // Requires user approval
},
],
})
);
In the visual builder, configure tool approvals using the unified tool grid:
Navigate to your agent in the visual builder
Select an MCP tool node connected to your agent
Use the tool configuration grid with two columns:
Tool column : Check to select/deselect tools
Approval column : Check to require approval (only enabled for selected tools)
When an agent encounters a tool requiring approval:
Approval Flow:
1. Agent calls tool → 2. Execution pauses → 3. Approval request emitted
↓
6. Tool executes ← 5. Execution resumes ← 4. User approves
Denial Flow:
1. Agent calls tool → 2. Execution pauses → 3. Approval request emitted
↓
6. Returns denial message ← 5. Execution resumes ← 4. User denies
The agent receives proper feedback in both cases, ensuring natural conversation flow.
Tool approval requests appear in the data stream as enhanced tool_call events:
{
"type" : "data-operation" ,
"data" : {
"type" : "tool_call" ,
"label" : "Tool call: delete_weather_data" ,
"details" : {
"timestamp" : 1726247200000 ,
"subAgentId" : "weather-agent" ,
"data" : {
"toolName" : "delete_weather_data" ,
"input" : {
"location" : "San Francisco" ,
"date_range" : "2024-01-01_to_2024-12-31"
},
"toolCallId" : "call_abc123def456" ,
"needsApproval" : true ,
"conversationId" : "conv_xyz789"
}
}
}
}
Key fields for approval handling:
needsApproval: true - Indicates approval is required
toolCallId - Unique identifier for this tool execution
conversationId - Conversation context for the approval request
input - Tool arguments for user review
Approved tools execute normally and return their actual results.
Denied tools return a clear message to the agent:
{
"type" : "tool_result" ,
"data" : {
"toolName" : "delete_weather_data" ,
"output" : "User denied approval to run this tool: Operation too risky" ,
"toolCallId" : "call_abc123def456"
}
}
This allows the agent to understand the denial and respond appropriately to the user.
Requires the same API key authentication as chat endpoints:
Authorization: Bearer YOUR_API_KEY
{
"conversationId" : "conv_xyz789" ,
"toolCallId" : "call_abc123def456" ,
"approved" : true ,
"reason" : "User confirmed the operation" // Optional
}
Parameters:
conversationId - The conversation context (from tool_call event)
toolCallId - The specific tool execution to approve/deny (from tool_call event)
approved - true to approve, false to deny
reason - Optional explanation for the decision
Success (200):
{
"success" : true ,
"message" : "Tool execution approved"
}
Error (404):
{
"error" : "Tool call not found or already processed"
}
curl -X POST http://localhost:3003/api/tool-approvals \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"conversationId": "conv_xyz789",
"toolCallId": "call_abc123def456",
"approved": true,
"reason": "User confirmed deletion is safe"
}'
Monitor the event stream for tool_call events with needsApproval: true:
// Example using EventSource for SSE
const eventSource = new EventSource ( "<RUN_API_URL>/api/chat" );
eventSource . onmessage = ( event ) => {
const { type , data } = JSON . parse ( event . data );
if (
type === "data-operation" &&
data . type === "tool_call" &&
data . details . data . needsApproval
) {
const { toolName , input , toolCallId , conversationId } = data . details . data ;
// Show approval UI to user
showApprovalUI ({
toolName ,
arguments : input ,
onApprove : () => sendApproval ( conversationId , toolCallId , true ),
onDeny : () => sendApproval ( conversationId , toolCallId , false ),
});
}
};
async function sendApproval (
conversationId ,
toolCallId ,
approved ,
reason = null
) {
try {
const response = await fetch ( "<RUN_API_URL>/api/tool-approvals" , {
method : "POST" ,
headers : {
Authorization : `Bearer ${ API_KEY } ` ,
"Content-Type" : "application/json" ,
},
body : JSON . stringify ({
conversationId ,
toolCallId ,
approved ,
... ( reason && { reason }),
}),
});
if ( ! response . ok ) {
throw new Error ( `Approval failed: ${ response . statusText } ` );
}
const result = await response . json ();
console . log ( "Approval processed:" , result . message );
} catch ( error ) {
console . error ( "Failed to process approval:" , error );
}
}
import { useChat } from 'ai/react' ;
export function ChatWithApprovals () {
const { messages , input , handleInputChange , handleSubmit } = useChat ({
api : '/api/chat' ,
headers : {
'x-emit-operations' : 'true' , // Enable data operations
},
});
const handleApproval = async ( toolCallId : string , conversationId : string , approved : boolean ) => {
await fetch ( '/api/tool-approvals' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON . stringify ({
conversationId ,
toolCallId ,
approved ,
}),
});
};
return (
< div >
{ /* Chat messages */ }
< div >
{ messages . map (( message ) => (
< div key = { message . id } >
< strong > { message . role } :</ strong > { message . content }
{ /* Render tool approval buttons inline */ }
{ message . parts ?. map (( part , index ) => {
if (
part . type === 'data-operation' &&
part . data . type === 'tool_call' &&
part . data . details . data . needsApproval
) {
const { toolName , toolCallId , conversationId } = part . data . details . data ;
return (
< div key = { index } >
< button
type = "button"
onClick = { () => handleApproval ( toolCallId , conversationId , true ) }
>
Approve { toolName }
</ button >
< button
type = "button"
onClick = { () => handleApproval ( toolCallId , conversationId , false ) }
>
Deny
</ button >
</ div >
);
}
return null ;
}) }
</ div >
)) }
</ div >
{ /* Chat input */ }
< form onSubmit = { handleSubmit } >
< input value = { input } onChange = { handleInputChange } placeholder = "Ask me anything..." />
< button type = "submit" >Send</ button >
</ form >
</ div >
);
}