In this tutorial, we're going to build a Model Context Protocol (MCP) server that connects AI models to MongoDB, using it to power a simple todo list application. If you've been working with AI applications, you've probably run into the challenge of getting LLMs to interact with your actual systems and data. That's exactly what the Model Context Protocol solves! It's quickly becoming the standard way to connect AI models to the real world. Whether you're building agents that need to query databases, access APIs, or interact with your company's internal tools, understanding MCP is becoming essential. By the end of this tutorial, you'll have a working server that exposes MongoDB operations as tools any MCP-compatible AI can use, and you'll understand the patterns for building your own servers for whatever data sources you need to connect.
MongoDB is a natural fit for AI applications—it's flexible, scalable, and has built-in vector search capabilities that make it easy to work with embeddings and semantic search. If you're curious about the AI side of MongoDB and want to dive deeper into vector search, there's a Vector Search Fundamentals course that'll walk you through those concepts (and you can earn a skill badge while you're at it).

If you want to take a look at the code used in this tutorial, check out the GitHub repository.
What is the Model Context Protocol?
The Model Context Protocol (MCP) is an open standard that enables AI applications to securely connect to various data sources and tools. Think of it as a universal adapter that lets your AI models interact with databases, APIs, file systems, and other services in a standardized way. Instead of building custom integrations for every data source, MCP provides a common language that both AI systems and data providers can speak.
At its core, MCP defines how servers expose their capabilities (like database queries or file operations) as tools, resources, and prompts that AI models can discover and use. This makes it incredibly powerful for building context-aware AI applications that need to pull from multiple sources.
The difference between an MCP server and an MCP client
An MCP server is the component that exposes capabilities to AI systems. It's what we'll be building in this tutorial. The server defines tools (functions the AI can call), resources (data the AI can access), and prompts (templated conversations). When we build our Spring AI MCP server, we're creating something that says, "Hey, I can interact with MongoDB for you—here are the operations I support."
An MCP client, on the other hand, is the component that consumes these capabilities. This is typically your AI application or agent that discovers available tools from MCP servers and decides when to use them. The client sends requests to servers and handles the responses. In the broader ecosystem, applications like Claude Desktop or other AI interfaces act as MCP clients that can connect to your servers.
What is Spring AI?
Spring AI is an application framework for AI engineering. Its goal is to apply to the AI domain Spring ecosystem design principles such as portability and modular design and promote using POJOs as the building blocks of an application to the AI domain.
Prerequisites
- Java 17+
- Maven
- A MongoDB Atlas account with a cluster set up
- An M0 free forever tier is perfect
- Node (version 22.7.5 or higher is recommended)
- We need this as we will be using the MCP Inspector to test our application
Build a Spring app and our dependencies
To start building our application, we will need to bring in a few dependencies. The easiest way to get started is using Spring Initializr. We are going to need the Model Context Protocol Server and the Spring Web dependencies. We'll be adding the Spring Data MongoDB dependency later on, but we'll keep it to just the first two for now to make getting started easier.

For Project, I have selected Maven; for language, Java; and Spring Boot version 3.5.7 (or the latest stable release). Now, we can generate our Jar and open the downloaded app in our IDE.
Configure our MCP server
First things first, we need to update our Spring AI version to the latest milestone 1.1.0-M3. It is possible to do all this with the latest stable release, but the newer interface provides a much cleaner setup.
Open the POM.XML and update the Spring AI version:
<spring-ai.version>1.1.0-M3</spring-ai.version>
Now, we can define our MCP details in the application.properties file:
spring.application.name=springai-mcp
spring.ai.mcp.server.name=mongodb-mcp
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.protocol=streamable
spring.ai.mcp.server.stdio=false
spring.ai.mcp.server.type=sync
We give our server a name and version—this helps clients identify what they're connecting to. The name mongodb-mcp tells clients this server provides MongoDB capabilities, and the version lets us track updates over time.
We want an HTTP streamable service, no stdio, and synchronous responses. This means our server will communicate over HTTP rather than standard input/output, and it'll handle requests synchronously rather than asynchronously. This keeps things simple for our tutorial, though async operations are definitely an option for production use cases.
Build your first MCP tool
An MCP tool is essentially a function that an AI model can call. When you annotate a method with @McpTool, you're telling the MCP framework, "This is something an AI can use." The AI will be able to see the tool's name, description, and parameters, then decide whether to call it based on what the user is asking for.
Create a class MongoDbTools.java, where we will create a @Component.
We will use the @McpTool annotation. The name we give it will be how the AI identifies and calls this tool—think of it as the function name the AI will invoke. The description is important as it tells the AI what this tool does and when to use it. A good description helps the AI make smart decisions about when to call your tool. Think of this like a prompt template. When we give our application a natural language prompt, these descriptions allow the LLM to discern which tool is best for the job, and orchestrate the tools to achieve what we are asking it to do.
package com.timkelly.springaimcp;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springframework.stereotype.Component;
@Component
public class MongoDbTools {
// Tools
@McpTool(name="get-todo-items", description = "I will return the items of a todo list")
public String getTodoItems() {
var todo = """
1. Make Spring AI MCP tutorial
2. Have a coffee
3. Walk the dog
""";
return todo;
}
}
This is where we would define @McpResources or @McpPrompts. The @McpResource annotation provides access to resources via URI templates. The @McpPrompt annotation generates prompt messages for AI interactions. These go beyond what we cover in this tutorial, but you can read about them in the Spring AI MCP documentation.
How we will test our MCP server
First, we need to run our application.
To test our application, we will use the MCP Inspector. This is an interactive developer tool for testing and debugging MCP servers.
If you want to read more about it, check out the Debugging guide, which covers the Inspector as part of the overall debugging toolkit. For this tutorial, all we are going to do is use it to make sure our application is up and running, exposing the tools we create, and testing those tools.
The Inspector runs with the following:
npx @modelcontextprotocol/inspector
If this is your first time running it, you will likely need to install it:
Need to install the following packages:
@modelcontextprotocol/inspector@0.17.2
Ok to proceed? (y) y
Once this spins up, it will open in the browser.

Now, for the transport type in the top left, we need to change this to Streamable HTTP. This will change the interface slightly, giving us the option to define the URL our app is running on. Mine is localhost:8080, so I will enter http://localhost:8080/mcp. The /mcp is important to add—this is the default endpoint where Spring AI MCP exposes the protocol.

At this point, we can just hit connect. We will receive some server logging in the history section. It will confirm the name of the server, update when tools change, all that good stuff.

From the top bar we can select tools, and here we can list all the tools we have available. If we select a tool, we can then choose to run it in the browser.

If everything is working correctly, we should be able to confirm our output.

Success! Now that we have this confirmed, we can look to add some functionality to our todo list by connecting to MongoDB as our database.
Connect to MongoDB
Add the Spring Data MongoDB dependency to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
Add connection details to the application.properties file:
#...
# MongoDB Configuration
# 1. Copy your connection string from MongoDB Atlas
# 2. Replace ?appName=Cluster with ?appName=SpringAiMcp, or add it if no appName is present
#
# Before: mongodb+srv://user:pass@cluster.abc12.mongodb.net/?appName=Cluster
# After: mongodb+srv://user:pass@cluster.abc12.mongodb.net/?appName=SpringAiMcp
spring.data.mongodb.uri=<YOUR-CONNECTION-STRING>
spring.data.mongodb.database=todo
For our connection string, it is best practice to set the app name so we can see clearly what queries or operations are tied to which apps. For this, set the appName to SpringAiMcp by adding it to the end of your connection string. The database name todo is where we'll store all our task data.
Quick connection check (optional)
Run:
Look for a line similar to:
Monitor thread successfully connected to server with description ServerDescription{address=...}
If you see authentication or timeout errors, check:
- The connection string credentials.
- Your IP access list in MongoDB Atlas.
- That the appName is added correctly at the end of the URI.
Build our MongoDB tool
Now that we have MongoDB connected, it's time to replace our hardcoded todo list with real database operations. We're going to build out a complete set of tools that let an AI manage tasks in MongoDB—adding new tasks, marking them as complete, and retrieving tasks with various filters.
The beauty of the MCP approach here is that we're not writing AI-specific code. We're just building normal Spring Data repositories and services, then exposing them as tools with a few annotations. The AI doesn't need to know anything about MongoDB connection strings, query syntax, or data modeling—it just needs to know, "Here's a tool called todo-add-task that takes a task name." Our MCP server handles all the translation between the AI's requests and the actual database operations.
We'll structure this in layers, following Spring best practices. First, we'll define our data model and repository for database access. Then, we'll create a service layer to handle our business logic. Finally, we'll expose these operations as MCP tools that AI models can discover and use. This keeps our code clean, testable, and easy to extend with new functionality later.
Let's start by setting up our data model.
Define our Task model and repository
First, let's create our Task model in Task.java. This represents a single todo item in our MongoDB collection:
package com.timkelly.springaimcp;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "tasks")
public class Task {
@Id
private ObjectId id;
private String name;
private boolean completed;
public Task(ObjectId id, String name) {
this.id = id;
this.name = name;
}
public ObjectId getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
}
Now, create our repository interface in TodoRepository.java. Spring Data MongoDB will handle the implementation for us—this is one of the best features of Spring Data. We just define an interface that extends MongoRepository, and Spring will automatically generate all the common CRUD operations (create, read, update, delete) at runtime. No need to write boilerplate code for basic database operations like findAll(), save(), or deleteById()—they're all provided out of the box.
package com.timkelly.springaimcp;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TodoRepository extends MongoRepository<Task, ObjectId> {
}
The generic types <Task, ObjectId> tell Spring what entity we're working with and what type the ID field is. Spring Data MongoDB is smart enough to map our Java objects to BSON documents in MongoDB and back again, handling all the serialization for us.
Creating our add task tool
Let's create a service layer to handle our business logic. Create TodoService.java:
package com.timkelly.springaimcp;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TodoService {
private final TodoRepository todoRepository;
public TodoService(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
public void addTask(String name) {
todoRepository.save(new Task(new ObjectId(), name));
}
}
Now, update our MongoDbTools.java class to wire in the service and add our first real tool:
package com.timkelly.springaimcp;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConditionalOnBean(TodoService.class)
public class MongoDbTools {
private final TodoService todoService;
public MongoDbTools(TodoService todoService) {
this.todoService = todoService;
}
@McpTool(
name = "todo-add-task",
description = "Add a new to-do task to MongoDB"
)
public String addTask(
@McpToolParam(
description = "The name or description of the new task",
required = true
) String name
) {
todoService.addTask(name);
return "Task added successfully: " + name;
}
}
The @McpToolParam annotation allows us to provide metadata about each parameter. The description helps the AI understand what to pass in, and marking it as required ensures the AI knows this parameter can't be optional. The better we describe our parameters, the better the AI can use our tools correctly.
Update our tasks
Let's add a query method to our TodoRepository.java so we can find tasks by name:
package com.timkelly.springaimcp;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Update;
import org.springframework.stereotype.Repository;
@Repository
public interface TodoRepository extends MongoRepository<Task, ObjectId> {
@Update("{ '$set' : { 'completed' : ?0 } }")
void updateCompletedByName(String name, boolean completed);
}
This is another powerful Spring Data feature—query derivation from method names. Spring Data MongoDB will parse the method name findByName and automatically generate the MongoDB query for us. It sees "findBy" and knows we want to query, then "Name" tells it which field to match against. At runtime, this becomes a query like db.tasks.find({ name: "some name" }) without us writing any query code. As long as we follow Spring Data's naming conventions, we can create complex queries just by naming our methods correctly—things like findByCompletedTrueAndNameContaining would work exactly as you'd expect.
Now, add the completeTask method to TodoService.java:
public void setCompletedByName(String name, boolean completed) {
todoRepository.updateCompletedByName(name, completed);
}
And add the corresponding tool to MongoDbTools.java:
@McpTool(
name = "todo-complete-task",
description = "Mark a to-do task as complete by name"
)
public String completeTask(
@McpToolParam(
description = "The name of the task to mark as complete or incomplete",
required = true
) String name,
@McpToolParam(
description = "The status of the task, either complete(true) or incomplete(false)"
) boolean status
) {
todoService.setCompletedByName(name, status);
return "Marked task as " + status + ": " + name;
}
}
Creating our get tasks tools
Let's add more query methods to our TodoRepository.java to support filtering by completion status:
package com.timkelly.springaimcp;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Update;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TodoRepository extends MongoRepository<Task, ObjectId> {
@Update("{ '$set' : { 'completed' : ?1 } }")
void updateCompletedByName(String name, boolean completed);
List<Task> findByCompletedTrue();
List<Task> findByCompletedFalse();
}
Add the getTasks method to TodoService.java:
public List<Task> getTasks(String filter) {
if (filter == null || filter.isBlank() || filter.equalsIgnoreCase("all")) {
return todoRepository.findAll();
}
return switch (filter.toLowerCase()) {
case "incomplete" -> todoRepository.findByCompletedFalse();
case "complete" -> todoRepository.findByCompletedTrue();
default -> todoRepository.findAll();
};
}
And finally, add the tool to MongoDbTools.java:
@McpTool(
name = "todo-get-tasks",
description = "Retrieve to-do list items, optionally filtered by completion status"
)
public List<Task> getTasks(
@McpToolParam(
description = "Filter tasks by completion status: 'complete', 'incomplete', or 'all' (default: 'all')",
required = false
) String filter
) {
return todoService.getTasks(filter);
}
Testing our application
Make sure to set your MongoDB connection string before running the application:
Now, if we go back to our MCP Inspector, we can see our new tools listed in the tools section.

You can test each one individually—try adding a task, marking it complete, and retrieving tasks with different filters.

The Inspector will show you the requests being sent and the responses coming back, making it easy to verify everything is working as expected.

And there you have it! You've built a fully functional MCP server that exposes MongoDB operations as tools that AI models can use. This same pattern can be extended to build servers for all kinds of data sources and operations—the possibilities are pretty much endless.
Conclusion
Congrats! You've just built a fully functional MCP server that bridges the gap between AI models and MongoDB. What started as a simple, hardcoded todo list evolved into a real application with persistent storage, CRUD operations, and a clean architecture that any AI client can interact with.
The power of what you've built goes way beyond just a todo list. You now understand the fundamental pattern for exposing any data source or service to AI models through MCP. The @McpTool, @McpToolParam, and the other MCP annotations give you a consistent way to make anything accessible to AI.
What's really cool is how little AI-specific code we actually wrote. Most of what we built was just normal Spring Boot application code—models, repositories, services. The MCP layer was just a thin wrapper on top that made everything discoverable and callable by AI models. This means you can take existing Spring applications and MCP-enable them without major rewrites.
From here, you could extend this in tons of directions. Add authentication so different users have their own task lists. Create more complex queries with date filters or priority sorting. Build out @McpResources to expose collection schemas or statistics. Add @McpPrompts to help guide AI models on how to best use your tools. The Spring AI MCP framework gives you all the building blocks you need.
The Model Context Protocol is still relatively new, but it's quickly becoming the standard way to connect AI models to real-world data and systems. Getting comfortable with building MCP servers now puts you ahead of the curve as this ecosystem continues to grow. Whether you're building internal tools for your team, creating services for AI agents, or just experimenting with what's possible when AI can access real data, MCP is the bridge that makes it all work.
If you found this tutorial useful, check out my other tutorial, Secure Local RAG With Role-Based Access: Spring AI, Ollama, & MongoDB.