Koog, announced recently at KotlinConf, is a new Kotlin-based framework designed to build and run AI agents. This article will outline initial exploration of using Koog along with a number of configured MCP servers (specifically mcp-jetbrains which we’ll use to control the IntelliJ/Android Studio IDE and also one based on the ClimateTraceKMP Kotlin Multiplatform (KMP) sample built using the Kotlin MCP SDK).

In this somewhat contrived example our agent will use a prompt that (1) requests climate emission data, (2) generates Compose UI code (and preview) that shows that data and, (3) adds file containing that code to an Android Studio project. It will also use the MCP servers we configured as needed. Koog can work with several different LLMs and we’ll use Google Gemini in this example.

Implementation

Koog is available through the following dependency

1
implementation("ai.koog:koog-agents:0.2.1")

The following then is all the code needed to create our initial basic AI agent (using Gemini LLM in this case). This is based on using a “Single-run agent” (Koog also supports creation of more complex workflow agents).

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
fun main() = runBlocking {
    val toolRegistry = createAndStartMCPServers()

    val agent = AIAgent(
        executor = simpleGoogleAIExecutor(apiKeyGoogle),
        llmModel = GoogleModels.Gemini1_5Pro,
        toolRegistry = toolRegistry
    )

    agent.run(
        """
            Get emissions for France, Germany and Spain for 2023 and 2024.
            Create Compose UI file using Material3 showing the emissions data in a grid
            and add to the project in the composeApp module.
            Use units of millions for the emissions data.
            Also create a Compose preview.
        """.trimIndent()
    )
}

suspend fun createAndStartMCPServers(): ToolRegistry {
    // ClimateTrace MCP Server
    val processClimateTrace = ProcessBuilder
        ("java", "-jar",
        "<path to climate trace mcp server jar file>",
        "--stdio"
    ).start()

    val transportClimateTrace = McpToolRegistryProvider.defaultStdioTransport(processClimateTrace)
    val toolRegistryClimateTrace = McpToolRegistryProvider.fromTransport(transportClimateTrace)

    // JetBrains MCP server
    val processIntelliJ = ProcessBuilder(
        "npx", "-y", "@jetbrains/mcp-proxy"
    ).start()

    val transportIntelliJ = McpToolRegistryProvider.defaultStdioTransport(processIntelliJ)
    val toolRegistryIntelliJ = McpToolRegistryProvider.fromTransport(transportIntelliJ)

    return toolRegistryClimateTrace + toolRegistryIntelliJ
}

This is the output when we run this code showing the various MCP server tool invocations made.

1
2
3
4
5
6
7
Tool called: tool get-emissions, args Args(arguments={"countryCode":"FRA","year":"2023"})
Tool called: tool get-emissions, args Args(arguments={"countryCode":"FRA","year":"2024"})
Tool called: tool get-emissions, args Args(arguments={"countryCode":"DEU","year":"2023"})
Tool called: tool get-emissions, args Args(arguments={"year":"2024","countryCode":"DEU"})
Tool called: tool get-emissions, args Args(arguments={"countryCode":"ESP","year":"2023"})
Tool called: tool get-emissions, args Args(arguments={"year":"2024","countryCode":"ESP"})
Tool called: tool create_new_file_with_text, args Args(arguments={"text":"```kotlin\npackage com.example.composeapp\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport com.example.composeapp.ui.theme.ComposeAppTheme\n\n\nclass MainActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContent {\n            ComposeAppTheme {\n                // A surface container using the 'background' color from the theme\n                EmissionsGrid()\n\n            }\n        }\n    }\n}\n\n@Composable\nfun EmissionsGridItem(country: String, year: Int, emissions: Double) {\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(4.dp),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant,\n        ),\n\n        ) {\n        Column(\n            modifier = Modifier.padding(16.dp)\n\n        ) {\n\n            Text(text = country, style = MaterialTheme.typography.titleMedium)\n            Text(\n                text = \"Year: $year\",\n                style = MaterialTheme.typography.bodyMedium,\n                color = Color.Gray\n\n            )\n            Text(\n                text = \"Emissions (millions): ${emissions}\",\n                style = MaterialTheme.typography.bodyLarge\n            )\n        }\n    }\n}\n\n@Composable\nfun EmissionsGrid() {\n    val emissionsData = listOf(\n        EmissionsData(\"France\", 2023, 367.2409),\n        EmissionsData(\"France\", 2024, 366.7807),\n        EmissionsData(\"Germany\", 2023, 690.8744),\n        EmissionsData(\"Germany\", 2024, 671.735),\n        EmissionsData(\"Spain\", 2023, 273.4409),\n        EmissionsData(\"Spain\", 2024, 277.5746)\n\n    )\n    Column {\n        emissionsData.forEach { data ->\n            EmissionsGridItem(data.country, data.year, data.emissions)\n        }\n    }\n}\n\n\ndata class EmissionsData(val country: String, val year: Int, val emissions: Double)\n\n\n@Preview(showBackground = true)\n@Composable\nfun DefaultPreview() {\n    ComposeAppTheme {\n        EmissionsGrid()\n    }\n}\n```","pathInProject":"composeApp/src/main/java/com/example/composeapp/EmissionsView.kt"})

And this is example then of the Compose code (and associated preview) that was added to the Android Studio project (using the mcp-jetbrains plugin). Koog Compose example

Koog can work with several different LLM providers (specifically Google, OpenAI, Anthropic, OpenRouter, and Ollama) and following shows how we can create an agent that uses for example OpenAI. You can also create prompt executors that works with multiple LLM providers.

1
2
3
4
5
val agent = AIAgent(
    executor = simpleOpenAIExecutor(openAIApiKey),
    llmModel = OpenAIModels.Chat.GPT4o,
    toolRegistry = toolRegistry
)