使用说明
Spring AI MCP注释示例
本目录包含使用Spring AI基于注释的方法演示模型上下文协议(MCP)的示例。这些示例展示了MCP的功能,包括工具、资源、提示、补全和使用声明性注释的客户端处理程序。
概述
MCP注释示例演示了:
- 注释驱动开发:使用简化的MCP开发
@McpTool,@McpResource,@McpPrompt,@McpComplete,以及客户端注释 - 声明性配置:通过注释自动注册和配置
- 客户端-服务器通信:全双向MCP通信模式
- 多种MCP功能:工具、资源、提示、补全和客户端处理程序
- 混合注释样式:MCP注释和Spring AI
@Tool注释
项目
1.mcp注释服务器
一个全面的MCP服务器实现,使用注释展示了所有主要的MCP功能。
工具
- 天气工具:来自Open-Meteo API的温度数据,使用
@McpTool - 其他工具:Weather.gov API使用Spring AI发布的天气预报和警报
@Tool
资源
- 用户档案:具有多种访问模式的完整用户信息
- 用户属性:使用URI变量进行单个属性访问
- 用户状态:具有不同返回类型的动态状态信息
- 用户连接:社会联系和关系
- 用户通知:动态通知生成
- 用户头像:使用自定义MIME类型的二进制数据处理
- 用户位置:简单的位置信息
提示
- 问候提示:简单的参数化问候语
- 个性化消息:具有多个参数和逻辑的复杂提示
- 对话启动:多消息对话流
- 动态内容:基于映射的参数处理
- 单一消息:单个消息响应
- 字符串列表:基于列表的响应
补全
- 用户名填写:自动完成用户状态URI
- 名称完成:自动完成个性化消息提示
- 国家完成:填写与旅行有关的国家名称
2.mcp注释客户端
一个简单的MCP客户端实现,使用注释演示客户端MCP功能。
处理程序类型
- 进度处理程序:跟踪长时间运行的操作
@McpProgress - 日志处理程序:MCP服务器的集中日志记录
@McpLogging - 采样处理器:LLM请求授权
@McpSampling - 激励处理人员:用户交互提示
@McpElicitation
架构概述
服务器架构
@SpringBootApplication
public class McpServerApplication {
// Traditional Spring AI tool integration
@Bean
public ToolCallbackProvider weatherTools(SpringAiToolProvider weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
}
服务器使用自动注释扫描来注册MCP提供者。全部 @McpTool, @McpResource, @McpPrompt,以及 @McpComplete 带注释的方法会被自动发现和注册。
客户端体系结构
@SpringBootApplication
public class McpClientApplication {
@Bean
public CommandLineRunner predefinedQuestions(List<McpSyncClient> mcpClients) {
return args -> {
for (McpSyncClient mcpClient : mcpClients) {
// Call tools and interact with MCP servers
CallToolRequest toolRequest = CallToolRequest.builder()
.name("tool1")
.arguments(Map.of("input", "test input"))
.progressToken("test-progress-token")
.build();
CallToolResult response = mcpClient.callTool(toolRequest);
}
};
}
}
客户端处理程序通过注释扫描自动注册。
关键实施示例
工具实施
@Service
public class ToolProvider {
@McpTool(description = "Get the temperature (in celsius) for a specific location")
public WeatherResponse getTemperature(
@McpToolParam(description = "The location latitude") double latitude,
@McpToolParam(description = "The location longitude") double longitude,
@McpToolParam(description = "The city name") String city) {
return restClient.get()
.uri("https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m",
latitude, longitude)
.retrieve()
.body(WeatherResponse.class);
}
}
资源实施
@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) {
String profileInfo = formatProfileInfo(userProfiles.getOrDefault(username.toLowerCase(), new HashMap<>()));
return new ReadResourceResult(List.of(new TextResourceContents(request.uri(), "text/plain", profileInfo)));
}
@McpResource(uri = "user-attribute://{username}/{attribute}",
name = "User Attribute",
description = "Provides a specific attribute from a user's profile")
public ReadResourceResult getUserAttribute(String username, String attribute) {
Map<String, String> profile = userProfiles.getOrDefault(username.toLowerCase(), new HashMap<>());
String attributeValue = profile.getOrDefault(attribute, "Attribute not found");
return new ReadResourceResult(
List.of(new TextResourceContents("user-attribute://" + username + "/" + attribute,
"text/plain", username + "'s " + attribute + ": " + attributeValue)));
}
}
迅速实施
@Service
public class PromptProvider {
@McpPrompt(name = "personalized-message",
description = "Generates a personalized message based on user information")
public GetPromptResult personalizedMessage(
McpSyncServerExchange exchange,
@McpArg(name = "name", description = "The user's name", required = true) String name,
@McpArg(name = "age", description = "The user's age", required = false) Integer age,
@McpArg(name = "interests", description = "The user's interests", required = false) String interests) {
// Log the prompt generation
exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.INFO)
.data("personalized-message event").build());
StringBuilder message = new StringBuilder();
message.append("Hello, ").append(name).append("!\n\n");
if (age != null) {
message.append("At ").append(age).append(" years old, you have ");
if (age < 30) {
message.append("so much ahead of you.\n\n");
} else if (age < 60) {
message.append("gained valuable life experience.\n\n");
} else {
message.append("accumulated wisdom to share with others.\n\n");
}
}
return new GetPromptResult("Personalized Message",
List.of(new PromptMessage(Role.ASSISTANT, new TextContent(message.toString()))));
}
}
竣工实施
@Service
public class CompletionProvider {
@McpComplete(uri = "user-status://{username}")
public List<String> completeUsername(String usernamePrefix) {
String prefix = usernamePrefix.toLowerCase();
if (prefix.isEmpty()) {
return List.of("Enter a username");
}
String firstLetter = prefix.substring(0, 1);
List<String> usernames = usernameDatabase.getOrDefault(firstLetter, List.of());
return usernames.stream()
.filter(username -> username.toLowerCase().startsWith(prefix))
.toList();
}
@McpComplete(prompt = "personalized-message")
public CompleteResult completeCountryName(CompleteRequest request) {
String prefix = request.argument().value().toLowerCase();
String firstLetter = prefix.substring(0, 1);
List<String> countries = countryDatabase.getOrDefault(firstLetter, List.of());
List<String> matches = countries.stream()
.filter(country -> country.toLowerCase().startsWith(prefix))
.toList();
return new CompleteResult(new CompleteCompletion(matches, matches.size(), false));
}
}
客户端处理程序实现
@Service
public class McpClientHandlerProviders {
@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) {
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()));
}
}
关键依赖关系
服务器依赖关系
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
客户端依赖关系
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
运行示例
先决条件
- Java 17或更高版本
- Maven 3.6+
步骤1:启动MCP注释服务器
cd mcp-annotations-server
./mvnw clean package -DskipTests
java -jar target/mcp-annotations-server-0.0.1-SNAPSHOT.jar
服务器将于启动 http://localhost:8080 启用SSE传输。
步骤2:运行MCP注释客户端
cd mcp-annotations-client
./mvnw clean package
java -jar target/mcp-annotations-client-0.0.1-SNAPSHOT.jar
可用特征
工具
getTemperature:使用Open-Meteo API获取温度数据
资源
user-profile://{username}:完整的用户资料信息user-attribute://{username}/{attribute}:特定用户属性user-status://{username}:用户在线状态user-connections://{username}:用户社交关系user-notifications://{username}:用户通知user-location://{username}:用户位置信息user-avatar://{username}:用户头像图像(base64)
提示
greeting:带名称参数的简单问候语personalized-message:包含年龄和兴趣的复杂个性化信息conversation-starter:多消息对话流map-arguments:演示基于映射的参数处理single-message:单个消息响应string-list:基于列表的响应
补全
- 用户名完成
user-status://URI - 姓名填写
personalized-message提示 - 旅行提示的国家名称填写
配置
服务器配置(application.properties)
# Server identification
spring.ai.mcp.server.name=my-weather-server
spring.ai.mcp.server.version=0.0.1
# Disable banner for STDIO transport compatibility
spring.main.banner-mode=off
# Logging configuration
logging.file.name=./model-context-protocol/mcp-annotations/mcp-annotations-server/target/mcp-annotations-server.log
# Uncomment for STDIO transport
# spring.ai.mcp.server.stdio=true
# spring.main.web-application-type=none
# Uncomment for different protocols
# spring.ai.mcp.server.protocol=STREAMABLE
# spring.ai.mcp.server.protocol=STATELESS
客户端配置(application.properties)
spring.application.name=mcp
spring.main.web-application-type=none
# MCP client connection
spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8080
# Logging configuration
logging.level.io.modelcontextprotocol.client=WARN
logging.level.io.modelcontextprotocol.spec=WARN
测试服务器
您可以使用附带的测试客户端测试服务器:
SSE客户端测试
// Located in src/test/java/.../client/ClientSse.java
var transport = HttpClientSseClientTransport.builder("http://localhost:8080").build();
var client = McpClient.sync(transport).build();
STDIO客户端测试
// Located in src/test/java/.../client/ClientStdio.java
var stdioParams = ServerParameters.builder("java")
.args("-jar", "target/mcp-annotations-server-0.0.1-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);
var client = McpClient.sync(transport).build();
高级功能
混合注释样式
服务器演示了MCP特定的注释(@McpTool)Spring AI注释(@Tool),展示了它们如何在同一应用程序中共存。
URI模板变量
资源支持具有多个变量的复杂URI模板,这些变量会自动提取并作为方法参数传递。
退货类型灵活性
方法可以返回各种类型:
ReadResourceResult完全控制List<ResourceContents>用于多个内容项ResourceContents对于单个内容项String用于简单的文本响应List<String>用于多个文本项
自定义MIME类型
资源可以为不同的内容类型指定自定义MIME类型,包括二进制数据处理。
服务器交换集成
方法可以访问 McpSyncServerExchange 用于高级服务器操作,如记录通知和进度跟踪。
样品数据
服务器包括用于测试的示例用户配置文件:
- 约翰约翰·史密斯(纽约,32岁)
- 简:无名氏(伦敦,28岁)
- 鲍勃鲍勃·约翰逊(东京,45岁)
- 爱丽丝:爱丽丝·布朗(悉尼,36岁)