본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 08. 01:46

실시간 LLM 웹 스크래핑을 위한 MCP 서버 구축하기

요약

LLM의 컨텍스트 창 소모와 환각 문제를 해결하기 위해 MCP(Model Context Protocol)를 활용한 실시간 웹 스크래핑 서버 구축 방법을 다룹니다. 원시 HTML 대신 토큰 효율적인 Markdown 형식을 사용하여 에이전트에게 정제된 데이터를 제공하는 미들웨어 계층 구현을 설명합니다.

핵심 포인트

  • 원시 HTML은 토큰 소모가 크고 모델의 추론 능력을 저하시킴
  • MCP를 통해 다양한 LLM과 외부 도구 간의 표준화된 통신 가능
  • 헤드리스 스크래핑 API를 결합해 깨끗한 Markdown 데이터 추출
  • Python 기반 MCP SDK를 활용한 read_webpage 도구 구현

요약 (TL;DR)

대규모 언어 모델(LLM)은 환각 현상(hallucinations)을 피하기 위해 실시간 데이터가 필요하지만, 원시 HTML을 직접 공급하면 컨텍스트 창(context windows)이 빠르게 소진됩니다. Model Context Protocol (MCP)은 AI 에이전트가 외부 도구에 접근하는 방식을 표준화합니다. MCP 서버를 헤드리스 스크래핑 API와 결합하여 모든 공개 웹 페이지의 실시간적이고 토큰 효율적인 Markdown 표현으로 에이전트를 기반 지식(ground)을 제공할 수 있습니다.

컨텍스트 창 문제 (The Context Window Problem)

AI 에이전트에게 라이브 공개 URL 분석을 요청하면, 해당 페이지를 가져와야 합니다. 표준 접근 방식은 HTTP GET 요청을 실행하고 응답을 프롬프트 컨텍스트에 직접 덤프하는 것입니다.

이는 최신 웹사이트에서는 즉시 실패합니다.

표준 전자상거래 제품 페이지나 뉴스 기사 하나만 해도 원시 HTML, 인라인 CSS, base64로 인코딩된 이미지를 합쳐 쉽게 2MB를 초과합니다. 이는 대략 500,000 토큰에 해당합니다. 이를 LLM 컨텍스트 창에 넣는 것은 느리고 비용이 많이 들며, 모델이 실제 콘텐츠에 대해 추론하는 능력을 저하시킵니다. 모델은 내비게이션 보일러플레이트(boilerplate)에 파묻힌 핵심 데이터 포인트를 놓치는 '중간에서 길을 잃음(lost in the middle)' 현상을 겪습니다.

따라서 미들웨어 계층이 필요합니다. 서버는 페이지를 가져오고, DOM 렌더링에 필요한 JavaScript를 실행하며, 보일러플레이트를 제거하고, 핵심 콘텐츠를 깨끗한 Markdown으로 변환하여 그 특정 문자열을 모델에게 전달해야 합니다.

Model Context Protocol (MCP) 소개

MCP는 LLM이 외부 도구와 통신하는 방식을 규정하는 개방형 표준입니다. 모든 제공업체(OpenAI, Anthropic, Google 등)에 대해 사용자 지정 도구 호출 루프를 작성하는 대신, 하나의 MCP 서버만 작성하면 됩니다. Claude Desktop과 같은 호환 가능한 클라이언트는 이 서버에 연결하여 그 기능을 발견할 수 있습니다.

우리는 read_webpage라는 단일 도구를 노출하는 Python 기반의 MCP 서버를 구축할 것입니다. LLM이 URL에서 정보를 필요로 한다고 판단하면, 이 도구를 호출합니다.

프로젝트 설정 (Setting Up the Project)

새로운 Python 환경을 초기화하고 필요한 종속성 (dependencies)을 설치합니다. 브라우저 렌더링 (browser rendering)과 포맷팅 (formatting)을 처리하기 위해 공식 MCP SDK와 Python SDK가 필요합니다.

python -m venv venv  
source venv/bin/activate  
pip install mcp alterlab

MCP 서버 구축하기 (Building the MCP Server)

...


from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, CallToolResult

# MCP 서버 초기화
app = Server("web-reader")

# 추출 클라이언트 (extraction client) 초기화
api_key = os.environ.get("ALTERLAB_API_KEY")
client = alterlab.Client(api_key)

@app.list_tools()
async def list_tools() -> list[Tool]:
    """LLM이 사용할 수 있는 도구들을 정의합니다."""
    return [
        Tool(
            name="read_webpage",
            description="공개된 웹 페이지의 주요 콘텐츠를 추출하여 깨끗한 Markdown 형식으로 반환합니다. 기사, 문서 또는 공개 데이터를 읽을 때 사용하세요.",
            inputSchema={
                "type": "object",
                "properties": {
                    "url": {
                        "type": "string",
                        "description": "읽을 절대 경로 URL"
                    }
                },
                "required": ["url"]
            }
        )
    ]

list_tools 데코레이터 (decorator)가 스키마 (schema)를 등록합니다. inputSchema는 표준 JSON 스키마 (JSON Schema) 형식을 따릅니다. LLM은 이 설명을 읽고 도구를 정확히 언제 어떻게 사용할지 이해합니다.

...

url = arguments.get("url")
if not url:
    return CallToolResult(
        content=[TextContent(type="text", text="Error: URL is required")]
    )

try:
    # 토큰 (tokens)을 절약하기 위해 Markdown 형식을 직접 요청합니다
    response = client.scrape(
        url,
        formats=["markdown"],
        wait_for_network_idle=True
    )

    markdown_content = response.markdown

# 추출 실패에 대한 안전장치 (Failsafe)
    if not markdown_content:
         markdown_content = "페이지를 가져왔으나 읽을 수 있는 콘텐츠를 찾지 못했습니다. 이미지이거나 인증이 필요할 수 있습니다."

    return CallToolResult(
        content=[TextContent(type="text", text=markdown_content)]
    )

except Exception as e:
    # LLM이 오류를 인지하고 조정하여 재시도할 수 있도록 항상 오류를 텍스트로 반환합니다.
    return CallToolResult(
        content=[TextContent(type="text", text=f"페이지 추출 실패: {str(e)}")]
    )

async def main():
async with stdio_server() as (read_stream, write_stream):
...


오류 처리(error handling) 블록에 주목하세요. MCP 서버를 구축할 때는 서버 프로세스를 중단시키는 예외(exception)를 발생시키는 일이 거의 없어야 합니다. 만약 404 오류나 타임아웃(timeout)으로 인해 추출에 실패한다면, 해당 오류를 `CallToolResult` 내의 문자열로 반환하세요. 그러면 LLM이 이 오류를 읽고 요청이 실패했음을 이해한 뒤, 다른 URL이나 전략을 시도할 수 있습니다.

### cURL 대안

Node.js나 Go와 같은 다른 언어로 MCP 서버를 구축하는 것을 선호한다면, SDK가 반드시 필요하지는 않습니다. REST 엔드포인트로 표준 HTTP 요청을 보낼 수 있습니다. 아래는 기본 요청 구조를 보여주기 위해 cURL을 사용하여 작성한 동일한 코드입니다:

```bash title="Terminal" {3-5}
curl -X POST https://api.alterlab.io/v1/scrape \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
...

formats=["markdown"] 파라미터는 토큰 효율성(token efficiency)을 위한 핵심 요소입니다. 이 파라미터는 API가 내비게이션, 푸터(footer), 스크립트(script), CSS를 제거하고 페이지의 의미론적 핵심(semantic core)만을 반환하도록 지시합니다.

JavaScript 및 안티 봇(Anti-Bot) 조치 처리

LLM을 위한 웹 읽기 도구를 구축할 때 흔히 하는 실수는 requestsaxios와 같은 표준 HTTP 라이브러리를 사용하는 것입니다.

많은 현대적인 웹사이트들은 빈 HTML 셸(shell)만 전송하고, 실제 콘텐츠는 React, Vue 또는 Angular를 통해 렌더링(render)합니다. 단순한 GET 요청을 보내면 LLM에게 빈 페이지가 반환됩니다. 게다가, 공개 데이터베이스와 디렉토리는 종종 공격적인 속도 제한(rate limiting)이나 기본적인 봇 방지(bot protection) 챌린지를 적용합니다.

LLM이 실제로 데이터를 받을 수 있도록 보장하려면, 서버가 JavaScript를 실행하고 브라우저 챌린지를 처리해야 합니다. 위의 Python 코드에서 wait_for_network_idle=True를 설정하면, 헤드리스 브라우저(headless browser)가 DOM을 추출하기 전에 모든 XHR 요청이 완료될 때까지 기다리도록 강제합니다. 프록시 회전(proxy rotation) 및 우회(bypass)와 같은 힘든 작업은 API 레이어에 내장된 전용 anti-bot solution을 사용하여 자동으로 처리됩니다.

Claude Desktop으로 테스트하기

이 서버를 로컬에서 테스트하려면 Claude Desktop에 직접 연결할 수 있습니다. Claude Desktop은 로컬 MCP 서버를 서브프로세스(subprocess)로 실행하는 것을 지원합니다.

Claude Desktop 설정 파일을 찾으세요. macOS의 경우 ~/Library/Application Support/Claude/claude_desktop_config.json에 위치합니다. Linux의 경우 ~/.config/Claude/claude_desktop_config.json을 확인하세요.

설정을 업데이트하여 사용자의 Python 스크립트를 가리키도록 합니다.

{
"mcpServers": {
"web-reader": {
"command": "/path/to/your/venv/bin/python",
"args": [
"/path/to/your/mcp_server.py"
],
"env": {
"ALTERLAB_API_KEY": "your_api_key_here"
}
}
}
}

Claude Desktop을 재시작하세요. 이제 서버가 연결되었음을 나타내는 도구 아이콘이 보일 것입니다. 이제 Claude에게 자연스럽게 프롬프트를 입력할 수 있습니다: "https://github.com/mcp/server 에 있는 최신 릴리스 노트를 요약해줘."

...


```python title="mcp_server.py" {12-16}

# 새로운 도구 함수 내부: extract_structured_data
response = client.scrape(
    url,
    extract={
        "schema": {
            "type": "object",
            "properties": {
                "pricing_tiers": {
                    "type": "array",
                    "items": {"type": "string"}
                }
            }
        }
    }
)
return CallToolResult(
    content=[TextContent(type="text", text=json.dumps(response.extracted_data))]
)

이 설정은 무거운 추론 작업을 스크래핑 API로 오프로드합니다. MCP 서버는 깔끔한 JSON 객체를 받아 에이전트에게 직접 전달합니다.

핵심 요약 (Takeaways)

MCP 서버를 구축하면 AI 에이전트가 실시간 공개 데이터에 신뢰성 있게 접근할 수 있습니다. 렌더링 및 마크다운 변환을 전문 API에 아웃소싱함으로써, 사용자는 서버 로직을 최소화할 수 있습니다. 컨텍스트 창 비대화를 방지하고, 토큰 비용을 절감하며, 모델을 현실(실제 데이터)에 기반하여 근거화(grounding)함으로써 환각성 답변을 제거합니다.

도구 응답 내에서 오류를 텍스트로 반환하여 서버를 견고하게 유지하세요. 이를 통해 LLM이 실패를 우아하게 처리할 수 있습니다. 에이전트에게 작업에 맞는 적절한 형식을 제공하기 위해 마크다운 읽기 전용과 구조화된 JSON 추출을 위한 전문 도구를 구축하십시오.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0