[DEVELOP] Chrome DevTools에 생긴 WebMCP — 웹사이트가 에이전트에게 도구를 넘기는 법

Application 탭의 WebMCP는 사이트 기능이 아닙니다. MCP와 무엇이 다른지, HTML/JS로 어떻게 붙이는지, 프론트가 지금부터 알아둘 점을 정리합니다.

2026년 09월 02일 · lim.Chuck

[DEVELOP]

  1. [DEVELOP] DDD, TDD, BDD
  2. [DEVELOP] 개인정보 보호 웹사이트 구축을 위한
  3. [DEVELOP] 예제로 이해하는 웹 접근성 (accessibility)
  4. [DEVELOP] 예제로 보는 이미지 사용법 (Images)
  5. [DEVELOP] 예제로 보는 반응형 디자인 사용법 (Responsive Design)
  6. [DEVELOP] PWA 이해하기 (Progressive Web App)
  7. [DEVELOP] 개발 프로세스 Agile / Waterfall 이란?
  8. [DEVELOP] 주니어 개발자의 역습
  9. [DEVELOP] MCP(Model Context Protocol)
  10. [DEVELOP] MCP claude 적용하고 사용해보기
  11. [DEVELOP] 실시간 통신 방식 비교 (HTTP, SSE, WebSocket, stdio)
  12. [DEVELOP] 클로드 코드 50만 줄 소스코드 유출 사건 분석
  13. [DEVELOP] Claude Code ‘컨텍스트 로트’ 현상과 비용 80% 절감법
  14. [DEVELOP] 에이전트 하네스 엔지니어링이란? — AI 에이전트를 제대로 다루는 기술
  15. [DEVELOP] Turborepo 캐시로 CI/CD 빌드 시간을 90% 줄이는 법
  16. [DEVELOP] 모노레포 빌드 10분 → 2분, Turborepo 캐시와 CI/CD 최적화 실전기
  17. [DEVELOP] Cursor Agent로 PR 자동 리뷰 시스템 만들기 — Skill 기반 워크플로우 설계
  18. [DEVELOP] Claude Fable 5·Mythos 5 전면 중단 — 출시 3일 만에 꺼진 AI
  19. [DEVELOP] 실무 모노레포에 에이전트 하네스를 깔고 운영해본 기록
  20. [DEVELOP] Chrome DevTools에 생긴 WebMCP — 웹사이트가 에이전트에게 도구를 넘기는 법

Chrome DevTools 애플리케이션 패널을 열었더니, 저장용량 위에 WebMCP 항목이 있었습니다. 빈 페이지에서는 도구가 없고, WebMCP zaMaker 를 열면 add_topping, set_pizza_size 같은 목록이 뜹니다. 사이트 기능이 아니라 브라우저가 에이전트에게 넘기는 도구 인스펙터입니다. 이 글은 그게 왜 생겼는지, MCP와 무엇이 다른지, 데모가 폴리필로 도구를 어떻게 심는지 정리한 기록입니다.


DevTools에 보이는 그것

WebMCP 패널은 크게 두 칸입니다.

  • 도구 활동 — 에이전트가 이 탭에서 도구를 호출한 실시간 로그
  • 사용 가능한 도구 — 현재 페이지가 등록한 tool 목록

도구를 등록하지 않은 페이지는 이렇게 비어 있습니다.

Chrome DevTools Application 탭의 WebMCP 패널. 등록된 도구가 없어 안내 문구만 보인다

같은 패널에서 피자 만들기 데모를 열면 목록이 채워집니다. 페이지가 폴리필로 document.modelContext를 만들고, 피자 UI 함수를 registerTool로 올렸기 때문입니다.

WebMCP 패널의 사용 가능한 도구 목록. add_topping, manage_pizza, set_pizza_size 등이 표시된다

로컬에서 보려면 Chrome에서 플래그를 켭니다.

  1. chrome://flags/#enable-webmcp-testing → Enabled
  2. 브라우저 재시작
  3. HTTPS 또는 localhost 페이지에서 DevTools → 애플리케이션 → WebMCP

프로덕션은 Chrome 149부터 origin trial입니다. 토큰을 meta 또는 Origin-Trial 헤더로 넣습니다.

1
<meta http-equiv="origin-trial" content="TOKEN_HERE" />

공식 패널 설명은 Debug WebMCP tools에 있습니다. 도구를 클릭하면 파라미터를 넣고 Run tool로 에이전트 없이 직접 실행해볼 수 있습니다.

패널은 UI일 뿐이고, 실제 목록은 window.document.modelContext에서 가져옵니다. Application 탭이 비어 있으면 콘솔에서 같은 객체를 보면 됩니다.

1
console.log(await window.document.modelContext.getTools());

자동완성에 registerTool, getTools, executeTool, ontoolchange가 뜹니다. modelContextEventTarget이라 addEventListener도 있습니다.

콘솔에서 window.document.modelContext. 자동완성. registerTool, getTools, executeTool이 보인다

피자 데모에서 getTools()를 치면 배열 7개가 나옵니다. Application 탭의 도구 목록과 같습니다. 각 원소는 name, description, inputSchema, origin, window입니다. registerTool로 올린 계약이 그대로 돌아옵니다.

getTools() 결과. add_topping, manage_pizza 등 7개 도구 객체

undefined이거나 메서드가 없으면 그 탭에 WebMCP가 안 켜진 것입니다. 플래그·origin trial·폴리필 중 하나는 있어야 document.modelContext가 생깁니다.


왜 생겼나

에이전트가 웹을 쓰는 방식은 지금까지 대략 이랬습니다.

  • DOM을 읽고 버튼을 추측해서 클릭한다
  • 셀렉터가 바뀌면 깨진다
  • 커스텀 위젯(날짜 피커, 장바구니)은 의미를 모른다

Google은 이걸 actuation(사람이 하듯 클릭·입력을 흉내 내는 것)이라고 부릅니다. 단계는 많고, 매 단계가 해석에 열려 있습니다.

WebMCP는 방향을 뒤집습니다. 에이전트가 화면을 추측하는 대신, 사이트가 “이 페이지에서 할 수 있는 일”을 JSON Schema 도구로 선언하고 브라우저가 그걸 에이전트에게 넘깁니다. 도구의 execute는 페이지 JS에서 돌아가므로 쿠키, 로그인 세션, 기존 클릭 핸들러를 그대로 씁니다. 브랜딩과 확인 다이얼로그도 사이트가 유지합니다.

W3C Web Machine Learning Community Group 초안이고, Chrome 구현은 아직 바뀌는 중입니다. 특히 getter가 navigator.modelContext에서 document.modelContext로 옮겨졌고, Chrome 150에서 navigator 쪽은 deprecated입니다. 새 코드는 이렇게 잡으면 됩니다.

1
const modelContext = document.modelContext ?? navigator.modelContext;

MCP와 WebMCP는 같은 이름이 아니다

예전에 MCP(Model Context Protocol)를 USB-C에 비유한 적이 있습니다. LLM이 외부 도구·데이터에 붙는 표준 플러그 규격이라는 뜻이었습니다. Cursor, Claude Code, 서버 사이드 에이전트가 Figma·Sentry·DB에 붙을 때 쓰는 그 MCP입니다.

WebMCP는 그 아이디어를 브라우저 탭에 가져온 것이지, MCP 서버를 웹에 그대로 심은 것이 아닙니다. 이름에 MCP가 들어 있어서 헷갈리기 쉽습니다.

  MCP (Model Context Protocol) WebMCP
누가 만들었나 Anthropic이 공개한 개방형 프로토콜. 에디터·에이전트 런타임이 채택 W3C WebML CG 초안 + Chrome origin trial
어디서 도나 로컬 프로세스, 원격 서버, IDE 확장. stdio / HTTP+SSE 현재 열린 웹 문서. document.modelContext
도구가 가리키는 것 GitHub, DB, 파일시스템, 사내 API 같은 페이지 밖 시스템 장바구니 담기, 필터, 예약 제출 같은 이 화면의 액션
누가 등록하나 개발자가 MCP 서버를 켜고 클라이언트가 연결 프론트가 페이지에서 registerTool 또는 form 어노테이션
실행 컨텍스트 서버/로컬 권한, API 키, 워크스페이스 유저 브라우저. 쿠키와 동일 출처 JS
발견 방식 mcp.json, 클라이언트 설정에 서버를 명시 탭을 열어야 도구가 보임. 방문하지 않으면 모름
디버깅 Cursor MCP 패널, 서버 로그 Chrome Application → WebMCP
라이프사이클 세션 동안 서버가 살아 있으면 유지 페이지/컴포넌트와 함께 등록·해제. 탭을 닫으면 끝

한 줄로 말하면 이렇습니다.

MCP는 에이전트의 작업실에 공구를 다는 규격이고, WebMCP는 지금 보고 있는 웹페이지가 그 공구 목록을 직접 적어 주는 브라우저 API입니다.

둘은 대체재가 아니라 층이 다릅니다. 코딩 에이전트는 여전히 Sentry MCP로 에러를 보고, 같은 사용자가 쇼핑몰 탭에서는 WebMCP로 add_to_cart를 호출할 수 있습니다. 전자는 개발자 도구 벨트, 후자는 제품 화면의 공개 액션 계약입니다.

실무에서 이미 겪는 차이도 있습니다. 하네스 운영 기록에서 MCP를 많이 붙이면 컨텍스트가 새는 이야기를 했습니다. WebMCP도 도구를 페이지에 잔뜩 올리면 에이전트가 고를 후보만 늘어납니다. 지금 화면에 있는 일만 등록하는 것이 MCP 예산을 줄이는 것과 같은 종류의 설계입니다.


데모가 도구를 붙이는 방법: zaMaker + 폴리필

스크린샷의 도구 목록은 WebMCP zaMaker 입니다. Google Chrome Labs의 Imperative 데모이고, 소스는 webmcp-tools 저장소에 있습니다.

화면은 피자를 만드는 UI입니다. 크기·스타일·소스/치즈·토핑 버튼이 있고, 클릭하면 DOM을 바꿉니다. WebMCP는 그 위에 같은 함수를 에이전트가 호출할 수 있게 한 층입니다. 서버 MCP를 붙인 게 아닙니다.

페이지 끝 스크립트 순서가 핵심입니다.

1
2
3
4
<meta http-equiv="origin-trial" content="…WebMCP…" />
<!-- 중략: 피자 UI 버튼들 -->
<script src="../shared/webmcp-polyfill.js"></script>
<script type="module" src="script.js"></script>
  1. origin trial 토큰으로 Chrome에 WebMCP를 켠다 (이 origin에서).
  2. webmcp-polyfill.js 가 네이티브 API가 없을 때만 document.modelContext를 만든다.
  3. script.js가 피자 함수를 registerTool로 올린다.

네이티브가 이미 있으면 폴리필은 바로 return 합니다. DevTools Application 패널이 도구를 보여주는 건 Chrome이 구현한 document.modelContext 를 보기 때문입니다. 플래그/OT가 없는 브라우저에서는 폴리필이 같은 모양의 객체를 만들어서, 페이지 안 에이전트나 getTools() 테스트가 돌아가게 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function () {
  if (window.document.modelContext) {
    return; // 네이티브가 있으면 건드리지 않음
  }

  window.__webmcp_registered_tools =
    window.__webmcp_registered_tools || new Map();

  class ModelContext extends EventTarget {
    async registerTool(tool, options = {}) { /* … */ }
    async getTools(options = {}) { /* … */ }
    async executeTool(tool, args, options) { /* … */ }
  }

  Object.defineProperty(window.document, "modelContext", {
    value: new ModelContext(),
    writable: false,
    configurable: true,
  });
})();

폴리필이 하는 일은 대략 네 가지입니다.

등록. registerTool은 이름·description 검증 후 Map에 넣습니다. 이름은 a-zA-Z0-9_.-, 1~128자. 같은 이름 두 번은 InvalidStateError. options.signal이 abort되면 Map에서 지웁니다. execute 함수는 _execute로 보관합니다.

발견. getTools()는 두 소스를 합칩니다.

  • Imperative: __webmcp_registered_tools에 넣어 둔 것
  • Declarative: form[toolname]을 훑어서 input/select/textarea의 name, toolparamdescription, required로 JSON Schema를 만듦

피자 데모는 폼 어노테이션을 안 씁니다. 버튼 UI라 Imperative만 등록합니다. 같은 폴리필이 French Bistro 같은 폼 데모도 받기 때문에 declarative 분기가 들어 있습니다.

실행. executeTool은 인자가 문자열이면 JSON.parse 한 뒤, Map에 있으면 _execute(parsedArgs)를 호출합니다. 폼 도구면 필드를 채우고 toolactivated를 쏘고, toolautosubmit이면 submit, 아니면 제출 버튼에 포커스만 줍니다. submit 이벤트에 agentInvokedrespondWith를 붙이는 것도 여기입니다.

iframe. 다른 origin 창은 postMessageWEBMCP_GET_TOOLS_REQUEST / WEBMCP_EXECUTE_TOOL_REQUEST를 보냅니다. 페이지 에이전트가 iframe 안 도구를 쓸 때를 위한 브리지입니다. 피자 한 장짜리 데모에서는 거의 안 탑니다.

그다음 script.js가 사람용 함수를 먼저 만들고, API가 있을 때만 도구로 감쌉니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function addTopping(emoji, size, count) {
  // DOM에 토핑 div를 붙이는 기존 UI 로직
}

if (document.modelContext) {
  document.modelContext.registerTool(
    {
      name: "add_topping",
      description: "Add one or more toppings to the pizza",
      inputSchema: {
        type: "object",
        properties: {
          topping: {
            type: "string",
            enum: ["🍕", "🍄", "🌿" /* … */],
          },
          size: {
            type: "string",
            enum: ["Small", "Medium", "Large", "Extra Large"],
          },
          count: { type: "integer", minimum: 1 },
        },
        required: ["topping"],
      },
      execute: ({ topping, size = "Medium", count = 5 }) => {
        addTopping(topping, size, count);
        return `Added ${count} ${topping} topping(s)`;
      },
    },
    { exposedTo: ["http://localhost:8080", "https://chrome.dev"] }
  );
}

onclick="addTopping('🍄', …)"execute같은 함수를 부릅니다. 에이전트용 백엔드를 새로 만든 게 아닙니다. DevTools에 보이던 목록은 이렇게 올라간 것들입니다.

도구 하는 일
set_pizza_size 크기 이름, 또는 인원 수로 크기 추정
set_pizza_style Classic / Bianca / BBQ / Pesto / Wales
toggle_layer 소스·치즈 레이어 add / remove / toggle
add_topping 이모지 토핑 추가
remove_topping 특정 토핑 제거
manage_pizza 마지막 토핑 삭제 또는 리셋
share_pizza 현재 상태를 URL에 인코딩해 공유

set_pizza_size는 schema에 number_of_persons를 넣어 두었습니다. 에이전트가 “4명용으로”라고 해도 버튼을 네 번 누르지 않고 Medium으로 매핑합니다. DOM을 긁는 에이전트로는 잘 안 나오는 종류의 계약입니다.

네이티브가 감지되면 bodywebmcp-supported를 붙여 수동 버튼을 숨깁니다. 사람이 쓰는 UI와 에이전트가 쓰는 도구가 같은 상태 머신을 공유하되, 입력 경로만 갈라집니다. ?showButtons가 있으면 버튼을 다시 보여 줍니다.

정리하면 이 사이트가 MCP 서버를 추가한 게 아닙니다. HTML에 폴리필을 넣고, 기존 클릭 핸들러를 document.modelContext.registerTool로 한 번 더 선언한 것입니다. 그게 Application 탭에 도구로 나타납니다.


프론트에서 붙이는 방법

패키지를 깔 필요는 없습니다. 브라우저 API에 도구를 등록하면 DevTools가 감지합니다. 두 갈래입니다.

  • Imperative — JavaScript registerTool. SPA, 상태, 장바구니처럼 폼이 아닌 액션
  • Declarative<form>toolname 같은 속성. 검색·예약처럼 이미 폼이 있는 화면

Imperative: JS로 등록

위에서 본 피자 데모가 이 방식입니다. 우리 사이트에 옮길 때도 폴리필(또는 네이티브) 다음에 registerTool만 호출하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<button id="add-cart">장바구니 담기</button>

<script>
  async function registerTools() {
    const ctx = document.modelContext ?? navigator.modelContext;
    if (!ctx) {
      console.warn("이 브라우저는 WebMCP를 지원하지 않습니다.");
      return;
    }

    await ctx.registerTool({
      name: "add_to_cart",
      title: "장바구니에 담기",
      description:
        "현재 상품을 장바구니에 담습니다. 수량을 지정할 수 있습니다.",
      inputSchema: {
        type: "object",
        properties: {
          quantity: {
            type: "integer",
            minimum: 1,
            description: "담을 수량",
          },
        },
        required: ["quantity"],
      },
      annotations: { readOnlyHint: false },
      execute: async ({ quantity }, { signal }) => {
        await fetch("/api/cart", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ quantity }),
          signal,
        });
        return `장바구니에 ${quantity}개를 담았습니다.`;
      },
    });
  }

  registerTools();
</script>

name / description / inputSchema / execute가 세트입니다. description은 에이전트가 도구를 고르는 프롬프트에 가깝습니다. “버튼을 누른다”가 아니라 무엇을 하는지를 적습니다.

SPA에서는 화면과 수명을 맞춰야 합니다. 언마운트 후에도 도구가 남으면 에이전트가 없는 UI를 호출합니다. Chrome은 AbortSignal로 해제합니다.

1
2
3
4
5
6
7
const controller = new AbortController();
await document.modelContext.registerTool(tool, {
  signal: controller.signal,
});

// 컴포넌트 unmount
controller.abort();

콘솔 확인은 위에서 쓴 getTools()와 같습니다. window.document.modelContext가 등록 저장소입니다.

React는 experimental usewebmcp, Angular는 Signal Forms 연동이 있습니다. 타입은 webmcp-types를 쓰면 됩니다. 자세한 API는 Imperative API에 있습니다.

Declarative: HTML form에 속성만

이미 <form>이 있으면 JS registerTool 없이 브라우저가 폼을 도구로 해석합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<form
  toolname="search_cars"
  tooldescription="차종, 좌석, 연식 등으로 차량을 검색합니다."
  toolautosubmit
>
  <label for="car_type">차종</label>
  <select id="car_type" name="car_type" toolparamdescription="차량 유형">
    <option value="suv">SUV</option>
    <option value="sedan">세단</option>
  </select>

  <label for="seats">최소 좌석</label>
  <input
    type="number"
    id="seats"
    name="seats"
    min="1"
    max="9"
    toolparamdescription="필요한 최소 좌석 수"
  />

  <button type="submit">검색</button>
</form>

에이전트가 도구를 호출하면 브라우저가 필드를 채웁니다. toolautosubmit이 없으면 사용자가 제출 버튼을 눌러야 합니다. 결제처럼 민감한 액션에 맞습니다. submit 시 SubmitEvent.agentInvoked === true로 사람이 누른 건지 에이전트가 채운 건지 구분할 수 있습니다. 문서는 Declarative API입니다.


프론트가 지금부터 알아둘 것

WebMCP 없어도 사람이 쓰는 UI는 그대로여야 합니다. progressive enhancement입니다. modelContext가 없으면 등록을 건너뛰면 됩니다.

항목 실무에서 하는 일
화면과 tool 수명 장바구니 페이지에만 add_to_cart. 라우트가 바뀌면 해제
Schema를 좁게 { type: "object" }만 주면 에이전트가 못 씀. enum, required, description을 구체적으로
파괴적 액션 결제/삭제/공유는 확인 UI. 사람이 submit하도록 두거나 annotation을 명시
보안 기본 same-origin. cross-origin iframe은 allow="tools". 외부에 열려면 exposedTo. document.domain을 풀면 API가 꺼짐
기존 로직 재사용 execute에서 새 백엔드를 만들지 말고, 지금 클릭 핸들러·API를 호출
디버깅 Application → WebMCP에서 수동 Run, 스키마 에러, invocation 로그

숨은 관리자 메뉴를 도구로 올리면 에이전트가 그걸 호출할 수 있습니다. 사람이 이 페이지에서 해도 되는 일만 등록하는 편이 맞습니다.

먼저 붙이기 좋은 후보는 이미 이름이 있는 액션입니다.

  • 검색 / 필터 / 정렬
  • 장바구니 담기, 옵션 변경
  • 예약·폼 제출
  • 이 화면에서만 의미 있는 상태 변경 (데모의 토핑처럼)

앞으로

웹은 “에이전트가 DOM을 해킹하는 사이트”에서 사이트가 에이전트용 공개 API를 제공하는 사이트로 갑니다. REST가 서버 API였다면, WebMCP는 현재 화면의 클라이언트 액션 계약에 가깝습니다.

아직 표준이 아니라 origin trial입니다. API 이름도 한 번 바뀌었습니다. 지금 전 사이트에 깔 단계는 아니고, 핵심 플로우 한두 개에 feature detect로 심고 DevTools로 검증하는 쪽이 맞습니다. 스펙 논의는 Chrome WebMCP 문서와 explainer를 보면 됩니다.

에이전트 쪽에서는 Chrome DevTools for agents가 WebMCP 도구를 읽고 실행하는 경로를 열고 있습니다. 프론트가 도구를 잘 적어 두면, 에이전트는 셀렉터를 맞추는 대신 스키마에 맞춰 인자를 넣는 쪽으로 붙게 됩니다.


함께 보면 좋은 글

참고자료

분류 링크
Chrome WebMCP developer.chrome.com/docs/ai/webmcp
DevTools 패널 Debug WebMCP tools
Imperative API registerTool
Declarative API HTML form 어노테이션
데모 WebMCP zaMaker
폴리필 webmcp-polyfill.js
저장소 GoogleChromeLabs/webmcp-tools