2026.02.20프로젝트 회고
claude-codeChrome ExtensionCDP브라우저 자동화Socket.io통합 테스트

Chrome DevTools Protocol로 AI에게 로그인된 브라우저를 맡기다 — Browser Relay 아키텍처와 통합 검증기

왜 로그인된 브라우저가 필요한가

AI 에이전트에게 웹 자동화를 시키려면 보통 Puppeteer나 Playwright로 새 브라우저 인스턴스를 띄웁니다. 문제는 증권사 사이트처럼 로그인 세션이 필수인 서비스입니다. 매번 로그인 플로우를 자동화하는 건 OTP, 공인인증, 캡차 등 인증 장벽 때문에 현실적이지 않습니다.

해결 아이디어는 단순합니다. 이미 로그인된 Chrome 브라우저 탭을 AI가 그대로 제어하면 됩니다. Chrome DevTools Protocol(CDP)과 Socket.io를 조합한 Browser Relay 아키텍처로 이 문제를 풀었습니다.

전체 아키텍처

code
[AI 에이전트 (MCP 도구)]
       │
       ▼
[OpenClaude 서버 - Browser Relay API]
       │ Socket.io (/browser namespace)
       ▼
[Chrome Extension (background.js)]
       │ chrome.debugger API (CDP)
       ▼
[로그인된 Chrome 탭]

핵심은 Chrome Extension이 CDP 명령의 릴레이 역할을 한다는 점입니다. AI 에이전트가 MCP 도구로 browser_navigate, browser_screenshot, browser_click 같은 명령을 호출하면, 서버가 Socket.io로 Extension에 전달하고, Extension이 chrome.debugger API를 통해 실제 탭에서 CDP 명령을 실행합니다.

브라우저 자동화 아키텍처

Phase별 팀 에이전트 분담 개발

Claude Code의 팀 에이전트 기능을 활용해 Phase별로 작업을 분담했습니다.

Phase담당주요 산출물
Phase 4Extension 에이전트manifest.json, background.js, popup.html
Phase 7Orchestrator 에이전트통합 검증, 버그 수정, 빌드 확인

Chrome Extension 구조

Manifest V3 기반으로, 필요한 권한은 debugger, activeTab, tabs, storage, alarms입니다.

json
{
  "manifest_version": 3,
  "name": "OpenClaude Browser Relay",
  "permissions": ["debugger", "activeTab", "tabs", "storage", "alarms"],
  "background": {
    "service_worker": "background.js"
  }
}

background.js(Service Worker)가 핵심 로직을 담당합니다. Socket.io 클라이언트로 서버와 연결하고, 서버에서 CDP 명령이 오면 chrome.debugger.sendCommand로 실행한 뒤 결과를 돌려보냅니다.

javascript
// background.js - CDP 명령 릴레이 핵심 로직
socket.on('cdp:execute', async ({ tabId, method, params, requestId }) => {
  try {
    const target = { tabId };
    await chrome.debugger.attach(target, '1.3');
    const result = await chrome.debugger.sendCommand(target, method, params);
    socket.emit('cdp:result', { requestId, result });
  } catch (error) {
    socket.emit('cdp:result', { requestId, error: error.message });
  }
});

Popup UI에서는 서버 URL과 JWT 토큰을 설정하고, 연결 상태를 확인하며, 탭별로 CDP 제어를 활성화/비활성화할 수 있습니다.

통합 검증에서 발견한 실전 버그

Phase 7 통합 검증 단계에서 전체 코드를 병렬로 리뷰하면서 필드명 불일치 버그를 발견했습니다. 이 버그는 단위 테스트만으로는 잡기 어려운, E2E 흐름에서만 드러나는 유형입니다.

버그: 스크린샷 API의 data vs screenshot 필드명 불일치

Browser Relay API는 스크린샷 결과를 data 필드로 반환합니다.

typescript
// browser-relay.ts (API 서버)
const screenshot = await executeCdp(tabId, 'Page.captureScreenshot', { format: 'png' });
return { data: screenshot.data };  // "data" 필드

MCP 서버의 browser_screenshot 도구는 응답에서 screenshot 필드를 참조하고 있었습니다.

typescript
// 수정 전 (버그)
const response = await callBrowserAPI('screenshot', { tabId });
return { content: [{ type: 'image', data: response.screenshot }] };
//                                                ^^^^^^^^^^  존재하지 않는 필드

response.screenshotundefined가 되어 스크린샷이 항상 빈 값으로 반환됩니다. 수정은 간단합니다.

typescript
// 수정 후
const response = await callBrowserAPI('screenshot', { tabId });
return { content: [{ type: 'image', data: response.data }] };
//                                                ^^^^  올바른 필드

통합 테스트와 디버깅

추가 발견: 도구의 네비게이션 헬퍼 불일치

통합 검증 과정에서 파싱 도구들이 executeCdp() 헬퍼를 직접 호출하는 대신 다른 경로를 사용하는 불일치도 함께 발견하고 수정했습니다. 이런 류의 버그는 개별 모듈이 독립적으로 개발될 때 자주 발생합니다.

팀 에이전트 협업에서 얻은 교훈

팀 에이전트 방식의 가장 큰 장점은 Phase별 병렬 개발 속도입니다. Extension 에이전트가 Chrome Extension을 만드는 동안, 다른 에이전트는 서버 측 API를 구현할 수 있습니다.

반면 통합 검증 Phase가 반드시 필요하다는 점도 확인했습니다. 서로 다른 에이전트가 만든 코드 사이의 **인터페이스 계약(필드명, 타입, 엔드포인트 경로)**이 어긋나는 건 피하기 어렵습니다. Orchestrator 에이전트가 전체 파일을 병렬로 읽으면서 불일치를 잡아내는 과정이 핵심이었습니다.

핵심 정리

Browser Relay 아키텍처를 도입할 때 체크리스트:

  • Chrome Extension은 Manifest V3 + debugger 권한이 필수
  • Service Worker의 생명주기를 고려해 alarms 기반 keepalive 구현 필요
  • Socket.io 네임스페이스(/browser)로 일반 채팅과 브라우저 제어 트래픽을 분리
  • JWT 토큰으로 Extension ↔ 서버 간 인증

팀 에이전트 통합 검증 체크리스트:

  • API 응답 필드명이 호출 측 참조 필드명과 일치하는지 확인
  • npm run build 성공 여부로 타입 레벨 불일치 검출
  • E2E 흐름(MCP → API → Extension → CDP → 결과 반환)을 순서대로 추적
  • 공용 헬퍼 함수가 있다면 모든 모듈이 동일한 헬퍼를 사용하는지 확인