DEV Community

Vinicius Senger
Vinicius Senger

Posted on

Adding MCP to a Quarkus App: One Dependency, a Few Annotations, Done

I added MCP (Model Context Protocol) to re:Money — my Quarkus + DynamoDB financial tracking app — in under 30 minutes. One dependency, a few annotations on existing service methods, and my MCP client (Kiro / Claude) could call my actual backend during development. Here's exactly what I did.

New to re:Money? Check out the full tutorial on AWS Community to get started.


The Starting Point

re:Money is a standard three-tier Quarkus app:

Layer What It Does
Model Entry — accountID, category, amount, balance, date
Service EntryService — business logic and DynamoDB queries
Resource JAX-RS REST endpoints

The service layer already had methods like findByAccountID(), findByCategory(), listCategories(), getLastBalance(). All I needed was to make them discoverable by AI tools.


Step 1: Add the Dependency

<dependency>
    <groupId>io.quarkiverse.mcp</groupId>
    <artifactId>quarkus-mcp-server-http</artifactId>
    <version>1.10.1</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

No configuration needed. Quarkus auto-discovers MCP tools and exposes them at /mcp.


Step 2: Annotate Your Service Methods

Add @Tool and @ToolArg to existing methods — no new code:

import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.ToolArg;

@ApplicationScoped
public class EntryService extends AbstractService {

    @Tool(description = "Find all financial entries for a specific bank account")
    public List<Entry> findByAccountID(
            @ToolArg(description = "The account identifier, e.g. ACC001")
            String accountID) {
        return findAll().stream()
                .filter(e -> e.getAccountID().equals(accountID))
                .collect(Collectors.toList());
    }

    @Tool(description = "Find financial entries by spending category")
    public List<Entry> findByCategory(
            @ToolArg(description = "Category name like Food, Transport, Housing")
            String category) {
        return findAll().stream()
                .filter(e -> category.equals(e.getCategory()))
                .collect(Collectors.toList());
    }

    @Tool(description = "List all available spending categories")
    public List<String> listCategories() {
        return findAll().stream()
                .map(Entry::getCategory)
                .distinct().sorted()
                .collect(Collectors.toList());
    }

    @Tool(description = "List all bank accounts")
    public List<String> listAccounts() { /* ... */ }

    @Tool(description = "Get the last known balance for a bank account")
    public BigDecimal getLastBalance(
            @ToolArg(description = "The account identifier")
            String accountID) { /* ... */ }

    @Tool(description = "Find entries for an account within a date range")
    public List<Entry> findByAccountIDAndDates(
            @ToolArg(description = "Account identifier") String accountID,
            @ToolArg(description = "Start date as epoch millis") Long init,
            @ToolArg(description = "End date as epoch millis") Long end) { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

The description fields matter — they're what the AI reads to decide which tool to call. Be specific to your domain.


Step 3: Run It

./mvnw compile quarkus:dev
Enter fullscreen mode Exit fullscreen mode

MCP endpoint is live at http://localhost:8080/mcp.


Step 4: Configure Kiro as MCP Client

With re:Money running, you tell Kiro where to find the MCP server. Create .kiro/settings/mcp.json in your project root:

{
  "mcpServers": {
    "remoney": {
      "url": "http://localhost:8080/mcp"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a workspace-scoped configuration — it only applies when you're working in this project. You can also set it globally at ~/.kiro/settings/mcp.json if you want it available everywhere.

Another option is to create a dedicated Kiro agent for working with re:Money. Create .kiro/agents/finance-dev.json:

{
  "name": "finance-dev",
  "description": "Development assistant with live access to re:Money",
  "mcpServers": {
    "remoney": {
      "url": "http://localhost:8080/mcp"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Once configured, start a Kiro chat session and run /mcp to verify the server is connected and the tools are loaded:

You should see remoney listed with all your @Tool-annotated methods as available tools.


Demo: Kiro Calling re:Money

Once connected, Kiro chat now can reach your financial data and I love it because I can keep coding and using the chat to input data and query my re:Money app in a single place.

One Catch: MCP and Lambda Don't Mix

re:Money deploys to AWS Lambda using Quarkus's Lambda HTTP extension. But MCP requires a persistent HTTP connection — the server needs to stay alive to handle tool calls from the AI client. Lambda's request-response model doesn't support that.

So if your app runs on Lambda (or any short-lived serverless compute), you can't just add MCP to the same module and deploy it. You need to separate the MCP server into its own module and deploy it on a service that keeps a long-running process: ECS, App Runner, EC2, or even a local dev server.

In practice, this means:

  • Your Lambda module stays as-is — REST API, UI, the production workload
  • A new MCP module imports your service layer and exposes it via quarkus-mcp-server-http, deployed on ECS/App Runner/EC2

The service layer stays shared. The only difference is how it's exposed and where it runs. This is another reason to keep your business logic in the service layer and out of your REST controllers — it makes splitting modules painless.

For local development, this isn't an issue. ./mvnw quarkus:dev runs a long-lived process and MCP works fine. The separation only matters when you deploy to production.


What I Learned

Your service layer is the API. If your business logic lives in the service layer and not in your REST controllers, adding MCP is almost free.

Descriptions are the real work. The AI picks tools based on descriptions alone. Writing clear, domain-specific descriptions took more thought than any code change.

Start read-only. Expose queries first. Open up writes when you're ready.

Quarkus makes this trivial. One dependency, a few annotations, no config. The Quarkiverse MCP extension handles protocol, discovery, and transport. If you have a Quarkus app with a clean service layer, try it.


Resources

Top comments (0)