> ## Documentation Index
> Fetch the complete documentation index at: https://anthale.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool Call Validation and Approval Guide

> Validate model-proposed tool calls with least privilege, deterministic checks, and approval gates before execution in agent and MCP workflows.

Use this guide when a model can propose API calls, browser actions, connector requests, or writes to downstream systems. Anthale helps you evaluate the surrounding content path, but your application still decides whether an action is allowed to happen.

## Assumptions

* Your application already defines the tools an agent can call.
* Tool credentials and permissions are controlled outside the prompt.
* You can require approval or rejection before a sensitive action executes.

## Gate the actions

<Steps>
  <Step title="Classify tools by impact">
    Separate read-only tools from tools that write, send, delete, purchase, or change state. The higher the impact, the
    stronger the approval and validation path should be.
  </Step>

  <Step title="Keep credentials narrow and separate">
    Give each tool the smallest set of permissions it needs. Do not reuse one broad service credential for unrelated
    tools or workflows.
  </Step>

  <Step title="Validate arguments deterministically">
    Require structured arguments, schema checks, allowlisted destinations, and strict parameter validation before the
    action reaches the real system.
  </Step>

  <Step title="Add approval for high-impact actions">
    Require explicit approval for actions that are destructive, external, financial, regulated, or hard to reverse. A
    model suggestion is not the same thing as user authorization.
  </Step>

  <Step title="Evaluate tool results before reuse">
    Treat browser results, connector responses, and other tool output as untrusted text until Anthale and your
    application have evaluated the next boundary.
  </Step>

  <Step title="Log the decision and the outcome">
    Record whether the action was proposed, validated, approved, blocked, or executed. Include the tool name, request
    identifier, and the policy or rule set that governed the path.
  </Step>
</Steps>

<Warning>
  Do not let the model decide that it is authorized because the prompt sounded urgent, administrative, or high-priority.
  Authorization belongs to the application.
</Warning>

## What this looks like in code

<CodeGroup>
  ```text title="Python" theme={null}
  import json
  from os import environ

  from anthale import Anthale

  client = Anthale(api_key=environ["ANTHALE_API_KEY"])
  TOOL_OUTPUT_POLICY_ID = environ["ANTHALE_TOOL_OUTPUT_POLICY_ID"]

  ALLOWED_TOOLS = {"search_docs", "create_ticket"}
  ALLOWED_PRIORITIES = {"low", "medium", "high"}

  def execute_tool(name: str, arguments: dict) -> dict:
  raise NotImplementedError("Connect this helper to your real tool runner.")

  def validate_tool_call(proposed_call: dict, approved: bool) -> dict:
  if proposed_call["name"] not in ALLOWED_TOOLS:
  raise RuntimeError("Tool is not allowed for this workflow.")

      if proposed_call["name"] == "create_ticket":
          priority = proposed_call["arguments"]["priority"]
          if priority not in ALLOWED_PRIORITIES:
              raise RuntimeError("Priority must be low, medium, or high.")
          if not approved:
              raise RuntimeError("create_ticket requires explicit approval.")

      return proposed_call

  def guard_tool_output(tool_name: str, tool_output: dict) -> dict:
  response = client.organizations.policies.enforce(
  policy_identifier=TOOL_OUTPUT_POLICY_ID,
  direction="input",
  messages=[{"role": "user", "content": json.dumps(tool_output)}],
  metadata={
  "boundary": "tool_output",
  "feature": "support-agent",
  "toolName": tool_name,
  "sourceType": "tool_output",
  },
  )
  if response.action == "block":
  raise RuntimeError(f"Anthale blocked output from {tool_name}.")

      safe_message = response.redacted_messages[0] if response.action == "redact" else {"content": json.dumps(tool_output)}
      return json.loads(safe_message["content"])

  def handle_tool_proposal(proposed_call: dict, approved: bool) -> dict:
  validated_call = validate_tool_call(proposed_call, approved)
  raw_tool_output = execute_tool(validated_call["name"], validated_call["arguments"])
  return guard_tool_output(validated_call["name"], raw_tool_output)

  ```

  ```text title="TypeScript" theme={null}
  import Anthale from "anthale"

  const client = new Anthale({ apiKey: process.env.ANTHALE_API_KEY })
  const TOOL_OUTPUT_POLICY_ID = process.env.ANTHALE_TOOL_OUTPUT_POLICY_ID!

  const ALLOWED_TOOLS = new Set(["search_docs", "create_ticket"])
  const ALLOWED_PRIORITIES = new Set(["low", "medium", "high"])

  type ToolCall = {
    name: string
    arguments: Record<string, string>
  }

  async function executeTool(name: string, arguments_: Record<string, string>): Promise<Record<string, unknown>> {
    throw new Error("Connect this helper to your real tool runner.")
  }

  function validateToolCall(proposedCall: ToolCall, approved: boolean): ToolCall {
    if (!ALLOWED_TOOLS.has(proposedCall.name)) {
      throw new Error("Tool is not allowed for this workflow.")
    }

    if (proposedCall.name === "create_ticket") {
      if (!ALLOWED_PRIORITIES.has(proposedCall.arguments.priority)) {
        throw new Error("Priority must be low, medium, or high.")
      }
      if (!approved) {
        throw new Error("create_ticket requires explicit approval.")
      }
    }

    return proposedCall
  }

  async function guardToolOutput(toolName: string, toolOutput: Record<string, unknown>): Promise<Record<string, unknown>> {
    const response = await client.organizations.policies.enforce(TOOL_OUTPUT_POLICY_ID, {
      direction: "input",
      messages: [{ role: "user", content: JSON.stringify(toolOutput) }],
      metadata: {
        boundary: "tool_output",
        feature: "support-agent",
        toolName,
        sourceType: "tool_output",
      },
    })

    if (response.action === "block") {
      throw new Error(`Anthale blocked output from ${toolName}.`)
    }

    const safeContent =
      response.action === "redact" ? response.redacted_messages[0].content : JSON.stringify(toolOutput)

    return JSON.parse(safeContent) as Record<string, unknown>
  }

  export async function handleToolProposal(proposedCall: ToolCall, approved: boolean): Promise<Record<string, unknown>> {
    const validatedCall = validateToolCall(proposedCall, approved)
    const rawToolOutput = await executeTool(validatedCall.name, validatedCall.arguments)
    return guardToolOutput(validatedCall.name, rawToolOutput)
  }
  ```
</CodeGroup>

Anthale evaluates the tool output before it re-enters the workflow. Your application still decides which tools exist,
which arguments are allowed, and whether the action needs approval.

## Common failure patterns

* One agent can access more tools than the user workflow actually needs.
* Natural-language arguments are passed directly into downstream systems without schema validation.
* Read-only and write-capable tools share the same approval path.
* Tool output is trusted as if it were system instruction instead of untrusted content.

## Related pages

* [Secure Agent Workflows](/docs/learn/secure-ai-systems/secure-agent-workflows)
* [Validate Model Output](/docs/learn/secure-ai-systems/validate-model-output)
* [Prompt Injection Impact Paths](/docs/learn/guardrails/prompt-injection/impact-paths)

## Next steps

Continue with [Validate Model Output](/docs/learn/secure-ai-systems/validate-model-output) or [Link Control](/docs/learn/guardrails/link-control).
