Tools

Tool Approvals in TypeScript SDK

Copy page

Require explicit user approval before running selected tools using the TypeScript SDK.

Overview

Tool approvals let you mark specific tools as “requires approval”. When the agent tries to run one, execution pauses and your client must approve or deny before the run can continue.

Configure tools to require approval

TypeScript SDK (MCP tools)

import { agent, tool } from "@inkeep/agents-sdk";

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
        },
      ],
    })
  );

See also: MCP tools

Visual Builder

In the Visual Builder, select an MCP server node connected to a Sub Agent and toggle approval for individual tools in the Tool Configuration section. You can also bulk toggle approvals for all enabled tools using the header checkbox.

See also: Tool Approvals in Visual Builder.

Runtime Behavior

When a tool requires approval, you’ll see a tool-approval-request in the stream.

See Chat API for the tool event payloads (including approval requests, tool inputs, and tool outputs).

Note
Note

In durable execution mode, the workflow suspends instead of holding the HTTP connection open. Approvals can be submitted via a dedicated REST endpoint, and the workflow automatically resumes after a response is received.

Denial reason propagation

When a user denies a tool request with a reason, the agent receives the denial reason as a clean, human-readable string. This allows the agent to understand why the request was denied and adapt its behavior — for example, trying a different approach based on the user's feedback.

This is especially useful for redirecting agents. If a user denies a request for "San Francisco weather" with the reason "I want Tokyo instead", the agent can immediately act on that feedback and fetch Tokyo weather without requiring a separate follow-up message.

Responding to an approval request

You can approve/deny via the chat endpoint (message part)

POST /run/api/chat with an assistant message that includes a tool-* part:

{
  "conversationId": "conv_xyz789",
  "messages": [
    {
      "role": "assistant",
      "content": null,
      "parts": [
        {
          "type": "tool-get_weather_forecast",
          "toolCallId": "call_abc123def456",
          "state": "approval-responded",
          "approval": {
            "id": "aitxt-call_abc123def456",
            "approved": true,
            "reason": "User confirmed"
          }
        }
      ]
    }
  ]
}

Vercel AI SDK (useChat) example

If you’re using the Vercel AI SDK UI message stream, approval requests show up as tool-* parts (e.g. tool-getWeather) with state: "approval-requested".

After calling addToolApprovalResponse(...), call sendMessage() so the updated messages (including the approval response) are POSTed back to /run/api/chat and the run can continue.

"use client";

import { useChat } from "@ai-sdk/react";

export default function Chat() {
  const { messages, addToolApprovalResponse, sendMessage } = useChat();

  return (
    <>
      {messages.map((message) => (
        <div key={message.id}>
          {message.parts.map((part) => {
            if (part.type === "tool-getWeather") {
              switch (part.state) {
                case "approval-requested":
                  return (
                    <div key={part.toolCallId}>
                      <p>Get weather for {part.input.city}?</p>
                      <button
                        type="button"
                        onClick={() => {
                          addToolApprovalResponse({
                            id: part.approval.id,
                            approved: true,
                          });
                          sendMessage();
                        }}
                      >
                        Approve
                      </button>
                      <button
                        type="button"
                        onClick={() => {
                          addToolApprovalResponse({
                            id: part.approval.id,
                            approved: false,
                          });
                          sendMessage();
                        }}
                      >
                        Deny
                      </button>
                    </div>
                  );
                case "output-available":
                  return (
                    <div key={part.toolCallId}>
                      Weather in {part.input.city}: {String(part.output)}
                    </div>
                  );
              }
            }

            return null;
          })}
        </div>
      ))}
    </>
  );
}
Note
Note

Only state: "approval-responded" triggers an approval/denial. If your client includes other tool approval parts in message history (for example state: "approval-requested"), the server ignores them.

Durable execution mode

When an agent runs with executionMode: 'durable', tool approvals work differently from the default in-memory flow. Instead of holding the HTTP connection open while waiting for a response, the durable workflow suspends and frees server resources.

You can submit approvals via the dedicated REST endpoint:

POST /run/api/executions/:executionId/approvals/:toolCallId

The executionId is returned in the x-workflow-run-id response header when the execution starts. The toolCallId comes from the tool-approval-request event in the stream.

Example: approve a tool call

curl -X POST "http://localhost:3002/run/api/executions/$EXECUTION_ID/approvals/$TOOL_CALL_ID" \
  -H "Authorization: Bearer $INKEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "approved": true
  }'

To deny a tool call, set approved to false and optionally include a reason:

curl -X POST "http://localhost:3002/run/api/executions/$EXECUTION_ID/approvals/$TOOL_CALL_ID" \
  -H "Authorization: Bearer $INKEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "approved": false,
    "reason": "Use a different city"
  }'

After the approval is submitted, the workflow automatically resumes and continues execution. If your client was disconnected, you can reconnect to the stream using GET /run/api/executions/:executionId/stream.

See the Chat API for the full durable executions reference.

Tool approvals in Slack

When you run agents in Slack (via the Inkeep Slack app), tool approvals appear as interactive messages with Approve and Deny buttons.

How it works

  1. The agent requests to run a tool that requires approval
  2. Slack posts a message showing the tool name and its input parameters
  3. The user clicks Approve or Deny
  4. The agent continues based on the user's decision

Permissions

Only the user who started the conversation can approve or deny a tool request. If another user clicks the buttons, they see an error message.

Approval timeout

If no one responds to an approval request within the timeout period, the request expires and the message updates to show "Expired". The agent receives a timeout notification and can inform the user.

Note
Note

Tool approvals in Slack require account linking. Run /inkeep link to connect your Slack and Inkeep accounts before using agents with approval-required tools.