Playwright MCP 기반의 지능형 브라우저 자동화 에이전트 구축

Playwright는 최근 MCP(Model Context Protocol)를 지원하며 AI 모델이 웹 브라우저를 직접 제어할 수 있는 표준화된 인터페이스를 제공합니다. 이를 통해 단순한 스크립트 실행을 넘어, LLM(대형 언어 모델)이 웹 페이지의 상태를 이해하고 동적으로 상호작용하는 자동화 에이전트를 구축할 수 있습니다.

1. Playwright MCP 환경 설정

Playwright MCP는 Node.js 환경에서 실행되며, npx를 통해 최신 버전을 설치하고 실행할 수 있습니다.

npx @playwright/mcp@latest

프로젝트 설정 파일(예: Cursor 또는 사용자 정의 클라이언트 구성)에서 다음과 같이 브라우저 유형 및 실행 인자를 정의합니다.

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "-y",
        "@playwright/mcp@latest",
        "--browser",
        "chrome"
      ]
    }
  }
}

2. 주요 기능 및 API 범위

Playwright MCP 서버는 다음과 같은 핵심 브라우저 제어 기능을 도구(Tools) 형태로 제공합니다.

  • 핵심 제어: 페이지 이동, 클릭, 텍스트 입력, 드래그 앤 드롭, JavaScript 실행, 접근성 스냅샷 조회.
  • 세션 관리: 쿠키(Cookies) 및 로컬 스토리지 읽기/쓰기, 로그인 상태 유지를 위한 스토리지 상태 저장 및 복구.
  • 네트워크 제어: 요청 가로채기(Mocking), 오프라인 모드 전환, 네트워크 라우팅 관리.
  • 디버깅 및 출력: 트레이스(Trace) 기록, 비디오 녹화, PDF 내보내기, 스크린샷 캡처.
  • 테스트 보조: 로케이터(Locator) 생성, 요소 가시성 및 텍스트 검증.

3. Java 및 Spring Boot 기반 커스텀 클라이언트 구현

AI 코딩 IDE에 의존하지 않고 독립적인 자동화 플랫폼을 구축하기 위해, Spring Boot와 Redis를 활용한 MCP 클라이언트를 구성할 수 있습니다. 이 시스템은 LLM의 결정에 따라 Playwright 도구를 호출하고 브라우저 작업을 수행합니다.

의존성 설정 (pom.xml)

<properties>
    <playwright.mcp.version>1.49.0</playwright.mcp.version>
    <mcp.sdk.version>1.1.0</mcp.sdk.version>
</properties>

<dependencies>
    <dependency>
        <groupId>io.modelcontextprotocol.sdk</groupId>
        <artifactId>mcp</artifactId>
        <version>${mcp.sdk.version}</version>
    </dependency>
    <dependency>
        <groupId>com.microsoft.playwright</groupId>
        <artifactId>playwright</artifactId>
        <version>${playwright.mcp.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

에이전트 실행 로직 (McpAgentService.java)

LLM과의 대화를 관리하고 Playwright 도구를 실행하는 핵심 서비스 클래스입니다.

@Service
public class McpAgentService {
    private final ObjectMapper jsonMapper = new ObjectMapper();
    private final Map<String, AgentSession> activeSessions = new ConcurrentHashMap<>();

    private static final String SYSTEM_INSTRUCTION = """
        당신은 브라우저 자동화 에이전트입니다.
        제공된 도구를 사용하여 사용자의 요청을 수행하세요.
        응답은 반드시 아래 JSON 형식을 따라야 합니다:
        {"action":"tool_call", "toolName":"도구이름", "params":{}, "reason":"사유"}
        또는
        {"action":"complete", "response":"최종 답변"}
        """;

    public String processRequest(String sid, String userInput, List<HistoryEntry> history) throws IOException {
        AgentSession session = activeSessions.computeIfAbsent(sid, k -> initializeMcpSession());
        
        synchronized (session.lock) {
            return executeLoop(session, userInput, history);
        }
    }

    private AgentSession initializeMcpSession() {
        try {
            ServerParameters params = ServerParameters.builder("npx")
                .args("-y", "@playwright/mcp@latest", "--browser", "chrome")
                .build();

            StdioClientTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMapper());
            McpSyncClient client = McpClient.sync(transport)
                .requestTimeout(Duration.ofMinutes(3))
                .build();
            client.initialize();

            return new AgentSession(client, client.listTools().toString());
        } catch (Exception e) {
            throw new RuntimeException("MCP 세션 초기화 실패", e);
        }
    }

    private String executeLoop(AgentSession session, String input, List<HistoryEntry> history) throws IOException {
        // LLM 호출 및 도구 실행 로직 (반복문으로 최대 단계 제어)
        // 1. LLM에게 현재 상황과 도구 목록 전달
        // 2. LLM이 tool_call을 반환하면 session.client.callTool() 호출
        // 3. 결과를 다시 LLM에게 전달하여 다음 단계 결정
        return "수행 완료 메시지";
    }

    private record AgentSession(McpSyncClient client, String toolDefinitions, Object lock) {
        AgentSession(McpSyncClient client, String toolDefinitions) {
            this(client, toolDefinitions, new Object());
        }
    }
}

대화 이력 저장소 (RedisHistoryProvider.java)

Redis를 사용하여 세션별 대화 맥락을 유지합니다.

@Component
public class RedisHistoryProvider {
    private final StringRedisTemplate redisTemplate;
    private static final String KEY_PREFIX = "mcp:session:";

    public RedisHistoryProvider(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void saveTurn(String sid, String userMsg, String aiMsg) {
        String key = KEY_PREFIX + sid;
        String data = serialize(new HistoryEntry(userMsg, aiMsg));
        redisTemplate.opsForList().rightPush(key, data);
        redisTemplate.opsForList().trim(key, -10, -1); // 최근 10개 유지
    }

    public List<HistoryEntry> getHistory(String sid) {
        List<String> records = redisTemplate.opsForList().range(KEY_PREFIX + sid, 0, -1);
        return records == null ? List.of() : records.stream()
            .map(this::deserialize)
            .collect(Collectors.toList());
    }

    private String serialize(HistoryEntry entry) { /* JSON 변환 로직 */ return ""; }
    private HistoryEntry deserialize(String raw) { /* 객체 변환 로직 */ return null; }
}

4. 에이전트 인터페이스 (API Controller)

외부에서 에이전트에게 명령을 내릴 수 있는 엔드포인트를 제공합니다.

@RestController
@RequestMapping("/v1/browser-agent")
public class AgentController {
    private final McpAgentService agentService;
    private final RedisHistoryProvider historyProvider;

    public AgentController(McpAgentService agentService, RedisHistoryProvider historyProvider) {
        this.agentService = agentService;
        this.historyProvider = historyProvider;
    }

    @PostMapping("/chat")
    public ResponseEntity<Map<String, Object>> chat(@RequestBody Map<String, String> payload) throws IOException {
        String sid = payload.getOrDefault("sessionId", "default-session");
        String message = payload.get("message");

        List<HistoryEntry> history = historyProvider.getHistory(sid);
        String result = agentService.processRequest(sid, message, history);
        
        historyProvider.saveTurn(sid, message, result);

        return ResponseEntity.ok(Map.of("status", "success", "data", result));
    }
}

이러한 구조를 통해 Playwright MCP를 기업 내부의 LLM 게이트웨이와 연결하면, 복잡한 웹 UI를 자동으로 탐색하고 데이터를 추출하거나 테스트를 수행하는 지능형 자동화 플랫폼을 구축할 수 있습니다.

태그: Playwright MCP browser-automation AI-Agent spring-boot

7월 23일 10:32에 게시됨