Skip to main content
Glama

Web Speed

Web Speed는 AI 에이전트를 위한 신호 대 잡음(Signal-to-Noise) 문제를 해결합니다. 현대의 웹은 인간의 눈에 최적화되어 있지만(지저분한 HTML, 복잡한 레이아웃, JS 위주의 인터페이스), Web Speed는 이러한 혼란을 고처리량 에이전트 군단을 위해 설계된 결정론적이고 토큰 효율적인 구조적 맵으로 변환합니다.

AI를 포함하지 않습니다. anthropic, openai 또는 어떠한 종류의 LLM 의존성도 없습니다. 모든 해석은 호출하는 에이전트 내에서 이루어집니다.


존재 이유

문제

Web Speed 솔루션

원시 HTML은 150,000자 이상의 스크립트, 스타일, SVG 노이즈를 포함함

구조적이지 않은 모든 것을 제거 → 최대 97% 토큰 절감

LLM은 원시 DOM에서 요소 ID를 환각하거나 상호작용 지점을 놓침

고정된 구조적 맵을 반환 — 존재하는 것만 표시되며, 없는 것을 지어내지 않음

사이트마다 커스텀 스크래퍼가 깨짐

결정론적 프로토콜 — 웹상의 모든 사이트에 대해 동일한 JSON 형태

에이전트가 한 번에 한 번의 왕복으로 페이지를 재발견해야 함

site_map은 한 번의 호출로 전체 도메인을 크롤링함


Related MCP server: Delta-MCP

도구

도구

설명

interpret_page

전체 구조화된 맵: 제목, 내비게이션, 콘텐츠 링크, 폼, 테이블, 텍스트, 메타데이터

submit_form

폼 제출(GET 또는 POST) 후 결과 페이지의 맵을 반환

site_map

루트 URL에서 크롤링하여 모든 페이지의 결합된 맵을 반환

inspect_element

CSS 선택자와 일치하는 노드에 대한 심층 구조 데이터

page_type

즉각적인 페이지 분류 — login, listing, article, form, navigation, other

invalidate_cache

캐시된 맵을 삭제하여 다음 호출 시 최신 데이터를 가져오도록 함


설치

Mac / Linux

cd web-interpreter
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Windows

cd web-interpreter
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt

실행

MCP 인스펙터를 사용한 로컬 개발:

mcp dev server.py

stdio를 통해 직접 실행(MCP 클라이언트가 실행하는 방식):

python server.py

Claude / Cowork에 등록

~/Library/Application Support/Claude/claude_desktop_config.json(Mac) 또는 Windows의 해당 경로에 추가하세요:

{
  "mcpServers": {
    "web-speed": {
      "command": "/absolute/path/to/web-interpreter/venv/bin/python",
      "args": ["/absolute/path/to/web-interpreter/server.py"]
    }
  }
}

그런 다음 Claude Desktop / Cowork를 종료하고 다시 시작하세요. 6개의 도구가 web-speed MCP 서버 아래에 나타납니다.


출력 스키마

interpret_page

{
  "url": "https://example.com/",
  "fetched_at": "2025-01-01T12:00:00Z",
  "page_type": "other",
  "title": "Example Domain",
  "description": "",
  "headings": [
    { "level": 1, "text": "Example Domain" }
  ],
  "navigation": [
    { "label": "Home", "url": "https://example.com/", "location": "header" }
  ],
  "content_links": {
    "total": 47,
    "truncated": false,
    "items": [
      { "label": "More information...", "url": "https://www.iana.org/domains/example" }
    ]
  },
  "forms": [
    {
      "id": "search",
      "action": "https://example.com/search",
      "method": "GET",
      "fields": [
        {
          "name": "q",
          "type": "text",
          "label": "Search",
          "placeholder": "Search...",
          "required": false,
          "value": ""
        },
        {
          "name": "_csrf",
          "type": "hidden",
          "label": "",
          "placeholder": "",
          "required": false,
          "value": "abc123"
        }
      ]
    }
  ],
  "tables": [
    {
      "id": "results",
      "headers": ["Name", "Price", "Stock"],
      "rows": [["Widget A", "$9.99", "In stock"]]
    }
  ],
  "text_blocks": [
    { "tag": "p", "text": "This domain is for use in illustrative examples." }
  ],
  "metadata": {
    "lang": "en",
    "canonical": "",
    "open_graph": { "title": "", "description": "", "image": "" }
  }
}

주요 필드:

  • navigation — 의미론적 nav/header/footer 요소 내부의 링크(사이트 크롬, 메뉴). 60개로 제한.

  • content_links — 페이지 본문 내부의 링크(기사, 검색 결과, 목록). 항상 total을 포함하여 잘리더라도 실제 개수를 알 수 있음.

  • forms — 모든 필드가 포함된 모든 폼, CSRF 토큰은 숨겨진 필드 value에 그대로 보존됨.

  • page_type — 구조에서 추론: 비밀번호 필드 → login, 많은 항목/링크 → listing, 단락이 있는 <article> → article, 폼 → form, 대부분 링크 → navigation.


page_type

경량 — 분류 결과만 반환. 페이지가 캐시되어 있으면 즉시 반환.

{
  "url": "https://example.com/login",
  "fetched_at": "2025-01-01T12:00:00Z",
  "page_type": "login",
  "title": "Sign In"
}

submit_form

interpret_page와 동일한 출력 형태, 제출 후 서버가 도달하는 페이지에 대한 정보.

{
  "url": "https://example.com/login",
  "method": "POST",
  "fields": {
    "email": "user@example.com",
    "password": "hunter2",
    "_csrf": "abc123"
  }
}

CSRF 토큰은 fields에 그대로 포함됨 — 이전 interpret_page 호출의 forms 배열에 있는 숨겨진 필드에서 가져오세요.


inspect_element

CSS 선택자와 일치하는 노드에 대한 심층 구조 데이터. 25개 요소로 제한.

{
  "url": "https://example.com/shop",
  "selector": ".product-card",
  "matched": 48,
  "truncated": true,
  "elements": [
    {
      "tag": "div",
      "id": "product-42",
      "classes": ["product-card", "featured"],
      "text": "Widget Pro $49.99 Add to cart",
      "attributes": { "id": "product-42" },
      "links": [{ "label": "Add to cart", "url": "https://example.com/cart/add/42" }],
      "fields": [],
      "children": [
        { "tag": "h3", "text": "Widget Pro" },
        { "tag": "span", "text": "$49.99" },
        { "tag": "a", "text": "Add to cart", "href": "https://example.com/cart/add/42" }
      ]
    }
  ]
}

예시 선택자: #login-form, .product-card, table.results tbody tr, nav a, [data-testid="price"]


site_map

{
  "root_url": "https://example.com",
  "crawled_at": "2025-01-01T12:00:00Z",
  "total_pages": 8,
  "pages": [
    {
      "url": "https://example.com",
      "title": "Home",
      "page_type": "navigation",
      "depth": 0,
      "links_to": ["https://example.com/about", "https://example.com/contact"]
    }
  ],
  "all_forms": [
    {
      "found_on": "https://example.com/contact",
      "id": "contact",
      "action": "https://example.com/contact/submit",
      "method": "POST",
      "fields": [
        { "name": "email", "type": "email", "label": "Your email", "placeholder": "", "required": true, "value": "" },
        { "name": "message", "type": "textarea", "label": "Message", "placeholder": "", "required": true, "value": "" }
      ]
    }
  ],
  "all_navigation": [
    { "label": "About", "url": "https://example.com/about" },
    { "label": "Contact", "url": "https://example.com/contact" }
  ]
}

invalidate_cache

{ "url": "https://example.com", "invalidated": true }

오류

도구는 절대 예외를 발생시키지 않습니다. 실패 시:

{
  "error": true,
  "code": "FETCH_FAILED | PARSE_FAILED | TIMEOUT | NOT_HTML",
  "message": "human-readable explanation",
  "url": "https://example.com/broken"
}

에이전트가 출력을 사용하는 방법

사이트 탐색: 사이트 크롬(메뉴, 헤더, 푸터)은 navigation을 읽고, 페이지 본문 링크는 content_links를 읽으세요. content_links.total은 목록이 잘려도 전체 개수를 알려줍니다. 목표에 맞는 링크를 선택하고 interpret_page를 호출하세요.

폼 제출: forms를 읽으세요. 각 필드에는 name(전송할 값), type(기대 데이터 유형), label/placeholder(용도), required, value가 있습니다. 숨겨진 필드(type: "hidden")는 CSRF 토큰을 포함하므로 value를 그대로 전달하세요. 평면적인 name → value 딕셔너리를 구성하고 submit_form을 호출하세요.

커밋 전 분류: 전체 interpret_page 비용을 지불하지 않고 로직을 분기해야 할 때(예: 로그인 페이지인가 대시보드인가?) 먼저 page_type을 호출하세요.

구성 요소 자세히 보기: 맵에서 테이블을 보았지만 개별 행을 원하나요? 상품 목록을 보았지만 각 카드의 링크와 가격을 원하나요? CSS 선택자와 함께 inspect_element를 호출하여 전체 페이지를 다시 로드하지 않고도 특정 노드에 대한 구조적 세부 정보를 얻으세요.

다단계 워크플로우 사전 계획: 시작하기 전에 site_map을 호출하세요. 모든 페이지의 제목, 유형, 깊이, 나가는 링크, 사이트 전체의 모든 폼을 얻을 수 있습니다. 단 한 번의 왕복 없이도 전체 워크플로우(로그인 폼 찾기, 데이터 입력 페이지 찾기, 제출 엔드포인트 찾기)를 계획할 수 있습니다.

page_type은 보장이 아닌 신호입니다: 분류는 휴리스틱입니다. 빈 HTML 셸을 제공하는 JS 렌더링 SPA는 종종 other로 분류됩니다. 비밀번호 필드는 JavaScript가 실행되기 전까지 HTML에 없기 때문입니다. page_type을 빠른 필터로 취급하고, 실제 forms 및 headings를 확인하세요.


공유 레지스트리 동기화

기본적으로 OSS 서버가 구축하는 모든 새로운 페이지 맵은 api.getwebspeed.io의 Web Speed 공유 레지스트리에 비동기적으로 기여됩니다. 이는 크라우드소싱 플라이휠입니다. URL을 가져오는 모든 에이전트가 이를 글로벌 캐시에 추가하므로, 어디서든 다음 에이전트는 즉각적인 응답을 받을 수 있습니다.

이는 옵트인(opt-in)이 아닌 옵트아웃(opt-out) 방식입니다. 기여자가 많을수록 모든 사람의 에이전트가 더 빠르게 실행되기 때문에 기본적으로 켜져 있습니다.

공유되는 데이터

구조적 페이지 데이터만 공유됩니다:

  • 페이지 유형, 제목, 설명

  • 제목, 내비게이션 링크, 콘텐츠 링크

  • 폼 필드 이름, 유형, 레이블(값은 제외)

  • 테이블, 텍스트 블록

  • Open Graph 메타데이터

절대 공유되지 않는 데이터: 쿠키, 세션 토큰, 폼 값, JS 렌더링 맵(세션별 로그인 상태가 포함될 수 있음).

동기화 비활성화

서버를 시작하기 전에 환경 변수를 설정하세요:

WEB_SPEED_REGISTRY_SYNC=false python server.py

또는 MCP 클라이언트 설정에서:

{
  "mcpServers": {
    "web-speed": {
      "command": "/path/to/venv/bin/python",
      "args": ["/path/to/server.py"],
      "env": {
        "WEB_SPEED_REGISTRY_SYNC": "false"
      }
    }
  }
}

자체 호스팅 레지스트리 지정

자체 호스팅 인스턴스를 실행 중인 경우 동기화를 해당 위치로 지정하세요:

WEB_SPEED_REGISTRY_URL=https://your-instance.example.com python server.py

동기화 동작

  • Fire-and-forget: 기여는 백그라운드에서 전송됩니다. 에이전트의 요청은 핑 성공 여부와 관계없이 최고 속도로 완료됩니다.

  • 캐시 MISS 시에만: 로컬 24시간 디스크 캐시에 이미 있는 맵은 다시 전송되지 않습니다.

  • 실패는 조용히 처리됨: 네트워크 오류, 타임아웃, 서버 거부는 DEBUG 레벨에서만 기록되며 에이전트에게 노출되지 않습니다.


아키텍처

URL  ──▶  fetcher.py         (httpx: 10s timeout, 5 redirects, Chrome UA
                ▼             OR Playwright headless Chromium for js=true)
          cleaner.py         (BeautifulSoup/lxml: strip noise, split nav vs content
                ▼             links, filter layout tables, deduplicate text blocks,
          structured map      infer page_type, detect auth_gated)
                ▼
          cache.py           (24h TTL, MD5 keyed JSON files in ./cache/)
                ▼
          registry_sync.py   (fire-and-forget POST to api.getwebspeed.io/v1/contribute)
                ▼
          server.py          (FastMCP: 8 tools over stdio)

AI 없음. 해석 없음. 에이전트가 두뇌입니다.


알려진 제한 사항

  • JS 렌더링 SPA: JavaScript(React, Vue, Angular)를 통해 콘텐츠를 로드하는 페이지는 사전 렌더링된 HTML 셸만 반환합니다. JS에 의해 주입되는 비밀번호 필드, 검색 결과, 내비게이션은 누락됩니다. 보이는 것에 대해 inspect_element를 사용하고 SPA가 많은 대상에는 브라우저 자동화 도구와 함께 사용하세요.

  • page_type 휴리스틱: 분류는 구조적이고 빠르지만 완벽하지는 않습니다. 내부 링크가 많은 마케팅 페이지는 listing으로 분류될 수 있으며, 이메일 필드는 있지만 비밀번호 필드가 없는 페이지는 login이 아닐 수 있습니다.

  • 캐시는 로컬 디스크: ./cache/ 디렉토리는 로컬입니다. 다중 프로세스 또는 분산 배포 환경에서는 캐시 항목이 인스턴스 간에 공유되지 않습니다. 공유 캐싱을 위해서는 cache.py를 Redis 또는 Memcached 백엔드로 교체하세요.

  • 속도 제한 미적용: Web Speed는 아웃바운드 요청을 제한하지 않습니다. 대규모 에이전트 군단의 경우 서버 앞에 속도 제한 프록시(예: Cloudflare, nginx)를 두세요.

Related MCP Connectors

Related MCP Servers