DEV Community

ANIL LALAM
ANIL LALAM

Posted on

Building an Agentic AI Application with Google ADK, Gemini on Vertex AI, and MCP tools — ANIL LALAM

Introduction:

Modern AI agents are most powerful whey they can interact with external systems through tools. MCP (Model Context Protocol) provides a standardized mechanism for exposing tools, while Google ADK simplifies agent development using Gemini models.

In this article, we will build an AI agent using Google’s Agent Development Kit (ADK ) and java, configure Gemini through Vertex AI, integrate with tools exposed by an MCP Server, and expose the agent as a Spring Boot REST API. Along the way, we will explore how Google ADK orchestrates interactions between Gemini and MCP tools using JSON-RPC and Server-Sent Events(SSE).

GitHub: https://github.com/lalamanil/AgenticAIADKSpringBoot.git

GitHub: https://github.com/lalamanil/MCPServerForTools.git

Architecture:

Prerequisites Reading:

This article builds upon concepts introduced in my previous article, “Building an MCP Server using Spring AI, JSON-RPC, and SSE( Server-Sent Events).” Since the Google ADK agent developed in this tutorial interacts with tools exposed by an MCP Server, I strongly recommend reviewing that article first to gain a solid understanding of MCP architecture, JSON-RPC communication, SSE transport, tool registration, and tool invocation workflows.

You can find the complete article and implementation details here:
https://github.com/lalamanil/MCPServerForTools/blob/main/SpringAIMCPServerImplementation.pdf

Prerequisites:

Before building an enterprise-grade AI agents using Google ADK, it is important to understand how the agent interacts with the underlying Large Language Model (LLM).

The brain of an AI agent is the LLM that performs reasoning, planning, and tool-selection tasks. In the Google Cloud ecosystem, Gemini foundation models are available through Vertex AI and are the recommended approach for enterprise and production workloads.

To access Gemini models from a Java application using Google ADK, complete the following prerequisites:

Enable Gemini Enterprise Agent Platform APIs:

Enable the required Google Cloud APIs in your project:

  • Agent Platform API (aiplatform.googleapis.com)

The Agent Platform API (formerly know as the Vertex AI API) provides access to Gemini foundation models and other AI services hosted on Google Cloud.

Create a Service Account:
Production applications should authenticate using a dedicated service account rather than user credentials.
Create a service account that will be used by Google ADK application to access Agent Platform resources.

Go to https://console.cloud.google.com/

Click on APIs & Services/Credentials


Click on Create credentials and select Service account.

Fill the Service account name , Service account description and click on Create and continue.

Assign Agent Platform User role permissions to Service Account.

Click on Continue

Click on the created service account under Service Accounts

Click on Keys

Create new Key

Select key type as JSON

Service account is downloaded to your computer

Note: The downloaded service account key contains sensitive credentials. Store it securely and restrict access appropriately, as it is required for authenticating the application with Vertex AI and accessing Gemini foundation models.

Implementation:
Folder structure:

Place the dowloaded service account file under src/main/resources
Include below dependencies in pom.xml

Include below plugins in pom.xml

Configure the following environment variables so that Google ADK routes all Gemini model requests through Vertex AI infrastructure instead of directly accessing the Gemini API:

GOOGLE_GENAI_USE_VERTEXAI: true
GOOGLE_CLOUD_PROJECT: GCP project ID
GOOGLE_CLOUD_LOCATION: GCP Location

When GOOGLE_GENAI_USE_VERTEXAI is set to true, the Google Gen AI SDK automatically uses Vertex AI as the backend for Gemini model access. The GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION variables specify the Google Cloud project and region where Vertex AI services are hosted. This configuration enables enterprise-grade authentication, Security, governance, monitoring and quota management through Google Cloud’s Vertex AI Platform.

Implementing Tool Bridges in Google ADK:

Large language Models (LLMs) cannot directly interact with external systems. To perform real-world tasks such as retrieving data, calculating values, or invoking enterprise services, they must use tools.

Google ADK provides a simple mechanism for defining tools that can be invoked by Gemini during agent execution. A tool can be created by defining a Java method and annotating it using @schema from the Google ADK library.

The @schema annotation can be applied to both methods and method parameters:

  • name - Defines the tool name exposed to LLM.
  • description - Explains the purpose of the tool and helps the LLM decide when to invoke it.

Note: It is recommended to keep the tool name and description consistent with the corresponding tool definition exposed by the MCP server. This ensures alignment between the LLM’s understanding of tool and the actual MCP implementation.

Tool Bridge Implementation
The tool method acts as a bridge between Google ADK and the MCP Server. When Gemini decides to invoke a tool, Google ADK calls the corresponding java method. The bridge method then delegates the request to the MCP Client, which performs the actual MCP tool invocation.

In the above tool bridge method, the method itself contains no business logic. Instead, it serves as a bridge that forward request to the MCP Client. The MCP Client construct a JSON-RPC request with the method set to tools/call and sent it to the MCP Server.
Example: {“jsonrpc”:”2.0",“id”:3,“method”:”tools/call”,“params”:{“name”:”calculate_discount”,“arguments”:{“originalPrice”:100,“discountPercentage”:20}}}

MCP Tool Invocation Flow

When Gemini invokes the tool, the following sequence occurs:

  1. Gemini selects the calculate_disount tool.
  2. Google ADK invokes the corresponding Java bridge method.
  3. The bridge method calls the MCP Client.
  4. The MCP Client construct a JSON-RPC request using the tools/call method.
  5. The request is sent to the MCP Server.
  6. The MCP Server executes the tool and publishes the result asynchronously through the SSE transport.
  7. The MCP Client receives the response from the SSE stream.
  8. The result is returned back to Gemini
  9. Gemini incorporates the tool output into the final response.

Request-Response Correlation Using CompletetableFuture
Since MCP communication over SSE is asynchronous, the HTTP POST request used for tools/call does not directly return the tool results. Instead, the MCP Server publishes the response asynchronously on the SSE stream.

To correlate requests with responses, the MCP Client maintains the following structure:

private final Map> pendingResponse = new ConcurrentHashMap<>();

Each JSON-RPC request is assigned a unique identifier (id). Before sending the request, the client creates a new CompletableFuture and stores it in pendingResponse map using request id as the key.

CompletableFuture future = new CompletableFuture<>();
pendingResponse.put(id, future);

The same id is included in the outgoing JSON-RPC request.

Continuous SSE Listener:

A dedicated background thread continuously listens to the /sse endpoint exposed by the MCP Server.


Whenever a new event is received:

  1. The response payload is parsed.
  2. The JSON-RPC id is extracted.
  3. The corresponding CompletableFuture is retrieved from pendingResponse.
  4. The future is completed with the response payload.

CompletableFuture future = pendingResponse.remove(id);
if (future != null) {
future.complete(responseJsonNode); }

Completing the future automatically wakes up the thread waiting on: future.get()

The waiting tool invocation thread then extracts the tool results from the MCP response and returns it to Google ADK.

This design enables seamless integration between Google ADK and MCP while supporting asynchronous request-response communication through JSON-RPC and Server-Sent Events.

Building and Initializing the Google ADK Agent:

At this stage, the MCP Tool Bridge has already been implemented and is capable of invoking tools exposed by the MCP Server. The next step is to build a Google ADK Agent, configure Gemini as the underlying Large Language Model (LLM), register the available tools, and provide instructions that guide the model’s decision-making process.

Loading Vertex AI Credentials:

Before an agent can communicate with Gemini, it must authenticate with Google Cloud. In this implementation, a service account is loaded and converted into a GoogleCredentials object.

The credentials are then wrapped inside a VertexCredentials object that contains the Google Cloud project and region information. This enables Google ADK Agent to access Gemini models through Vertex AI using IAM-based authentication.

Registering Gemini as the Agent’s LLM:

Once the credentials are available, a Gemini model instance is created.
Gemini gemini = Gemini.builder() .vertexCredentials(vertexCredentials) .modelName("gemini-2.5-pro") .build();
The Gemini instance becomes the reasoning engine for the agent. All user requests, tool selection decisions, and final response generation are performed by this model.

Registering Tools with the Agent:

The MCP Tool Bridge method must be registered with the agent so that Gemini is aware of the available capabilities.

FunctionTool.create( mcpToolBridge, "calculateDiscount");

Google ADK inspect the @schema annotations and automatically generates the tool metadata required by Gemini.
When Gemini receives a user query, it can analyze the available tool descriptions and determine whether a tool invocation is required.

Providing Agent Instructions:

Instructions plays a critical role in controlling agent behavior. They act as the system prompt that guides Gemini’s reasoning process.

.instruction( "You are a discount calculation assistant. " + "Always use the calculateDiscount tool to calculate discounts and savings. " + "Never calculate discounts manually." )

These instructions influence how the model behaves during execution.
For example:

  • The agent understands that its primary responsibility is discount calculation.
  • The agent knows that a dedicated tool exists for performing calculations.
  • The model is explicitly prohibited from performing manual calculations.

As a result, Gemini consistently invokes the tool instead of generating estimated values from its own reasoning.

Creating the Agent:

After the model, tools, and instructions are defined, the final agent is created.

baseAgent = LlmAgent.builder()
.name("calculate-discount-agent")
.description("Provides the how much amount is saved after the discount is applied on original price")
.instruction("You are a discount calculation assistant.Always use the calculateDiscount tool to calculate discounts and savings.Never calculate discounts manually.”)
.model(gemini)
.tools(FunctionTool.create(mcpToolBridge, "calculateDiscount”))
.build();

The agent now contains four key components:
Gemini Model : Provides reasoning and decision-making capabilities.
Tool Registry: Defines actions the agent can perform.
Instructions: Guides model behavior
Agent metadata: Provides identity and purpose

Running the Agent:

At this point, the agent has been initialized with:

-  Gemini as the underlying Large Language Model (LLM)
-  Vertex AI authentication
-  Tool definitions exposed through the MCP Tool Bridge.
-  Agent instructions that guide Gemini’s behavior.
Enter fullscreen mode Exit fullscreen mode

The final step is to execute the agent when a user submits a request.

Creating the Runner:

Google ADK separates agent definition from agent execution.

An LlmAgent defines the model, instructions, and tools available to the agent. However, an agent cannot execute requests by itself. A Runner is responsible for orchestrating conversation, maintaining session state, invoking tools, and interacting with the underlying LLM.


In this implementation, we use an InMemoryRunner
InMemoryRunner runner = new InMemoryRunner(baseAgent);
The InMemoryRunner stores conversation state and session information in JVM memory. It is ideal for local development, proof-of-concepts, and lightweight applications.

Conceptually, the runner acts as the execution engine for the agent.

LlmAgent -> InMemoryRunner

  • Session Management
  • LLM Interaction
  • Tool Execution
  • Event Streaming

Creating a Session

Before a conversation can begin, a session must be created.

Session session = runner.sessionService().createSession(runner.appName(), "anillalam123").blockingGet();

A session represents an ongoing conversation between a user and the agent.

The session maintains:

  • User identity
  • Conversation history
  • Agent context
  • Tool execution context

This allows the agent to support Muti-turn conversation where previous interactions can influence future response.

Converting User Input into Content:

The incoming user query is transformed into a Google Gen AI content object.

Content userMsg = Content.fromParts(Part.fromText(agentRequest.getUserQuery()));

This is the format expected by Gemini and the Google ADK runtime.

For example:

Calculate the discount for an item priced at $100 with a 20% discount. Is converted into structured content object that can be processed by Gemini.

Executing the Agent:

The actual agent execution begins with the following call:

Flowable events = runner.runAsync(session.userId(), session.id(), userMsg, runConfig);

Although the method name contains Async, it does much more than simply invoke Gemini.

Internally, Google ADK performs the following operations:

  1. Sends the user query to Gemini.
  2. Provides Gemini with the available tool definitions.
  3. Allows Gemini to decide whether a tool should be invoked.
  4. Execute the selected tool if necessary
  5. Returns the tool results back to Gemini.
  6. Generate the final response.

In our implementation, if user ask for a discount calculation, Gemini determines that the calculate_disount tool should be used and invokes the corresponding Tool Bridge method. The tool Bridge then delegates the request to the MCP client, which invokes the MCP server using JSON-RPC and SSE support.
Event-Based Execution Model:
Google ADK follows an event-driven execution model.

Instead of returning a single response, the Runner produces a stream of events represented by:

Flowable

Example of events include:
User Request Received
Model Processing Started
Tool Selection
Tool Execution Started
Tool Execution Completed
Model Response Generated.
Final Response.

This event-driven approach allows applications to steam responses, monitor tool executions, and implement advanced tracing or observability features.

Capturing the Final Response:

The application listens for events emitted by the Runner.

events.blockingForEach(event -> {
if (event.finalResponse()) {
agentResponse.setResult(event.stringifyContent());
}});

The finalResponse() flag indicates that Gemini has completed its reasoning process and generated the final answer.

Once the final response event is received, the response content is extracted and stored in the application’s response object.

Exposing the Agent through a Spring Boot REST Endpoint:

After configuring Gemini, registering MCP-backed tools, and implementing the agent execution flow, the final step is to expose the agent as a REST API. This allows external applications to interact with the agent using Standard HTTP requests.

In this implementation, a spring Boot REST controller acts as the entry point for user requests. The controller accepts a user query, delegates the request to the Google ADK Agent, and returns the generated response.

End-to-End Execution and Testing
Starting the MCP Server

The first step is to start the Spring AI MCP Server. The MCP Server registers the available tools and expose the SSE and JSON-RPC endpoints required for MCP communication.

For readers interested in the complete MCP server implementation discussed in this article, the full source code and supporting documentation are available in the GitHub repository below.

GitHub Repository: https://github.com/lalamanil/MCPServerForTools/

The repository contains a complete Spring AI-based MCP Server implementation that demonstrates tool registration, JSON-RPC communication, Server-Sent Events (SSE) transport, MCP initialization, tool discovery, and end-to-end tool invocation workflow.

Starting the Google ADK Agent Application:

Next, start the Agentic AI Spring Boot application. During initialization, the application establishes an SSE connection with MCP Server, performs the MCP initialization handshake, discovers the available tools, and initializes the Google ADK Agent with Gemini running on Vertex AI.

Invoking the Agent Through REST API:

A user query is submitted to the spring Boot REST endpoint using Postman.

MCP Tool Invocation:

Based on the user query, Gemini determines that a tool invocation is required and triggers the registered tool bridge. The bridge delegates the request to MCP Client, which issues a JSON-RPC tool/call request to the MCP Server.

Processing Tool Request in MCP Server:

The MCP Server receives the JSON-RPC request, executes the corresponding tool, and prepare the response.The MCP Server publishes the tool results synchronously through SSE stream. The MCP client receives the events, correlates the response using JSON-RPC request id, and completes the waiting CompletableFuture.

Final Response Generated by Gemini:

After receiving the tool results, Gemini generates the final natural language response, which is returned to REST client.

Top comments (0)