AI EngineeringZero to ProductionHome·About·What’s new·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

Picture the two ways a restaurant can get you a glass of water. In the first, the waiter brings you an empty glass and a note: "the tap is down the hall, third door, mind the step" — and you get up, walk down, fill it, and carry it back. In the second, you just ask, and a full glass arrives at your table; the fetching happened in the kitchen and you never saw it. Client-side tools are the first restaurant. Server-side tools are the second. That single difference — who gets up to do the work — is what this whole lesson turns on, and getting it wrong is the most common reason people either write a pointless while loop around a server tool or wait forever for a "tool call" that already happened.

Here's why the distinction earns its own section instead of a footnote. When you first meet tools, everything looks like the first restaurant: the model asks, you run, you reply. So your instinct — a good instinct, honestly — is to reach for the loop every time. But some capabilities aren't yours to run at all; there's no tap down your hall. Live web search needs a search engine you don't host; a Python sandbox needs an execution environment you'd have to build and secure. Anthropic already runs those, so you don't write the function, you don't spin up the sandbox, and you don't drive a loop — you just tick a box in your request. The trap is treating a server tool like a client tool and hunting for a tool_use block to answer; there isn't one, and the code sits waiting on a reply that will never come.

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.

Model wants a tool declared in tools=[...] Client-side (C2) YOUR code runs it · you loop you write the function Server-side (this lesson) ANTHROPIC runs it · no loop web search · code exec · web fetch A tool is a capability — the split is who runs it. Both are declared in the same tools=[...] list; the only question is whether your code executes it (client-side) or Anthropic's infrastructure does (server-side).
🗺️ How to read this diagram

Read it top-down: one request that names a tool, then the fork that decides who actually does the work.

  • The top box is the model asking for a capability — a tool it wants to invoke instead of just writing text. You always declare tools the same way, in tools=[...].
  • The two arrows are the fork: the same request can route to either kind of tool.
  • The left amber box is client-side (from C2): your code runs the function and you drive a loop. The right green box is server-side: Anthropic runs the built-in tool (web search, code execution, web fetch) and hands back the result — nothing for you to write or loop.

In short: Same declaration, opposite runtime — client-side means you execute it, server-side means Anthropic does.

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

If you remember one thing from this lesson, make it this: who owns the loop. Everything else — how you read results, how errors surface, how you budget cost — falls out of that one fact. People who skip this section end up writing code that either double-loops a server tool (harmless but confused) or, worse, tries to intercept a server tool that already ran and can't be stopped. The bug isn't a typo; it's a wrong mental model, and it's expensive to debug because the code looks reasonable.

The cleanest way to feel the difference is to think about who is holding the phone during the call. With a client-side tool, the model calls you, says "I need the weather in Paris — go find it," and then holds the line while you look it up; nothing continues until you speak the answer back. You are the runtime, and the conversation is a series of these back-and-forths. With a server-side tool, the model makes the call, does the lookup itself in the back office, and only rings you once — when the whole thing is finished and the answer is already written down. One conversation, one hang-up. The word "loop" is literal in the first case (you genuinely write a while that keeps going) and absent in the second.

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 sendtool_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 runsthe tool server-side Result alreadyin 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

A model's knowledge has a freeze date, and that gap is exactly where confident-sounding wrong answers come from. Ask it about a release from last week and it will either admit it doesn't know or, worse, invent something plausible. Web search closes that gap: the model runs a live search, reads real pages, and cites them — so instead of guessing from stale memory it answers from what's on the web right now. Turning it on is almost insultingly easy, which is the point of this section — the hard part isn't enabling it, it's remembering that you don't then write a loop.

Think of enabling a server tool as flipping a switch on a request rather than installing a machine. For your own tools you hand over a full instruction manual — a name, a description, a JSON input_schema spelling out every argument — because your code has to know how to run the function. A server tool needs none of that; Anthropic already knows how to run it. So you just name the kind of tool with a type string (plus its fixed name), and that's the entire declaration. The one wrinkle that trips people is the version suffix: the type is dated (web_search_20260209), the right date depends on the model, and pasting an old one against a new model — or a new one against an old model — is a quiet mismatch. Pin it, and re-check the docs when you change models.

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).

client-side tool · a full manual name description input_schemaevery argument spelled out server-side tool · one switch type: web_search_20260209 name: web_search no schema — Anthropic already knows how to run it · pin the date Enabling a server tool is flipping a switch, not writing a manual. Your own tool needs name + description + full input_schema; a server tool needs only a dated type and its fixed name.
🗺️ How to read this diagram

Compare the two columns side by side — left is what you must supply for a client-side tool, right is all a server tool needs.

  • The left amber stack is the full declaration your own function requires: a name, a description, and a JSON input_schema spelling out every argument — because your code has to know how to run it.
  • The right green pair is the entire server-tool declaration: a dated type string (web_search_20260209) plus its fixed name. No schema, because Anthropic already knows how to run it.
  • The note under the right column is the one wrinkle: the type is version-dated and the right date depends on the model — pin it and re-check when you switch models.

In short: Client-side = a full instruction manual; server-side = a dated type and a name, nothing more.

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 name — not 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

A response with a server tool in it is not a flat string — it's a small transcript, and if you only read the text you throw away half the value. The citations that let you fact-check the answer, the URLs a user might click, the very query the model chose to run: all of that lives in extra blocks alongside the prose. Teams that treat the response as "just .text" ship agents that sound grounded but can't show their work, and then can't explain to a reviewer where a claim came from. Reading the whole transcript is what turns web search from a party trick into something you can defend.

The mental model is a reporter's notebook, not a finished article. As the model works, it jots things down in order: "I searched for this" (a server_tool_use block), "here's what came back" (a web_search_tool_result block holding the list of hits), "here's my sentence" (a text block) — and pinned to that sentence, "…and I got it from these pages" (a citations array). Your job when the call returns is simply to walk that notebook page by page and pick out what you need, branching on each block's type. There's no loop and no callback — the notebook is already complete when you get it; you're a reader, not a participant. The common mistake here is assuming the blocks come in a tidy fixed order and indexing blindly; instead, iterate and switch on type, because text and tool blocks interleave however the model reasoned.

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.

response.content — walk it block by block, branch on .type server_tool_use the query it chose web_search_tool_result .content = list of hits text the answer sentence citations[ ] url + title of each source a reporter's notebook: searched → got hits → wrote a sentence → pinned its sources The response is a transcript, not a string. Iterate the blocks and branch on type: the query, the list of hits, the answer text, and the citations pinned to each sentence all arrive as separate blocks.
🗺️ How to read this diagram

Follow the arrows left to right — this is the order the model jotted things into its notebook as it worked.

  • server_tool_use records the query the model chose to run — you didn't pick it.
  • web_search_tool_result holds what came back: on success its .content is a list of hits (title + url).
  • text is the model's answer sentence, and the green box hanging off it is the citations array — the sources pinned to that sentence so you can fact-check it.
  • The blocks interleave however the model reasoned, so you iterate and switch on type rather than indexing a fixed position.

In short: Read the whole notebook — query, hits, text, citations are four block types you pick out by .type, not one flat string.

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

Language models are famously bad at arithmetic — ask one to average eight numbers and it may fudge the last digit. Not because it's dumb, but because it's predicting the answer, not computing it. Code execution fixes this by letting the model stop guessing and actually run the numbers: it writes real Python, a real interpreter runs it, and the real output comes back. The same trick turns "I think the CSV has about a thousand rows" into a counted, exact answer. If web search grounds the model in facts, code execution grounds it in computation.

The instinct that makes this section feel hard is a false alarm: "surely running code needs a callback — I have to hand the program somewhere and get output back?" You don't. The sandbox is a pop-up kitchen Anthropic sets up and tears down — the model writes the recipe and cooks it in there, and you're only handed the finished plate. Nothing runs on your machine, so there's no container to build, no dependency to install (data-science libraries are pre-loaded), and no security surface of your own to worry about. The flip side, and the thing to internalize: because it's their kitchen with no internet, the code can't reach your files, your database, or the web unless you explicitly upload data — which is exactly why it's safe to let run automatically. The one habit to build is reading the result like a shell command: check return_code first (0 means success, read stdout; anything else means it errored, read stderr) instead of assuming the run worked.

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).

Model writesPython server_tool_use Anthropicsandbox runs it no internet · libs ready result block check return_code == 0 → read stdout the program's output else → read stderr it errored The model writes it, Anthropic's sandbox runs it, you read the result. Nothing runs on your machine; you branch on return_code like a shell command — 0 is stdout, anything else is stderr.
🗺️ How to read this diagram

Follow the top row left to right, then take the branch at the end.

  • Model writes Python — a server_tool_use block; the model both writes and owns the code.
  • Anthropic sandbox runs it — a pop-up kitchen with no internet and data-science libraries preloaded; nothing touches your machine, which is why it's safe to run automatically.
  • result block — the finished plate; you check return_code first, like reading a shell command.
  • The green branch (return_code == 0) means success — read stdout. The red branch (anything else) means it errored — read stderr.

In short: Model writes it, sandbox runs it, you branch on return_code: 0 → stdout, else → stderr.

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_code — 0 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

Here's the line item that ends up in a surprised postmortem: the model, not you, decides how many times to spend. With ordinary token billing you can reason about cost from the size of your prompt. With server tools that intuition breaks, because a single request can trigger three searches, or ten, or a two-minute sandbox session — and each of those has a price on top of tokens. An agent told to "research this thoroughly" and left uncapped is a metaphorical taxi with the meter running and no destination. This section is the seatbelt.

The mental shift is from a fixed-price ticket to a metered ride. Tokens are the ticket — priced by length, roughly predictable. Server tools are the meter — priced by use, and the model is driving. Web search bills per search; code execution bills by sandbox time (with a monthly free allowance). The fix is to bound what the driver can do: max_uses caps how many searches per request, max_tokens caps the length, and allow/block domain lists fence where web search may go. The safety story rhymes with the cost story — the two things that leave your control are the same two you should bound: web search reaches the live internet (fence it with domains) and code execution runs model-written code (but in Anthropic's sandbox, so it can't touch your machine). The common mistake is enabling a tool, watching it work beautifully on one demo query, and shipping without a cap — then discovering the bill after a week of real traffic. Estimate first; the helper below lets you do exactly that, offline, before you spend a cent.

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.

per-request bill = three stacked costs tokens (predictable) + per search (model) + sandbox minutes = total_usd the caps that bound it max_uses → cap searches max_tokens → cap length allowed/blocked_domains a metered ride — bound what the driver can do Tokens are a ticket; server tools are a meter. The bill stacks token cost + per-search fees + sandbox time — and since the model decides how often to spend, you bound it with max_uses, max_tokens, and domain lists.
🗺️ How to read this diagram

Read the left column top-down as a running total, then the right column as the levers that cap it.

  • The left stack adds up one request's bill: ordinary tokens (blue — priced by length, predictable), plus per-search fees and sandbox minutes (amber — priced by use, and the model decides how many), summing to total_usd.
  • The amber rows are the surprise: unlike tokens, you don't set them directly — a request told to "research thoroughly" can fire many searches.
  • The right green boxes are the fixes: max_uses caps searches, max_tokens caps length, and allow/block domain lists fence where search may go — bounding both the cost and the safety surface.

In short: The meter runs on searches and sandbox time the model chooses — cap it with max_uses, max_tokens, and domain lists before you ship.

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 wrong split here doesn't cause a bug — it causes an incident. Route "charge the customer's card" to a tool that runs automatically with no veto from your code, and one confused model turn can move real money before a human ever sees it. Route "summarize today's news" to a hand-built client-side tool, and you've spent a sprint reimplementing a search engine Anthropic would have run for free. Both mistakes come from the same root: not asking the one question that actually decides it.

That question is whose house does the action happen in? Think of it as a trust boundary drawn around your systems, your credentials, and your data. Anything that stays outside that boundary — looking something up on the public web, doing a throwaway calculation, fetching a page anyone could fetch — is safe to let Anthropic run automatically, so it wins as a server-side tool: generic, no infra, nothing of yours at risk. Anything that reaches inside the boundary — your database, your billing API, an email sent as you, anything needing your secrets or a human's approval — must stay a client-side tool, because only your code can inject the credentials, gate the call, and log it for audit. When in doubt, keep it client-side: it's easy to move a safe capability out to a server tool later, but you can't un-spend money a server tool already spent. The mistake to avoid is deciding by convenience ("server tools are less work, so use them") instead of by boundary — the extra work of a client-side tool is the control you're paying for.

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.

your trust boundary — systems · credentials · data web searchpublic web code execthrowaway calc fetch a public URL → server-side (safe to run) your database billing API email as you needs approval → client-side (only your code can gate it) Draw the trust boundary, then place the tool. Outside it — public web, throwaway compute — is safe to let Anthropic run server-side; inside it — your data, credentials, anything needing approval — must stay a client-side tool.
🗺️ How to read this diagram

The big box is the line drawn around your systems, credentials, and data. Sort each capability by which side of it the action happens.

  • The green boxes outside the boundary — web search, a throwaway calculation, fetching a public URL — touch nothing of yours, so they're safe to run automatically as server-side tools.
  • The red boxes inside — your database, billing API, sending email as you, anything needing a human's approval — reach your credentials and data, so only your code can inject secrets, gate the call, and log it: client-side.
  • The rule of thumb: when in doubt, keep it client-side — you can promote a safe capability to a server tool later, but you can't un-spend what a server tool already did.

In short: Outside your boundary → server-side; inside it (data, creds, approval) → client-side.

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

An individual contributor asks "does the tool work?"; a lead asks "what happens when it works ten thousand times a day, and what happens the day the tool version changes underneath us?" Server-side tools are seductive precisely because they're so easy to turn on — one type string and you're live — and that ease is exactly what makes them easy to get wrong at scale. The convenience hides real ownership: a cost meter you don't directly control, a data path that leaves your walls, an error model that never raises, and a version pin that silently depends on the model. None of these bite in a demo; all of them bite in production. The lead's job is to make each one a deliberate decision written down somewhere, not an accident discovered in an incident channel.

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.

Lead owns 4 decisions easy to turn on, easy to get wrong Cost governance cap max_uses, monitor tool bill Data boundary know what leaves, fence with domains Result-reading errors don't raise — branch on shape Client/server boundary decides; pin the version Four decisions, none of which bite in a demo. Cost, data boundary, result-reading, and the client/server split are the ownership the one-line type switch hides — make each a deliberate call.
🗺️ How to read this diagram

Start at the top box — the deceptively simple "turn it on" — then follow the four connectors down to what a lead actually owns.

  • Cost governance — cap invocations with max_uses/max_tokens and monitor the per-request tool bill, not just tokens.
  • Data boundary — web search reaches the public internet and code 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 differs in shape, so the parser must branch.
  • The client/server split — the trust boundary decides placement, and you pin the exact tool type/version because the available variant depends on the model.

In short: The one-line switch hides four production decisions — cost, data boundary, result-reading, and the split — so write each one down deliberately.

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