---
title: 서버 도구
url: https://platform.claude.com/docs/ko/agents-and-tools/tool-use/server-tools
description: "Anthropic이 실행하는 도구 다루기: server_tool_use 블록, pause_turn 이어가기, 서버 도구와 클라이언트 도구가 혼합된 턴, 도메인 필터링."
---

서버에서 실행되는 도구는 다음과 같은 메커니즘을 공유합니다: `server_tool_use` 블록, `pause_turn` 이어가기, 서버 도구와 클라이언트 도구가 혼합된 턴, "Zero Data Retention"(제로 데이터 보존), 즉 ZDR 적격성, 그리고 도메인 필터링입니다. 개별 도구에 대해서는 [도구 레퍼런스](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/tool-reference)를 참조하세요.

## server\_tool\_use 블록

`server_tool_use` 블록은 서버에서 실행되는 도구가 실행될 때 Claude의 응답에 나타납니다. 이 블록의 `id` 필드는 클라이언트 도구 호출과 구분하기 위해 `srvtoolu_` 접두사를 사용합니다:

```json
{
  "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` 중지 사유를 처리하는 방법은 다음과 같습니다:

<CodeGroup>
  ```bash cURL
  # 초기 요청입니다. 응답의 "stop_reason"이 "pause_turn"이면,
  # assistant 콘텐츠를 messages에 추가한 뒤 요청을 다시 보내 턴을 이어 가세요.
  curl https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-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}]
    }'
  ```

  ```bash CLI
  # 초기 요청입니다. 출력의 "stop_reason"이 "pause_turn"이면 다음과 같이 다시 실행하세요:
  # assistant 콘텐츠를 messages에 추가한 후 실행합니다(SDK 탭 참조).
  ant messages create <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  tools:
    - {type: web_search_20250305, name: web_search, max_uses: 10}
  messages:
    - {role: user, content: "Search for comprehensive information about quantum computing breakthroughs in 2025"}
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  # 웹 검색을 사용하는 초기 요청
  response = client.messages.create(
      model="claude-opus-5-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-5",
          max_tokens=1024,
          messages=messages,
          tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 10}],
      )

      print(continuation)
  else:
      print(response)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  // 웹 검색을 사용한 초기 요청
  const response = await client.messages.create({
    model: "claude-opus-5-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") {
    // 일시 중지된 콘텐츠로 대화를 이어갑니다
    const messages: Anthropic.MessageParam[] = [
      {
        role: "user",
        content:
          "Search for comprehensive information about quantum computing breakthroughs in 2025"
      },
      { role: "assistant", content: response.content }
    ];

    // 후속 요청을 전송합니다
    const continuation = await client.messages.create({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      messages,
      tools: [
        {
          type: "web_search_20250305",
          name: "web_search",
          max_uses: 10
        }
      ]
    });

    console.log(continuation);
  } else {
    console.log(response);
  }
  ```

  ```csharp C#
  AnthropicClient client = new();

  var parameters = new MessageCreateParams
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Messages = [
          new() {
              Role = Role.User,
              Content = "Search for comprehensive information about quantum computing breakthroughs in 2025"
          }
      ],
      Tools = [new ToolUnion(new WebSearchTool20250305 { MaxUses = 10 })]
  };

  var response = await client.Messages.Create(parameters);

  if (response.StopReason?.Value() == StopReason.PauseTurn)
  {
      // 일시 중지된 콘텐츠로 대화를 이어 갑니다
      var continuationParams = new MessageCreateParams
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          Messages = [
              new() {
                  Role = Role.User,
                  Content = "Search for comprehensive information about quantum computing breakthroughs in 2025"
              },
              new() {
                  Role = Role.Assistant,
                  Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList()
              }
          ],
          Tools = [new ToolUnion(new WebSearchTool20250305 { MaxUses = 10 })]
      };

      var continuation = await client.Messages.Create(continuationParams);
      Console.WriteLine(continuation);
  }
  else
  {
      Console.WriteLine(response);
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  webSearchTool := []anthropic.ToolUnionParam{
  	{OfWebSearchTool20250305: &anthropic.WebSearchTool20250305Param{
  		MaxUses: anthropic.Int(10),
  	}},
  }

  response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Search for comprehensive information about quantum computing breakthroughs in 2025")),
  	},
  	Tools: webSearchTool,
  })
  if err != nil {
  	log.Fatal(err)
  }

  if response.StopReason == anthropic.StopReasonPauseTurn {
  	// Claude가 턴을 이어갈 수 있도록 일시 중지된 응답을 그대로 다시 전달합니다
  	continuation, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  		Model:     anthropic.ModelClaudeOpus5_5,
  		MaxTokens: 1024,
  		Messages: []anthropic.MessageParam{
  			anthropic.NewUserMessage(anthropic.NewTextBlock("Search for comprehensive information about quantum computing breakthroughs in 2025")),
  			response.ToParam(),
  		},
  		Tools: webSearchTool,
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	fmt.Println(continuation)
  } else {
  	fmt.Println(response)
  }
  ```

  ```java Java
  import com.anthropic.models.messages.StopReason;
  import com.anthropic.models.messages.WebSearchTool20250305;

  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024L)
          .addUserMessage("Search for comprehensive information about quantum computing breakthroughs in 2025")
          .addTool(WebSearchTool20250305.builder()
              .maxUses(10L)
              .build())
          .build();

      Message response = client.messages().create(params);

      if (response.stopReason().isPresent()
              && response.stopReason().get().equals(StopReason.PAUSE_TURN)) {
          MessageCreateParams continuationParams = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024L)
              .addUserMessage("Search for comprehensive information about quantum computing breakthroughs in 2025")
              .addMessage(response)
              .addTool(WebSearchTool20250305.builder()
                  .maxUses(10L)
                  .build())
              .build();

          Message continuation = client.messages().create(continuationParams);
          IO.println(continuation);
      } else {
          IO.println(response);
      }
  }
  ```

  ```php PHP
  $client = new Client();

  $response = $client->messages->create(
      maxTokens: 1024,
      messages: [
          [
              'role' => 'user',
              'content' => 'Search for comprehensive information about quantum computing breakthroughs in 2025'
          ]
      ],
      model: 'claude-opus-5-5',
      tools: [
          [
              'type' => 'web_search_20250305',
              'name' => 'web_search',
              'max_uses' => 10
          ]
      ],
  );

  if ($response->stopReason === '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(
          maxTokens: 1024,
          messages: $messages,
          model: 'claude-opus-5-5',
          tools: [
              [
                  'type' => 'web_search_20250305',
                  'name' => 'web_search',
                  'max_uses' => 10
              ]
          ],
      );

      echo $continuation;
  } else {
      echo $response;
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  response = client.messages.create(
    model: "claude-opus-5-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
      }
    ]
  )

  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-5",
      max_tokens: 1024,
      messages: messages,
      tools: [
        {
          type: "web_search_20250305",
          name: "web_search",
          max_uses: 10
        }
      ]
    )

    puts continuation
  else
    puts response
  end
  ```
</CodeGroup>

`pause_turn`을 처리할 때:

* **대화 이어가기:** 일시 중지된 응답을 후속 요청에 그대로 전달하여 Claude가 턴을 계속하도록 합니다.
* **도구 상태 유지:** 이어가기 요청에 동일한 도구를 포함하세요. 일시 중지된 턴은 아직 실행되지 않은 도구의 `server_tool_use` 블록으로 끝날 수 있으며, 이어가기 요청에 해당 도구가 없으면 API가 유효성 검사 오류를 반환합니다.
* **필요에 따라 반복:** 이어진 턴이 다시 일시 중지될 수 있습니다. 각 응답에서 `stop_reason`을 확인하고 다른 중지 사유를 받을 때까지 계속하되, 다른 재시도 루프와 마찬가지로 이어가기 횟수에 상한을 두세요.

다른 `stop_reason` 값과 일반적인 처리 패턴에 대해서는 [중지 사유와 폴백](https://platform.claude.com/docs/ko/build-with-claude/handling-stop-reasons)을 참조하세요.

## 한 턴에서 서버 도구와 클라이언트 도구 혼합하기

Claude는 동일한 병렬 도구 호출 그룹에서 서버 도구와 클라이언트 도구를 함께 호출할 수 있습니다. 예를 들어 `web_fetch`와 사용자 정의 도구를 함께 호출할 수 있습니다. 클라이언트 도구란 여러분의 코드가 실행하고 `tool_use` 블록을 생성하는 모든 도구를 말하며, 사용자 정의 도구이든 [Bash 도구](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/bash-tool)와 같은 Anthropic 스키마 클라이언트 도구이든 상관없습니다. 이런 경우 API는 서버 도구를 실행하지 않습니다. 클라이언트 도구를 먼저 실행할 수 있도록 즉시 반환합니다:

* `stop_reason`은 `"pause_turn"`이 아니라 `"tool_use"`입니다.
* `content`에는 `server_tool_use` 블록과 클라이언트 `tool_use` 블록이 포함되지만, 서버 도구의 결과 블록은 없습니다. 해당 호출이 아직 완료되지 않았기 때문입니다.
* 다른 표시는 없습니다. 응답 내에 일치하는 결과 블록이 없는 `id`를 가진 `server_tool_use` 블록을 찾아 이 상태를 감지하세요. [MCP 커넥터](https://platform.claude.com/docs/ko/agents-and-tools/mcp-connector)의 `mcp_tool_use` 블록도 동일하게 동작합니다. 동일한 응답에 이미 결과 블록이 있는 서버 도구 호출은 완료된 것이며 여러분이 할 일은 없습니다.

<Note>
  [프로그래매틱 도구 호출](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/programmatic-tool-calling)을 사용하는 경우, 동일한 응답 형태가 다른 의미를 가집니다. 클라이언트 `tool_use` 블록은 Claude가 직접 생성한 것이 아니라 `code_execution` 도구에서 실행 중인 코드에서 나온 것이며, 그 `caller` 필드는 이를 호출한 `code_execution` 블록을 가리킵니다. 해당 코드는 이미 시작된 상태입니다. 여러분의 `tool_result` 블록을 기다리며 일시 중지되어 있고, 이를 보내면 지연된 도구를 시작하는 것이 아니라 실행이 재개됩니다. `code_execution` 블록 자체의 결과 블록은 코드가 완료되면 도착하며, 이는 한 번 이상의 도구 결과 라운드가 걸릴 수 있습니다. 후속 사용자 메시지 자체는 두 경우 모두 동일합니다. 프로그래매틱 도구 호출의 경우 해당 페이지에서 보여주는 것처럼 응답의 `container` 필드에 있는 `id`도 함께 전달하세요.
</Note>

```json
{
  "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 오류와 함께 실패합니다.

```json
{
  "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`이 뒤따릅니다:

```json
{
  "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` 메시지로 넣습니다. 다른 도구 사용 교환을 누적하는 것과 같은 방식입니다.

<Warning>
  후속 사용자 메시지에는 `tool_result` 블록 외에 아무것도 포함되어서는 안 됩니다. 결과 뒤에 텍스트와 같은 블록이 추가되면 API는 어시스턴트 턴이 끝났다고 판단합니다. Claude가 직접 호출한 서버 도구의 경우, 이렇게 하면 턴에 해결되지 않은 서버 도구 호출이 남게 되어 요청이 400 `invalid_request_error`와 함께 실패합니다:

  ```text wrap
  `web_fetch` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_fetch_tool_result` block
  ```

  결과 앞에 콘텐츠를 넣거나, 클라이언트 `tool_use` ID 중 일부에만 답하거나, `tool_result` 블록이 전혀 없는 후속 메시지는 더 일찍 실패하며, [도구 호출 처리](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/handle-tool-calls)에 설명된 클라이언트 도구 오류가 발생합니다:

  ```text wrap
  `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에게 추가 입력을 제공하려면 턴이 완료된 후 별도의 사용자 메시지로 보내세요.
</Warning>

**`pause_turn`과의 차이점:** [`pause_turn` 응답](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/server-tools#the-server-side-loop-and-pause-turn)도 아직 실행되지 않은 `server_tool_use` 블록으로 끝날 수 있지만, 여러분을 기다리는 클라이언트 `tool_use` 블록을 남기지는 않으므로 어시스턴트 콘텐츠를 그대로 다시 보내서 이어갑니다. 여러분을 기다리는 클라이언트 `tool_use` 블록을 남기는 응답은 `stop_reason`이 `pause_turn`인 경우가 절대 없습니다. Claude가 여러분의 도구를 호출하기 위해 멈출 때 `stop_reason`은 `tool_use`이며, 응답을 다시 보내는 것이 아니라 클라이언트 `tool_result` 블록을 보내서 이어갑니다. 두 경우 모두 API는 다음 요청이 시작될 때 대기 중인 서버 도구를 실행합니다.

다음 예제는 웹 가져오기를 사용자 정의 `run_command` 도구와 함께 활성화하고 혼합된 응답을 처리합니다:

<CodeGroup>
  ```bash cURL
  # "stop_reason"이 "tool_use"이고 server_tool_use 블록에 일치하는
  # 결과 블록이 없으면 해당 호출은 완료되지 않은 것입니다. 클라이언트 도구를 실행한 다음,
  # 해당 tool_result 블록만 포함하는 user 메시지 하나와 동일한 tools 배열을 추가하여
  # 다시 POST 하세요(SDK 탭 참조).
  curl https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "messages": [
        {
          "role": "user",
          "content": "Summarize https://example.com/article and run uname -a to tell me what system this is on."
        }
      ],
      "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"]
          }
        }
      ]
    }'
  ```

  ```bash CLI
  # "stop_reason"이 "tool_use"이고 server_tool_use 블록에 일치하는
  # 결과 블록이 없으면 클라이언트 도구를 실행한 뒤 해당 tool_result 블록만 담은
  # 사용자 메시지를 추가하여 다시 실행하세요(SDK 탭 참조).
  ant messages create <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  messages:
    - role: user
      content: "Summarize https://example.com/article and run uname -a to tell me what system this is on."
  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]
  YAML
  ```

  ```python Python
  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-5-5", 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-5-5",
          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)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const webFetchTool = {
    type: "web_fetch_20250910",
    name: "web_fetch",
    max_uses: 5
  } as const;
  const runCommandTool: Anthropic.Tool = {
    name: "run_command",
    description: "Run a shell command on this computer and return its output.",
    input_schema: {
      type: "object" as const,
      properties: {
        command: { type: "string", description: "The command to run" }
      },
      required: ["command"]
    }
  };
  const messages: Anthropic.MessageParam[] = [
    {
      role: "user",
      content:
        "Summarize https://example.com/article and run uname -a to tell me what system this is on."
    }
  ];

  const response = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    tools: [webFetchTool, runCommandTool],
    messages
  });

  const toolResults: Anthropic.ToolResultBlockParam[] = [];
  for (const block of response.content) {
    if (block.type === "tool_use") {
      toolResults.push({
        type: "tool_result",
        tool_use_id: block.id,
        // 여기서 도구를 실행합니다. 이 예제는 고정된 문자열을 반환합니다.
        content: "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
      });
    }
  }

  if (response.stop_reason === "tool_use" && toolResults.length > 0) {
    // 이 응답에서 결과 블록이 없는 server_tool_use 블록은 완료되지 않은 것이며, 그 결과는 이후 응답에서 도착합니다.
    // 동일한 도구와 함께 클라이언트 tool_result 블록만 다시 전송합니다.
    const continuation = await client.messages.create({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [webFetchTool, runCommandTool],
      messages: [
        ...messages,
        { role: "assistant", content: response.content },
        { role: "user", content: toolResults }
      ]
    });
    // web_fetch가 지연된 경우 이 요청에서 실행되며, 그
    // web_fetch_tool_result는 continuation.content의 첫 번째 블록이 됩니다.
    console.log(continuation);
  } else {
    console.log(response);
  }
  ```

  ```csharp C#
  AnthropicClient client = new();

  List<ToolUnion> tools =
  [
      new ToolUnion(new WebFetchTool20250910() { MaxUses = 5 }),
      new ToolUnion(new Tool()
      {
          Name = "run_command",
          Description = "Run a shell command on this computer and return its output.",
          InputSchema = new InputSchema()
          {
              Properties = new Dictionary<string, JsonElement>
              {
                  ["command"] = JsonSerializer.SerializeToElement(
                      new { type = "string", description = "The command to run" }
                  ),
              },
              Required = ["command"],
          },
      }),
  ];
  MessageParam userMessage = new()
  {
      Role = Role.User,
      Content = "Summarize https://example.com/article and run uname -a to tell me what system this is on."
  };

  var response = await client.Messages.Create(new MessageCreateParams
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Tools = tools,
      Messages = [userMessage]
  });

  var toolResults = new List<ContentBlockParam>();
  foreach (var block in response.Content)
  {
      if (block.TryPickToolUse(out var toolUse))
      {
          toolResults.Add(new ContentBlockParam(new ToolResultBlockParam()
          {
              ToolUseID = toolUse.ID,
              // 여기서 도구를 실행하세요. 이 예제는 고정된 문자열을 반환합니다.
              Content = "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux",
          }));
      }
  }

  if (response.StopReason?.Value() == StopReason.ToolUse && toolResults.Count > 0)
  {
      // 이 응답에서 결과 블록이 없는 server_tool_use 블록은 완료되지 않은 것이며, 그 결과는 이후 응답에서 도착합니다.
      // 동일한 도구와 함께 클라이언트 tool_result 블록만 다시 보내세요.
      var continuation = await client.Messages.Create(new MessageCreateParams
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          Tools = tools,
          Messages =
          [
              userMessage,
              new()
              {
                  Role = Role.Assistant,
                  Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList()
              },
              new() { Role = Role.User, Content = new MessageParamContent(toolResults) }
          ]
      });
      // web_fetch가 지연된 경우 이 요청에서 실행되며, 그
      // web_fetch_tool_result는 continuation.Content의 첫 번째 블록이 됩니다.
      Console.WriteLine(continuation);
  }
  else
  {
      Console.WriteLine(response);
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  tools := []anthropic.ToolUnionParam{
  	{OfWebFetchTool20250910: &anthropic.WebFetchTool20250910Param{
  		MaxUses: anthropic.Int(5),
  	}},
  	{OfTool: &anthropic.ToolParam{
  		Name:        "run_command",
  		Description: anthropic.String("Run a shell command on this computer and return its output."),
  		InputSchema: anthropic.ToolInputSchemaParam{
  			Properties: map[string]any{
  				"command": map[string]any{
  					"type":        "string",
  					"description": "The command to run",
  				},
  			},
  			Required: []string{"command"},
  		},
  	}},
  }
  userMessage := anthropic.NewUserMessage(anthropic.NewTextBlock("Summarize https://example.com/article and run uname -a to tell me what system this is on."))

  response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Tools:     tools,
  	Messages:  []anthropic.MessageParam{userMessage},
  })
  if err != nil {
  	log.Fatal(err)
  }

  var toolResults []anthropic.ContentBlockParamUnion
  for _, block := range response.Content {
  	if toolUse, ok := block.AsAny().(anthropic.ToolUseBlock); ok {
  		// 여기서 도구를 실행하세요. 이 예제는 고정된 문자열을 반환합니다.
  		output := "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
  		toolResults = append(toolResults, anthropic.NewToolResultBlock(toolUse.ID, output, false))
  	}
  }

  if response.StopReason == anthropic.StopReasonToolUse && len(toolResults) > 0 {
  	// 이 응답에서 결과 블록이 없는 server_tool_use 블록은 완료되지 않은 것이며, 그 결과는 이후 응답에서 도착합니다.
  	// 동일한 도구와 함께 클라이언트 tool_result 블록만 다시 전송합니다.
  	continuation, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  		Model:     anthropic.ModelClaudeOpus5_5,
  		MaxTokens: 1024,
  		Tools:     tools,
  		Messages: []anthropic.MessageParam{
  			userMessage,
  			response.ToParam(),
  			anthropic.NewUserMessage(toolResults...),
  		},
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	// web_fetch가 지연된 경우 이 요청에서 실행되며, 그
  	// web_fetch_tool_result는 continuation.Content의 첫 번째 블록이 됩니다.
  	fmt.Println(continuation)
  } else {
  	fmt.Println(response)
  }
  ```

  ```java Java
  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      Tool runCommandTool = Tool.builder()
          .name("run_command")
          .description("Run a shell command on this computer and return its output.")
          .inputSchema(Tool.InputSchema.builder()
              .properties(JsonValue.from(Map.of(
                  "command", Map.of("type", "string", "description", "The command to run")
              )))
              .putAdditionalProperty("required", JsonValue.from(List.of("command")))
              .build())
          .build();
      String prompt = "Summarize https://example.com/article and run uname -a to tell me what system this is on.";

      Message response = client.messages().create(MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024L)
          .addTool(WebFetchTool20250910.builder().maxUses(5L).build())
          .addTool(runCommandTool)
          .addUserMessage(prompt)
          .build());

      List<ContentBlockParam> toolResults = new ArrayList<>();
      for (ContentBlock block : response.content()) {
          block.toolUse().ifPresent(toolUse -> toolResults.add(ContentBlockParam.ofToolResult(
              ToolResultBlockParam.builder()
                  .toolUseId(toolUse.id())
                  // 여기서 도구를 실행하세요. 이 예제는 고정된 문자열을 반환합니다.
                  .content("Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux")
                  .build()
          )));
      }

      boolean isToolUse = response.stopReason()
          .map(StopReason.TOOL_USE::equals)
          .orElse(false);
      if (isToolUse && !toolResults.isEmpty()) {
          // 이 응답에서 결과 블록이 없는 server_tool_use 블록은 완료되지 않은 것이며, 그 결과는 이후 응답에서 도착합니다.
          // 동일한 도구와 함께 클라이언트 tool_result 블록만 다시 전송합니다.
          Message continuation = client.messages().create(MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024L)
              .addTool(WebFetchTool20250910.builder().maxUses(5L).build())
              .addTool(runCommandTool)
              .addUserMessage(prompt)
              .addMessage(response)
              .addUserMessageOfBlockParams(toolResults)
              .build());
          // web_fetch가 지연된 경우 이 요청에서 실행되며, 그
          // web_fetch_tool_result는 continuation.content()의 첫 번째 블록이 됩니다.
          IO.println(continuation);
      } else {
          IO.println(response);
      }
  }
  ```

  ```php PHP
  $client = new Client();

  $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']
          ]
      ]
  ];
  $userMessage = ['role' => 'user', 'content' => 'Summarize https://example.com/article and run uname -a to tell me what system this is on.'];

  $response = $client->messages->create(
      maxTokens: 1024,
      messages: [$userMessage],
      model: 'claude-opus-5-5',
      tools: $tools,
  );

  $toolResults = [];
  foreach ($response->content as $block) {
      if ($block->type === 'tool_use') {
          $toolResults[] = [
              'type' => 'tool_result',
              'tool_use_id' => $block->id,
              // 여기서 도구를 실행합니다. 이 예제는 고정된 문자열을 반환합니다.
              'content' => 'Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux'
          ];
      }
  }

  if ($response->stopReason === 'tool_use' && count($toolResults) > 0) {
      // 이 응답에서 결과 블록이 없는 server_tool_use 블록은 완료되지 않은 것이며, 그 결과는 이후 응답에서 도착합니다.
      // 동일한 도구와 함께 클라이언트 tool_result 블록만 다시 전송합니다.
      $continuation = $client->messages->create(
          maxTokens: 1024,
          messages: [
              $userMessage,
              ['role' => 'assistant', 'content' => $response->content],
              ['role' => 'user', 'content' => $toolResults],
          ],
          model: 'claude-opus-5-5',
          tools: $tools,
      );
      // web_fetch가 지연된 경우 이 요청에서 실행되며, 그
      // web_fetch_tool_result는 $continuation->content의 첫 번째 블록이 됩니다.
      echo $continuation;
  } else {
      echo $response;
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  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"]
      }
    }
  ]
  user_message = {
    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-5-5",
    max_tokens: 1024,
    tools: tools,
    messages: [user_message]
  )

  tool_results = []
  response.content.each do |block|
    next unless block.type == :tool_use

    tool_results << {
      type: "tool_result",
      tool_use_id: block.id,
      # 여기서 도구를 실행하세요. 이 예제는 고정된 문자열을 반환합니다.
      content: "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
    }
  end

  if response.stop_reason == :tool_use && !tool_results.empty?
    # 이 응답에서 결과 블록이 없는 server_tool_use 블록은 아직 완료되지 않은 것이며, 그 결과는 이후 응답에서 도착합니다.
    # 동일한 도구와 함께 클라이언트 tool_result 블록만 다시 전송하세요.
    continuation = client.messages.create(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: tools,
      messages: [
        user_message,
        { role: "assistant", content: response.content },
        { role: "user", content: tool_results }
      ]
    )
    # web_fetch가 지연된 경우 이 요청에서 실행되며, 그
    # web_fetch_tool_result는 continuation.content의 첫 번째 블록이 됩니다.
    puts continuation
  else
    puts response
  end
  ```
</CodeGroup>

이 코드는 Claude가 두 종류의 호출을 혼합하지 않는 경우에도 올바르게 동작합니다. 클라이언트 `tool_use` 블록만 있는 턴은 동일한 이어가기 경로를 따르며, 서버 도구 호출만 있는 턴은 여러분의 클라이언트 `tool_result` 블록이 필요하지 않습니다. 그 결과 블록은 보통 이미 존재하며, [`pause_turn` 응답](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/server-tools#the-server-side-loop-and-pause-turn)처럼 중단된 상태로 돌아오는 경우에는 대신 그대로 다시 보냅니다.

## ZDR과 allowed\_callers

웹 검색(`web_search_20250305`)과 웹 가져오기(`web_fetch_20250910`)의 기본 버전은 [Zero Data Retention (ZDR)](https://platform.claude.com/docs/ko/manage-claude/api-and-data-retention) 적격입니다.

동적 필터링이 포함된 `_20260209` 및 이후 버전은 동적 필터링이 내부적으로 코드 실행에 의존하기 때문에 기본적으로 ZDR 적격이 **아닙니다**.

`_20260209` 또는 이후 버전의 서버 도구를 ZDR과 함께 사용하려면, 도구에 `"allowed_callers": ["direct"]`를 설정하여 동적 필터링을 비활성화하세요:

```json
{
  "type": "web_search_20260209",
  "name": "web_search",
  "allowed_callers": ["direct"]
}
```

이렇게 하면 도구가 직접 호출로만 제한되어 내부 코드 실행 단계를 건너뜁니다.

`allowed_callers`는 도구를 호출할 수 있는 방식을 제어합니다. Claude가 직접 호출(`"direct"`), 코드 실행 컨테이너 내부에서 호출(예: `"code_execution_20260120"`), 또는 둘 다 가능합니다. 웹 도구의 `_20260209` 버전은 기본적으로 코드 실행 호출자만 허용하며, 이전 버전은 기본값이 `["direct"]`입니다. 프로그래매틱 도구 호출을 지원하지 않는 모델에서는 이러한 버전에 `allowed_callers: ["direct"]`가 필요하며, 이것이 없으면 API는 이를 설정하라는 유효성 검사 오류를 반환합니다.

<Note>
  웹 가져오기가 ZDR 적격 구성으로 사용되더라도, Claude가 웹사이트에서 콘텐츠를 가져오는 경우 웹사이트 게시자는 URL에 전달된 모든 매개변수를 보존할 수 있습니다.
</Note>

## 도메인 필터링

웹에 접근하는 서버 도구는 Claude가 도달할 수 있는 도메인을 제어하기 위해 `allowed_domains` 및 `blocked_domains` 매개변수를 받습니다. 둘 다 도구 객체의 필드입니다:

```json
{
  "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`로 거부됩니다.

<Note>
  요청 수준 도메인 제한은 Claude Console에서 구성된 조직 수준 도메인 제한과 함께 작동합니다. 요청 수준 `allowed_domains`는 조직 수준 허용 목록의 부분 집합이어야 하며, 그 밖의 항목이 있으면 API가 유효성 검사 오류를 반환합니다. 조직에서 차단한 도메인을 포함하는 요청 수준 허용 목록은 충돌하는 항목을 명시하는 `400` 오류로 거부됩니다.
</Note>

<Warning>
  도메인 이름의 유니코드 문자는 동형 문자 공격을 통해 도메인 필터를 우회할 수 있습니다. `аmazon.com`(키릴 문자 `а` 사용)은 `amazon.com`과 동일하게 보이지만 다른 도메인입니다. 허용 및 차단 목록에는 ASCII 전용 도메인 이름을 사용하고, 기존 항목에 비ASCII 문자가 있는지 점검하세요.
</Warning>

[Claude Managed Agents](https://platform.claude.com/docs/ko/managed-agents/overview)는 에이전트 도구 세트의 `web_search` 및 `web_fetch` 항목에서 동일한 `allowed_domains` 및 `blocked_domains` 필드를 사용합니다. Managed Agents에서는 각 목록에 최대 64개의 항목을 넣을 수 있고, `web_fetch`에 나열된 도메인에는 경로를 포함할 수 없으며, `max_uses`, `citations`, `cache_control`과 같은 Messages API 도구 전용 필드는 사용할 수 없습니다. 전체 규칙은 [도메인 목록 규칙](https://platform.claude.com/docs/ko/managed-agents/tools-web-restrictions#domain-list-rules)을 참조하세요.

Claude Console의 조직 수준 웹 검색 및 웹 가져오기 설정은 Messages API 요청에만 적용됩니다. Managed Agents 세션에는 적용되지 않으며, 이 세션은 대신 에이전트 도구 세트의 도구별 목록을 사용합니다. `limited` 네트워킹이 설정된 클라우드 환경의 세션에서는 환경의 `allowed_hosts`도 `web_search` 및 `web_fetch`에 적용됩니다. [네트워킹](https://platform.claude.com/docs/ko/managed-agents/environments#networking)을 참조하세요. 도구별 목록은 `allowed_hosts`가 허용하는 호스트 범위 내에서 이 도구들을 추가로 제한합니다.

## 코드 실행을 통한 동적 필터링

웹 검색과 웹 가져오기의 `_20260209` 및 이후 버전은 검색 결과에 동적 필터를 적용하기 위해 내부적으로 코드 실행을 사용합니다.

<Note>
  이러한 버전에는 `code_execution` 도구를 추가할 필요가 없습니다. 동적 필터링이 실행될 때 API가 요청에 대해 코드 실행을 자동으로 프로비저닝하며, 두 도구는 단일 실행 컨테이너를 공유합니다. 직접 포함하는 경우 `code_execution_20260120` 또는 이후 버전을 사용하세요. API는 이러한 웹 도구 버전과 함께 사용되는 이전 코드 실행 버전을 거부합니다.
</Note>

## 서버 도구 이벤트 스트리밍

서버 도구 이벤트는 일반적인 "server-sent events"(서버 전송 이벤트), 즉 SSE 흐름의 일부로 스트리밍됩니다. Claude가 직접 호출하는 `server_tool_use` 블록은 클라이언트 `tool_use` 블록처럼 스트리밍됩니다. `content_block_start` 이벤트 뒤에 `input_json_delta` 이벤트가 이어집니다. 결과 블록은 델타 없이 단일 `content_block_start` 이벤트로 완전한 형태로 도착합니다.

전체 이벤트 레퍼런스는 [스트리밍](https://platform.claude.com/docs/ko/build-with-claude/streaming)을 참조하세요. 개별 도구 페이지에서는 도구별 이벤트 이름이 다른 경우 이를 문서화합니다.

## 배치 요청

모든 서버 도구는 배치 처리를 지원합니다. 배치에서 에이전트 루프는 동기 요청과 동일하게 실행되며, 턴당 반복 한도가 더 높습니다. 루프가 해당 한도에 도달하면 응답은 `stop_reason: "pause_turn"`으로 끝나며, 반환된 콘텐츠로 후속 요청을 제출하여 이어갈 수 있습니다. 자세한 내용은 [서버 도구와 에이전트 루프](https://platform.claude.com/docs/ko/build-with-claude/batch-processing#server-tools-and-the-agentic-loop)를 참조하세요.

일반적인 배치 워크로드에는 웹의 정보로 데이터셋 보강하기, 대량의 문서를 최신 출처와 대조하여 확인하기, 많은 파일에 대해 분석 코드 실행하기 등이 있습니다.

## 다음 단계

<CardGroup cols={2}>
  <Card title="도구 사용 문제 해결" icon="wrench" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/troubleshooting-tool-use">
    증상별 해결 진단 표로 가장 흔한 도구 사용 오류를 해결하세요.
  </Card>

  <Card title="웹 검색 도구" icon="magnifying-glass" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/web-search-tool">
    웹을 검색하고 결과를 인용하세요.
  </Card>

  <Card title="웹 가져오기 도구" icon="download" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/web-fetch-tool">
    특정 URL에서 콘텐츠를 가져와 읽어 실시간 웹 콘텐츠로 Claude의 컨텍스트를 보강하세요.
  </Card>

  <Card title="코드 실행 도구" icon="terminal" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/code-execution-tool">
    샌드박스 컨테이너에서 Python 및 bash 코드를 실행하여 데이터를 분석하고, 파일을 생성하고, 솔루션을 반복 개선하세요.
  </Card>

  <Card title="도구 검색 도구" icon="compass" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/tool-search-tool">
    필요에 따라 도구를 발견하고 로드하세요.
  </Card>
</CardGroup>
