Let's get started with a Microservice Architecture with Spring Cloud:
Overview of MCP Annotations in Spring AI
Last updated: February 27, 2026
1. Overview
The Model Context Protocol (MCP) has emerged as a standard for connecting AI models to external data and tools. Rather than building bespoke integrations for every data source, MCP enables developers to create universal connectors that work across various AI clients.
Spring AI supports this protocol through a dedicated module that introduces a declarative, annotation-based programming model. Instead of manually registering tool callbacks or configuring JSON schemas, we can annotate standard Java methods to expose them as AI capabilities.
In this tutorial, we’ll explore the core MCP annotations in Spring AI: @McpTool, @McpResource, and @McpPrompt.
2. Maven Dependencies
We use the spring-ai-starter-mcp-server dependency. This starter handles the auto-configuration and component scanning for MCP beans:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
<version>1.1.2</version>
</dependency>
3. Server Configuration
MCP servers often communicate via Standard Input/Output (Stdio) when running locally. This means the AI client launches our Java application as a subprocess and “talks” to it via the console.
For this to work, our application must not print any logs to the console, as this corrupts the protocol JSON messages.
We need to configure our application.properties to use Stdio and silence the logs:
spring.application.name=my-spring-calculator
# 1. Use STDIO transport
spring.ai.mcp.server.stdio=true
# 2. CRITICAL: Disable the Banner and Web Server
# Any text printed to console will break the connection
spring.main.banner-mode=off
spring.main.web-application-type=none
# 3. Redirect logs away from Console
logging.pattern.console=
4. Exposing Functionality with @McpTool
The @McpTool annotation is the workhorse of MCP development. It marks a Java method as an executable “tool” that the AI model can call. When the application starts, Spring AI analyzes the method signature to build a tool definition.
4.1. Basic Tool Definition
Let’s create a service that exposes a simple calculator function. We use @McpTool on the method and @McpToolParam on the arguments to provide metadata:
@Service
public class CalculatorService {
@McpTool(
name = "calculate_sum",
description = "Calculates the sum of two integers. Useful for basic arithmetic."
)
public int add(
@McpToolParam(description = "The first number to add", required = true) int a,
@McpToolParam(description = "The second number to add", required = true) int b
) {
return a + b;
}
}
The description fields are functionally important. The LLM uses them to understand when to call this tool and what the parameters represent. If the name is omitted, the method name is used by default.
4.2. Handling Complex Objects
For more sophisticated tools, we often need to pass structured objects rather than simple primitives. Spring AI supports Java Records (and POJOs), automatically serializing them to JSON schemas. Consider a customer search tool that accepts filter criteria:
public record CustomerSearchCriteria(
String region,
boolean activeOnly,
@JsonProperty(required = false) Integer limit
) {}
@Service
public class CustomerService {
@McpTool(description = "Search for customers using structured criteria")
public List<String> searchCustomers(
@McpToolParam(description = "The search filters") CustomerSearchCriteria criteria
) {
// In a real app, this would query a database
return List.of("Customer A", "Customer B");
}
}
By using a Record, we group related parameters. The AI client receives a schema indicating that it should send a JSON object containing the region and activeOnly fields.
4.3. Accessing Request Context
Sometimes a tool needs access to the underlying MCP session, for example, to log messages back to the client or track progress. We can inject McpSyncRequestContext as an argument. This parameter is part of the internal infrastructure and is excluded from the generated tool schema visible to the AI.
@McpTool(name = "long_running_process")
public String processData(
String dataId,
McpSyncRequestContext context
) {
context.info("Starting processing for ID: " + dataId);
// Simulate work and report detailed progress
// 50% complete (0.5 out of 1.0)
context.progress(p -> p.progress(0.5).total(1.0).message("Processing records..."));
return "Processed " + dataId;
}
4.4. Enabling Auto-Completion with @McpComplete
Modern AI clients like Claude support auto-completion when users type arguments for a Prompt. The @McpComplete annotation allows us to provide dynamic suggestions. For example, we can suggest programming languages for our review_code prompt:
@McpComplete(prompt = "review_code")
public List<String> completeLanguage(McpSchema.CompleteRequest.CompleteArgument argument) {
if (!"language".equals(argument.name())) {
return List.of();
}
String token = argument.value();
return List.of("Java", "Python", "TypeScript", "Go").stream()
.filter(lang -> lang.toLowerCase().startsWith(token.toLowerCase()))
.toList();
}
Spring AI links this method to the review_code prompt. When the user selects that prompt and starts typing in the language field, this method filters the suggestions.
5. Exposing Data with @McpResource
While tools represent actions, resources represent data. The @McpResource annotation exposes data to the AI in a read-only form, similar to how the AI would read a file. Each resource is assigned a URI.
5.1. URI Templates
We can use URI templates to create dynamic resources. Spring AI extracts variables from the URI template and maps them to method parameters:
@Service
public class SystemLogService {
@McpResource(
uri = "logs://{serviceName}/{date}",
name = "System Logs",
description = "Read logs for a specific service and date"
)
public String readLog(
@McpToolParam(description = "Service Name") String serviceName,
@McpToolParam(description = "Date YYYY-MM-DD") String date
) {
return "Logs for " + serviceName + " on " + date + ": No errors found.";
}
}
When an AI client requests logs://payment-service/2023-12-01, this method is invoked with the extracted values.
5.2. Returning Binary Data
Resources can include more than just text. To serve binary data (like images or PDFs), the method should return a ReadResourceResult. This allows us to set the MIME type explicitly:
@McpResource(uri = "diagrams://{id}", name = "System Architecture Diagram")
public ReadResourceResult getDiagram(String id) {
String base64Image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
return new ReadResourceResult(List.of(
new BlobResourceContents(
"diagrams://" + id,
"image/png",
base64Image
)
));
}
6. Standardizing Prompts with @McpPrompt
The @McpPrompt annotation allows the server to define reusable prompt templates. This is useful for sharing standardized instructions or “best practices” for interacting with the server’s data:
@Service
public class CodeReviewPrompts {
@McpPrompt(
name = "review_code",
description = "Generates a standard code review request"
)
public GetPromptResult generateReviewPrompt(
@McpArg(name = "language", description = "The programming language", required = true) String language,
@McpArg(name = "code", description = "The code snippet", required = true) String code
) {
String template = """
Please review the following %s code.
Focus on thread safety and performance:
%s
""";
String content = String.format(template, language, code);
return new GetPromptResult(
"Code Review",
List.of(new PromptMessage(Role.USER, new TextContent(content)))
);
}
}
Here, the @McpArg annotation works similarly to @McpToolParam, defining the variables the client must provide to populate the template.
7. Connecting to Claude Desktop
The Claude Desktop App acts as an MCP Client. To connect it to our Spring Boot server, we must configure it to launch our JAR file directly.
7.1. Building the Application
Because we are using the Stdio transport, we need a runnable JAR file. We’ll build the application using Maven:
./mvnw clean package
Verify that the JAR file exists in your target/ directory (e.g., target/mcp-demo-0.0.1-SNAPSHOT.jar).
7.2. Configuring Claude Desktop
To configure the MCP connection, we need to open the claude_desktop_config.json file. We can access this file easily through the Claude UI by navigating to Settings > Developer and clicking Edit Config. Alternatively, we can locate the file directly at these paths:
- Windows: %APPDATA%\Claude\claude_desktop_config.json
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Once opened, we need to add our server configuration:
{
"mcpServers": {
"my-spring-calculator": {
"command": "java",
"args": [
"-jar",
"C:\Users\yourname\projects\mcp-demo\target\mcp-demo-0.0.1-SNAPSHOT.jar"
]
}
}
}
If we are on Windows, we must escape our backslashes. For example, if your real path is C:\Users\me\app.jar, you must write it as C:\\Users\\me\\app.jar inside the JSON file.
After saving the file, we must restart the Claude Desktop App completely to pick up the changes. When we jump back in, we look for the Connectors (plug icon) in the input bar, and we should see my-spring-calculator with a green active status.
8. Conclusion
In this article, we discussed the Spring AI MCP annotations that significantly lower the barrier to entry for building agentic AI systems.
By leveraging familiar concepts like @Service and @McpTool, developers can expose existing business logic to AI models with minimal friction. We explored how @McpTool converts methods into AI-callable actions, how @McpResource creates virtual file systems for AI context, and how @McpPrompt standardizes interactions. Furthermore, because these components remain POJOs, they are easily testable using standard Java practices.
















