=================
== The Archive ==
=================

Koog 라이브러리로 MCP 서버 연결하기: Kotlin 기반 AI 에이전트 개발 가이드

|

개요

Koog는 JetBrains에서 개발한 Kotlin 기반의 AI 에이전트 프레임워크로, Model Context Protocol(MCP) 를 통해 AI 모델과 외부 도구 및 서비스를 연결할 수 있는 표준화된 방법을 제공한다.

MCP는 AI 모델이 외부 데이터 소스와 도구 간의 상호작용을 위한 표준 프로토콜로, USB-C가 다양한 주변기기를 연결하는 것처럼 AI 모델을 다양한 데이터 소스와 도구에 연결하는 표준화된 방법을 제공한다.

MCP의 핵심 특징

MCP는 다음과 같은 주요 특징을 가지고 있다:

Koog의 MCP 통합 아키텍처

Koog 프레임워크는 MCP SDK를 통해 다음과 같은 핵심 컴포넌트를 제공:

컴포넌트역할
McpToolKoog 도구 인터페이스와 MCP SDK 간의 브릿지 역할
McpToolDescriptorParserMCP 도구 정의를 Koog 도구 디스크립터 형식으로 파싱
McpToolRegistryProvider다양한 전송 메커니즘을 통해 MCP 서버와 연결하는 MCP 도구 레지스트리 생성

MCP 서버 연결 방법

1. 전송 메커니즘 설정

MCP 서버와 연결하기 위해 두 가지 전송 프로토콜을 지원:

stdio 전송 (프로세스 기반)

1
2
3
4
5
// MCP 서버를 프로세스로 시작
val process = ProcessBuilder("path/to/mcp/server").start()

// stdio 전송 생성
val transport = McpToolRegistryProvider.defaultStdioTransport(process)

SSE 전송 (HTTP 기반)

1
2
// SSE 전송 생성
val transport = McpToolRegistryProvider.defaultSseTransport("http://localhost:8931")

2. 도구 레지스트리 생성

전송 메커니즘을 설정한 후 도구 레지스트리를 생성:

1
2
3
4
5
6
// 전송을 통한 도구 레지스트리 생성
val toolRegistry = McpToolRegistryProvider.fromTransport(
    transport = transport,
    name = "my-client",
    version = "1.0.0"
)

3. AI 에이전트와 통합

생성된 도구 레지스트리를 AI 에이전트와 통합한다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 도구와 함께 에이전트 생성
val agent = AIAgent(
    promptExecutor = executor,
    strategy = strategy,
    agentConfig = agentConfig,
    toolRegistry = toolRegistry
)

// MCP 도구를 사용하는 작업 실행
val result = agent.run("Use the MCP tool to perform a task")

실제 구현 예제

Google Maps MCP 연동 예제

Google Maps API를 사용하여 지리적 정보를 조회하는 MCP 서버 연동 예제:

 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
class GoogleMaps(
    private val openAIApiToken: String,
    private val googleMapsApiKey: String,
) {
    suspend fun run() {
        // Google Maps MCP 서버가 포함된 Docker 컨테이너 시작
        val process =
            ProcessBuilder(
                "docker",
                "run",
                "-i",
                "-e",
                "GOOGLE_MAPS_API_KEY=$googleMapsApiKey",
                "mcp/google-maps",
            ).start()

        // MCP 서버로부터 도구 레지스트리 생성
        val toolRegistry =
            McpToolRegistryProvider.fromTransport(
                transport = McpToolRegistryProvider.defaultStdioTransport(process),
            )

        // 에이전트 생성 및 실행
        val agent =
            AIAgent(
                executor = simpleOpenAIExecutor(openAIApiToken),
                llmModel = OpenAIModels.Chat.GPT4o,
                toolRegistry = toolRegistry,
            )
        LOGGER.info(agent.runAndGetResult("Get elevation of the Jetbrains Office in Munich, Germany?"))
    }
}

Playwright MCP 연동 예제

웹 브라우저 자동화를 위한 Playwright MCP 서버 연동 예제:

  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
    }
}

직접 MCP 도구 사용하기

에이전트를 통하지 않고 MCP 도구를 직접 실행할 수도 있다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 특정 도구 가져오기
val tool = toolRegistry.getTool("tool-name") as McpTool

// 도구 인수 생성
val args = McpTool.Args(buildJsonObject {
    put("parameter1", "value1")
    put("parameter2", "value2")
})

// 도구 실행
val toolResult = tool.execute(args)
println(toolResult)

// 모든 도구 가져오기
val allTools = toolRegistry.tools

장점과 활용 분야

주요 장점

활용 분야

마무리

Koog 라이브러리를 통한 MCP 서버 연결은 AI 에이전트의 기능을 크게 확장시킨다.

표준화된 프로토콜을 통해 다양한 외부 도구와 서비스를 쉽게 통합할 수 있으며, 재사용 가능한 MCP 서버를 통해 개발 효율성을 높일 수 있다.

특히 Kotlin 기반의 타입 안전성과 코루틴 지원으로 안정적이고 효율적인 AI 에이전트 시스템을 구축할 수 있다.

Categories:

Tags: