블로그 목록
ai

Claude web_fetch 도구로 특정 URL 통째로 읽히기 — 검색 말고 "이 페이지 읽어줘"

얼마 전에 web_search 도구 얘기를 썼는데, 쓰다 보니 결이 다른 상황이 자꾸 생겼다. 검색은 "최신 React 버전 뭐야?"처럼 모델이 알아서 찾아 헤매야 할 때 좋다. 그런데 "이 링크 좀 읽고 요약해줘" 하면서 URL을 딱 던지는 경우엔 검색이 오히려 번거롭다. 검색어를 만들어 돌리고, 결과 목록에서 그 페이지를 다시 골라야 하니까.

이럴 때 쓰라고 있는 게 web_fetch 도구다. 검색이 아니라, 지정된 URL 하나를 서버에서 그대로 긁어와 본문을 모델 컨텍스트에 넣어준다. 내가 fetch 코드를 짜고, HTML을 파싱해서, 텍스트만 발라낸 뒤 프롬프트에 끼워 넣던 과정이 통째로 사라진다.

도구 선언

web_search와 똑같이 tools 배열에 넣는 게 전부다. 클라이언트에서 실행할 함수는 없다.

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

const response = await client.messages.create({
  model: 'claude-opus-4-8',
  max_tokens: 4096,
  tools: [{ type: 'web_fetch_20260209', name: 'web_fetch' }],
  messages: [
    {
      role: 'user',
      content: '이 글 핵심만 정리해줘: https://nextjs.org/blog/next-15',
    },
  ],
});

for (const block of response.content) {
  if (block.type === 'text') {
    console.log(block.text);
  }
}

web_fetch_20260209은 도구 버전이다. Opus 4.6·4.7·4.8, Sonnet 5 같은 최신 모델에서 돌아가고, 구형 모델이면 기본형인 web_fetch_20250910을 쓴다.

제일 중요한 제약 하나

web_fetch는 대화에 이미 등장한 URL만 가져온다. 모델이 "아무 주소나 상상해서" 긁어오는 게 아니다. 사용자 메시지든 앞선 검색 결과든, 컨텍스트 어딘가에 그 URL이 실제로 있어야 한다. 처음엔 이게 불편해 보였는데, 생각해보면 SSRF 같은 사고를 막는 안전장치라 오히려 맘이 편하다.

범위를 더 조이고 싶으면 옵션을 건다.

tools: [{
  type: 'web_fetch_20260209',
  name: 'web_fetch',
  max_uses: 2,                       // 한 응답에서 가져오기 횟수 제한
  allowed_domains: ['nextjs.org'],   // 이 도메인만 허용
  max_content_tokens: 20000,         // 긴 문서 잘라 비용 방어
  citations: { enabled: true },      // 답변에 출처 인용 붙이기
}]

max_content_tokens는 특히 챙겨두는 게 좋다. 안 걸면 긴 문서가 통째로 들어와서 토큰이 훅 나간다.

주의할 점

web_search와 마찬가지로 서버 도구는 에러가 나도 예외를 던지지 않는다. HTTP 200으로 오되 web_fetch_tool_result 블록 안에 { error_code: 'url_not_accessible' } 같은 객체가 담겨온다. try/catch만 믿으면 조용히 놓치니, 결과 블록 타입을 직접 확인하는 분기를 넣어둬야 한다.


정리하면 web_search는 "찾아줘", web_fetch는 "이거 읽어줘"다. 사용자가 링크를 들고 오는 기능 — 아티클 요약, 문서 Q&A 같은 데선 이쪽이 훨씬 직관적이다. 둘을 같이 선언해두면 모델이 상황 봐서 알아서 골라 쓴다.