TimoBy Amotion AI

Tool Implementation: CCDV-F study guide

CCDV-F · Tools and MCPs, topic weight 4.4% of the exam

Tool Implementation is the largest topic in Domain 8, Tools and MCPs (10.6% of the CCDV-F exam), at 4.4% on its own. It tests whether you can write tools Claude uses correctly and the code that runs them safely: the definition, the dispatch loop, error results and approval before risky actions. The design principles for tool descriptions and structured errors are covered in CCAR-F 2.1 and 2.2; this page is about building them.

What the official guide covers

The Claude Certified Developer Foundations exam guide (version 1.0, effective July 2026) describes this topic as tool implementation practices for Claude applications:

What the guide listsWhat it means in practice
Tool use and function callingClaude returns tool_use blocks; your code runs each one and sends back a tool_result
Configuration for external system interactionEndpoints, credentials and timeouts live in your handler's configuration, not in the schema Claude sees
Tool description writingDescribe the tool's job, when to call it and when to avoid it, what each parameter means, and what it does not return
Error handlingReturn failures as tool_result blocks with is_error: true and a message that says what to try next
Tool usage patterns: harness dispatch, client-side vs server-side tools, approval patternsMap tool names to handlers, know who executes each tool, and pause for a person before risky calls
Tool set constructionFewer, consolidated tools with namespaced names and responses that carry only what Claude needs

Client tools and server tools

Client toolsServer tools
Who runs themYour applicationAnthropic's infrastructure
ExamplesTools you define; Anthropic-defined tools such as bash, text editor, memory and computer useWeb search, web fetch, code execution, tool search
What you seestop_reason: "tool_use" and tool_use blocks you must executeThe results, already in the response
What you buildThe handler, the loop and the tool_resultNothing to execute

Anthropic-defined client tools have a schema Anthropic designed, but your code still carries out the action. That matters for security: a bash tool runs commands wherever your handler runs them.

Anatomy of a tool definition

A tool definition has a name (letters, digits, _ and -, up to 128 characters), a description, and an input_schema in JSON Schema. Two optional fields help:

  • input_examples: example inputs that must validate against the schema. Useful for nested or format-sensitive parameters.
  • strict: true: constrains Claude's tool inputs so they always match your schema, which removes missing parameters and type mismatches. Strict schemas set "additionalProperties": false.

Anthropic calls detailed descriptions "by far the most important factor" in tool performance and suggests at least three to four sentences per tool.

The dispatch loop with errors and an approval gate (Python)

import json
import anthropic

client = anthropic.Anthropic()

TOOLS = [
    {
        "name": "orders_get",
        "description": (
            "Look up one order by its order ID and return its status, items and total. "
            "Use this before answering any question about a specific order. "
            "Order IDs look like ORD-12345. It does not return payment card details."
        ),
        "input_schema": {
            "type": "object",
            "properties": {"order_id": {"type": "string", "description": "Order ID, e.g. ORD-12345"}},
            "required": ["order_id"],
            "additionalProperties": False,
        },
        "strict": True,
    },
    {
        "name": "orders_cancel",
        "description": (
            "Cancel an order that has not shipped. Use only when the customer clearly asks "
            "to cancel. Fails if the order has shipped; offer a return instead in that case."
        ),
        "input_schema": {
            "type": "object",
            "properties": {"order_id": {"type": "string", "description": "Order ID, e.g. ORD-12345"}},
            "required": ["order_id"],
            "additionalProperties": False,
        },
        "strict": True,
    },
]

HANDLERS = {"orders_get": get_order, "orders_cancel": cancel_order}
NEEDS_APPROVAL = {"orders_cancel"}

def run_tool(block) -> dict:
    result = {"type": "tool_result", "tool_use_id": block.id}
    if block.name in NEEDS_APPROVAL and not ask_operator(block.name, block.input):
        return result | {"content": "A person declined this action. Tell the customer it was not done.",
                         "is_error": True}
    try:
        return result | {"content": json.dumps(HANDLERS[block.name](**block.input))}
    except OrderNotFound:
        return result | {"content": f"No order {block.input['order_id']}. Ask the customer to check the ID.",
                         "is_error": True}
    except OrderShipped:
        return result | {"content": "Order has shipped and cannot be cancelled. Offer a return.",
                         "is_error": True}

messages = [{"role": "user", "content": "Please cancel ORD-48213, it hasn't shipped yet."}]
while True:
    response = client.messages.create(model=MODEL, max_tokens=1024, tools=TOOLS, messages=messages)
    messages.append({"role": "assistant", "content": response.content})
    if response.stop_reason == "end_turn":
        break                                   # the task is finished
    if response.stop_reason == "pause_turn":
        continue                                # server tool paused: send the turn back as it is
    if response.stop_reason != "tool_use":      # max_tokens, refusal, context full
        raise RuntimeError(f"Loop stopped early: {response.stop_reason}")
    messages.append({"role": "user", "content": [run_tool(b) for b in response.content if b.type == "tool_use"]})

Three things to notice. Every tool_use gets a tool_result with the same ID, all in one user message. Failures come back as results Claude can act on, not as exceptions that end the loop. The approval check runs in your code before the handler, so a declined cancel never reaches the order system.

When a call is invalid or missing parameters, Claude typically retries a few times with corrections; strict: true stops those calls happening in the first place. In the Agent SDK, a can_use_tool callback only runs when a call would otherwise prompt, so the SDK documentation points to a PreToolUse hook when every call of a tool must be checked.

Parallel calls and streamed calls

Claude makes several tool calls in one response when the calls do not depend on each other. Run them concurrently, then send every tool_result back together in one user message; splitting them across separate messages teaches Claude to stop calling tools in parallel. When one call needs another's output, Claude makes them in separate turns on its own. To force one call at a time, set disable_parallel_tool_use: true inside tool_choice, not at the top level of the request.

tool_choice itself sets whether Claude must call a tool: auto (the default when tools are present), any (some tool), tool (one named tool) or none. With any or tool the API forces the call, so Claude writes no text before it. Forced choice returns an error with manual extended thinking and on some current models; there, use auto with strict: true, or structured outputs.

With streaming, a tool call arrives in pieces: content_block_start opens a tool_use block with an empty input, input_json_delta events carry fragments of the JSON, and only at content_block_stop is the string complete enough to parse. Two rules follow:

  • Never run a tool on a partial block. Parse and execute only after the block closes.
  • Append the assistant turn to history only after message_stop. If the stream drops, throw the partial turn away and retry; a half-built tool_use block in history makes the next request fail, and the error then points at the wrong turn.

Setting eager_input_streaming: true on a tool streams its input without server-side buffering or validation, which cuts the wait for large parameters. You then receive fragments that may be invalid JSON, so guard the parse and return an is_error result when it fails.

Required fields and exclusion conditions

Put a parameter in required only when the call makes no sense without it. A required field forces Claude to supply a value even when the conversation holds none, which invites a guess. Leave the rest optional and give them defaults in your handler.

When two tools overlap, write the boundary into both descriptions: one sentence on when to use the tool and one on when not to. A search_docs tool that says "do not use this when the answer is already in this conversation" and a session_summary tool that says "use this only when the answer is already in this conversation" give Claude a rule instead of two lookalikes. An exclusion that refers to earlier turns only works if you send the full history. If the descriptions still blur, merge the tools into one with an action parameter.

Start with the smallest tool set the task needs and add a tool only when a missing capability is confirmed. Registering tools "just in case" makes selection worse as the list grows.

Building a tool set

Anthropic's tool definition guidance gives four habits:

  1. Consolidate. One pull_request tool with an action parameter beats separate create_pr, review_pr and merge_pr tools. Fewer tools mean less confusion about which to pick.
  2. Namespace. Prefix tools with the service, such as github_list_prs or slack_send_message, once your tools span several systems.
  3. Return high-signal data. Return stable identifiers and only the fields Claude needs for its next step. Bloated responses waste context.
  4. Write instructive errors. "Rate limit exceeded. Retry after 60 seconds." gives Claude something to do; "failed" does not.

Which pattern fits

SituationChooseWhy
Claude needs current public web resultsA server tool such as web searchAnthropic runs it; no handler to build
Claude needs your internal order systemA client tool your code executesOnly your code can reach the system
The same capability is needed by several Claude appsAn MCP server (next topic)Build once, connect many
The action moves money or deletes dataAn approval gate before executionA person decides before anything happens
Five near-identical tools confuse ClaudeOne tool with an action parameterLess selection ambiguity
Claude sends malformed inputsstrict: true on the toolInputs match the schema
A call failstool_result with is_error: true and a next stepClaude can recover
Independent lookups must run one at a timedisable_parallel_tool_use: true in tool_choiceAt most one tool call per response
A streamed tool call is cut off mid-inputDiscard the partial turn and retry the requestA broken tool_use block in history fails the next call
A refund or delete is about to runPause for a person before the handler executesIrreversible actions need a check before, not after

Rules that decide exam answers

  • Every tool_use gets a matching tool_result. Same tool_use_id, all results in the next user message, results before any text.
  • Errors are results, not crashes. Catch failures in the handler and return is_error: true with a useful message.
  • Approval happens before execution, in your code. Asking Claude to "check with the user first" is not an approval gate. Gate each risky call by tool name; one approval at the start cannot cover a call Claude has not proposed yet.
  • Descriptions decide tool choice. When Claude picks the wrong tool, improve or consolidate the descriptions before adding more tools.
  • strict: true fixes the shape, not the logic. Your handler still checks business rules such as whether an order has shipped.
  • Know who executes. Server tools run on Anthropic's side; client tools, including Anthropic-defined ones, run in your code.

Where it appears in the exam

Tools and MCPs is Domain 8, 10.6% of the exam, and Tool Implementation is 4.4% of scored items, so expect two or three questions in a 53-item exam. Expect agents that call internal systems, loops that crash or stall on tool errors, tools Claude picks wrongly, and actions that need a person's approval.

Two sample questions

These are original Timo practice questions. They are not official exam questions.

Question 1

An agent calls crm_update_contact with a contact ID that does not exist. The handler raises an exception, the loop crashes, and the user sees a server error. What is the best fix?

Answer: C. An error result keeps the loop alive and tells Claude how to recover. A loses the conversation and hides the cause. B throws away a working capability. D adds a call to every turn and still leaves other failures unhandled.

Question 2

A finance assistant has 14 tools, including separate invoice_create, invoice_update, invoice_void and invoice_send, each with a one-line description. Claude sometimes calls invoice_update when a user asks to void an invoice. Which change follows Anthropic's tool guidance?

Answer: D. Fewer, clearly described tools reduce selection mistakes. A removes the little detail that exists. B doubles the number of similar tools. C changes the order, not the ambiguity.

Build exercise

  1. Define orders_get and orders_cancel as above with stub handlers, and run the loop. Log stop_reason and every tool call.
  2. Make orders_get raise for an unknown ID and return the is_error message. Check that Claude asks the user for the right ID instead of guessing.
  3. Implement ask_operator as a yes/no prompt in your terminal. Decline one cancellation and read how Claude explains it to the user.
  4. Cut the orders_get description to five words and ask the same questions again. Note any change in which tool Claude picks, then restore it.

Practise this topic

Sources