CertSafari
    CLAUDE-CERTIFIED-DEVELOPER-FOUNDATIONS-CCDV-F · Lessons

    Domain 8 · Lesson 23/25

    Claude Tool Call Loop: Client vs Server Tools, Errors and Approvals

    Tool Implementation

    10 min read
    3.53% of exam
    4 sources
    Published 29 Sep 2026
    Docs as of 24 Sep 2026

    What you will be able to do

    • Classify a tool as user-defined, Anthropic-schema or server-executed, and say what your application owes each one
    • Build the stop_reason-driven loop that dispatches tool_use blocks and returns matching tool_result blocks
    • Handle pause_turn and turns that mix server and client tools
    • Report tool failures with is_error, and add an approval step before tools run in the Agent SDK

    1.Client-side vs. server-side tools

    Where a tool's code runs decides how much work your application does. Every tool falls into one of three groups:

    The three tool groups and what your application is responsible for
    GroupExamplesSchema written byExecuted byYou send tool_result?
    User-defined client toolyour own, e.g. get_weatherYouYour applicationYes
    Anthropic-schema client toolmemory, bash, text_editor, computer, browserAnthropicYour applicationYes
    Server-executed toolweb_search, web_fetch, code_execution, tool_searchAnthropicAnthropicNo

    Most tool-use traffic is user-defined tools that call application-specific logic. Anthropic-schema tools run the same way: you get a tool_use block, run the operation and send back a tool_result. So why not write your own equivalent? Because these schemas are trained-in. Claude was optimized on thousands of successful trajectories that use them, so it calls them more reliably and recovers from errors better than it would with a custom tool that does the same thing. Server tools differ in kind: you enable them in the request, and Anthropic's servers do everything else.

    Sources1

    2.Dispatching tool calls in the agentic loop

    Client-executed tools require your application to drive a loop, because Claude can't run your code. Every call is a round trip: the model asks, you execute, you report back. When Claude wants a client tool, the response has stop_reason "tool_use" and one or more tool_use blocks. Each block carries three fields:

    - id, which you use to match up the result - name, the tool being called - input, an object that conforms to the tool's input_schema

    A response asking your application to run a client tooljson
    {
      "id": "msg_01Aq9w938a90dw8q",
      "model": "claude-opus-5-5",
      "stop_reason": "tool_use",
      "role": "assistant",
      "content": [
        {
          "type": "text",
          "text": "I'll check the current weather in San Francisco for you."
        },
        {
          "type": "tool_use",
          "id": "toolu_01A09q90qw90lq917835lq9",
          "name": "get_weather",
          "input": { "location": "San Francisco, CA", "unit": "celsius" }
        }
      ]
    }

    Your harness dispatches on the tool name. It takes the name, id and input from the block, runs the matching function in your codebase, and replies with a user message containing a tool_result block. That block's tool_use_id must equal the block's id. content is optional. It can be a string, or a list of text, image, document or search_result blocks. Computer use and browser use member blocks also carry a toolset_name, so dispatch those on both fields and echo toolset_name in the result.

    The standard loop runs on stop_reason:

    1. Send the request with your tools. 2. On "tool_use", run each tool. 3. Append the assistant response and a user message of tool_result blocks. 4. Repeat.

    Any other stop reason ("end_turn", "max_tokens", "stop_sequence" or "refusal") ends the loop, and your application handles it.

    Sources12

    3.When server tools hand control back

    Server tools run their own loop inside Anthropic's infrastructure. One request can trigger several searches before a response comes back. The response contains a server_tool_use block (id prefix srvtoolu_) followed by its result block, and you never build a tool_result for it. Two cases bring your application back into the loop.

    pause_turn. The server loop has an iteration limit. A paused turn means the work isn't finished. Re-send the conversation with the paused response as-is, and include the same tools. If a tool is missing, the API returns a validation error. A continued turn can pause again, so cap the number of continuations as you would in any retry loop.

    Mixed turns. Claude may call a server tool and a client tool in the same parallel group. In that case the API does not run the server tool and returns immediately. stop_reason is "tool_use", and the server_tool_use block has no result block yet. There is no other marker, so look for a server_tool_use id with no matching result.

    Sources13

    4.Error handling

    The documentation describes three kinds of error. Each is handled differently.

    The tool fails during execution, for example with a network error. Return the error message as the tool_result content and set is_error to true. Claude includes the error in its reply, for example by telling the user the weather service is unavailable.

    Reporting an execution failure back to Claudejson
    {
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
          "content": "ConnectionError: the weather service API is not available (HTTP 500)",
          "is_error": true
        }
      ]
    }

    Claude's call is invalid, for example a required parameter is missing. This usually means Claude didn't have enough information to use the tool correctly. During development, the fix is more detailed descriptions in your tool definitions. At runtime, you can return an is_error tool_result that names the problem, such as "Error: Missing required 'location' parameter". Claude retries 2-3 times with corrections before apologizing to the user.

    A server tool fails. Claude handles these errors transparently and tries to give an alternative response. You don't handle is_error results for server tools. Web search, for example, can report codes such as too_many_requests, max_uses_exceeded and query_too_long.

    Sources2

    5.Approval patterns before a tool runs

    Some calls shouldn't run just because Claude asked. In the Claude Agent SDK, you register a can_use_tool callback (canUseTool in TypeScript). It fires when Claude wants a tool that no permission rule or permission mode has auto-approved. The callback receives three things:

    - the tool name, such as Bash, Write or Edit - the input Claude is passing; for Bash this includes command, and for Write it includes file_path and content - context, including optional suggestions

    It returns allow or deny.

    The decision at the core of an approval callbackpython
        # Get user approval
        response = input("Allow this action? (y/n): ")
    
        # Return allow or deny based on user's response
        if response.lower() == "y":
            # Allow: tool executes with the original (or modified) input
            return PermissionResultAllow(updated_input=input_data)
        else:
            # Deny: tool doesn't execute, Claude sees the message
            return PermissionResultDeny(message="User denied this action")

    Allow runs the tool, either with the original input or with a modified one passed through updated_input. Deny stops the tool from executing and shows your message to Claude, so it can change course instead of failing blindly. The callback sees the full input before anything runs, so this is where you check the actual command or file path. The same callback also handles AskUserQuestion when Claude asks a clarifying question; if you specify a tools array, it must include AskUserQuestion. The Python example also registers a dummy PreToolUse hook, which the documentation labels a required workaround to keep the stream open for can_use_tool.

    A security review requires that a specific destructive shell pattern, `rm -rf *`, always be blocked regardless of any other configuration, including future changes to permission mode. Which rule configuration satisfies this requirement, based on how the SDK's permission evaluation order treats deny rules?

    Sources4

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.After Claude runs web_search, your application must send a tool_result block back, just as it does for a client tool.Why is that wrong?

      Anthropic runs server-executed tools and pairs their result blocks automatically. You enable the tool and read the answer.

      Covered in Client-side vs. server-side tools

    2. 2.A server_tool_use block with no result means the turn paused, so re-send the assistant content as-is.Why is that wrong?

      If a client tool was called in the same group, the turn is a mixed turn. You continue it by sending the client tool_result blocks, not by re-sending the response.

      Covered in When server tools hand control back

    3. 3.When your tool throws during execution, the harness should abort the loop and show the exception to the user.Why is that wrong?

      Return the failure to Claude as a tool_result with is_error set, and Claude includes it in its reply.

      Covered in Error handling

    Practise it for real

    Run one full client-tool round trip with get_weather, including a failed execution.

    1. 1.Call messages.create with the get_weather definition in tools and the user message "What's the weather like in San Francisco?".

      Why: Client tools are declared in the tools top-level parameter.

      You should see: stop_reason is "tool_use" and there is a tool_use block named get_weather whose input includes location.

    2. 2.Append the assistant response, then a user message with a tool_result whose tool_use_id equals that block's id and whose content is "15 degrees".

      Why: tool_use_id is how the result is matched to the request.

      You should see: Claude uses the result to finish answering the original question.

    3. 3.Repeat, but answer with content "ConnectionError: the weather service API is not available (HTTP 500)" and "is_error": true.

      Why: Execution failures go back to Claude instead of breaking the loop.

      You should see: Claude tells the user it could not retrieve the weather.

    4. 4.Wrap the request and result steps in a loop that keeps going while stop_reason == "tool_use".

      Why: This is the standard shape of the client-side agentic loop.

      You should see: The loop ends on "end_turn" or another non-tool_use stop reason.

    Stuck? Get a nudge

    If Claude never calls the tool, check that the user message actually needs current weather. Tools don't fit questions the model can answer from training.

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “The primary axis along which tools differ is where the code executes.”
      ↩︎ Client-side vs. server-side tools
      “these schemas are trained-in”
      ↩︎ Client-side vs. server-side tools
      “The canonical shape is a while loop keyed on stop_reason”
      ↩︎ Dispatching tool calls in the agentic loop
      “A paused turn means the work isn't finished”
      ↩︎ When server tools hand control back
      “You never construct a tool_result block for these tools.”
      ↩︎ Exam trap 1
    2. 2.
      “tool_use_id: The id of the tool use request this is a result for.”
      ↩︎ Dispatching tool calls in the agentic loop
      “so dispatch those blocks on both fields.”
      ↩︎ Dispatching tool calls in the agentic loop
      “Your best bet during development is to try the request again with more-detailed description values in your tool definitions.”
      ↩︎ Error handling
      “Claude will retry 2-3 times with corrections before apologizing to the user.”
      ↩︎ Error handling
      “Unlike client tools, you do not need to handle is_error results for server tools.”
      ↩︎ Error handling
      “you can return the error message in the content along with "is_error": true”
      ↩︎ Exam trap 3
    3. 3.
      “When that happens, the API does not run the server tool. It returns immediately so that you can run the client tool first”
      ↩︎ When server tools hand control back
      “Detect the state by looking for a server_tool_use block whose id has no matching result block in the response.”
      ↩︎ When server tools hand control back
      “stop_reason is "tool_use", not "pause_turn".”
      ↩︎ Exam trap 2
    4. 4.
      “Deny: tool doesn't execute, Claude sees the message”
      ↩︎ Approval patterns before a tool runs
      “If you specify a tools array, include AskUserQuestion for this to work.”
      ↩︎ Approval patterns before a tool runs
      “Required workaround: dummy hook keeps the stream open for can_use_tool”
      ↩︎ Approval patterns before a tool runs

    Ready to test yourself?

    Practise the 20 questions on this subdomain.