MCP 客户端注解提供了一种声明式的方式,通过 Java 注解实现 MCP 客户端处理器。这些注解简化了服务端通知和客户端操作的处理逻辑。
所有 MCP 客户端注解必须包含一个 clients 参数,用于将处理器与指定的 MCP 客户端连接关联。该 clients 值必须与应用配置文件中配置的连接名称一致。
客户端注解
@McpLogging
@McpLogging 注解用于处理来自 MCP 服务端的日志消息通知。
基础用法
@Component
public class LoggingHandler {
@McpLogging(clients = "my-mcp-server")
public void handleLoggingMessage(LoggingMessageNotification notification) {
System.out.println("Received log: " + notification.level() +
" - " + notification.data());
}
}独立参数接收方式
@McpLogging(clients = "my-mcp-server")
public void handleLoggingWithParams(LoggingLevel level, String logger, String data) {
System.out.println(String.format("[%s] %s: %s", level, logger, data));
}@McpSampling
@McpSampling 注解用于处理 MCP 服务端发送的大语言模型补全采样请求。
同步实现
@Component
public class SamplingHandler {
@McpSampling(clients = "llm-server")
public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) {
// 处理请求并生成响应
String response = generateLLMResponse(request);
return CreateMessageResult.builder()
.role(Role.ASSISTANT)
.content(new TextContent(response))
.model("gpt-4")
.build();
}
}异步实现
@Component
public class AsyncSamplingHandler {
@McpSampling(clients = "llm-server")
public MonohandleAsyncSampling(CreateMessageRequest request) {
return Mono.fromCallable(() -> {
String response = generateLLMResponse(request);
return CreateMessageResult.builder()
.role(Role.ASSISTANT)
.content(new TextContent(response))
.model("gpt-4")
.build();
}).subscribeOn(Schedulers.boundedElastic());
}
}@McpElicitation
@McpElicitation 注解用于处理信息诱导请求,以从用户处收集额外信息。
基础用法
@Component
public class ElicitationHandler {
@McpElicitation(clients = "interactive-server")
public ElicitResult handleElicitationRequest(ElicitRequest request) {
// 向用户展示请求并收集输入信息
MapuserData = presentFormToUser(request.requestedSchema());
if (userData != null) {
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
} else {
return new ElicitResult(ElicitResult.Action.DECLINE, null);
}
}
}用户交互实现
@McpElicitation(clients = "interactive-server")
public ElicitResult handleInteractiveElicitation(ElicitRequest request) {
Mapschema = request.requestedSchema();
MapuserData = new HashMap<>();
// 检查需要请求的信息
if (schema != null && schema.containsKey("properties")) {
Mapproperties = (Map) schema.get("properties");
// 根据模式收集用户输入
if (properties.containsKey("name")) {
userData.put("name", promptUser("请输入你的姓名:"));
}
if (properties.containsKey("email")) {
userData.put("email", promptUser("请输入你的邮箱:"));
}
if (properties.containsKey("preferences")) {
userData.put("preferences", gatherPreferences());
}
}
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
}异步信息诱导
@McpElicitation(clients = "interactive-server")
public MonohandleAsyncElicitation(ElicitRequest request) {
return Mono.fromCallable(() -> {
// 异步用户交互
MapuserData = asyncGatherUserInput(request);
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
}).timeout(Duration.ofSeconds(30))
.onErrorReturn(new ElicitResult(ElicitResult.Action.CANCEL, null));
}@McpProgress
@McpProgress 注解用于处理长时间运行操作的进度通知。
基础用法
@Component
public class ProgressHandler {
@McpProgress(clients = "my-mcp-server")
public void handleProgressNotification(ProgressNotification notification) {
double percentage = notification.progress() * 100;
System.out.println(String.format("进度:%.2f%% - %s",
percentage, notification.message()));
}
}独立参数接收方式
@McpProgress(clients = "my-mcp-server")
public void handleProgressWithDetails(
String progressToken,
double progress,
Double total,
String message) {
if (total != null) {
System.out.println(String.format("[%s] %.0f/%.0f - %s",
progressToken, progress, total, message));
} else {
System.out.println(String.format("[%s] %.2f%% - %s",
progressToken, progress * 100, message));
}
// 更新界面进度条
updateProgressBar(progressToken, progress);
}客户端专属进度处理
@McpProgress(clients = "long-running-server")
public void handleLongRunningProgress(ProgressNotification notification) {
// 跟踪指定服务端的进度
progressTracker.update("long-running-server", notification);
// 按需发送通知
if (notification.progress() >= 1.0) {
notifyCompletion(notification.progressToken());
}
}@McpToolListChanged
@McpToolListChanged 注解用于处理服务端工具列表发生变更时的通知。
基础用法
@Component
public class ToolListChangedHandler {
@McpToolListChanged(clients = "tool-server")
public void handleToolListChanged(ListupdatedTools) {
System.out.println("工具列表已更新:可用工具数量 " + updatedTools.size());
// 更新本地工具注册表
toolRegistry.updateTools(updatedTools);
// 记录新工具
for (McpSchema.Tool tool : updatedTools) {
System.out.println(" - " + tool.name() + ": " + tool.description());
}
}
}异步处理
@McpToolListChanged(clients = "tool-server")
public MonohandleAsyncToolListChanged(ListupdatedTools) {
return Mono.fromRunnable(() -> {
// 异步处理工具列表更新
processToolListUpdate(updatedTools);
// 通知相关组件
eventBus.publish(new ToolListUpdatedEvent(updatedTools));
}).then();
}客户端专属工具更新
@McpToolListChanged(clients = "dynamic-server")
public void handleDynamicServerToolUpdate(ListupdatedTools) {
// 处理来自频繁变更工具的指定服务端的工具
dynamicToolManager.updateServerTools("dynamic-server", updatedTools);
// 重新评估工具可用性
reevaluateToolCapabilities();
}@McpResourceListChanged
@McpResourceListChanged 注解用于处理服务端资源列表发生变更时的通知。
基础用法
@Component
public class ResourceListChangedHandler {
@McpResourceListChanged(clients = "resource-server")
public void handleResourceListChanged(ListupdatedResources) {
System.out.println("资源已更新:数量 " + updatedResources.size());
// 更新资源缓存
resourceCache.clear();
for (McpSchema.Resource resource : updatedResources) {
resourceCache.register(resource);
}
}
}资源变更分析
@McpResourceListChanged(clients = "resource-server")
public void analyzeResourceChanges(ListupdatedResources) {
// 分析变更内容
SetnewUris = updatedResources.stream()
.map(McpSchema.Resource::uri)
.collect(Collectors.toSet());
SetremovedUris = previousUris.stream()
.filter(uri -> !newUris.contains(uri))
.collect(Collectors.toSet());
if (!removedUris.isEmpty()) {
handleRemovedResources(removedUris);
}
// 更新跟踪记录
previousUris = newUris;
}@McpPromptListChanged
@McpPromptListChanged 注解用于处理服务端提示词列表发生变更时的通知。
基础用法
@Component
public class PromptListChangedHandler {
@McpPromptListChanged(clients = "prompt-server")
public void handlePromptListChanged(ListupdatedPrompts) {
System.out.println("提示词已更新:数量 " + updatedPrompts.size());
// 更新提示词目录
promptCatalog.updatePrompts(updatedPrompts);
// 按需刷新界面
if (uiController != null) {
uiController.refreshPromptList(updatedPrompts);
}
}
}异步处理
@McpPromptListChanged(clients = "prompt-server")
public MonohandleAsyncPromptUpdate(ListupdatedPrompts) {
return Flux.fromIterable(updatedPrompts)
.flatMap(prompt -> validatePrompt(prompt))
.collectList()
.doOnNext(validPrompts -> {
promptRepository.saveAll(validPrompts);
})
.then();
}Spring Boot 集成
通过 Spring Boot 自动配置,客户端处理器会被自动检测并注册:
@SpringBootApplication
public class McpClientApplication {
public static void main(String[] args) {
SpringApplication.run(McpClientApplication.class, args);
}
}
@Component
public class MyClientHandlers {
@McpLogging(clients = "my-server")
public void handleLogs(LoggingMessageNotification notification) {
// 处理日志
}
@McpSampling(clients = "my-server")
public CreateMessageResult handleSampling(CreateMessageRequest request) {
// 处理采样
}
@McpProgress(clients = "my-server")
public void handleProgress(ProgressNotification notification) {
// 处理进度
}
}自动配置会执行以下操作:
扫描带有 MCP 客户端注解的 Bean
创建对应的规范定义
将其注册到 MCP 客户端
支持同步和异步两种实现方式
通过客户端专属处理器支持多客户端场景
配置属性
配置客户端注解扫描器和客户端连接:
spring: ai: mcp: client: type: SYNC # 或 ASYNC annotation-scanner: enabled: true # 配置客户端连接 - 连接名称会作为注解中clients的值 sse: connections: my-server: # 该名称作为注解的clients值 url: http://localhost:8080 tool-server: # 另一个clients值 url: http://localhost:8081 stdio: connections: local-server: # 该名称作为注解的clients值 command: /path/to/mcp-server args: - --mode=production
注解中的 clients 参数必须与配置中定义的连接名称匹配。在上述示例中,合法的 clients 值为:"my-server"、"tool-server" 和 "local-server"。
MCP 客户端使用方式
带注解的处理器会自动与 MCP 客户端集成:
@Autowired private ListmcpClients; // 客户端会根据clients配置自动使用你定义的注解处理器 // 无需手动注册 - 处理器会通过名称与客户端匹配关联
对于每个 MCP 客户端连接,带有匹配 clients 值的处理器会被自动注册,并在对应事件触发时自动调用。