Leveraging Gemini CLI and the underlying Gemini LLM to build Model Context Protocol (MCP) AI applications with Kotlin in a local development environment.
Why not just use Python?
Python has traditionally been the main coding language for ML and AI tools. One of the strengths of the MCP protocol is that the actual implementation details are independent of the development language. The reality is that not every project is coded in Python- and MCP allows you to use the latest AI approaches with other coding languages. The goal of this article is to provide a minimal viable basic working MCP stdio server in Kotlin that can be run locally without any unneeded extra code or extensions.
What is Kotlin?
Kotlin is a modern, statically typed, general-purpose programming language developed by JetBrains that runs on the Java Virtual Machine (JVM). It is primarily known as the preferred language for Android app development , as announced by Google in 2019.
Kotlin MCP Documentation
Kotlin has an official MCP SDK implementation:
Kotlin Version Management
One of the downsides of the wide deployment of Java and Java based tools has been managing the language versions across platforms and maintaining supported versions.
There are several Version Managers available- some may be more suited to your specific environment than others. A good all-around Java Version Manager is SDKMAN:
Home | SDKMAN! the Software Development Kit Manager
The SDKMAN tool is used to download the Java system version- typically this will be a Temurin build. As of writing — the mainstream Java version is Java 25.
To validate your current version:
xbill@penguin:~$ java --version
openjdk 25.0.1 2025-10-21 LTS
OpenJDK Runtime Environment Temurin-25.0.1+8 (build 25.0.1+8-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.1+8 (build 25.0.1+8-LTS, mixed mode, sharing)
xbill@penguin:~$
Install System Packages
Use SDKMAN or another package manager to install the dependent packages — including Kotlin, Java, Gradle, and Maven:
xbill@penguin:~$ sdk install kotlin
kotlin 2.3.0 is already installed.
xbill@penguin:~$ sdk install gradle
gradle 9.2.1 is already installed.
xbill@penguin:~$ sdk install java
java 25.0.1-tem is already installed.
xbill@penguin:~$
xbill@penguin:~$ sdk install maven
maven 3.9.12 is already installed.
Gemini CLI
If not pre-installed you can download the Gemini CLI to interact with the source files and provide real-time assistance:
npm install -g @google/gemini-cli
Testing the Gemini CLI Environment
Once you have all the tools and the correct Node.js version in place- you can test the startup of Gemini CLI. You will need to authenticate with a Key or your Google Account:
gemini
Node Version Management
Gemini CLI needs a consistent, up to date version of Node. The nvm command can be used to get a standard Node environment:
Gemini CLI needs a minimum Node version of 20 — but as of writing the mainstream Node version is 25.
Where do I start?
The strategy for starting MCP development is a incremental step by step approach.
First, the basic development environment is setup with the required system variables, and a working Gemini CLI configuration.
Then, a minimal Hello World Style Kotlin MCP Server is built with stdio transport. This server is validated with Gemini CLI in the local environment.
This setup validates the connection from Gemini CLI to the local process via MCP. The MCP client (Gemini CLI) and the Kotlin MCP server both run in the same local environment.
Next- the basic MCP server is extended with Gemini CLI to add several new tools.
Setup the Basic Environment
At this point you should have a working Kotlin environment and a working Gemini CLI installation. The next step is to clone the GitHub samples repository with support scripts:
cd ~
git clone https://github.com/xbill9/gemini-cli-codeassist
Then run init.sh from the cloned directory.
The script will attempt to determine your shell environment and set the correct variables:
cd gemini-cli-codeassist
source init.sh
If your session times out or you need to re-authenticate- you can run the set_env.sh script to reset your environment variables:
cd gemini-cli-codeassist
source set_env.sh
Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.
Hello World with STDIO Transport
One of the key features that the standard MCP libraries provide is abstracting various transport methods.
The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.
The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.
One of the common problems with stdio MCP servers is that the stdout stream gets corrupted with logs or errors and this interferes with the message handling. All logs and errors need to get directed to the stderr stream.
Key Kotlin Configuration Files
The key files for the Kotlin build are reviewed with Gemini CLI:
cd ~/gemini-cli-codeassist/firestore-stdio-kotlin
gemini
> review the project
✦ I have completed the review of the project.
Review Summary
The project strongly adheres to the best practices defined in GEMINI.md.
1. Kotlin & Code Quality
* ✅ Idiomatic Kotlin: Uses data class for the Product model, suspend functions for async operations, and extension functions
(e.g., ApiFuture.await()) to bridge Java Futures to Kotlin Coroutines cleanly.
* ✅ Coroutines: Proper usage of suspend functions and runBlocking for the main server loop.
* ✅ Linting: The project uses ktlint and currently passes all checks (./gradlew ktlintCheck succeeded).
2. Firestore & Google Cloud
* ✅ Authentication: correctly uses FirestoreOptions.getDefaultInstance() to leverage Application Default Credentials (ADC),
avoiding hardcoded secrets.
* ✅ Data Modeling: Product is a flat, serializable data class. The docToProduct mapper handles potential nulls safely.
* ✅ Error Handling: Service methods are wrapped in runCatching, and errors are strictly typed as Result objects, which are
then gracefully handled in the tool implementation to return user-friendly error messages to the client.
3. MCP Server Development
* ✅ Stdio Transport: The logback.xml is correctly configured to direct all logs to System.err. Crucially, SLF4JBridgeHandler
is installed to capture logs from the Firestore Java SDK (which uses java.util.logging) and redirect them to SLF4J/stderr,
preventing protocol corruption on stdout.
* ✅ Tool Definitions: Tools are well-defined with schemas. get_product_by_id correctly describes its input parameter.
* ✅ Atomic Operations: reset uses a Firestore WriteBatch for efficiency.
Future Improvements (Optional)
* Resources: The server currently only implements Tools. To fully utilize MCP capabilities, you could implement Resources
(e.g., a products://list resource). This would allow clients to "subscribe" to the inventory list, enabling the server to
send notifications/resources/updated notifications when seed or reset are called.
So How Does this all Fit Together?
Kotlin is a bit different than many other languages- a combination of settings and conventions allow the application to be auto configured as a MCP stdio server.
Gemini CLI was used to generate a high level summary of the project:
> what does this project do?
✦ This project is a Model Context Protocol (MCP) server written in Kotlin that manages a product inventory using Google Cloud
Firestore.
It is designed to be connected to an MCP client (like an LLM interface or IDE) to allow it to interact with the database.
Key Capabilities:
* List Products: Retrieve the full inventory list (get_products).
* Get Product: Fetch details for a specific product by ID (get_product_by_id).
* Database Management:
* seed: Populates the database with sample data.
* reset: Clears all data.
* check_db: Verifies the database connection.
* Transport: It communicates over standard input/output (Stdio), making it compatible with local MCP clients.
In essence, it acts as a bridge that allows an AI assistant to "see" and modify a Firestore database.
Running the Kotlin Code
First- switch the directory with the Kotlin MCP sample code, then run the release version on the local system:
cd ~/gemini-cli-codeassist/firestore-stdio-kotlin
make release
BUILD SUCCESSFUL in 4s
16 actionable tasks: 9 executed, 7 up-to-date
Configuration cache entry reused.
./gradlew test
Reusing configuration cache.
BUILD SUCCESSFUL in 572ms
5 actionable tasks: 5 up-to-date
Configuration cache entry reused.
Gemini CLI settings.json
The default Gemini CLI settings.json has an entry for the Kotlin source:
{
"mcpServers": {
"firestore-stdio-kotlin": {
"command": "$HOME/gemini-cli-codeassist/firestore-stdio-kotlin/build/install/firestore-stdio-kotlin/bin/firestore-stdio-kot
lin",
"args": []
}
}
}
Validation with Gemini CLI
Finally- Gemini CLI is restarted and the MCP connection over stdio to the Python Code is validated, The full Gemini CLI Session will start:
gemini
> /mcp list
Configured MCP servers:
🟢 firestore-stdio-kotlin - Ready (6 tools)
Tools:
- check_db
- get_product_by_id
- get_products
- get_root
- reset
- seed
> check_db
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ? check_db (firestore-stdio-kotlin MCP Server) {} ← │
│ │
│ MCP Server: firestore-stdio-kotlin │
│ Tool: check_db │
│ │
│ Allow execution of MCP tool "check_db" from server "firestore-stdio-kotlin"? │
│ │
│ 1. Allow once │
│ 2. Allow tool for this session │
│ ● 3. Allow all server tools for this session │
│ 4. No, suggest changes (esc) │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ check_db (firestore-stdio-kotlin MCP Server) {} │
│ │
│ Database running: true │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ OK. The database is running.
> get_products
✦ The inventory contains various products including Green Tea, Jasmine Rice, Coffee Beans, Smores Cereal, Walnuts, Shrimp, Eggs,
Rice, Cheddar Cheese, Watermelon, Pineapple Kombucha, Wasabi Party Mix, Mint Chocolate Cookies, Cinnamon, Parmesan Crisps,
Beef, White Chocolate Caramel Corn, Maple Almond Butter, Black Beans, Broccoli, Whole Wheat Bread, Peanut Butter and Jelly
Cups, Apples, Bananas, Jalapeno Seasoning, Cola, Bottled Water, Acai Smoothie Packs, Yogurt, Milk, Fresh Basil, Sunflower
Seeds, Whole Chicken, and Apple Juice.
> find Coffee
✦ I found one product matching "Coffee":
Coffee Beans
- ID: 37kWbZHTwg5nbrSD13PB
- Price: $5
- Quantity: 488
- Image: product-images/coffeebeans.png
- Date Added: 2026-01-06 (Actual) / 2025-02-22 (Timestamp)
Extending the Kotlin MCP Server with Gemini CLI
Now that the basic MCP functionality has been tested and verified with Gemini CLI- the server code can be extended with new MCP tools:
> extend the MCP Firestore kotlin stdio project with new MCP tools -
one that searchs products, one that recommends a menu based
on inventory, one that returns firestore statistics
✦ I have successfully extended the MCP Firestore Kotlin server with the following tools:
1. `search_products`: Allows searching for products by name (case-insensitive).
* Input: query (string)
* Output: List of matching products.
2. `recommend_menu`: Generates menu recommendations based on the items currently in stock.
* Input: None
* Output: A text description of recommended meals.
3. `get_firestore_stats`: Returns statistics about the inventory.
* Input: None
* Output: JSON object containing total_products, total_quantity, total_value, and average_price.
I verified the functionality of all new tools using a test script.
Build the new release- then restart Gemini CLI and check for the new MCP tools:
xbill@penguin:~/gemini-cli-codeassist/firestore-stdio-kotlin$ make release
./gradlew ktlintFormat
Reusing configuration cache.
> /mcp list
Configured MCP servers:
🟢 firestore-stdio-kotlin - Ready (9 tools)
Tools:
- check_db
- get_firestore_stats
- get_product_by_id
- get_products
- get_root
- recommend_menu
- reset
- search_products
- seed
The new tools can be used directly in the current Gemini CLI context:
> get_firestore_stats
✦ I will retrieve the statistics for the Firestore inventory.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ? get_firestore_stats (firestore-stdio-kotlin MCP Server) {} ← │
│ │
│ MCP Server: firestore-stdio-kotlin │
│ Tool: get_firestore_stats │
│ │
│ Allow execution of MCP tool "get_firestore_stats" from server "firestore-stdio-kotlin"? │
│ │
│ 1. Allow once │
│ 2. Allow tool for this session │
│ ● 3. Allow all server tools for this session │
│ 4. No, suggest changes (esc) │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> get_firestore_stats
✦ I will retrieve the statistics for the Firestore inventory.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ? get_firestore_stats (firestore-stdio-kotlin MCP Server) {} ← │
│ │
│ MCP Server: firestore-stdio-kotlin │
│ Tool: get_firestore_stats │
│ │
│ Allow execution of MCP tool "get_firestore_stats" from server "firestore-stdio-kotlin"? │
│ │
│ 1. Allow once │
│ 2. Allow tool for this session │
│ ● 3. Allow all server tools for this session │
│ 4. No, suggest changes (esc) │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> get_firestore_stats
✦ I will retrieve the statistics for the Firestore inventory.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ get_firestore_stats (firestore-stdio-kotlin MCP Server) {} │
│ │
│ { │
│ "total_products": 34, │
│ "total_quantity": 8255, │
│ "total_value": 38952, │
│ "average_price": 5.0588235294117645 │
│ } │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ The inventory currently contains 34 products with a total quantity of 8,255 and a total value of 38,952. The average price per
product is approximately 5.06.
And product searches:
> search_products Coffee
✦ I will search for products matching "Coffee" in the inventory.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ search_products (firestore-stdio-kotlin MCP Server) {"query":"Coffee"} │
│ │
│ [ │
│ { │
│ "id": "37kWbZHTwg5nbrSD13PB", │
│ "name": "Coffee Beans", │
│ "price": 6, │
│ "quantity": 375, │
│ "imgfile": "product-images/coffeebeans.png", │
│ "timestamp": "2024-11-30T08:40:02.515Z", │
│ "actualdateadded": "2026-01-07T16:53:00.672Z" │
│ } │
│ ] │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ I found one product matching "Coffee": Coffee Beans (ID: 37kWbZHTwg5nbrSD13PB), priced at 6 with 375 units in stock.
> search_products Milk
✦ I will search for products matching "Milk" in the inventory.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ search_products (firestore-stdio-kotlin MCP Server) {"query":"Milk"} │
│ │
│ [ │
│ { │
│ "id": "ohLPGO1wvIX2tw5zW8AS", │
│ "name": "Milk", │
│ "price": 4, │
│ "quantity": 62, │
│ "imgfile": "product-images/milk.png", │
│ "timestamp": "2025-08-11T08:32:01.394Z", │
│ "actualdateadded": "2026-01-07T16:52:58.809Z" │
│ } │
│ ] │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ I found Milk (ID: ohLPGO1wvIX2tw5zW8AS), priced at 4 with 62 units in stock.
And menu recommendations:
> recommend_menu
✦ I will recommend a menu based on the available inventory.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ recommend_menu (firestore-stdio-kotlin MCP Server) {} │
│ │
│ Based on the current inventory, here are some menu recommendations: │
│ │
│ - **Classic Breakfast** : Scrambled Eggs with Toast and a glass of Milk. │
│ - **Rice and Beans** : A hearty bowl of Rice and Black Beans. │
│ - **Roast Chicken Dinner** : Roast Chicken with steamed Broccoli. │
│ - **Fresh Fruit Salad** : A mix of Apples, Bananas, and Watermelon. │
│ - **Afternoon Snack** : Green Tea paired with Mint Chocolate Cookies. │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ Based on the current inventory, here are some menu recommendations:
- Classic Breakfast: Scrambled Eggs with Toast and a glass of Milk.
- Rice and Beans: A hearty bowl of Rice and Black Beans.
- Roast Chicken Dinner: Roast Chicken with steamed Broccoli.
- Fresh Fruit Salad: A mix of Apples, Bananas, and Watermelon.
- Afternoon Snack: Green Tea paired with Mint Chocolate Cookies.
Use MCP Tools in Gemini CLI Context
All of the MCP tools can be used directly from Gemini CLI in the current context. Some sample investigations include:
> how many cafe lattes can I make?
✦ Based on your inventory, the limiting ingredient is Milk.
* Milk: 62 units
* Coffee Beans: 375 units
* Bottled Water: 319 units
Assuming you need one unit of each to make a Cafe Latte, you can make 62 lattes.
> what menu items have the highest potential profits?
✦ I will fetch all products to calculate their total potential value (Price × Quantity) and identify the highest-value items.
✦ Based on the total value of current stock (Price × Quantity), here are the items with the highest potential profit:
1. Whole Wheat Bread - Potential Revenue: 3,280 (328 units @ 10)
2. Pineapple Kombucha - Potential Revenue: 3,216 (402 units @ 8)
3. Green Tea - Potential Revenue: 2,844 (474 units @ 6)
4. Beef - Potential Revenue: 2,380 (238 units @ 10)
5. Maple Almond Butter - Potential Revenue: 2,355 (471 units @ 5)
> what sandwiches do you recommend I make based on current inventory levels and ingrediants?
✦ Based on your current inventory, you have plenty of Whole Wheat Bread (328 units) and various fillings. Here are some sandwich
recommendations:
1. Chicken & Cheddar Sandwich: Utilize your high stock of Whole Chicken (200 units) and Cheddar Cheese (132 units).
2. Beef & Cheese Melt: A hearty option using Beef (238 units) and Cheddar Cheese.
3. Classic Egg Sandwich: You have an abundance of Eggs (496 units), making this a very cost-effective and available option.
4. Maple Almond Butter & Apple Sandwich: A sweet and savory choice using Maple Almond Butter (471 units) and sliced Apples
(423 units).
5. Shrimp & Basil Sandwich: For a more gourmet option, use Shrimp (408 units) paired with Fresh Basil (81 units).
Since you have Parmesan Crisps (363 units) and Wasabi Party Mix (though currently at 0 units, keep an eye on it), these could
be served as sides
Summary
The strategy for using Kotlin for MCP development with Gemini CLI was validated with a incremental step by step approach.
A minimal stdio transport MCP Server was started from Kotlin source code and validated with Gemini CLI running as a MCP client in the same local environment.
Gemini CLI was then used to extend the sample Kotlin code with several MCP tools and use these tools inside the context for the underlying LLM.
This approach can be extended to more complex deployments using other MCP transports and Cloud based options.


Top comments (0)