AI EngineeringZero to ProductionHome·About·Contact
Anthropic API in Practice · Part 5

Server-side tools

Some tools Anthropic runs for you. Learn the built-in, server-executed tools — web search and code execution — how they differ from the client-side tool loop you built in C2 (no manual loop: the result is already in the response), how to switch them on in tools=[...], how to read their results and citations, and how to reason about their cost and safety.

⏱️ ~1.5 hours🧪 5 recipes🎯 Beginner→Tech-lead

Learning objectives

  • Say what a server-side tool is and how Anthropic runs it for you.
  • Contrast client-side tool use (your manual loop) with server-side tools (no loop — the result is already in the response).
  • Enable the web search tool and read its results and citations.
  • Enable the code execution tool and read its stdout/stderr.
  • Reason about cost, safety/permissions, and when to pick server-side vs your own tools.

1 · What a server-side tool is essential

A tool is a capability the model can invoke instead of just writing text. In C2 you built client-side tools: you describe a function, the model asks to call it, and your code runs it and feeds the result back in a loop. A server-side tool is different — it's a capability Anthropic has already built and runs on its own infrastructure. You just switch it on; the model calls it and the result comes back inside the same response. There is no function for you to write and no loop for you to run.

Two of the most useful ones ship today: web search (the model searches the live web and cites its sources) and code execution (the model writes Python and runs it in a sandbox). A close cousin, web fetch, pulls the full text of a specific URL. All three execute on Anthropic's side — you never see the search engine or the sandbox, only the results.

Server-side ≠ the same as your toolsBoth are declared in the same tools=[...] list, but the mechanics are opposite. For your tools you get a tool_use block and must reply with a tool_result. For a server tool, Anthropic runs it and the result block is already sitting in response.content when the call returns.

2 · Client-side vs server-side: the two loops essential

The single most important idea in this lesson is the difference in control flow. With a client-side tool, you are the runtime: the model pauses, hands you a request, and waits for you to run the function and send the answer back — round-trip after round-trip until it's done (the while True loop from C2). With a server-side tool, Anthropic is the runtime: it runs the tool for you and only returns when the answer is already folded into the response.

You send request one call Model asks: tool_use pauses YOUR code runs it you execute You send tool_result feed back Model answers may loop again
🗺️ How to read this diagram

This is the tool loop you built in C2. You are the runtime — the model can't run anything itself, so it hands the work back to you and waits.

  • You send request — one API call, with your tool's JSON schema in tools.
  • Model asks: tool_use — the model stops and returns a tool_use block saying which tool it wants and with what arguments. stop_reason is "tool_use".
  • YOUR code runs it — you execute the function on your machine. Anthropic never sees it happen.
  • You send tool_result — you package the answer as a tool_result block and send the whole conversation back.
  • Model answers — now it can reply, or ask for another tool, which loops you back to the start.

In short: The loop is yours to drive. That's control — you can log, gate, or refuse each call — but it's also work: you build the execution environment and the while loop.

You send request one call Anthropic runs the tool server-side Result already in response done
🗺️ How to read this diagram

Same picture, but Anthropic is the runtime. The whole middle of the client-side loop collapses into one server-side step you never see.

  • You send request — one API call. The only difference from a plain call is a tool type (like web_search_20260209) in tools.
  • Anthropic runs the tool — the search or the sandbox runs on Anthropic's infrastructure. There is no tool_use handed to you and nothing for you to execute.
  • Result already in response — when the single call returns, the tool has already run and its result blocks are sitting in response.content.

In short: No while loop, no tool_result to send. You gave up the ability to intercept the tool, and in exchange you skip building and running it entirely.

So the client-side path is request → tool_use → you run it → tool_result → answer, looping as many times as the task needs. The server-side path collapses to request → answer (with the tool already run). You trade control (you can't intercept or gate a server tool) for simplicity (no loop, no execution environment to build).

Client-side tool (C2)Server-side tool (this lesson)
Who executes ityour codeAnthropic's servers
You write the function?yesno — it's built in
Manual loop needed?yes (while loop)no
How you enable ityour JSON schema in toolsthe tool type in tools
Result locationyou build the tool_resultalready in response.content
Can you gate/approve it?yesno (runs automatically)

3 · Enabling the web search tool intermediate

You enable a server-side tool by adding its spec to the tools=[...] list — the same list you'd use for your own tools, but instead of a name + schema you give a type (and the tool's fixed name). For web search the current dynamic-filtering variant is web_search_20260209 on Opus 4.8/4.7/4.6 and Sonnet 4.6; older models use the basic web_search_20250305. Optional keys let you cap uses or restrict domains.

The block below is real anthropic SDK code. It needs an API key and network — and web search has its own per-search pricing on top of tokens (see §6).

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Python · enable web search (server-side) ▶ needs API key + network
web_search.py# needs: pip install anthropic + ANTHROPIC_API_KEY; server tools have their own pricing
import anthropic

client = anthropic.Anthropic()

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{"role": "user",
               "content": "What did Anthropic announce most recently? Cite your sources."}],
    tools=[{
        # Server-side tool: a `type` + fixed `name`, NOT a custom input_schema.
        # web_search_20260209 = dynamic-filtering variant (Opus 4.8/4.7/4.6, Sonnet 4.6).
        # Older models use "web_search_20250305".
        # check the current tool type/version in the docs before shipping.
        "type": "web_search_20260209",
        "name": "web_search",
        "max_uses": 3,                     # optional: cap how many searches
        # "allowed_domains": ["anthropic.com"],   # optional allow/block lists
    }],
)

# You do NOT run a tool loop. Anthropic already searched and folded the
# results into this one response. Just read stop_reason + content.
print("stop_reason:", resp.stop_reason)
for block in resp.content:
    if block.type == "text":
        print(block.text)
▶ How this works

This enables a server-side tool and — crucially — has no loop. Compare it to the manual client-side loop in C2: there you had to detect tool_use, run a function, and re-send. Here you just make one call and read the answer.

  1. tools=[{...}] is the same list you'd use for your own tools, but a server tool is declared by its type and fixed namenot a custom input_schema. That type string is how you switch the built-in capability on.
  2. web_search_20260209 is the current dynamic-filtering variant (Opus 4.8/4.7/4.6, Sonnet 4.6); older models use web_search_20250305. The version depends on the model — check the docs when you upgrade.
  3. max_uses caps how many searches the model may run (you're billed per search — see §6). allowed_domains/blocked_domains fence where it can look.
  4. After the call there is no tool loop: Anthropic already searched. You just read resp.stop_reason and loop over resp.content for the text blocks.

What the output means: The model's answer prints, already grounded in a live web search that happened server-side — no while True and no tool_result anywhere.

Try this: This block needs an API key and network (and web search has its own per-search price). Put the same request through the C2 client-side loop in your head — you'd need three more steps. That gap is the whole lesson.

No loop — that's the whole pointNotice there is no while True and no tool_result being sent back. Compare this to the manual client-side loop in C2: there you had to detect stop_reason == "tool_use", run the function, and re-send. Here the search already happened server-side.

4 · Reading search results & citations intermediate

When a server tool runs, its output arrives as extra content blocks in the response, interleaved with the model's text. For web search you'll see a server_tool_use block (the query the model chose) followed by a web_search_tool_result block whose content is a list of results. The model's answer text then arrives in normal text blocks, and cited sentences carry a citations array pointing back at the sources it used.

The block below is real anthropic SDK code that walks the response and prints the results and citations. It needs an API key and network.

Python · read web-search results + citations ▶ needs API key + network
read_results.py# needs: pip install anthropic + ANTHROPIC_API_KEY; server tools have their own pricing
import anthropic

client = anthropic.Anthropic()

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Summarize the latest on X. Cite sources."}],
    tools=[{"type": "web_search_20260209", "name": "web_search"}],
)

for block in resp.content:
    if block.type == "server_tool_use":
        # The query the model chose to run, server-side.
        print("SEARCHED:", block.input.get("query"))

    elif block.type == "web_search_tool_result":
        # On success .content is a LIST of results; on error it is a single
        # error object (e.g. {"error_code": "max_uses_exceeded"}) — branch on that.
        results = block.content
        if isinstance(results, list):
            for r in results:
                print("  -", r.title, "->", r.url)
        else:
            print("  search error:", getattr(results, "error_code", results))

    elif block.type == "text":
        print(block.text)
        # Cited sentences carry a citations array pointing at the sources used.
        for c in (block.citations or []):
            print("    cited:", getattr(c, "url", None), getattr(c, "title", None))
▶ How this works

When a server tool runs, its output arrives as extra content blocks mixed in with the model's text. This walks the response and pulls out the query, the results, and the citations — the loop-free equivalent of reading a client-side tool_result.

  1. server_tool_use — the query the model chose to run. You didn't pick it; the model did, server-side. block.input.get("query") shows what it searched for.
  2. web_search_tool_result — on success its .content is a list of results (title + url). On error it's a single error object (e.g. max_uses_exceeded). The isinstance(results, list) check is why: server tools don't raise, so you branch on the block, not a try/except.
  3. text blocks carry the model's answer, and cited sentences have a citations array pointing back at the sources — that's how web search shows its work.

What the output means: You see the query the model ran, each result's title and URL, the answer text, and the sources cited for each claim.

Try this: Force an error (set max_uses: 0) and confirm the code prints "search error" instead of crashing — proof you understood that a server-tool failure is a result block, not an exception.

Server-tool errors don't raise — they're a result blockA failed search still returns HTTP 200. The error shows up inside the web_search_tool_result block as an error object, not as a Python exception. For web search a success content is a list and an error content is an object — check which before you index it.

5 · The code execution tool advanced

Code execution lets the model write Python and run it in an Anthropic-hosted sandbox (no internet, data-science libraries pre-installed). You don't run anything — you declare the tool and the model's code, its stdout/stderr, and any files it produced come back in the response. The current type is code_execution_20250825; its result arrives as a bash_code_execution_tool_result block with stdout / stderr / return_code.

The block below is real anthropic SDK code. It needs an API key and network, and code execution is billed by sandbox time (see §6).

Python · code execution (server-side sandbox) ▶ needs API key + network
code_execution.py# needs: pip install anthropic + ANTHROPIC_API_KEY; server tools have their own pricing
import anthropic

client = anthropic.Anthropic()

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=2048,
    messages=[{"role": "user",
               "content": "Compute the mean and standard deviation of "
                          "[2, 4, 4, 4, 5, 5, 7, 9] with numpy."}],
    tools=[{
        # Server-side sandbox — the model writes AND runs the code.
        # check the current tool type/version in the docs before shipping.
        "type": "code_execution_20250825",
        "name": "code_execution",
    }],
)

for block in resp.content:
    if block.type == "text":
        print(block.text)                       # the model's explanation
    elif block.type == "server_tool_use":
        print("RUNNING:", block.name)           # what the model chose to do
    elif block.type == "bash_code_execution_tool_result":
        result = block.content
        if getattr(result, "return_code", 0) == 0:
            print("stdout:", result.stdout)     # the program's output
        else:
            print("stderr:", result.stderr)     # it errored — read stderr
▶ How this works

Code execution is the second big server-side tool: the model writes Python and runs it in an Anthropic-hosted sandbox. You don't run the code — you enable the tool and read the output that comes back.

  1. The tool is enabled the same way — a type (code_execution_20250825) and name in tools. No schema, because the model writes the code itself.
  2. server_tool_use tells you the model chose to run code; the actual program runs in the sandbox, not on your machine.
  3. bash_code_execution_tool_result carries the run: check return_code0 means read result.stdout (the output), anything else means read result.stderr (it errored).
  4. As with web search, there's no callback and no loop — the output is already in the response when the call returns.

What the output means: The model's explanation plus the numeric result the sandbox computed with numpy — mean and standard deviation — printed from stdout.

Try this: This needs an API key and network, and code execution is billed by sandbox time. If the server loop hits its cap you'll see stop_reason == "pause_turn"; re-send the messages to resume — you still never run the code yourself.

You still don't run a loopCode execution feels like it should need a callback, but it doesn't: the model writes the code, Anthropic runs it in the sandbox, and the output is in the response. If the server loop hits its internal iteration cap it returns stop_reason == "pause_turn" — you re-send the messages to let it resume, but you never execute the code yourself.

6 · Cost & safety advanced

Server-side tools have their own pricing on top of tokens. Web search is billed per search; code execution is billed by sandbox time (with a monthly free allowance). Because the model decides how many times to invoke a tool, an unbounded task can run several searches or long sandbox sessions — so cap it. The offline helper below (pure stdlib, runs offline) estimates a per-request bill from your assumptions so you can sanity-check before enabling a tool in production.

Python · server-tool cost estimate (runs offline)
server_cost.pydef server_tool_cost(searches, search_price, sandbox_minutes,
                     sandbox_price_per_min, tokens, token_price_per_1k):
    """Estimate one request's bill: search fees + sandbox time + tokens."""
    search_cost = searches * search_price
    sandbox_cost = sandbox_minutes * sandbox_price_per_min
    token_cost = tokens / 1000 * token_price_per_1k
    total = search_cost + sandbox_cost + token_cost
    return {
        "search_cost": round(search_cost, 4),
        "sandbox_cost": round(sandbox_cost, 4),
        "token_cost": round(token_cost, 4),
        "total_usd": round(total, 4),
    }

print(server_tool_cost(searches=3, search_price=0.01,
                       sandbox_minutes=0.5, sandbox_price_per_min=0.05,
                       tokens=4000, token_price_per_1k=0.005))
{'search_cost': 0.03, 'sandbox_cost': 0.025, 'token_cost': 0.02, 'total_usd': 0.075}
▶ How this works

Server tools cost money on top of tokens — per search for web search, per sandbox minute for code execution. This helper is pure stdlib and runs offline: it estimates one request's bill so you can sanity-check before turning a tool on.

  1. search_cost = number of searches × the per-search price. Because the model decides how many searches to run, this is the line that surprises people.
  2. sandbox_cost = sandbox minutes × per-minute price — code execution is billed by time in the sandbox, not by tokens.
  3. token_cost is the ordinary token bill; the total sums all three so you see the true per-request cost, not just the token slice.

What the output means: A breakdown: 3 searches cost $0.03, half a sandbox minute $0.025, the tokens $0.02 — $0.075 total for the request. Change the inputs to model your own volume.

Try this: Bump searches to 10 and watch total_usd jump — that's why you cap the model with max_uses. This helper runs with a plain python server_cost.py; no key needed.

The model chooses how often to invoke — so cap itYou are billed for every search and every sandbox second the model decides to spend. Use max_uses on web search, and set a sensible max_tokens. For safety: web search reaches the live internet (use allowed_domains/blocked_domains to fence it), and code execution runs model-written code — but in Anthropic's sandbox, not yours, so it can't touch your machine or your data unless you upload it.
Permissions are all-or-nothing per requestYou cannot approve an individual server-tool call the way you can gate a client-side tool before running it. Enabling the tool grants the model permission to use it for that request. If you need a human in the loop before an action happens, that action belongs in a client-side tool where your code decides.

7 · When to use server-side vs your own tools professional

The choice is about who should run the action. Reach for a server-side tool when the capability is generic, safe to run automatically, and you'd rather not build it: live web info, ad-hoc computation, fetching a public URL. Reach for a client-side tool when the action touches your systems (your database, your API, sending an email), needs your credentials, or must be gated/approved/audited before it runs.

The helper below (pure stdlib, runs offline) encodes that decision so you can defend the choice by criteria rather than by taste.

Python · client-side vs server-side decision (runs offline)
choose_side.pydef choose_tool_side(touches_your_systems, needs_your_credentials,
                     must_gate_or_audit, is_generic_capability):
    """Decide whether an action belongs in a client-side or server-side tool."""
    if touches_your_systems or needs_your_credentials or must_gate_or_audit:
        return "client-side (your code runs it: control, credentials, approval)"
    if is_generic_capability:
        return "server-side (Anthropic runs it: web search / code exec, no loop)"
    return "client-side (default: keep control unless it's clearly generic)"

# a) live web info, nothing of yours involved -> server-side
print(choose_tool_side(False, False, False, True))
# b) charge a customer's card -> client-side (credentials + must gate)
print(choose_tool_side(True, True, True, False))
# c) query YOUR order database -> client-side (your systems)
print(choose_tool_side(True, False, False, False))
server-side (Anthropic runs it: web search / code exec, no loop)
client-side (your code runs it: control, credentials, approval)
client-side (your code runs it: control, credentials, approval)
▶ How this works

The real design question is who should run the action. This helper (pure stdlib, runs offline) encodes the rule so you can defend the choice by criteria instead of by taste.

  1. If the action touches your systems, needs your credentials, or must be gated/audited, it belongs in a client-side tool — you keep control, hold the secrets, and can approve it before it runs.
  2. Otherwise, if it's a generic capability (live web info, ad-hoc computation, fetching a public URL), a server-side tool is the win: Anthropic runs it, no loop, nothing to build.
  3. The default when nothing clearly applies is client-side — keep control unless the capability is obviously generic and safe to run automatically.

What the output means: (a) news summary → server-side; (b) charging a card → client-side (credentials + must gate); (c) querying your order DB → client-side (your systems).

Try this: Run it on an action you're building. If any of the first three flags is true, the answer is client-side — because a server tool runs automatically with no veto from your code.

Mix them freelyThe tools=[...] list can hold both kinds at once — e.g. a server-side web_search alongside your own client-side place_order. The model picks the right one per step; you only run the client-side ones.

8 · Tech-lead — owning server-side tools in production tech-lead

A lead owns four decisions. Cost governance: cap invocations (max_uses, max_tokens) and monitor the per-request server-tool bill, not just tokens — an agent that searches on every turn is a budget leak. Data boundary: web search reaches the public internet and code execution runs in Anthropic's sandbox — know what leaves and fence it with allow/block domain lists. Correct result-reading: server tools never raise; errors are result blocks, and success vs error content can be a list vs an object — the response-parsing code must branch on that or it breaks in production. The client/server split: keep anything that touches your systems or needs approval on the client side; let generic, safe capabilities run server-side. Pin the exact tool type/version and re-check it against the docs when you upgrade models, since the available variant depends on the model.

The reasoning is the same either wayWhether a tool runs on your side or Anthropic's, the mental model from C2 holds: the model asks for a capability, something runs it, and the result flows back into context. Server-side tools just move the 'something runs it' off your machine — which removes the loop but also removes your veto.

Exercise AP5.1 — Read a server-tool response

Context: Reading a server-tool response cleanly means two things: pulling every result's title/URL and citation out of the result block, and proving you understood that a failed search returns an error block at HTTP 200 rather than raising.

Your task: Using web_search.py and read_results.py as templates, enable web search and write the loop-free parser that prints each result's title/URL and every citation, then deliberately trigger an error and show your code handles the error result block without crashing.

Requirements:

  • Declare the web_search server tool and iterate resp.content
  • Print each result's title/url and any citations from the web_search_tool_result block
  • Force an error (e.g. max_uses: 0 or a bogus domain) and read the error result's error_code
  • Handle the error result block without a crash — server tools don't raise
  • Requires an API key to run

💡 Hint: Branch on whether block.content is a list (success) or an object (error) before you index into it — that single check is what keeps the error path from crashing.

Exercise AP5.2 — Decide the split, then budget it

Context: The server-vs-client choice and its cost are two halves of one decision: place each capability by its trust boundary, then size the bill from how many server-tool uses each request triggers — and cap that with max_uses.

Your task: For three actions — summarize today's news, refund a customer, run an ad-hoc numpy calculation — use choose_side.py to place each on the client or server side and justify it, then use server_cost.py to estimate the monthly bill if the news summary runs 3 searches per request across your volume.

Requirements:

  • Place news summary and the numpy calc on server tools (web_search, code_execution) and the refund on a client-defined tool
  • Justify each placement by its trust/data boundary
  • Estimate monthly cost assuming 3 searches per news-summary request at your expected volume
  • State the max_uses cap you'd set to bound that cost
  • Run offline — routing and cost estimation only

💡 Hint: The refund touches your money and auth, so it can't be a server tool; for the estimate, multiply searches-per-request by request volume before applying the per-search price.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Enable the web search server toolBeginner

Context: Server tools run on Anthropic's own infrastructure, so unlike a client-defined tool you never write an execution loop — you just declare the tool and Claude does the query, the fetch, and the citation for you in a single response.

Your task: Add the web search server tool so Claude can answer with current information, with no client-side execution loop.

Requirements:

  • Pass tools=[{"type": "web_search_20260209", "name": "web_search"}] to messages.create
  • Do not implement any tool-execution loop — Anthropic runs the search
  • Iterate resp.content and print blocks where block.type == "text"
  • Note that the results and citations arrive in the same response
  • Requires an API key to run

💡 Hint: The dated _20260209 suffix selects the current web-search version with dynamic model filtering — use the version string exactly.

Show solution

Server tools run on Anthropic's infra — just declare them:

import anthropic
client = anthropic.Anthropic()

resp = client.messages.create(
    model="claude-opus-4-8", max_tokens=1024,
    tools=[{"type": "web_search_20260209", "name": "web_search"}],
    messages=[{"role": "user",
               "content": "What are the latest Claude model releases?"}],
)
for block in resp.content:
    if block.type == "text":
        print(block.text)

Unlike a client-defined tool, you never execute anything — Claude issues the query, Anthropic runs the search, and the results plus citations come back in the same response. The _20260209 version adds dynamic filtering (Opus 4.8 / 4.7 / 4.6, Sonnet 4.6).

Exercise 2 · Read search results and their citationsIntermediate

Context: A web-search response interleaves the answer text with a result block, and a critical gotcha lurks in the error path: server tools don't raise — a failed search returns HTTP 200 with an error object inside the result block.

Your task: Iterate a web-search response, distinguishing the web_search_tool_result block from the answer text, and handle the fact that a successful result is a list while an error result is a single object.

Requirements:

  • Branch on block.type to separate text from web_search_tool_result
  • For a success, block.content is a list — iterate it and read each result's title/url
  • For an error, block.content is a single object — read its error_code
  • Guard the shape with an isinstance(content, list) check before indexing
  • Requires an API key to run

💡 Hint: Because errors come back at HTTP 200, a try/except won't catch them — inspect the block's content shape instead.

Show solution

Branch on block type; guard the error shape:

resp = client.messages.create(
    model="claude-opus-4-8", max_tokens=1024,
    tools=[{"type": "web_search_20260209", "name": "web_search"}],
    messages=[{"role": "user", "content": "Who won the 2024 Turing Award?"}],
)
for block in resp.content:
    if block.type == "text":
        print("ANSWER:", block.text)
    elif block.type == "web_search_tool_result":
        content = block.content
        if isinstance(content, list):            # success -> list of results
            for r in content:
                print("SRC:", r.title, r.url)
        else:                                    # error -> single object
            print("SEARCH ERROR:", content.error_code)

Server-tool errors do not raise — they return HTTP 200 with an error inside the result block. A successful content is a list of results; an error content is a single object with error_code. Branch on that before indexing.

Exercise 3 · Run the code execution toolAdvanced

Context: Code execution is another fully server-side tool: Claude writes and runs code in an Anthropic-hosted sandbox and hands you back stdout/stderr/return-code — no container for you to manage, but the result block type is versioned and easy to mismatch.

Your task: Add the code execution server tool and read its output block, matching on the bash_code_execution_tool_result type rather than the bare tool name.

Requirements:

  • Pass tools=[{"type": "code_execution_20260120", "name": "code_execution"}]
  • Iterate resp.content and print text blocks
  • Match the result on bash_code_execution_tool_result, not the legacy code_execution_tool_result name
  • On success read stdout and return_code; on failure read error_code
  • Requires an API key to run

💡 Hint: The _20260120 version emits the bash_-prefixed result type; check the inner content.type to tell a result from an error.

Show solution

Code execution is fully server-side — declare it, read the result block:

resp = client.messages.create(
    model="claude-opus-4-8", max_tokens=2048,
    tools=[{"type": "code_execution_20260120", "name": "code_execution"}],
    messages=[{"role": "user",
               "content": "Compute the mean and stdev of [1,2,3,4,5,6,7,8,9,10]."}],
)
for block in resp.content:
    if block.type == "text":
        print(block.text)
    elif block.type == "bash_code_execution_tool_result":
        r = block.content
        if r.type == "bash_code_execution_result":
            print("stdout:", r.stdout, "rc:", r.return_code)
        else:
            print("exec error:", r.error_code)

Claude writes and runs the code in a sandboxed container; you get stdout/stderr/return_code back. Match on bash_code_execution_tool_result — the legacy bare code_execution_tool_result name is not what _20260120 emits.

Exercise 4 · Handle pause_turn in the server-tool loopExpert

Context: Server tools run their own internal sampling loop that is capped at roughly ten iterations; when it hits that cap it returns stop_reason == "pause_turn" — not an error, just a checkpoint you resume by re-sending the turn.

Your task: Handle pause_turn in the server-tool loop by resuming correctly: re-send the same turn without appending a literal 'Continue.' message.

Requirements:

  • Loop on messages.create, breaking when resp.stop_reason != "pause_turn"
  • To resume, re-send the original user turn plus an assistant message carrying resp.content
  • Do not append a 'Continue.' user message — the server detects the trailing server-tool block itself
  • Cap the number of continuations so a stuck loop can't run forever
  • Requires an API key to run

💡 Hint: Rebuild messages as [original_user_turn, {"role": "assistant", "content": resp.content}] and call again; the server picks up where it paused.

Show solution

Resume by re-sending the same turn; the server detects the trailing server-tool block:

messages = [{"role": "user", "content": "Research X thoroughly and summarize."}]
for _ in range(5):                          # cap continuations
    resp = client.messages.create(
        model="claude-opus-4-8", max_tokens=2048,
        tools=[{"type": "web_search_20260209", "name": "web_search"}],
        messages=messages,
    )
    if resp.stop_reason != "pause_turn":
        break
    # Re-send: user query + assistant response. Do NOT append "Continue."
    messages = [messages[0], {"role": "assistant", "content": resp.content}]
print(next(b.text for b in resp.content if b.type == "text"))

pause_turn means the server-side sampling loop hit its iteration limit, not that anything failed. Re-send the user turn plus the assistant's partial response and the server resumes automatically — adding a literal "Continue." message would confuse it. Always cap continuations so a stuck loop can't run forever.

Exercise 5 · Constrain and price server-tool usageProfessional

Context: Left unbounded, a web-search tool can loop, run up cost, and fetch from anywhere; max_uses and domain scoping turn it into a predictable, auditable capability. There's also a subtle interaction: the current web tools already run code internally, so stacking a second execution environment backfires.

Your task: Limit web search with max_uses and an allowed-domain list, and explain why you should not also declare code_execution alongside the _20260209 web tools.

Requirements:

  • Set max_uses to cap the number of searches per request
  • Scope fetching with allowed_domains (or blocked_domains)
  • Explain that max_uses bounds cost and stops runaway search loops
  • State that the _20260209 web tools already run code under the hood, so adding code_execution confuses the model
  • Requires an API key to run

💡 Hint: Both controls are just extra keys on the tool declaration dict — add them next to type and name, not as separate parameters.

Show solution

Bound the tool and scope its domains:

resp = client.messages.create(
    model="claude-opus-4-8", max_tokens=1024,
    tools=[{
        "type": "web_search_20260209", "name": "web_search",
        "max_uses": 3,                              # cap searches per request
        "allowed_domains": ["docs.anthropic.com", "platform.claude.com"],
    }],
    messages=[{"role": "user", "content": "How do I enable prompt caching?"}],
)

max_uses caps cost and stops runaway search loops; allowed_domains (or blocked_domains) scopes what the model may fetch. Do not separately declare code_execution next to the _20260209 web tools — those versions already run code under the hood for dynamic filtering, and a second execution environment confuses the model.

Exercise 6 · Decide server-side vs your own tools as the platform ownerIndustry scenario

Context: The platform owner's core decision for every capability is server tool vs client-defined tool, and it comes down to trust and hosting: Anthropic-hosted tools need zero infra, but anything touching your private data, auth, or security boundary must run in your own harness.

Your task: Write an offline selector that, for four needs — current web info, running untrusted code, calling your internal billing API, and querying your private DB — chooses a server tool or a client-defined tool with reasoning.

Requirements:

  • Route current web info to the web_search server tool
  • Route sandboxed/untrusted code to the code_execution server tool
  • Route the internal billing API and the private DB to client-defined tools
  • Attach a one-line justification per choice (hosting, citations, or data/auth boundary)
  • Run entirely offline — pure routing logic, no API key

💡 Hint: The dividing question is 'can Anthropic host this safely?' — if it touches your credentials or private data, only your code can gate the call, so it must be client-defined.

Show solution

Route each need to the tool type that fits its trust and hosting model (pure logic):

def choose_tool(need):
    table = {
        "current web info":    ("server: web_search",
                                "Anthropic hosts search; no infra, citations included"),
        "run sandboxed code":  ("server: code_execution",
                                "Anthropic-hosted sandbox; no container to manage"),
        "internal billing API":("client-defined tool",
                                "your data/auth boundary — you must execute & gate it"),
        "private database":    ("client-defined tool",
                                "private data never leaves your infra; you run the query"),
    }
    return table.get(need, ("client-defined tool", "default: you own execution"))

for n in ["current web info", "run sandboxed code",
          "internal billing API", "private database"]:
    tool, why = choose_tool(n)
    print(f"{n:22} -> {tool:22} ({why})")

Server tools win when Anthropic can host the capability (web, sandboxed code) — zero infra, built-in citations/sandboxing. Anything touching your private data, auth, or security boundary must be a client-defined tool: only your harness can gate the call, inject credentials, and keep the data on your side.

✓ Checkpoint — you can move on when you can…

  • Explain what a server-side tool is and that Anthropic executes it for you.
  • Contrast the client-side manual loop with the loop-free server-side path.
  • Enable web search via the tool type and read its results + citations.
  • Enable code execution and read stdout/stderr from the result block.
  • Reason about cost, safety/permissions, and when to pick server- vs client-side.

Knowledge check check yourself

✓ Knowledge check

How does control flow differ between a server-side tool and a client-side tool, and what do you trade for that difference?

Show answer
Client-side tool use is a loop you drive: request produces a tool_use block, you run it, return a tool_result, then get the answer. A server-side tool runs inside Anthropic's call, so the result is already in response.content when it returns; you trade control (can't gate or intercept the call) for simplicity (no loop, no sandbox to run).
✓ Knowledge check

Why can't you branch on try/except to handle a failed server-side tool call, and what must you inspect instead?

Show answer
A failed web search or code execution still returns HTTP 200, so no Python exception is raised. The error surfaces inside the result block, so you branch on shape: for web search success content is a list of results while an error is an object with an error_code; for code execution you check return_code (0 = read stdout, else stderr).
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in