MCP 安全与注解
本教程共 45 篇 · 第 31 篇 · 更新于 2026-08-16 · 约 8 分钟阅读
本节目标:了解 MCP 安全现状(官方标注 WIP),掌握服务端/客户端注解和特殊参数,最后认识动态工具发现。学完你能用注解快速开发 MCP 能力,并对安全方案有正确预期。
31.1 安全:先说结论
官方文档把 MCP Security 明确标注为 WIP(Work In Progress)。意思是文档和 API 后续可能变,生产落地前务必查最新状态。
它还不是 Spring AI 主项目的一部分,而是社区项目 spring-ai-community/mcp-security,官方尚未正式背书。依赖坐标是 org.springaicommunity 组,比如 mcp-server-security、mcp-client-security。
模块分三块:MCP Server Security(服务端)、MCP Client Security(客户端)、MCP Authorization Server(授权服务器)。下面分别介绍。
31.2 服务端安全
服务端安全支持两种方式:OAuth2 资源服务器认证,和 API Key 认证。
限制要先说清楚:只兼容 WebMVC 服务器,WebFlux 不支持;SSE 传输不支持,要用 Streamable-HTTP 或 Stateless;token 只支持 JWT,不透明 token 不支持。
OAuth2 配置用 Spring Security 标准 API,加上框架提供的 McpServerOAuth2Configurer:
@Configuration
@EnableWebSecurity
class McpServerConfiguration {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.with(McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> mcpAuthorization.authorizationServer(issuerUrl))
.build();
}
}
这段配置让每个请求都要带有效 token。如果只想保护工具调用,可以放行 /mcp 路径,再用 @PreAuthorize(“isAuthenticated()”) 标注工具方法。方法里还能用 SecurityContextHolder 拿当前用户。
API Key 认证要自己实现 ApiKeyEntityRepository 存 key。框架附带的 InMemoryApiKeyEntityRepository 用 bcrypt 存 key,计算开销大,只适合演示,生产环境必须自己实现。
31.3 客户端安全
客户端安全模块只支持 McpSyncClient,提供三种 OAuth2 流程。
Authorization Code(授权码)适合每个请求都在用户上下文里发生。Client Credentials(客户端凭证)适合机器对机器,没有人在场。Hybrid(混合)两者结合:启动时的工具发现用客户端凭证,工具调用用授权码。用 Spring Boot 属性配置客户端时推荐 Hybrid,因为工具发现发生在启动阶段,没有用户在场。
HttpClient 版客户端配置一个定制器 Bean 加对应的请求定制器:
@Bean
McpCustomizer<McpClient.SyncSpec> syncClientCustomizer() {
return (name, syncSpec) ->
syncSpec.transportContextProvider(new AuthenticationMcpTransportContextProvider());
}
@Bean
McpSyncHttpClientRequestCustomizer requestCustomizer(OAuth2AuthorizedClientManager clientManager) {
return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(clientManager, "authserver");
}
三种流程对应三个定制器类,WebClient 版则对应三个 ExchangeFilterFunction。
还有个已知问题:Spring AI 自动配置会在应用启动时就初始化客户端,用户级授权流程会因此出问题。官方文档给了两种绕法:发布一个空的 ToolCallbackResolver Bean,或者改为编程式配置客户端。这块内容复杂,真要接安全,直接读官方文档原文。
31.4 注解总览
MCP Annotations 模块用注解声明式地开发 MCP 能力,替代手写大量规范对象。所有 MCP Boot Starter 都自带 spring-ai-mcp-annotations,不需要额外依赖。注解扫描默认开启,用 spring.ai.mcp.server.annotation-scanner.enabled 或 client 对应属性控制。
服务端注解:@McpTool、@McpResource、@McpPrompt、@McpComplete。 客户端注解:@McpLogging、@McpSampling、@McpElicitation、@McpProgress、@McpToolListChanged、@McpResourceListChanged、@McpPromptListChanged。
31.5 服务端注解
@McpTool 把方法声明成工具,参数自动生成 JSON Schema:
@Component
public class CalculatorTools {
@McpTool(name = "add", description = "Add two numbers together")
public int add(
@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b) {
return a + b;
}
}
name 不写就用方法名。description 要写清楚,模型靠它决定何时调用。注解还支持 title、generateOutputSchema、annotations 等属性。其中 annotations 提供 readOnlyHint、destructiveHint、idempotentHint 这类客户端提示,模型和 UI 会参考它们判断调用风险。
@McpResource 用 URI 模板暴露资源,模板变量直接变成方法参数:
@McpResource(uri = "config://{key}", name = "Configuration")
public String getConfig(String key) {
return configData.get(key);
}
@McpPrompt 提供提示词模板,返回 GetPromptResult,参数用 @McpArg 标注。@McpComplete 给提示词参数或资源 URI 提供自动补全,用 prompt 或 uri 属性指定目标,两者不能同时用。
想给工具声明附加元数据,用 metaProvider 属性。实现 MetaProvider 接口返回一个 Map,内容会写进声明的 _meta 字段,客户端能看到。不指定时,框架使用默认的 DefaultMetaProvider。
31.6 客户端注解
客户端注解处理服务器发来的通知和请求。关键约束:所有客户端注解必须带 clients 参数,值要匹配配置里的连接名。
@Component
public class McpClientHandlers {
@McpLogging(clients = "weather-server")
public void handleLoggingMessage(LoggingMessageNotification notification) {
System.out.println("Received log: " + notification.level() + " - " + notification.data());
}
@McpProgress(clients = "weather-server")
public void handleProgressNotification(ProgressNotification notification) {
System.out.println("Progress: " + notification.progress());
}
}
@McpLogging 收日志,@McpProgress 收进度,@McpToolListChanged、@McpResourceListChanged、@McpPromptListChanged 收三类列表变更通知。@McpSampling 处理服务器的采样请求:服务器没有 LLM API Key,借客户端的模型能力生成内容。@McpElicitation 处理服务器向用户追问信息的请求。都支持同步和 Mono 异步两种写法。
方法参数可以写整个通知对象,也可以拆成单个参数。比如 @McpProgress 的方法参数写 progressToken、progress、total、message,框架会逐个注入。拆开写适合只需要其中几个字段的场景。
31.7 特殊参数
注解方法还能注入框架自动填充的特殊参数。它们不参与 JSON Schema 生成,客户端看不到。
McpMeta 提供请求的 _meta 元数据,比如用户 ID、角色,用 get(key) 取值。@McpProgressToken 接收进度令牌,用于长任务进度上报。没有令牌时注入 null,记得判空。
McpSyncRequestContext / McpAsyncRequestContext 是推荐用的统一上下文。能取原始请求、会话 ID、客户端信息,还能发日志、进度、ping。检测到能力可用时,可以做 sampling、elicitation、roots 访问。
McpTransportContext 是轻量上下文,给 Stateless 服务器用,只能碰传输层信息。CallToolRequest 是完整工具请求,适合参数不确定的动态工具。
@McpTool(name = "context-tool", description = "Tool with context")
public String contextTool(
McpSyncRequestContext context,
@McpToolParam(description = "Input", required = true) String input) {
context.info("Processing: " + input);
context.progress(50);
return "Processed: " + input;
}
Note框架按服务器类型过滤方法:SYNC 服务器只注册同步方法,ASYNC 只注册响应式方法;Stateless 服务器会跳过(忽略)带 McpSyncRequestContext 的方法。写代码时让方法风格和服务器类型保持一致。
过滤规则可以汇总成一张表:
| 服务器类型 | 接受的方法 | 被过滤的方法 |
|---|---|---|
| Sync Stateful | 非响应式返回 + 双向上下文 | Mono/Flux 返回 |
| Async Stateful | Mono/Flux 返回 + 双向上下文 | 非响应式返回 |
| Sync Stateless | 非响应式返回 + 无双向上下文 | 响应式返回或双向上下文参数 |
| Async Stateless | Mono/Flux 返回 + 无双向上下文 | 非响应式返回或双向上下文参数 |
实践建议:同步方法和异步方法分开放类,别混在同一个类里;启动时留意日志,确认方法都注册上了。
31.8 动态工具发现
工具多了,全部塞进上下文既费 token 又降低模型选工具的准确率。多个 MCP 服务器接进来,工具轻松超过 50 个,模型面对 30+ 相似名字时选择质量明显下降。
Tool Search Tool 模式解决这个问题:初始只给模型一个搜索工具,模型需要能力时先搜,再加载命中的工具定义。Spring AI 的实现基于 Recursive Advisor,实测省 34%-64% 的 token。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-tool-search-advisor</artifactId>
</dependency>
spring.ai.chat.client.tool-search-advisor.enabled=true
索引支持三种搜索策略:语义搜索用 VectorToolIndex,适合自然语言模糊匹配;关键词用 LuceneToolIndex,适合精确匹配;正则用 RegexToolIndex,适合按名字模式匹配。工具超过 20 个,或者接了多个 MCP 服务器,就值得上。
31.9 小结
MCP 安全官方仍是 WIP,生产使用要自己核实最新状态。注解开发是日常主力:服务端 @McpTool 一行声明工具,客户端注解处理通知,特殊参数注入上下文。工具多了上动态发现,省 token 又提准确率。