DEV Community

xbill for Google Developer Experts

Posted on • Originally published at Medium on

Firestore MCP Development with Kotlin and Gemini CLI

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:

GitHub - modelcontextprotocol/kotlin-sdk: The official Kotlin SDK for Model Context Protocol servers and clients. Maintained in collaboration with JetBrains

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:~$
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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": []
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)