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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
| class Playwright(
private val googleAIStudioApiKey: String,
) {
companion object {
private const val DEFAULT_MCP_PORT = 8931
private const val DEFAULT_CONNECTION_RETRIES = 10
private const val DEFAULT_RETRY_DELAY_MS = 2000L
}
/**
* Playwright MCP 를 사용해서 브라우저 자동화를 실행
* 이 메서드는 Playwright MCP 서버를 실행하고, 연결한 다음, 사전 정의된 작업을 실행합니다.
*/
suspend fun run() {
// Playwright MCP 서버 실행
val process = startPlaywrightMcpServer()
try {
// MCP 서버로부터 도구 레지스트리를 생성 (실패시 재시도)
val serverUrl = "http://localhost:$DEFAULT_MCP_PORT"
val toolRegistry = createToolRegistryWithRetries(serverUrl)
// 브라우저 자동화 작업 실행
executeBrowserTask(toolRegistry)
} catch (e: Exception) {
// 예외가 발생하면 process 가 종료되도록 처리
process.destroy()
throw e
}
}
/**
* 정해진 포트로 Playwright MCP 서버를 실행
*/
private fun startPlaywrightMcpServer(port: Int = DEFAULT_MCP_PORT): Process {
LOGGER.info("Starting Playwright MCP server on port $port...")
return ProcessBuilder(
"npx",
"@playwright/mcp@latest",
"--port",
port.toString(),
).start().also {
LOGGER.info("Playwright MCP server process started")
}
}
/**
* 제공된 도구 레지스트리를 사용하여 브라우저 자동화 작업을 실행합니다.
*/
private suspend fun executeBrowserTask(toolRegistry: ToolRegistry) {
LOGGER.info("Creating AI agent for browser automation...")
val agent =
AIAgent(
executor = simpleGoogleAIExecutor(googleAIStudioApiKey),
llmModel = GoogleModels.Gemini2_0Flash,
toolRegistry = toolRegistry,
)
LOGGER.info("Executing browser automation task...")
agent.run("Open a browser, navigate to jetbrains.com, accept all cookies, click AI in toolbar")
LOGGER.info("Browser automation task completed")
}
/**
* 서버가 시작될 때 지연을 다루기 위해 재시도 로직을 포함하여 도구 레지스트리를 생성합니다.
*/
private suspend fun createToolRegistryWithRetries(
url: String,
retries: Int = DEFAULT_CONNECTION_RETRIES,
delayMillis: Long = DEFAULT_RETRY_DELAY_MS,
): ToolRegistry {
LOGGER.info("Attempting to connect to Playwright MCP server at $url...")
var lastException: Exception? = null
for (attempt in 1..retries) {
try {
val transport = McpToolRegistryProvider.defaultSseTransport(url)
val toolRegistry = McpToolRegistryProvider.fromTransport(transport)
LOGGER.info("Successfully connected to Playwright MCP server")
return toolRegistry
} catch (e: Exception) {
if (isConnectException(e)) {
lastException = e
LOGGER.warn("Connection to Playwright MCP server failed (attempt $attempt/$retries). Retrying in ${delayMillis}ms...")
delay(delayMillis)
} else {
LOGGER.error("Unexpected error while connecting to Playwright MCP server: ${e.message}")
throw e // Re-throw exceptions that are not related to connection refusal
}
}
}
throw IllegalStateException(
"Failed to connect to Playwright MCP server at $url after $retries attempts.",
lastException
)
}
/**
* 주어진 예외(exception) 또는 그 원인(cause)에 ConnectException이 포함되어 있는지 확인합니다.
*/
private fun isConnectException(exception: Exception): Boolean {
var cause: Throwable? = exception
while (cause != null) {
if (cause is ConnectException) {
return true
}
cause = cause.cause
}
return false
}
}
|