This sample project demonstrates how to create an MCP server using Spring AI's MCP annotations. It showcases a comprehensive implementation of MCP server capabilities including tools, resources, prompts, and completions using a clean, declarative approach with Java annotations.
For more information, see the MCP Server Boot Starter reference documentation.
- Overview
- Features
- Dependencies
- Building the Project
- Running the Server
- Configuration
- Server Implementation
- MCP Capabilities
- MCP Clients
- Additional Resources
The sample showcases a comprehensive MCP server implementation with:
- Integration with
spring-ai-starter-mcp-server-webmvc - Support for both SSE (Server-Sent Events) and STDIO transports
- Automatic registration of MCP capabilities using annotations:
@Toolfor Spring AI tool registration@McpToolfor MCP-specific tool registration@McpResourcefor resource registration@McpPromptfor prompt registration@McpCompletefor completion registration
- Comprehensive examples of each capability type
This sample demonstrates:
- Weather Tools - Tools for retrieving weather forecasts and alerts using both Spring AI
@Tooland MCP@McpToolannotations - User Profile Resources - Resources for accessing user profile information with various URI patterns
- Prompt Generation - Various prompt templates for different use cases
- Auto-completion - Completion suggestions for usernames and countries
The project requires the Spring AI MCP Server WebMVC Boot Starter and MCP Annotations:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>These dependencies provide:
- HTTP-based transport using Spring MVC (
WebMvcSseServerTransport) - Auto-configured SSE, Streamable-HTTP or Stateless endpoints, configured by the
spring.ai.mcp.server.protocol=...property and defaulting toSSE. - Optional STDIO transport, if the
spring.ai.mcp.server.stdio=trueis set - Annotation-based method handling for MCP operations
Build the project using Maven:
./mvnw clean install -DskipTestsThe server supports two transport modes:
Mode depends on the spring.ai.mcp.server.protocol=... setting.
java -Dspring.ai.mcp.server.protocol=STREAMABLE -jar target/mcp-annotations-server-0.0.1-SNAPSHOT.jarTo enable STDIO transport, set the appropriate properties:
java -Dspring.ai.mcp.server.stdio=true -Dspring.main.web-application-type=none -jar target/mcp-annotations-server-0.0.1-SNAPSHOT.jarConfigure the server through application.properties:
# Server identification
spring.ai.mcp.server.name=my-weather-server
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.protocol=STREAMABLE
# spring.ai.mcp.server.protocol=STATELESS
# Transport configuration (uncomment to enable STDIO)
# spring.ai.mcp.server.stdio=true
# spring.main.web-application-type=none
# Logging (required for STDIO transport)
spring.main.banner-mode=off
# logging.pattern.console=
# Log file location
logging.file.name=./mcp-annotations-server/target/server.logThe server uses Spring Boot and MCP annotations for automatic registration of capabilities:
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
// (Optional) not MCP annotation. Just demostrates how to use the @Tool along with the @McpTool to provision MCP Server Tools.
@Bean
public ToolCallbackProvider weatherTools(SpringAiToolProvider weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
}The project includes two different tool providers demonstrating different annotation approaches:
Uses Spring AI's @Tool annotation for weather-related tools:
@Service
public class SpringAiToolProvider {
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(double latitude, double longitude) {
// Implementation using weather.gov API
}
@Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g., CA, NY)")
public String getAlerts(String state) {
// Implementation using weather.gov API
}
}Uses MCP-specific @McpTool annotation for temperature retrieval:
@Service
public class McpToolProvider {
@McpTool(description = "Get the temperature (in celsius) for a specific location")
public WeatherResponse getTemperature(McpSyncServerExchange exchange,
@McpProgressToken String progressToken
@McpToolParam(description = "The location latitude") double latitude,
@McpToolParam(description = "The location longitude") double longitude,
@McpToolParam(description = "The city name") String city) {
// Implementation using open-meteo.com API
}
}- Weather Forecast Tool (Spring AI)
- Name:
getWeatherForecastByLocation - Description: Get weather forecast for a specific latitude/longitude
- Parameters:
latitude: double - Latitude coordinatelongitude: double - Longitude coordinate
- Weather Alerts Tool (Spring AI)
- Name:
getAlerts - Description: Get weather alerts for a US state
- Parameters:
state: String - Two-letter US state code (e.g., CA, NY)
- Temperature Tool (MCP)
- Name:
getTemperature - Description: Get the temperature (in celsius) for a specific location
- Parameters:
latitude: double - The location latitudelongitude: double - The location longitudecity: String - The city name
The UserProfileResourceProvider implements resource access using the @McpResource annotation with comprehensive examples:
@Service
public class UserProfileResourceProvider {
@McpResource(uri = "user-profile://{username}",
name = "User Profile",
description = "Provides user profile information for a specific user")
public ReadResourceResult getUserProfile(ReadResourceRequest request, String username) {
// Implementation to retrieve user profile
}
// Additional resource methods...
}- User Profile
- URI:
user-profile://{username} - Description: Provides user profile information for a specific user
- User Details
- URI:
user-profile://{username} - Description: Provides user details using URI variables
- User Attribute
- URI:
user-attribute://{username}/{attribute} - Description: Provides a specific attribute from a user's profile
- User Profile with Exchange
- URI:
user-profile-exchange://{username} - Description: Provides user profile with server exchange context
- User Connections
- URI:
user-connections://{username} - Description: Provides a list of connections for a user
- User Notifications
- URI:
user-notifications://{username} - Description: Provides notifications for a user
- User Status
- URI:
user-status://{username} - Description: Provides the current status for a user
- User Location
- URI:
user-location://{username} - Description: Provides the current location for a user
- User Avatar
- URI:
user-avatar://{username} - Description: Provides a base64-encoded avatar image for a user
- MIME Type:
image/png
The provider includes sample data for users: john, jane, bob, and alice with profiles containing name, email, age, and location information.
The PromptProvider implements prompt generation using the @McpPrompt annotation:
@Service
public class PromptProvider {
@McpPrompt(name = "greeting", description = "A simple greeting prompt")
public GetPromptResult greetingPrompt(
@McpArg(name = "name", description = "The name to greet", required = true) String name) {
// Implementation to generate greeting prompt
}
// Additional prompt methods...
}- Greeting
- Name:
greeting - Description: A simple greeting prompt
- Parameters:
name: String - The name to greet (required)
- Personalized Message
- Name:
personalized-message - Description: Generates a personalized message based on user information
- Parameters:
name: String - The user's name (required)age: Integer - The user's age (optional)interests: String - The user's interests (optional)
- Conversation Starter
- Name:
conversation-starter - Description: Provides a conversation starter with the system
- Map Arguments
- Name:
map-arguments - Description: Demonstrates using a map for arguments
- Single Message
- Name:
single-message - Description: Demonstrates returning a single PromptMessage
- Parameters:
name: String - The user's name (required)
- String List
- Name:
string-list - Description: Demonstrates returning a list of strings
- Parameters:
topic: String - The topic to provide information about (required)
The AutocompleteProvider implements auto-completion using the @McpComplete annotation:
@Service
public class AutocompleteProvider {
@McpComplete(uri = "user-status://{username}")
public List<String> completeUsername(String usernamePrefix) {
// Implementation to provide username completions
}
// Additional completion methods...
}- Username Completion
- URI:
user-status://{username} - Provides completion suggestions for usernames based on prefix
- Name Completion
- Prompt:
personalized-message - Provides completion suggestions for names in personalized message prompts
- Country Name Completion
- Prompt:
travel-planner - Provides completion suggestions for country names
- Returns:
CompleteResultwith completion details
You can connect to the server using either STDIO or SSE transport:
For a better development experience, consider using the MCP Client Boot Starters and related MCP Client Annotations support.
The MCP Client Boot Starter provides:
- Auto-configuration for MCP client connections
- Declarative annotation-based handlers for server notifications
- Support for multiple transport protocols (SSE, Streamable-HTTP, STDIO)
- Automatic registration of client handlers
Add the MCP Client Boot Starter to your project:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>The client supports several annotations for handling server notifications:
@McpLogging- Handle logging message notifications from MCP servers@McpProgress- Handle progress notifications for long-running operations@McpSampling- Handle sampling requests from MCP servers for LLM completions@McpElicitation- Handle elicitation requests to gather additional information from users@McpToolListChanged- Handle notifications when the server's tool list changes@McpResourceListChanged- Handle notifications when the server's resource list changes@McpPromptListChanged- Handle notifications when the server's prompt list changes
Important: All MCP client annotations MUST include a clients parameter to associate the handler with a specific MCP client connection.
@SpringBootApplication
public class McpClientApplication {
public static void main(String[] args) {
SpringApplication.run(McpClientApplication.class, args).close();
}
@Bean
public CommandLineRunner predefinedQuestions(List<McpSyncClient> mcpClients) {
return args -> {
for (McpSyncClient mcpClient : mcpClients) {
System.out.println(">>> MCP Client: " + mcpClient.getClientInfo());
// Call a tool that sends progress notifications
CallToolRequest toolRequest = CallToolRequest.builder()
.name("tool1")
.arguments(Map.of("input", "test input"))
.progressToken("test-progress-token")
.build();
CallToolResult response = mcpClient.callTool(toolRequest);
System.out.println("Tool response: " + response);
}
};
}
}@Service
public class McpClientHandlerProviders {
private static final Logger logger = LoggerFactory.getLogger(McpClientHandlerProviders.class);
@McpProgress(clients = "server1")
public void progressHandler(ProgressNotification progressNotification) {
logger.info("MCP PROGRESS: [{}] progress: {} total: {} message: {}",
progressNotification.progressToken(), progressNotification.progress(),
progressNotification.total(), progressNotification.message());
}
@McpLogging(clients = "server1")
public void loggingHandler(LoggingMessageNotification loggingMessage) {
logger.info("MCP LOGGING: [{}] {}", loggingMessage.level(), loggingMessage.data());
}
@McpSampling(clients = "server1")
public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
logger.info("MCP SAMPLING: {}", llmRequest);
String userPrompt = ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text();
String modelHint = llmRequest.modelPreferences().hints().get(0).name();
return CreateMessageResult.builder()
.content(new McpSchema.TextContent("Response " + userPrompt + " with model hint " + modelHint))
.build();
}
@McpElicitation(clients = "server1")
public ElicitResult elicitationHandler(McpSchema.ElicitRequest request) {
logger.info("MCP ELICITATION: {}", request);
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("message", request.message()));
}
}Configure client connections in application.properties:
spring.application.name=mcp
spring.main.web-application-type=none
# Streamable-HTTP transport configuration
spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:8080
# SSE transport configuration (alternative)
# spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8080
# Global client settings
spring.ai.mcp.client.request-timeout=5m
# Logging configuration
logging.level.io.modelcontextprotocol.client=WARN
logging.level.io.modelcontextprotocol.spec=WARN
# Optional: Disable tool callback if not needed
# spring.ai.mcp.client.toolcallback.enabled=false- Start the MCP annotations server:
java -Dspring.ai.mcp.server.protocol=STREAMABLE -jar mcp-annotations-server-0.0.1-SNAPSHOT.jar- In another console, start the client configured with Streamable-HTTP transport:
java -Dspring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:8080 \
-jar mcp-annotations-client-0.0.1-SNAPSHOT.jar- Start the MCP annotations server with SSE protocol:
java -Dspring.ai.mcp.server.protocol=SSE -jar mcp-annotations-server-0.0.1-SNAPSHOT.jar- Start the client configured with SSE transport:
java -Dspring.ai.mcp.client.sse.connections.server1.url=http://localhost:8080 \
-jar mcp-annotations-client-0.0.1-SNAPSHOT.jarNOTE: the clients="server1" parameter of the @McpLogging, @McpSampling, @McpProgress and @McpElicitate annotations corresponds to the conneciton name you put in your spring.ai.mcp.client.streamable-http.connections.server1.url= or spring.ai.mcp.client.sse.connections.server1.url= configuraitons.
- Create a
mcp-servers-config.jsonconfiguration file:
{
"mcpServers": {
"annotations-server": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"/absolute/path/to/mcp-annotations-server-0.0.1-SNAPSHOT.jar"
]
}
}
}- Run the client using the configuration file:
java -Dspring.ai.mcp.client.stdio.servers-configuration=file:mcp-servers-config.json \
-Dlogging.pattern.console= \
-jar mcp-annotations-client-0.0.1-SNAPSHOT.jarThe client will automatically:
- Connect to the configured MCP servers
- Register annotated handlers based on the
clientsparameter - Handle server notifications and requests through the annotated methods
- Provide logging and progress updates as configured
- Spring AI Documentation
- MCP Server Boot Starter
- MCP Client Boot Starter
- Model Context Protocol Specification
- MCP Annotations Project
- Spring Boot Auto-configuration
{
"mcpServers": {
"annotations-server": {
"url": "http://localhost:8080/sse"
}
}
}
{ "mcpServers": { "annotations-server": { "command": "java", "args": [ "-Dspring.ai.mcp.server.stdio=true", "-Dspring.main.web-application-type=none", "-Dlogging.pattern.console=", "-jar", "/Users/karel/CursorProjects/mcp-annotations-server/target/mcp-annotations-server-0.0.1-SNAPSHOT.jar" ] } } }