In Part 4 of this series, we added tool calling to our AI agent, allowing it to access real-time information like weather forecasts and current dates. However, we discovered another limitation: when asked to book flights or hotels, the agent couldn’t access those systems because we’d need to hardcode every API integration into our application.

Hardcoding API integrations creates maintenance challenges. Every new service requires code changes, recompilation, and redeployment. As your organization adds more systems (booking platforms, inventory systems, CRM tools), your AI agent becomes increasingly difficult to maintain and extend.

In this post, we’ll add Model Context Protocol (MCP) to our AI agent, allowing it to dynamically discover and use tools from external services without code changes or redeployment. You’ll learn how MCP enables you to expose your legacy systems, enterprise applications, or microservices to AI agents without rewriting them.

Overview of the Solution

The Integration Challenge

Hardcoding tool integrations has several problems:

  • Every new API requires code changes and redeployment
  • Tools are tightly coupled to the AI agent application
  • Different teams can’t independently develop and deploy their tools
  • Testing and versioning tools becomes complex
  • Scaling to dozens or hundreds of integrations becomes unmanageable

We need a way to add new capabilities to our AI agent without modifying its code.

We’ll solve this with Model Context Protocol (MCP):

  1. Create MCP servers that expose tools via a standardized protocol
  2. Configure MCP client in our AI agent to discover available tools
  3. Let the AI automatically call tools from any connected MCP server
  4. Add new capabilities by simply starting new MCP servers

What is Model Context Protocol?

Model Context Protocol (MCP) is an open protocol that standardizes how AI applications connect to external tools and data sources. Think of it as a universal adapter that lets AI agents discover and use tools from any service that implements the protocol.

Key benefits:

  • Standardized Interface: All tools use the same protocol, regardless of implementation
  • Dynamic Discovery: AI agents automatically discover available tools from connected servers
  • Loose Coupling: Tools and AI agents can be developed, deployed, and scaled independently
  • Multiple Transports: Supports SSE (Server-Sent Events), Streamable HTTP, stdio, and custom transports
  • Language Agnostic: Servers can be written in any language (Java, Python, Node.js, etc.)

Architecture Overview

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AI Agent (MCP Client)

[MCP Protocol - SSE Transport]

┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Travel Server │ │ More MCP Servers can be │
│ (URL:8082) │ │ added via URL:PORT │
├─────────────────────────────┤ │ - GitHub Server │
│ - findFlightsByRoute │ │ - Jira Server │
│ - findFlightByNumber │ │ - CRM Server │
│ - findHotelsByCity │ │ - Inventory Server │
│ - findHotelsByName │ │ - ... │
│ - getHotel │ └─────────────────────────────┘
└─────────────────────────────┘

Key Spring AI Components

Prerequisites

Before you start, ensure you have:

  • Completed Part 4 of this series with the working ai-agent application
  • Java 21 JDK installed (Amazon Corretto 21)
  • Maven 3.6+ installed
  • Docker Desktop running (for Testcontainers with PostgreSQL/PGVector)
  • AWS CLI configured with access to Amazon Bedrock

Navigate to your project directory from Part 4:

1
cd ai-agent

MCP Server

We’ll create a separate Spring Boot application that acts as an MCP server, exposing travel-related tools (flight and hotel search/booking) that our AI agent can use.

Why Separate Server?

Separating the MCP server from the AI agent provides several benefits:

  • Independent Deployment: Update travel services without redeploying the AI agent
  • Team Autonomy: Different teams can own and maintain their own MCP servers
  • Scalability: Scale travel services independently based on demand
  • Reusability: Multiple AI agents or applications can use the same MCP server

Clone Travel Server

We’ll use a pre-built travel server that demonstrates MCP server capabilities:

1
2
3
cd ..
git clone https://github.com/aws-samples/java-on-aws.git
cd java-on-aws/samples/spring-ai-te-agent/travel

Explore Travel Server Structure

The travel server provides comprehensive travel management capabilities:

1
2
# View the project structure
ls -la src/main/java/com/example/travel/

Key components:

  • HotelTools: Search and booking tools for hotels
  • FlightTools: Search and booking tools for flights
  • AirportTools: Airport information lookup
  • Services: Business logic for travel operations
  • Repositories: Data access layer with Spring Data JPA

The travel server is configured as an MCP server with these key dependencies in pom.xml:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

And configuration in application.properties:

1
2
3
4
5
# MCP Server properties
spring.ai.mcp.server.name=travel
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.instructions="Travel management: search for airports, flights, hotels; book flights and hotels"
logging.level.org.springframework.ai=DEBUG

These properties configure the MCP server identity and provide instructions that help AI agents understand the server’s capabilities.

Review HotelTools

Let’s examine how tools are exposed via MCP:

1
cat src/main/java/com/example/travel/accommodations/HotelTools.java
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.example.travel.accommodations;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.util.List;

@Component
public class HotelTools {

private final HotelService hotelService;

public HotelTools(HotelService hotelService) {
this.hotelService = hotelService;
}

@Bean
public ToolCallbackProvider hotelToolsProvider(HotelTools hotelTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(hotelTools)
.build();
}

@Tool(description = """
Find hotels in a city for specific dates.
Requires: city - Name of the city to search in,
checkInDate - Check-in date (YYYY-MM-DD),
numberOfNights - Number of nights to stay.
Returns: List of available hotels sorted by price from lowest to highest.
Errors: NOT_FOUND if no hotels found in the specified city.
""")
public List<Hotel> findHotelsByCity(String city, LocalDate checkInDate, Integer numberOfNights) {
return hotelService.findHotelsByCity(city, checkInDate, numberOfNights);
}

@Tool(description = """
Find hotel details by hotel name.
Requires: hotelName - The name of the hotel.
Returns: Complete hotel details including amenities, pricing, and availability.
Errors: NOT_FOUND if hotel doesn't exist with the specified hotelName.
""")
public List<Hotel> findHotelsByName(String hotelName) {
return hotelService.findHotelsByName(hotelName);
}

@Tool(description = """
Get hotel details by ID.
Requires: id - The unique identifier of the hotel.
Returns: Complete hotel details including amenities, pricing, and availability.
Errors: NOT_FOUND if hotel doesn't exist with the specified ID.
""")
public Hotel getHotel(String id) {
return hotelService.getHotel(id);
}
}

Key points:

  • @Tool annotation: Marks methods as callable tools with detailed descriptions
  • ToolCallbackProvider bean: Registers tools with Spring AI’s MCP server
  • Separation of concerns: Tools delegate to service layer for business logic
  • Clear descriptions: Help the AI understand when and how to use each tool
  • Tools files pattern: Create separate Tools classes instead of annotating service methods directly, allowing you to expand complex objects into individual parameters and use descriptive method names that help the AI model understand which method to call with which parameters

Important: The real power of Spring AI’s MCP server starter is that you can add MCP capabilities to almost any existing Java application or microservice. By simply adding the starter dependency and annotating methods with @Tool, you can expose your legacy systems, enterprise applications, or microservices to AI agents without rewriting them. This enables AI integration across your entire application landscape.

Start Travel Server

Open a new terminal and run the travel server. It uses Testcontainers for automatic PostgreSQL management:

1
./mvnw spring-boot:test-run

You should see:

1
2
3
4
Creating container for image: postgres:16
Container postgres:16 is starting...
Container postgres:16 started
Started TravelApplication in X.XXX seconds

The server is now running on http://localhost:8082 with:

  • Over 100 hotels across major cities
  • Sample flights between popular destinations
  • MCP server endpoints for tool discovery

Keep this terminal open - the travel server needs to run while we test the AI agent.

Test Travel Server

Open a new terminal and verify the server is working:

1
2
3
4
5
# Search for hotels in Paris
curl "http://localhost:8082/api/hotels/search?city=Paris&checkInDate=2025-11-15&numberOfNights=3" | jq '.[0:2]'

# Search for flights from London to Paris
curl "http://localhost:8082/api/flights/search?departureCity=London&arrivalCity=Paris" | jq '.[0:2]'

Success! The travel server is running and responding to requests.

MCP Client

Now we’ll configure our AI agent to connect to the travel server via MCP, automatically discovering and using its tools.

Add MCP Client Dependency

Navigate back to your AI agent project:

Open pom.xml and add the MCP client dependency to the <dependencies> section:

1
2
3
4
5
<!-- MCP Client -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

Configure MCP Client

Add MCP client configuration to src/main/resources/application.properties:

1
2
3
4
5
6
cat >> src/main/resources/application.properties << 'EOF'

# MCP Client Configuration
spring.ai.mcp.client.toolcallback.enabled=true
spring.ai.mcp.client.sse.connections.travel.url=http://localhost:8082
EOF

This configuration:

  • Enables automatic tool callback registration
  • Connects to the travel server via SSE (Server-Sent Events) transport
  • Names the connection “travel” for identification in logs

Update ChatService with MCP

Update ChatService to use tools from MCP servers:

src/main/java/com/example/ai/agent/service/ChatService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
public ChatService(ChatMemoryService chatMemoryService,
VectorStore vectorStore,
DateTimeService dateTimeService,
WeatherService weatherService,
ToolCallbackProvider tools,
ChatClient.Builder chatClientBuilder) {

// Build ChatClient with RAG, tools, and memory
this.chatClient = chatClientBuilder
.defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build()) // RAG for policies
.defaultTools(dateTimeService, weatherService) // Custom tools
.defaultToolCallbacks(tools) // Auto-registered tools from MCP
.build();

this.chatMemoryService = chatMemoryService;
}
...

The key change is adding ToolCallbackProvider tools parameter and .defaultToolCallbacks(tools) method. Spring AI automatically:

  1. Connects to configured MCP servers
  2. Discovers available tools
  3. Registers them with the ChatClient
  4. Makes them available to the AI model

Testing MCP Integration

Let’s test the complete MCP integration with travel bookings:

1
./mvnw spring-boot:test-run

You should see in the logs:

1
2
3
4
...
Server response with Protocol: 2024-11-05, Capabilities: ServerCapabilities[completions=CompletionCapabilities[], experimental=null, logging=LoggingCapabilities[], prompts=PromptCapabilities[listChanged=true], resources=ResourceCapabilities[subscribe=false, listChanged=true], tools=ToolCapabilities[listChanged=true]], Info: Implementation[name=travel, version=1.0.0] and Instructions "Travel management: search for airports, flights, hotels; book flights and hotels"
...1
Started AiAgentApplication in 3.088 seconds (process running for 3.31)

Test with REST API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Search for hotels in Paris
curl -X POST http://localhost:8080/api/chat/message \
-H "Content-Type: application/json" \
-d '{"prompt": "Find me hotels in Paris for 3 nights starting November 15, 2025", "userId": "alice"}' \
--no-buffer
# Response: Lists available hotels with prices, amenities, and ratings

# Search for flights
curl -X POST http://localhost:8080/api/chat/message \
-H "Content-Type: application/json" \
-d '{"prompt": "Find flights from London to Paris", "userId": "alice"}' \
--no-buffer
# Response: Lists available flights with times, airlines, and prices

# Complex travel planning
curl -X POST http://localhost:8080/api/chat/message \
-H "Content-Type: application/json" \
-d '{
"prompt": "Please find me inbound and outbound flights and accommodations for a trip from London to Paris next week, from Monday to Friday. I travel alone, prefer BA flights in the first part of the day, and choose accommodation which is the most expensive but complies with our travel policy. Give me a travel itinerary with flights, accommodation, prices and weather forecast for each day of the travel.",
"userId": "alice"
}' \
--no-buffer
# Response: Complete travel itinerary with flights, hotel, prices, and weather

Success! The AI agent now has access to travel booking capabilities through MCP without any hardcoded integrations.

You can also test in the UI at http://localhost:8080 - ask about flights, hotels, or complete travel planning.

Complex request

Complex response

How MCP Works

When you ask “Find hotels in Paris for 3 nights”, here’s what happens:

  1. AI analyzes the question: Recognizes it needs hotel search capabilities
  2. Tool discovery: Finds findHotelsByCity tool from the travel MCP server
  3. Parameter extraction: Extracts city=”Paris”, numberOfNights=3, and determines check-in date
  4. Tool invocation: Calls the travel server via MCP protocol
  5. Response synthesis: Formats hotel results into a natural language response

The AI automatically decides which tools to call from which servers, all through the standardized MCP protocol.

Let’s continue the chat. Alice returned from her Paris trip and wants to submit her expenses:

1
2
3
4
5
curl -X POST http://localhost:8080/api/chat/message \
-H "Content-Type: application/json" \
-d '{"prompt": "I just returned from Paris and need to submit my hotel receipt and restaurant expenses. Can you help me process them?", "userId": "alice"}' \
--no-buffer
# Response: "I can help you submit expenses, but I need the receipt details. Please provide the amounts, dates, and merchant names."

Problem discovered: Alice has photos of her receipts on her phone, but the agent can only process text—it cannot analyze images or extract information from visual documents like receipts, invoices, or travel confirmations.

We’ll address this limitation in the next part of the series! Stay tuned!

Add More MCP Servers

The power of MCP is that you can add new capabilities without changing the AI agent code:

1
2
3
4
# Start another MCP server on a different port
# The AI agent will automatically discover its tools
export SPRING_AI_MCP_CLIENT_SSE_CONNECTIONS_CRM_URL=http://localhost:8083
./mvnw spring-boot:test-run

You can add as many MCP servers as needed, each providing different capabilities (CRM, inventory, analytics, etc.).

Cleanup

To stop the applications:

  1. Press Ctrl+C in the AI agent terminal
  2. Press Ctrl+C in the travel server terminal

The PostgreSQL containers will continue running (due to withReuse(true)). If necessary, stop and remove them:

1
2
docker stop ai-agent-postgres
docker rm ai-agent-postgres

(Optional) To remove all data and start fresh:

1
docker volume prune

Commit Changes

1
2
git add .
git commit -m "Add MCP client integration"

Conclusion

In this post, we’ve added dynamic tool integration to our AI agent through MCP:

  • MCP Server: Created a separate travel service exposing hotel and flight tools
  • MCP Client: Configured AI agent to discover and use tools from MCP servers
  • Dynamic Discovery: AI automatically finds and calls tools without hardcoded integrations
  • Loose Coupling: Travel services can be updated independently of the AI agent
  • Scalability: Add new capabilities by starting new MCP servers

Our AI agent now has a complete, production-ready architecture: memory (Part 2), knowledge (Part 3), real-time information (Part 4), and dynamic tool integration (Part 5). It can remember conversations, answer policy questions, access current information, and integrate with any service that implements the MCP protocol—all essential capabilities for enterprise AI applications.

What’s Next

Explore advanced features like multi-modal support (image and document analysis) using various models.

Challenges and Solutions - Part 5

Learn More

Let’s continue building intelligent Java applications with Spring AI!

Comments