콘텐츠로 이동
Study NoteAgent 배포 플랫폼

WebMCP 확장 — 기존 사이트에 AI 도구 덧붙이기

결론부터
  • 사이트가 AI용 도구를 제공하지 않아도 우리가 브라우저 확장으로 WebMCP 도구를 덧붙일 수 있다. 도구는 확장을 설치한 브라우저의 페이지에 생긴다.
  • 확장은 URL에 맞는 코드를 넣고, 그 코드가 도구를 등록한다. 실제 화면 읽기·조작은 호출받은 execute가 수행한다.
  • 도구 주입·등록과 AI 연결은 별도다. 로컬 Codex도 도구가 등록된 같은 브라우저·탭에 연결되어야 사용할 수 있다.
  • WebMCP의 이점은 AI가 기능을 발견·호출할 공통 인터페이스다. 내부 코드가 DOM에 의존하면 Selenium·Playwright처럼 사이트 개편에 맞춰 고쳐야 한다.

회사 회의실 사이트에는 사람용 검색 화면만 있고, 우리가 그 사이트의 소스 코드를 고칠 수 없다고 하자. 그래도 우리 브라우저에 확장을 설치해, 해당 사이트를 열 때 우리가 만든 코드를 함께 실행하게 할 수 있다. 그 코드가 WebMCP 도구를 등록하면 페이지와 연결된 AI가 사용할 수 있다.

WebMCP 기초의 “사람에게 검색 버튼을 제공하듯, AI에게 호출할 검색 도구도 제공한다”는 설명에서 도구를 만드는 사람만 사이트 개발자에서 우리로 바뀐 것이다. 원래 사이트의 배포 파일은 그대로이고, 확장을 설치한 브라우저의 페이지에 도구가 생긴다.

이 페이지는 어디에 코드를 넣고 로컬 AI에 어떻게 연결하는지 설명한다. 또한 Selenium·Playwright 자동화와 비교해 무엇이 달라지고, 사이트 개편 시 무엇을 고쳐야 하는지 구분한다. 코드는 가상 회의실 사이트를 전제로 한 설명용 예제다. 특정 서비스에서 검증한 완성 확장이나 설치 실습은 아니다.

이 장에서 처음 나오는 말3개
content script
확장이 지정한 웹 페이지에서 실행하는 JavaScript 파일이다. 페이지 요소를 읽거나 조작할 수 있다.
주입Injection
사이트 원본을 배포하지 않고, 열린 페이지에서 추가 코드를 실행하는 것을 말한다.
사이트별 연결 코드Site Adapter
이 사이트의 어느 입력칸·버튼·결과를 사용할지 알고 있는 코드다. 이 문서에서 사용하는 설명용 이름이다.

확장은 넣어 주고, 도구는 일을 한다

섹션 제목: “확장은 넣어 주고, 도구는 일을 한다”

확장 자체가 AI 모델은 아니다. 확장은 맞는 페이지에 코드를 넣는 역할이고, 주입된 코드는 AI가 부를 도구와 그 실행 방법을 정의한다. AI는 도구 이름과 입력 규칙을 읽고 호출할 것을 선택한다.

시점누가 무엇을 하는가그 시점의 결과
페이지 접속사용자가 회의실 사이트를 연다평소의 로그인된 화면이 있다
URL 확인확장이 주소에 맞는 rooms-tools.js를 주입한다우리 코드가 해당 페이지에서 실행된다
도구 등록우리 코드가 registerTool을 호출한다도구 정의가 등록된다. 업무 동작은 아직 실행하지 않는다
발견·호출연결된 AI가 도구를 발견하고 입력값을 보낸다브라우저가 등록된 execute를 실행한다
처리·반환우리 코드가 화면을 읽거나 조작하고 결과를 반환한다AI가 결과를 받아 사용자에게 답한다

Chrome의 content script는 URL에 맞춰 파일을 자동으로 실행할 수 있다. 실제로 Brave의 WebMCP 스크립트는 URL 패턴에 맞는 코드를 페이지에 주입하고, 등록한 도구를 Brave AI Chat에서 발견하는 구조다. Brave의 브라우저 내장 배포 방식과 우리가 만드는 Chrome 확장은 서로 다른 구현이지만, 위 역할 분담을 이해하는 사례가 된다.

페이지마다 무엇을 만들어야 하는가

섹션 제목: “페이지마다 무엇을 만들어야 하는가”

페이지 URL마다 무조건 파일을 하나씩 만들 필요는 없다. 같은 화면 구조와 동작을 공유하는 페이지 묶음마다 연결 코드를 두면 된다. 다음은 우리가 설계할 수 있는 가상 예다.

URL 범위주입할 파일AI에게 제공할 기능
https://rooms.example.com/rooms/*rooms-tools.js현재 목록 읽기, 검색 조건 입력
https://helpdesk.example.com/tickets/*ticket-tools.js현재 문의 읽기, 답변 초안 입력

AI는 “회의실 목록 읽기”라는 이름을 알면 되지만, 우리는 #room-results가 결과 영역이라는 사실까지 코드로 정해야 한다. 사이트가 DOM을 바꾸면 이 코드도 고쳐야 한다. 화면 해석 부담을 사이트별 코드에 미리 담아 재사용하는 방식이지, 사이트 구조를 몰라도 모든 기능이 자동으로 생기는 방식은 아니다.

Selenium·Playwright 자동화와 무엇이 다른가

섹션 제목: “Selenium·Playwright 자동화와 무엇이 다른가”

DOM을 읽거나 버튼을 누르는 코드를 WebMCP 도구로 감싸도 사이트 개편에 대응할 책임은 그대로다. 핵심 차이는 AI에게 기능을 공개하는 방식이다. WebMCP는 이름·설명·입력 규칙을 가진 도구를 페이지에 공개하고, 이를 지원하며 해당 페이지에 연결된 에이전트가 발견하고 호출하게 한다. 공식 제안도 기존 브라우저 자동화와 함께 사용할 수 있는 경로로 설명한다.

방식회의실 검색 절차를 구현하는 곳사이트 개편 시 책임
Selenium·Playwright 자동화자동화 코드가 입력칸·버튼·결과를 찾아 처리자동화 작성자가 영향을 받은 코드 수정
확장으로 WebMCP 도구 주입우리가 만든 search_rooms의 실행 코드가 입력칸·버튼·결과를 처리확장 작성자가 영향을 받은 코드 수정
사이트가 직접 WebMCP 제공사이트 개발자가 내부 검색 기능을 도구로 공개사이트 팀이 도구 구현을 수정. 이름·입력·출력 계약을 유지하면 사용하는 AI 쪽은 그대로 사용 가능

같은 화면 조작이면 같은 부분이 깨질 수 있다

섹션 제목: “같은 화면 조작이면 같은 부분이 깨질 수 있다”

자동화 코드가 하는 일이 인원수 입력 → 검색 버튼 클릭 → 새 결과 읽기라면, 확장의 search_rooms({ capacity: 4 }) 안에도 같은 절차가 들어갈 수 있다. 입력칸의 식별자나 결과 구조가 바뀌어 기존 코드가 대상을 찾지 못하면 두 방식 모두 고쳐야 한다.

아래 읽기 예제의 #room-results가 사이트 개편으로 #rooms-list로 바뀌었다고 하자. 도구 이름은 그대로여서 Codex가 발견할 수 있어도, 내부 선택자가 옛 이름을 쓰면 호출은 실패한다. 공개한 도구의 이름이 안정적이라는 것과 내부 구현이 계속 동작한다는 것은 별개다.

모든 화면 변경이 반드시 자동화를 깨뜨리는 것도 아니다. Playwright의 locator 안내처럼 역할·접근 가능한 이름이나 명시적인 test ID를 사용하면 DOM 배치 변경에 대한 의존성을 줄일 수 있다. WebMCP 여부만으로 견고함을 판정할 수 없다.

WebMCP 도구를 쓰는 AI는 사이트별 조작 절차 대신 기능 설명과 입력 규칙을 읽고 호출할 수 있다. 다만 잘 만든 Playwright 함수나 스킬도 절차를 재사용할 수 있으므로, 재사용 자체가 WebMCP만의 장점은 아니다. WebMCP는 그 기능을 지원 에이전트들이 사용할 수 있는 공통 브라우저 API로 공개한다는 데 의미가 있다. 각 에이전트가 도구가 등록된 브라우저·탭에 연결되어야 한다는 조건은 남는다.

다음은 이 페이지의 선택 기준이다.

  • 내 Codex로 정해진 사이트 작업만 수행한다: 기존 Playwright 코드·스킬로 충분하다면 WebMCP로 감쌀 필요는 크지 않다.
  • 같은 기능을 여러 WebMCP 지원 에이전트에 제공한다: 도구 설명·입력 규칙을 함께 공개하는 확장 방식이 유용할 수 있다. 도구 주입·등록과 사이트별 실행 코드의 유지보수 비용도 함께 생긴다.
  • 사이트 팀이 도구를 직접 제공할 수 있다: 외부 자동화 작성자가 DOM 변경을 따라가는 부담을 줄일 수 있다. 사이트 팀이 이름·입력·출력 계약을 유지한다는 전제이며, 그 계약이 바뀌면 호출하는 쪽도 수정해야 한다.

작은 예제 — 화면에 있는 회의실 목록 읽기

섹션 제목: “작은 예제 — 화면에 있는 회의실 목록 읽기”

처음부터 검색·예약을 모두 자동화하면 입력 이벤트와 화면 전환까지 다뤄야 한다. 먼저 현재 화면의 목록에서 인원수에 맞는 회의실을 골라 반환하는 읽기 전용 도구를 보자. 새 검색을 실행하거나 화면에 없는 회의실까지 조회하는 기능은 포함하지 않는다.

어떤 화면과 브라우저를 가정하는가

섹션 제목: “어떤 화면과 브라우저를 가정하는가”

대상은 https://rooms.example.com/rooms/list라는 가상 페이지다. 브라우저의 WebMCP 기능이 활성화되어 있고, 확장에 해당 사이트 접근이 허용되어야 한다. Chrome의 실험 기능 설정은 공식 WebMCP 안내를 따른다. 확장이 없는 API를 자동으로 구현해 주지는 않는다.

이 예의 화면은 로딩이 끝나면 아래처럼 생겼다고 가정한다. data-state와 data-capacity는 WebMCP 규칙이 아니라 이 가상 사이트가 가진 속성이다. 실제 사이트에서는 개발자 도구로 그 사이트의 표시 방식을 확인해야 한다.

<ul id="room-results" data-state="ready">
<li data-capacity="6"><span class="room-name">한라실</span></li>
<li data-capacity="2"><span class="room-name">소담실</span></li>
</ul>

manifest는 어느 페이지에 넣을지 정한다

섹션 제목: “manifest는 어느 페이지에 넣을지 정한다”

가상 확장 폴더 room-tools-extension/에는 manifest.json과 rooms-tools.js 두 파일을 둔다. 아래는 manifest.json이다.

{
"manifest_version": 3,
"name": "Room Tools Example",
"version": "0.1.0",
"content_scripts": [
{
"matches": ["https://rooms.example.com/rooms/*"],
"js": ["rooms-tools.js"],
"run_at": "document_idle",
"world": "MAIN"
}
]
}

manifest의 content script 설정에서 matches는 적용 주소, js는 주입 파일, run_at은 실행 시점이다. document_idle이어도 앱이 나중에 받아 오는 회의실 데이터까지 준비됐다는 뜻은 아니므로, 아래 도구는 호출 시 준비 상태를 따로 확인한다.

world: "MAIN"은 페이지의 JavaScript 실행 환경에 넣는다는 뜻이다. 기본 ISOLATED는 확장의 변수를 페이지 코드와 분리한다. 여기서는 페이지 쪽에 WebMCP 도구를 등록하는 경로를 명확히 하려고 MAIN을 사용한다. 이 설정으로 사이트 내부의 비공개 모듈 함수까지 전부 접근할 수 있게 되는 것은 아니다.

이는 자동 주입을 위한 정적 선언이다. 확장 버튼을 눌렀을 때만 주입하려면 별도 방식인 scripting.executeScript와 activeTab을 사용할 수 있다. 위 manifest와 그 방식의 설정을 혼동하지 않는다.

주입 파일은 도구와 실행 코드를 정한다

섹션 제목: “주입 파일은 도구와 실행 코드를 정한다”

다음은 rooms-tools.js다. 위의 화면 구조를 전제로 하며, 사이트 자체는 WebMCP를 등록하지 않아도 된다. 도구 이름 앞의 study_는 우리가 추가한 기능임을 구분하기 위한 예시 접두사다.

(async () => {
const context = document.modelContext;
if (typeof context?.registerTool !== 'function') {
console.warn('이 페이지에서 WebMCP를 사용할 수 없습니다.');
return;
}
await context.registerTool({
name: 'study_list_displayed_rooms',
description: '현재 표시된 목록에서 인원수에 맞는 회의실을 읽는다. 새 검색이나 예약은 하지 않는다.',
inputSchema: {
type: 'object',
properties: { capacity: { type: 'integer', minimum: 1 } },
required: ['capacity'],
additionalProperties: false,
},
annotations: { readOnlyHint: true, untrustedContentHint: true },
execute: async ({ capacity }) => {
if (!Number.isInteger(capacity) || capacity < 1) {
throw new Error('capacity는 1 이상의 정수여야 합니다.');
}
if (!location.pathname.startsWith('/rooms/')) {
throw new Error('회의실 페이지에서만 사용할 수 있습니다.');
}
const list = document.querySelector('#room-results[data-state="ready"]');
if (!list) throw new Error('목록이 준비되지 않았거나 화면 구조가 바뀌었습니다.');
const rooms = [...list.querySelectorAll('li')].map((row) => {
const name = row.querySelector('.room-name')?.textContent?.trim();
const seats = Number(row.getAttribute('data-capacity'));
if (!name || !Number.isInteger(seats) || seats < 1) {
throw new Error('회의실 항목을 해석할 수 없습니다.');
}
return { name, capacity: seats };
});
return JSON.stringify({
scope: '현재 화면에 로드된 목록만',
rooms: rooms.filter((room) => room.capacity >= capacity),
});
},
});
})().catch((error) => console.error('회의실 도구 등록 실패:', error));

등록 구조는 WebMCP JavaScript API를 따른다. 목록을 읽는 코드는 등록 시점이 아니라 호출 시점에 실행되므로, 첫 화면의 결과를 계속 재사용하지 않는다. untrustedContentHint는 사이트에서 읽은 결과를 신뢰할 수 있는 명령으로 취급하지 말라는 힌트다.

위 화면에서 입력이 {"capacity": 4}라면 예상 반환 문자열의 JSON 내용은 다음과 같다.

{
"scope": "현재 화면에 로드된 목록만",
"rooms": [{ "name": "한라실", "capacity": 6 }]
}

capacity: 7이면 rooms가 빈 배열이고, 결과 영역이 없거나 로딩 중이면 오류다. “조건에 맞는 방이 없음”과 “화면을 읽지 못함”을 구분해야 AI가 로딩 실패를 검색 결과로 오해하지 않는다. 정원 6명이라는 사실만 읽었으므로 그 시간에 예약 가능한지는 알 수 없다.

이 예제는 실제 확장으로 실행 검증하지 않았다. 적용할 때는 대상 사이트의 URL·화면 구조를 바꾸고, Chrome 확장 로드 안내를 따라 확장을 로드한 뒤 대상 페이지를 새로 연다. 등록 여부와 반환값은 공식 도구 검사 확장으로 먼저 확인할 수 있다. 되돌리려면 확장을 비활성화하고 대상 페이지도 새로고침해 이미 주입된 코드를 제거한다.

읽기 도구에 이어 study_search_rooms를 만든다면 execute에 아래 절차를 구현한다. 이름만 등록해서는 동작하지 않으며, 각 단계의 사이트별 처리 코드를 우리가 작성해야 한다.

  1. 인원수 입력칸과 검색 버튼을 찾는다. 요소가 없으면 화면 구조가 바뀌었다고 판단하고 오류를 반환한다.
  2. 입력값을 넣고 앱에 알린다. 실제 사이트가 사용하는 input·change 처리와 상태 갱신을 확인한다. 특히 React 등의 입력은 DOM의 value만 바꿔도 앱 상태가 그대로일 수 있다.
  3. 검색을 시작한다. 기존 버튼이나 form 동작을 사용한다. 서버는 평소처럼 로그인과 조회 권한을 검사한다.
  4. 이번 검색이 끝났는지 확인한다. 해당 요청의 로딩 상태·완료 신호를 확인하고 제한 시간을 둔다. 클릭 직후 남아 있는 이전 결과나 고정된 시간 대기만으로 성공을 판정하지 않는다.
  5. 새 결과를 읽고 반환한다. 결과 없음·실패·시간 초과를 구분한다. 페이지 전체가 이동한다면 이전 문서의 실행이 끝까지 유지된다고 가정하지 않고, 새 페이지의 도구로 이어 가도록 설계한다.

DOM 조작 대신 사이트의 API를 호출할 수도 있다. 그 경우에도 요청 형식·인증·CSRF 처리·화면 동기화를 우리가 알아야 하며, 사이트 내부 API가 바뀌면 따라 고쳐야 한다. MAIN에서 실행하는 코드는 페이지의 웹 권한으로 동작하므로, 확장 설치가 업무 서버의 권한 검사를 없애 주지는 않는다.

로컬 AI와 연결하는 부분은 별도다

섹션 제목: “로컬 AI와 연결하는 부분은 별도다”

도구를 페이지에 등록했다는 사실과 AI가 그 도구를 사용할 수 있다는 사실은 다르다. 실행하려면 해당 탭의 도구를 발견하고 호출하는 기능이 에이전트 쪽에 있어야 한다.

준비된 환경추가로 필요한 것
WebMCP를 지원하는 브라우저 AI주입된 도구가 그 탭의 도구 목록에 실제로 나타나고 호출되는지 확인
Playwright MCP를 쓰는 로컬 Codex같은 탭의 browser_evaluate에서 getTools()·executeTool() 실행
playwright-cli와 스킬을 쓰는 로컬 Codex같은 탭의 eval로 조회·호출. 스킬에는 그 절차를 안내
화면 클릭만 지원하는 브라우저 자동화 도구WebMCP 조회·호출도 지원하는지 별도로 확인. 클릭 가능 여부만으로는 판단할 수 없음

예를 들어 Codex → Playwright MCP → 페이지의 WebMCP 도구라는 경로를 사용할 수 있다. 이미 페이지 JavaScript 실행 기능이 연결되어 있으면 새로운 중계 서버 없이 조회·호출 코드를 전달하면 된다. Codex 연결과 호출 예제에서 설정·입력·예상 결과를 확인한다. 위의 두 확장 파일에는 Codex 연결이나 탭 선택 기능이 없다. 다른 Chrome에 도구를 등록했다고 내장 브라우저를 사용하는 에이전트에서 자동으로 보이지도 않는다.

Brave 사례에는 주입 코드와 AI Chat의 발견 경로가 함께 있다. 반면 GoogleChromeLabs의 WebMCP Tool Overrides는 도구 주입·변경을 다루는 확장 사례다. Google의 정식 지원 제품은 아니며, 그 확장 자체를 모든 로컬 AI와의 연결기로 해석하면 안 된다. 두 저장소의 예전 API 이름을 그대로 복사하기보다 대상 브라우저의 현재 API를 확인한다.

사이트가 직접 제공하면 사이트 팀이 관리하던 연결 부분을 우리가 맡는다. 다음은 위 예제에서 출발해 적용할 때의 점검 기준이다.

바뀌는 것우리가 처리할 일
버튼·목록의 HTML 구조선택자와 값 해석을 수정하고 예상 결과를 다시 확인
로그인 만료·로딩 실패빈 검색 결과로 돌려주지 않고 실행 실패로 구분
새로고침·다른 페이지 이동새 문서에 다시 주입. 확장을 끄면 열린 페이지도 새로고침
새로고침 없는 SPA 화면 이동URL·화면 상태를 다시 검사하고 도구를 해제·재등록. 진입 시 URL 매칭만으로 모든 이동을 처리할 수 없음
도구 재등록같은 이름의 중복 등록 방지와 이전 등록 해제 처리
화면에 일부 결과만 표시페이지 구분·가상 스크롤 범위를 명시하고 전체 데이터라고 보고하지 않음

Chrome의 실행 환경 설명에 따르면 MAIN 코드는 페이지와 실행 환경을 공유한다. 그래서 이곳에 확장 전용 비밀값을 두거나 임의의 명령을 확장 권한으로 실행하는 통로를 만들지 않는다. 위 예제처럼 먼저 대상 URL과 동작을 좁힌다.

사이트 개편 뒤에도 study_list_displayed_rooms가 목록에 보인다. 도구 코드를 고칠 필요가 없을까?

답과 이유

도구 발견 성공만으로 실행 성공을 판단할 수 없다. 내부 코드가 사용하는 #room-results나 회의실 항목 구조가 바뀌었다면 확장 코드를 고쳐야 한다. WebMCP로 감싸도 DOM 의존성은 남으며, 반환값까지 확인해야 한다.

확장을 설치했는데 다른 PC의 같은 사이트에는 도구가 없다. 문제가 생긴 것일까?

답과 이유

정상이다. 사이트 원본을 바꾼 것이 아니라 확장을 설치한 브라우저의 페이지에 코드를 추가했다. 다른 환경에도 같은 확장과 지원되는 브라우저·에이전트 연결이 필요하다.

도구 검사기에서는 호출되는데 로컬 AI는 찾지 못한다. 어디부터 볼까?

답과 이유

AI가 같은 브라우저의 같은 탭에 연결되어 있는지, 그 연결이 WebMCP 도구 발견·호출을 지원하는지부터 본다. 도구 주입이 성공했다고 에이전트 연결까지 자동으로 만들어지는 것은 아니다.