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).
Koog: a Kotlin-based framework designed to build and run AI agents entirely in idiomatic Kotlin (just announced at #KotlinConf keynote) https://t.co/TirxwuMRZ2
— John O'Reilly (@joreilly) May 22, 2025
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 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
)
Update July 11th 2025
Along with MCP based tools, Koog also supports having “local” annotation-based custom tools and the following is an example of such a tool (also merged to ClimateTraceKMP project).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@LLMDescription("Tools for getting climate emission information")
class ClimateTraceTool : ToolSet {
val climateTraceRepository = koin.get<ClimateTraceRepository>()
@Tool
@LLMDescription("Get the list of countries")
suspend fun getCountries(): List<String> {
val countries = climateTraceRepository.fetchCountries()
return countries.map { "${it.name}, ${it.alpha3}" }
}
@Tool(customName = "getEmissions")
@LLMDescription("Get the emission data for a country for a particular year.")
suspend fun getEmissions(
@LLMDescription("country code") countryCode: String,
@LLMDescription("year") year: String
): List<String> {
return climateTraceRepository.fetchCountryEmissionsInfo(countryCode, year).map {
it.emissions.co2.toString()
}
}
}
With that implemented we can now switch between adding this or the MCP based tool to the tool registry.
1
2
3
4
5
6
7
8
9
10
11
12
13
suspend fun createToolSetRegistry(): ToolRegistry {
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)
val localToolSetRegistry = ToolRegistry { tools(ClimateTraceTool().asTools()) }
// Can use either local toolset or one based on MCP server
//return toolRegistryClimateTrace
return localToolSetRegistry
}
Featured in Kotlin Weekly Issue #465 and Android Weekly #681
Related tweet
Wrote short article about use of Koog for developing Kotlin based AI agents (combined with a number of MCP servers).
— John O'Reilly (@joreilly) June 22, 2025
The example included is based on use of #BuildWithGemini LLM (really had to use that with #GoogleIOConnect fast approaching 😀) https://t.co/rd7X5vFWvp