AI 에이전트에게 웹 페치(Web-Fetch) 도구 제공하기: 60줄로 구현하는 MCP 서버 (무료, 셀프 호스팅)
요약
유료 API 대신 Python MCP SDK를 사용하여 60줄의 코드로 셀프 호스팅 가능한 웹 페치(Web-Fetch) 도구를 구현하는 방법을 소개합니다. 타임아웃, 크기 제한, SSRF 체크와 같은 필수 가드레일을 포함하여 에이전트가 실시간 웹 정보를 안전하게 읽을 수 있도록 구축합니다.
핵심 포인트
- MCP Python SDK를 활용한 60줄 내외의 간결한 구현
- 유료 API 없이 무료로 셀프 호스팅 가능한 웹 접속 도구 구축
- HTML 대신 정제된 텍스트를 반환하여 모델의 토큰 효율성 증대
- 안전한 에이전트 운영을 위한 SSRF 및 타임아웃 가드레일 설정 필수
이번 달에 읽은 모든 MCP 웹 접속 튜토리얼은 유료 API를 가리키고 있었습니다.
하지만 유료 API는 필요하지 않습니다. AI 에이전트가 공개 웹 페이지를 읽을 수 있도록 하려면, 공식 MCP Python SDK를 사용하여 60줄의 코드로 여러분의 머신에서 실행되는 셀프 호스팅(self-hosted) web_fetch 도구를 만들 수 있습니다. 키(key)도 필요 없고, 호출당 비용도 발생하지 않습니다.
제가 직접 구축하고 실행한 뒤, 실제 터미널 출력 내용을 아래에 붙여넣었습니다. 문제는 연결 방식(그 부분은 쉽습니다)이 아닙니다. 튜토리얼들이 생략한 네 가지 기본 설정이 핵심입니다. 이 설정들이 있어야 단순한 장난감이 아닌, 실제로 에이전트에게 맡길 수 있는 도구가 됩니다.
빠른 답변: Model Context Protocol (MCP) 서버는 LLM 에이전트가 호출할 수 있는 도구(tools)를 노출합니다. pip install mcp를 설치하고, 하나의 @mcp.tool() 함수와 mcp.run()을 사용하면, 약 60줄의 코드로 stdio를 통해 작동하는 web_fetch(url) -> clean text 도구를 얻을 수 있습니다. 셀프 호스팅 방식이며 무료이고, 가공되지 않은 HTML 대신 텍스트를 반환합니다. 핵심 작업은 타임아웃(timeout), 크기 제한(size cap), 그리고 SSRF 체크와 같은 가드레일(guardrails)을 구축하는 데 있습니다.
이 글은 에이전트나 RAG를 구축하면서 계속해서 "모델에게 실시간 웹 접속 권한을 주려면—여기 저희 API가 있습니다"라는 메시지를 마주하는 모든 분을 위한 것입니다. 만약 여러분의 목표가 문서, 기사, RSS, 또는 일반적인 GET 요청에 응답하는 JSON 엔드포인트라면, 굳이 비용을 지불할 필요가 없습니다.
결과물 확인: 에이전트가 실제로 받는 것
다음은 실제 왕복 과정(가독성을 위해 재구성한 터미널 출력이며, 실제 print() repr은 더 밀도가 높습니다)입니다. 서버를 시작하고, stdio를 통해 MCP 클라이언트를 연결한 뒤, 도구 목록을 요청하고 example.com에 대해 web_fetch를 호출했습니다:
=== AGENT가 보는 도구들 ===
- web_fetch: 공개 웹 페이지를 가져와서 읽기 쉬운 깨끗한 텍스트를 반환합니다 (가공되지 않은 HTML 아님).
...
그게 전부입니다. 에이전트는 URL을 요청했고, <div 태그가 가득한 덩어리가 아닌 읽기 쉬운 산문(prose)을 돌려받았습니다. 태그도, <style> 블록도, 네비게이션 크롬(nav chrome)도 없습니다.
제가 실행한 스택은 다음과 같습니다: mcp 1.27.2 (pip show mcp, 2026-06-11 설치), httpx 0.28.1, Python 3.13.5. MCP SDK API는 버전 간에 변경될 수 있으므로, 진행하면서 중요한 부분들을 표시하겠습니다.
왜 무료/셀프 호스팅인가, 그리고 왜 이 글을 쓰는가
배경을 설명드리겠습니다. Pierluigi Vinciguerra는 이 분야에서 가장 많이 읽히는 콘텐츠 중 하나인 The Web Scraping Club을 운영하고 있습니다. 그는 2026-06-07에 대략 'Decodo MCP를 사용하여 Claude에게 실시간 웹 접근 권한을 부여하는 방법'이라는 제목의 가이드를 게시했습니다. 좋은 글입니다. 하지만 Decodo는 유료 서비스입니다. 같은 주에는 HN(Hacker News) 메인 페이지에
두 개의 헬퍼 함수(_is_public_http_url, _html_to_text)와 임포트(import) 문을 포함하면 정확히 60줄로 마무리됩니다. 전체 파일은 끝부분에 있습니다. web_fetch에 작성된 독스트링(docstring)은 단순한 주석이 아닙니다. 이는 모델이 해당 도구를 호출할지 결정할 때 읽게 되는 도구 설명(tool description)이 되며, 그렇기 때문에 위에서 본 list_tools 출력 결과에 그 첫 번째 줄이 그대로 반영되는 것입니다. 독스트링은 당신이 아니라 에이전트를 위해 작성하세요.
사람들이 실수하기 쉬운 부분이라 버전 관련 참고 사항을 남깁니다. 저는 이 코드를 mcp 1.27.2 버전에서 실행했습니다. 해당 버전에서는 from mcp.server.fastmcp import FastMCP, @mcp.tool() 데코레이터(decorator), 그리고 mcp.run()이 모두 존재하며 예시와 같이 동작합니다. 로우 레벨(low-level) Server API와 일부 헬퍼 시그니처(signature)는 릴리스에 따라 변경되었습니다. 만약 더 오래되거나 최신 버전을 사용 중이라면, pip show mcp로 확인하고 선언적인 FastMCP 경로를 사용하세요. 이것이 버전 간에 가장 안정적인 인터페이스이며, 이 코드를 60줄 이내로 유지할 수 있는 비결입니다.
클라이언트에서 대화하기
에이전트가 실제로 도구에 도달할 수 있는지 증명하기 위해, SDK 자체 클라이언트를 사용하여 stdio를 통해 연결했습니다.
# client.py — 로컬에서 실행 가능
import asyncio, sys
from mcp.client.session import ClientSession
...
시간을 아껴드리기 위해 주의할 점 하나를 말씀드리자면, 처음에는 command="python"이라고 작성했는데, 깨끗한 가상 환경(venv)에서 FileNotFoundError: 'python' 에러가 발생했습니다. 바이너리가 PATH에 없고 python3만 있었기 때문입니다. sys.executable은 이미 실행 중인 인터셉리터(interpreter)를 가리키므로 문제없이 작동합니다. 사소한 문제로 10분을 허비했네요. 그냥 `
이 도구의 단순한 버전은 세 줄이면 충분합니다: httpx.get(url).text를 호출하고, 이를 반환하면 끝입니다. 데모용으로는 충분히 잘 작동하죠. 하지만 실제 에이전트를 개방된 웹(open web)에 연결하는 순간, 퀵스타트(quickstart)에서는 전혀 경고하지 않았던 방식들로 에이전트가 무너지기 시작합니다. 다음의 네 가지 기본 설정은 저희의 스크래핑 플릿(scraping fleet)에서 직접 가져온 것입니다. 이는 32개의 공개된 액터(actor)를 통해 약 2,190회의 프로덕션 실행을 거치며 검증되었으며(저희의 Trustpilot 스크래퍼 하나만으로도 962회의 실행이 기록되었습니다), 이 설정들은 하나같이 부재 시에 큰 고통을 유발함으로써 그 필요성을 증명했습니다.
1. 예의 바르고 연락 가능한 User-Agent. 기본 httpx/python-requests UA(User-Agent)는 조용히 속도 제한(throttled)을 당하거나 차단당하기 딱 좋은 방법입니다. 자신이 누구인지, 어떻게 연락할 수 있는지를 밝히는 UA는 가장 저렴하게 호의를 얻는 방법입니다. 실제로 저희의 몇몇 액터에서는 이 한 줄의 코드가 사이트의 응답을 403에서 200으로 바꾸어 놓았습니다. 기능적 가치 → 그래서 무엇인가: 당신의 에이전트가 알 수 없는 봇을 차단하는 서버들로부터 무시(ghosted)당하는 일을 멈추게 됩니다.
2. timeout + raise_for_status(). 느린 서버에서 응답을 기다리며 멈춰버리는 에이전트는 에러를 내는 에이전트보다 더 나쁩니다. 아무런 신호 없이 전체 도구 호출(tool call)을 얼려버리기 때문입니다. 15초의 타임아웃(timeout) 설정과 4xx/5xx 에러 시 예외를 발생시키는 raise_for_status()를 결합하면, 잘못된 URL이 에이전트가 반응할 수 있는 실제 에러로 드러나게 됩니다. 그렇지 않으면 에이전트는 빈 문자열을
4. 원시 HTML 대신 정리된 텍스트 제공. 이 도구는 태그를 제거하고 산문(prose) 형태로 결과를 반환합니다. 예를 들어 example.com과 같은 간단한 페이지의 경우, 페이로드 크기를 559개의 원시 HTML 문자에서 142개의 정리된 텍스트로 줄여 약 3.9배 작게 만듭니다 (단일 페이지 기준; 실제 웹사이트는 내비게이션, 스크립트, 인라인 스타일링 등으로 인해 훨씬 더 큰 편차를 보입니다). 이것이 토큰 비용에 미치는 영향은 또 다른 문제입니다. 저는 이미 Raw HTML Is a Token Tax — I Measured It에서 실제 페이지를 측정해 보았습니다. 간단히 말해, 에이전트는 자신이 받은 모든 HTML 문자에 대해 비용을 지불하지만, 그중 거의 아무것도 읽지 못합니다. 텍스트를 제공하세요.
개발자가 간과하기 쉬운 방어 장치: SSRF (Server-Side Request Forgery)
또 하나 더 언급할 것은 코드 리뷰에서 반드시 지적해야 할 사항입니다. LLM이 제어하는 web_fetch 도구는 모델이 어디든 요청을 보낼 수 있게 만듭니다. 여기에는 자격 증명을 유출하는 클라우드 메타데이터 엔드포인트인 http://169.254.169.254/나, Redis에 접근하기 위한 http://localhost:6379 등이 포함될 수 있습니다. 이것이 바로 서버 측 요청 위조(Server-Side Request Forgery)이며, 악의적인 페이지가 에이전트에게
그리고 디태거(de-tagger)는 이것이 장난감(toy) 수준이라는 점을 솔직하게 밝히고 있습니다. _html_to_text 정규식(regex)은 데모용으로는 괜찮지만, 실제 콘텐츠 추출기(content extractor)는 아닙니다. 프로덕션(production) 환경에서는 trafilatura나 readability-lxml로 교체하십시오. 이 도구들은 실제 기사 본문을 찾아내고 불필요한 요소(boilerplate)를 제거합니다. 파일의 의존성(dependency)을 하나로 유지하고 60줄 이내로 맞추기 위해 정규식을 남겨두었지만, 가장 먼저 교체해야 할 부분임을 말씀드립니다.
당신이 얻은 것, 그리고 솔직한 한계점
60줄의 코드, pip install mcp httpx, 그리고 당신의 에이전트는 읽기 가능한 텍스트를 반환하고, 정중하게 자신을 식별하며, 멈추지 않고, 컨텍스트(context)를 범람시키지 않으며, 직접적인 메타데이터-IP URL을 거부하는 web_fetch 도구를 갖게 됩니다. 무료이며, 당신의 머신에서 실행됩니다. 벤더(vendor)도 없습니다.
명확한 한계점은 다음과 같습니다: JavaScript 미지원, 안티 봇(anti-bot) 회피 불가, 프록시(proxy) 미지원, 기본적으로 장난감 수준의 추출기, 그리고 요새가 아닌 바닥 수준의 SSRF 방어 체계입니다. 문서/API/기사 본문을 읽는 용도로는 충분합니다. 하지만 Cloudflare로 보호되거나 JavaScript 비중이 높은 대상을 상대로는 헤드리스 브라우저(headless-browser) 영역으로 넘어가야 하며, 이는 별도의 포스트에서 다루겠습니다.
mcp 1.27.2 버전에서 검증된 server.py의 소스 코드(정확히 60줄, 제가 실행한 파일)는 아래와 같습니다.
# server.py — AI 에이전트에게 단 하나의 도구인 web_fetch를 제공하는 최소한의 MCP 서버입니다.
# 로컬 실행 방법: pip install mcp httpx → python server.py
# 여기에 설정된 기본값(UA, timeout, redirects, raise_for_status, size cap)은
...
저는 한 가지 설계 질문으로 계속 되돌아오지만, 아직 깔끔한 답을 내리지 못했습니다: 에이전트에게 도구를 건네줄 때, 도구가 얼마나 강제(enforce)해야 할까요, 아니면 모델이 제대로 행동할 것이라고 얼마나 신뢰해야 할까요? 저는 프롬프트 수준의 규칙이 적대적 입력(adversarial input) 상황에서 유지될 것이라고 믿지 않기 때문에 SSRF 체크와 크기 제한(size cap)을 도구에 포함했습니다. 하지만 그렇게 하면 모든 도구에 가드레일(guardrail) 코드가 들어가면서 비대해집니다. 그 경계선을 어디에 그어야 할까요 — 도구 내부인가요, 도구를 둘러싼 샌드박스(sandbox)인가요, 아니면 에이전트의 정책(policy)인가요?
당신이 에이전트에게 가장 먼저 건네줄 도구는 무엇인가요? 그리고 어떤 가드레일 없이는 절대 출시하지 않을 것인가요? 👇
저희의 프로덕션 실행 분석(teardown) 다음 편을 위해 팔로우해 주세요. 모든 댓글을 읽고 있습니다.
AI의 도움을 받아 작성되었습니다. 모든 코드는 실제로 실행되었으며, 위의 모든 출력값은 생성된 것이 아닌 실제 터미널 출력 결과입니다. 2026-06-11 기준 mcp 1.27.2 / Python 3.13.5 환경에서 테스트되었습니다. 출처: modelcontextprotocol.io의 공식 MCP 문서.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기