서버 도구 (Server Tools)¶
Anthropic이 실행하는 도구를 다루는 방법을 정리했어요. server_tool_use 블록, pause_turn 이어가기, 서버 도구와 클라이언트 도구가 섞인 턴, 그리고 도메인 필터링까지, 서버에서 실행되는 도구들이 공유하는 메커니즘을 하나씩 살펴볼게요. 개별 도구에 대한 자세한 내용은 각 도구 레퍼런스를 참고하면 돼요.
server_tool_use 블록¶
서버에서 실행되는 도구를 Claude가 호출하면, 응답에 server_tool_use 블록이 나타나요. 이 블록의 id 필드는 srvtoolu_ 접두사로 시작해서 클라이언트 도구 호출과 구분할 수 있어요.
{
"type": "server_tool_use",
"id": "srvtoolu_01A2B3C4D5E6F7G8H9",
"name": "web_search",
"input": { "query": "latest quantum computing breakthroughs" }
}
서버 도구는 API가 내부적으로 실행해요. 응답에서 호출과 그 결과를 볼 수 있지만, 우리가 직접 실행을 처리하지 않아요. 그래서 클라이언트 tool_use 블록과 달리 tool_result로 응답해 줄 필요가 없어요. 도구의 결과 블록(웹 검색이라면 web_search_tool_result)은 같은 어시스턴트 턴에서 server_tool_use 블록 바로 뒤에 tool_use_id로 짝지어져서 나와요.
한 가지 주의할 점이 있어요. Claude가 서버 도구와 함께 클라이언트 도구 중 하나를 동시에 호출하면, 그때는 server_tool_use 블록이 결과 없이 나타나고 응답이 stop_reason: "tool_use"로 끝나요. 이 경우 다음 요청에서 클라이언트 tool_result 블록을 돌려보내면 API가 해당 도구를 실행해 줘요.
서버 측 루프와 pause_turn¶
웹 검색 같은 서버 도구를 쓰면, API가 서버 측 에이전트 루프에서 도구 호출을 실행해요. 오래 걸리는 턴에서는 API가 그 루프를 일시 중지하고 pause_turn 중지 사유를 돌려줄 수 있어요.
pause_turn을 처리하는 기본 모습을 Python으로 보면 이래요.
client = anthropic.Anthropic()
# 웹 검색을 사용한 초기 요청
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Search for comprehensive information about quantum computing breakthroughs in 2025",
}
],
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 10}],
)
# 응답의 stop_reason이 pause_turn인지 확인
if response.stop_reason == "pause_turn":
# 일시 중지된 콘텐츠로 대화 계속
messages = [
{
"role": "user",
"content": "Search for comprehensive information about quantum computing breakthroughs in 2025",
},
{"role": "assistant", "content": response.content},
]
# 계속 요청 전송
continuation = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 10}],
)
print(continuation)
else:
print(response)
pause_turn을 다룰 때 세 가지만 기억하면 돼요.
- 대화 이어가기 — 일시 중지된 응답을 후속 요청에 그대로 전달해서 Claude가 턴을 계속하도록 해요.
- 도구 상태 유지 — 이어가기 요청에 같은 도구를 꼭 포함하세요. 일시 중지된 턴은 아직 실행되지 않은 도구의
server_tool_use블록으로 끝날 수 있는데, 이어가기 요청에 해당 도구가 없으면 API가 유효성 검사 오류를 돌려줘요. - 필요에 따라 반복 — 이어진 턴도 다시 일시 중지될 수 있어요. 각 응답에서
stop_reason을 확인하고 다른 중지 사유를 받을 때까지 계속하되, 다른 재시도 루프와 마찬가지로 이어가기 횟수에 상한을 두는 게 좋아요.
다른 stop_reason 값과 일반적인 처리 패턴은 중지 사유와 폴백 문서를 참고하세요.
한 턴에서 서버 도구와 클라이언트 도구 혼합하기¶
Claude는 같은 병렬 도구 호출 그룹에서 서버 도구와 클라이언트 도구를 함께 호출할 수 있어요. 예를 들어 web_fetch와 사용자 정의 도구를 동시에 부르는 식이죠. 여기서 클라이언트 도구란 우리 코드가 실행하고 tool_use 블록을 생성하는 모든 도구를 말해요. 사용자 정의 도구든, Bash 도구 같은 Anthropic 스키마의 클라이언트 도구든 상관없어요.
이런 경우 API는 서버 도구를 실행하지 않아요. 클라이언트 도구를 먼저 실행할 수 있도록 바로 반환하는데, 이때 응답의 특징은 다음과 같아요.
stop_reason은"pause_turn"이 아니라"tool_use"예요.content에는server_tool_use블록과 클라이언트tool_use블록이 모두 들어 있지만, 서버 도구의 결과 블록은 없어요. 그 호출이 아직 완료되지 않았기 때문이에요.- 다른 표시는 없어요. 응답 안에서 일치하는 결과 블록이 없는
id를 가진server_tool_use블록을 찾아 이 상태를 감지하세요. MCP 커넥터의mcp_tool_use블록도 똑같이 동작해요. 같은 응답에 이미 결과 블록이 있는 서버 도구 호출은 완료된 것이니 우리가 할 일은 없어요.
프로그래매틱 도구 호출을 쓰는 경우에는 같은 응답 형태가 다른 의미를 가져요. 클라이언트 tool_use 블록은 Claude가 직접 생성한 게 아니라 code_execution 도구에서 실행 중인 코드에서 나온 것이고, 그 caller 필드는 이것을 호출한 code_execution 블록을 가리켜요. 그 코드는 이미 시작된 상태라 우리의 tool_result 블록을 기다리며 일시 중지되어 있죠. 이것을 보내면 지연된 도구를 시작하는 게 아니라 실행이 재개돼요. code_execution 블록 자체의 결과 블록은 코드가 완료되면 도착하는데, 이는 한 번 이상의 도구 결과 라운드가 걸릴 수 있어요. 후속 사용자 메시지 자체는 두 경우 모두 같아요. 프로그래매틱 도구 호출이라면 해당 페이지에서 보여주듯 응답의 container 필드에 있는 id도 함께 전달하세요.
혼합된 응답의 실제 모습을 보면 이래요.
{
"stop_reason": "tool_use",
"content": [
{
"type": "text",
"text": "I'll fetch the article and check your system at the same time."
},
{
"type": "server_tool_use",
"id": "srvtoolu_01HxbWnMRmbWyMfUtJKC45rA",
"name": "web_fetch",
"input": { "url": "https://example.com/article" }
},
{
"type": "tool_use",
"id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"name": "run_command",
"input": { "command": "uname -a" }
}
]
}
턴을 이어가려면 클라이언트 도구를 실행하고, 해당 응답의 각 tool_use 블록마다 하나씩 tool_result 블록만으로 구성된 content를 가진 사용자 메시지를 보내세요. 동일한 tools 배열을 유지해야 해요. 대기 중인 서버 도구를 더 이상 정의하지 않는 재개 요청은 but no web_fetch tool was provided로 끝나는 400 오류와 함께 실패해요.
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
}
]
}
이 결과를 보내면 API가 우리의 결과를 아직 열려 있는 어시스턴트 턴에 첨부하고, 지연된 서버 도구를 실행한 다음(일시 중지된 코드 실행이라면 재개하고), Claude가 계속하도록 해요. Claude가 직접 호출한 서버 도구의 경우, 다음 응답은 이전 응답의 server_tool_use id에 답하는 결과 블록으로 시작하고, 이어서 새로 생성된 콘텐츠와 새로운 stop_reason이 따라와요.
{
"stop_reason": "end_turn",
"content": [
{
"type": "web_fetch_tool_result",
"tool_use_id": "srvtoolu_01HxbWnMRmbWyMfUtJKC45rA",
"content": {
"type": "web_fetch_result",
"url": "https://example.com/article",
"content": {
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "Full text content of the article..."
}
}
}
},
{
"type": "text",
"text": "The article argues that... and your machine is running Linux..."
}
]
}
server_tool_use 블록과 그 결과 블록은 위치가 아니라 tool_use_id로 짝지어져요. 이 흐름에서는 두 블록이 서로 다른 두 응답으로 도착하며, server_tool_use 블록은 두 번째 응답에서 반복되지 않아요. 이후 요청에서는 전체 교환을 messages 배열에 순서대로 유지하세요. 첫 번째 응답을 assistant 메시지로, tool_result 사용자 메시지를, 그리고 다음 응답을 또 다른 assistant 메시지로 넣어요. 다른 도구 사용 교환을 누적하는 것과 같은 방식이에요.
여기서 꼭 지켜야 할 규칙이 있어요. 후속 사용자 메시지에는 tool_result 블록 외에 아무것도 포함되면 안 돼요. 결과 뒤에 텍스트 같은 블록이 추가되면 API는 어시스턴트 턴이 끝났다고 판단해요. Claude가 직접 호출한 서버 도구라면, 이렇게 하면 턴에 해결되지 않은 서버 도구 호출이 남게 되어 요청이 400 invalid_request_error와 함께 실패해요.
`web_fetch` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_fetch_tool_result` block
반대로 결과 앞에 콘텐츠를 넣거나, 클라이언트 tool_use ID 중 일부에만 답하거나, tool_result 블록이 전혀 없는 후속 메시지는 더 일찍 실패해요. 이때는 도구 호출 처리 문서에서 설명하는 클라이언트 도구 오류가 발생해요.
`tool_use` ids were found without `tool_result` blocks immediately after: toolu_01PjgRJLbXrXEMZwDNYLnBqk. Each `tool_use` block must have a corresponding `tool_result` block in the next message.
Claude에게 추가 입력을 주고 싶다면, 턴이 완료된 후에 별도의 사용자 메시지로 보내세요.
pause_turn과의 차이점¶
pause_turn 응답도 아직 실행되지 않은 server_tool_use 블록으로 끝날 수 있어요. 하지만 우리를 기다리는 클라이언트 tool_use 블록을 남기지는 않으므로, 어시스턴트 콘텐츠를 그대로 다시 보내서 이어가면 돼요. 반면 우리를 기다리는 클라이언트 tool_use 블록을 남기는 응답은 stop_reason이 pause_turn인 경우가 절대 없어요. Claude가 우리의 도구를 호출하기 위해 멈출 때 stop_reason은 tool_use이며, 응답을 다시 보내는 게 아니라 클라이언트 tool_result 블록을 보내서 이어가요. 두 경우 모두 API는 다음 요청이 시작될 때 대기 중인 서버 도구를 실행해요.
혼합 응답을 처리하는 실제 예제¶
다음 예제는 웹 가져오기를 사용자 정의 run_command 도구와 함께 활성화하고, 혼합된 응답을 처리하는 코드예요.
client = anthropic.Anthropic()
tools = [
{"type": "web_fetch_20250910", "name": "web_fetch", "max_uses": 5},
{
"name": "run_command",
"description": "Run a shell command on this computer and return its output.",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "The command to run"}
},
"required": ["command"],
},
},
]
messages = [
{
"role": "user",
"content": "Summarize https://example.com/article and run uname -a to tell me what system this is on.",
}
]
response = client.messages.create(
model="claude-opus-4-8", max_tokens=1024, tools=tools, messages=messages
)
tool_results = [
{
"type": "tool_result",
"tool_use_id": block.id,
# 여기서 도구를 실행하세요. 이 예제는 고정된 문자열을 반환합니다.
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux",
}
for block in response.content
if block.type == "tool_use"
]
if response.stop_reason == "tool_use" and tool_results:
# 이 응답에서 결과 블록이 없는 server_tool_use 블록은 아직 완료되지 않은 것이며, 그 결과는 이후 응답에서 도착합니다.
# 동일한 도구와 함께 클라이언트 tool_result 블록만 다시 보내세요.
continuation = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
messages=[
*messages,
{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results},
],
)
# web_fetch가 지연된 경우, 이 요청에서 실행되며 그
# web_fetch_tool_result는 continuation.content의 첫 번째 블록이 됩니다.
print(continuation)
else:
print(response)
이 코드는 Claude가 두 종류의 호출을 혼합하지 않는 경우에도 올바르게 동작해요. 클라이언트 tool_use 블록만 있는 턴은 같은 이어가기 경로를 따르고, 서버 도구 호출만 있는 턴은 우리의 클라이언트 tool_result 블록이 필요 없어요. 그 결과 블록은 보통 이미 존재하며, pause_turn 응답처럼 중단된 상태로 돌아오는 경우에는 대신 그대로 다시 보내요.
ZDR과 allowed_callers¶
웹 검색(web_search_20250305)과 웹 가져오기(web_fetch_20250910)의 기본 버전은 Zero Data Retention(ZDR) 적격이에요. 반면 동적 필터링이 포함된 _20260209 및 이후 버전은, 동적 필터링이 내부적으로 코드 실행에 의존하기 때문에 기본적으로 ZDR 적격이 아니에요.
_20260209 또는 이후 버전의 서버 도구를 ZDR과 함께 쓰려면, 도구에 "allowed_callers": ["direct"]를 설정해서 동적 필터링을 비활성화하세요.
이렇게 하면 도구가 직접 호출로만 제한되어 내부 코드 실행 단계를 건너뛰어요.
allowed_callers는 도구를 호출할 수 있는 방식을 제어해요. Claude가 직접 호출하는 경우("direct"), 코드 실행 컨테이너 내부에서 호출하는 경우(예: "code_execution_20260120"), 또는 둘 다 가능하도록 할 수 있어요. 웹 도구의 _20260209 버전은 기본적으로 코드 실행 호출자만 허용하고, 이전 버전은 기본값이 ["direct"]예요. 프로그래매틱 도구 호출을 지원하지 않는 모델에서는 이러한 버전에 allowed_callers: ["direct"]가 필요해요. 이것이 없으면 API는 설정하라는 유효성 검사 오류를 돌려줘요.
한 가지 짚고 넘어갈 점이 있어요. 웹 가져오기가 ZDR 적격 구성으로 사용되더라도, Claude가 웹사이트에서 콘텐츠를 가져오는 경우 웹사이트 게시자는 URL에 전달된 모든 매개변수를 보존할 수 있어요.
도메인 필터링¶
웹에 접근하는 서버 도구는, Claude가 도달할 수 있는 도메인을 제어하기 위해 allowed_domains와 blocked_domains 매개변수를 받아요. 둘 다 도구 객체의 필드예요.
{
"type": "web_search_20250305",
"name": "web_search",
"allowed_domains": ["example.com", "docs.python.org"]
}
도메인 필터를 쓸 때 규칙을 정리하면 이래요.
- 도메인에는 HTTP/HTTPS 스킴을 포함하지 않아요(
https://example.com대신example.com사용). - 하위 도메인은 자동으로 포함돼요(
example.com은docs.example.com을 포함). - 특정 하위 도메인을 지정하면 결과가 해당 하위 도메인으로만 제한돼요(
docs.example.com은example.com이나api.example.com이 아닌 해당 하위 도메인의 결과만 반환). - 하위 경로는 웹 검색에서 지원되며, 경로 뒤의 모든 것과 일치해요(
example.com/blog는example.com/blog/post-1과 일치). - 웹 가져오기는 도메인만으로 일치 여부를 판단해요. 경로를 포함하는 항목은 웹 가져오기 URL과 절대 일치하지 않아요.
allowed_domains또는blocked_domains중 하나를 사용할 수 있지만, 같은 요청에서 둘 다 사용할 수 없어요.
와일드카드 지원도 확인해 둘게요.
- 와일드카드(
*)는 도메인 자체에는 허용되지 않고, 도메인 뒤의 경로에서만 허용돼요. - 유효:
example.com/*,example.com/*/articles - 무효:
*.example.com,ex*.com
잘못된 도메인 형식은 요청 시점에 400 invalid_request_error로 거부돼요.
요청 수준 도메인 제한은 Claude Console에서 구성된 조직 수준 도메인 제한과 함께 작동해요. 요청 수준 allowed_domains는 조직 수준 허용 목록의 부분 집합이어야 하며, 그 밖의 항목이 있으면 API가 유효성 검사 오류를 돌려줘요. 조직에서 차단한 도메인을 포함하는 요청 수준 허용 목록은, 충돌하는 항목을 명시하는 400 오류로 거부돼요.
보안 관점에서 꼭 잊지 말아야 할 게 있어요. 도메인 이름의 유니코드 문자는 동형 문자 공격을 통해 도메인 필터를 우회할 수 있어요. аmazon.com(키릴 문자 а 사용)은 amazon.com과 똑같이 보이지만 다른 도메인이에요. 허용 및 차단 목록에는 ASCII 전용 도메인 이름을 사용하고, 기존 항목에 비ASCII 문자가 있는지 점검하세요.
Claude Managed Agents는 에이전트 도구 세트의 web_search 및 web_fetch 항목에서 같은 allowed_domains와 blocked_domains 필드를 사용해요. Managed Agents에서는 각 목록이 최대 64개 항목을 가질 수 있고, web_fetch에 나열된 도메인은 경로를 포함할 수 없으며, max_uses, citations, cache_control 같은 Messages API 도구 전용 필드는 사용할 수 없어요. 전체 규칙은 웹 검색 및 웹 가져오기 도메인 제한 문서를 참고하세요.
참고로 Claude Console의 조직 수준 웹 검색 및 웹 가져오기 설정은 Messages API 요청에만 적용돼요. 에이전트 도구 세트의 도구별 목록만 사용하는 Managed Agents 세션에는 적용되지 않아요.
코드 실행을 통한 동적 필터링¶
웹 검색과 웹 가져오기의 _20260209 및 이후 버전은, 검색 결과에 동적 필터를 적용하기 위해 내부적으로 코드 실행을 사용해요.
이때 한 가지 알아두면 쉬워요. 이러한 버전에는 code_execution 도구를 추가할 필요가 없어요. 동적 필터링이 실행될 때 API가 요청에 대해 코드 실행을 자동으로 프로비저닝하며, 두 도구는 단일 실행 컨테이너를 공유해요. 직접 포함한다면 code_execution_20260120 또는 이후 버전을 사용하세요. API는 이러한 웹 도구 버전과 함께 사용되는 이전 코드 실행 버전을 거부해요.
서버 도구 이벤트 스트리밍¶
서버 도구 이벤트는 일반적인 server-sent events(SSE) 흐름의 일부로 스트리밍돼요. Claude가 직접 호출하는 server_tool_use 블록은 클라이언트 tool_use 블록처럼 스트리밍돼요. content_block_start 이벤트 뒤에 input_json_delta 이벤트가 이어져요. 결과 블록은 델타 없이 단일 content_block_start 이벤트로 완전한 형태로 도착해요.
전체 이벤트 레퍼런스는 스트리밍 문서를 참고하세요. 개별 도구 페이지에서는 도구별 이벤트 이름이 다른 경우 이를 문서화해요.
배치 요청¶
모든 서버 도구는 배치 처리를 지원해요. 배치에서 에이전트 루프는 동기 요청과 동일하게 실행되며, 턴당 반복 한도가 더 높아요. 루프가 그 한도에 도달하면 응답은 stop_reason: "pause_turn"으로 끝나고, 반환된 콘텐츠로 후속 요청을 제출해서 이어갈 수 있어요. 자세한 내용은 서버 도구와 에이전트 루프 문서를 참고하세요.
일반적인 배치 워크로드에는 웹의 정보로 데이터셋을 보강하기, 대량의 문서를 최신 출처와 대조해 확인하기, 많은 파일에 대해 분석 코드 실행하기 같은 작업이 있어요.
다음 단계¶
- 도구 사용 문제 해결 — 증상별 해결 진단 표로 가장 흔한 도구 사용 오류를 해결하세요.
- 웹 검색 도구 — 웹을 검색하고 결과를 인용하세요.
- 웹 가져오기 도구 — 특정 URL에서 콘텐츠를 가져와 읽어, 실시간 웹 콘텐츠로 Claude의 컨텍스트를 보강하세요.
- 코드 실행 도구 — 샌드박스 컨테이너에서 Python 및 bash 코드를 실행해 데이터를 분석하고, 파일을 생성하고, 솔루션을 반복 개선하세요.
- 도구 검색 도구 — 필요에 따라 도구를 발견하고 로드하세요.