Spring AI 1.x 系列【40】MCP 客户端 Spring Boot 启动器

发布时间:2026/6/8 0:17:45
Spring AI 1.x 系列【40】MCP 客户端 Spring Boot 启动器
1. 概述Spring AI提供的模型上下文协议MCP客户端 Spring Boot 启动器可为Spring Boot应用自动配置MCP客户端能力。该组件同时支持同步、异步客户端实现并兼容多种通信传输方式。本启动器具备以下能力支持管理多个客户端实例可配置客户端自动初始化兼容多种命名传输方式标准输入输出(STDIO)、HTTP/SSE、流式 HTTP无缝集成Spring AI工具调用框架提供工具过滤能力可按需启用/屏蔽指定工具支持自定义工具名称前缀生成避免命名冲突完善的生命周期管理应用上下文关闭时自动释放资源支持通过自定义扩展器定制客户端创建逻辑2. 启动器依赖2.1 标准 MCP 客户端标准启动器可通过进程内STDIO、SSE、流式HTTP、无状态流式HTTP多种方式同时连接一个或多个MCP服务端。其中SSE与流式HTTP基于JDK原生HttpClient实现。每一条MCP服务端连接都会创建独立客户端实例可选择同步(SYNC)或异步(ASYNC)模式两种模式不可混用。dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-mcp-client/artifactId/dependency生产环境推荐搭配spring-ai-starter-mcp-client-webflux使用基于WebFlux的SSE/流式HTTP连接。2.2 WebFlux 客户端功能与标准启动器一致但底层基于WebFlux实现流式HTTP、无状态流式HTTP及SSE传输。dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-mcp-client-webflux/artifactId/dependency3. 配置项说明3.1 通用配置配置前缀spring.ai.mcp.client配置项说明默认值enabled开启/关闭 MCP 客户端truenameMCP 客户端实例名称spring-ai-mcp-clientversionMCP 客户端版本1.0.0initialized创建客户端时是否自动初始化truerequest-timeout客户端请求超时时间20stype客户端类型SYNC(同步) / ASYNC(异步)全局只能选一种SYNCroot-change-notification开启/关闭所有客户端的根路径变更通知truetoolcallback.enabled开启/关闭 MCP 工具回调与 Spring AI 工具框架的集成true3.2 MCP 注解配置配置前缀spring.ai.mcp.client.annotation-scanner配置项说明默认值enabled开启/关闭 MCP 客户端注解自动扫描true3.3 标准输入输出(STDIO)传输配置配置前缀spring.ai.mcp.client.stdio配置项说明默认值servers-configurationJSON 格式的 MCP 服务端配置文件地址无connections命名式 STDIO 连接配置集合无connections.[name].command启动 MCP 服务端的执行命令无connections.[name].args命令行参数列表无connections.[name].env服务端进程环境变量无配置示例YAMLspring:ai:mcp:client:stdio:root-change-notification:trueconnections:server1:command:/path/to/serverargs:---port8080---modeproductionenv:API_KEY:your-api-keyDEBUG:true引用外部 JSON 配置文件兼容 Claude Desktop 格式spring:ai:mcp:client:stdio:servers-configuration:classpath:mcp-servers.jsonmcp-servers.json示例{mcpServers:{filesystem:{command:npx,args:[-y,modelcontextprotocol/server-filesystem,/Users/username/Desktop,/Users/username/Downloads]}}}3.3.1 Windows 平台特殊适配Windows系统中npx、npm、node、python等均为批处理文件.cmdJava的ProcessBuilder无法直接执行批处理必须通过cmd.exe /c包裹命令。Windows 配置示例JSON{mcpServers:{filesystem:{command:cmd.exe,args:[/c,npx,-y,modelcontextprotocol/server-filesystem,C:\\Users\\username\\Desktop]}}}Linux / macOS 配置示例JSON{mcpServers:{filesystem:{command:npx,args:[-y,modelcontextprotocol/server-filesystem,/Users/username/Desktop]}}}跨平台代码式配置通过代码判断操作系统实现一套配置兼容全平台同时添加ConditionalOnMissingBean避免与配置文件自动配置冲突Bean(destroyMethodclose)ConditionalOnMissingBean(McpSyncClient.class)publicMcpSyncClientmcpClient(){ServerParametersstdioParams;if(isWindows()){// Windows 系统使用 cmd.exe 包装命令varwinArgsnewArrayList(Arrays.asList(/c,npx,-y,modelcontextprotocol/server-filesystem,target));stdioParamsServerParameters.builder(cmd.exe).args(winArgs).build();}else{// Linux / Mac 系统直接执行命令stdioParamsServerParameters.builder(npx).args(-y,modelcontextprotocol/server-filesystem,target).build();}returnMcpClient.sync(newStdioClientTransport(stdioParams,McpJsonMapper.createDefault())).requestTimeout(Duration.ofSeconds(10)).build().initialize();}// 判断当前系统是否为 WindowsprivatestaticbooleanisWindows(){returnSystem.getProperty(os.name).toLowerCase().contains(win);}路径使用说明相对路径推荐可移植性强基于应用运行目录解析绝对路径Windows使用反斜杠\或转义正斜杠/Windows 下需要cmd.exe包裹的常用批处理npx.cmd、npm.cmd、python.cmd、pip.cmd、mvn.cmd、gradle.cmd及自定义.cmd/.bat脚本。3.4 流式 HTTPStreamable-HTTP传输配置配置前缀spring.ai.mcp.client.streamable-http配置项说明默认值connections命名式流式 HTTP 连接配置集合无connections.[name].urlMCP 服务端基础地址无connections.[name].endpoint接口后缀路径/mcp配置示例spring:ai:mcp:client:streamable-http:connections:server1:url:http://localhost:8080server2:url:http://otherserver:8081endpoint:/custom-sse3.5 SSE服务端推送事件传输配置配置前缀spring.ai.mcp.client.sse配置项说明默认值connections命名式 SSE 连接配置集合无connections.[name].urlMCP 服务端基础地址无connections.[name].sse-endpointSSE 接口后缀路径/sse配置示例spring:ai:mcp:client:sse:connections:server1:url:http://localhost:8080server2:url:http://otherserver:8081sse-endpoint:/custom-sseURL 拆分规则完整 SSE 地址需拆分为基础URL接口后缀完整地址基础 urlsse-endpoint 后缀http://localhost:3000/mcp-hub/sse/token123localhost:3000/mcp-hub/sse/token123https://api.service.com/v2/events?keysecretapi.service.com/v2/events?keysecrethttp://localhost:8080/sselocalhost:8080/sse可省略SSE 连接排错404 错误拆分地址时基础 URL 仅保留协议、域名、端口接口后缀必须以/开头包含完整路径与请求参数先通过浏览器或 curl 直接访问完整地址验证接口可用性4. 核心功能详解4.1 同步/异步客户端同步客户端默认 SYNC适用于传统阻塞式请求响应场景仅注册同步类型 MCP 注解方法异步方法会被忽略。异步客户端ASYNC适用于响应式非阻塞应用仅注册异步类型 MCP 注解方法同步方法会被忽略。4.2 客户端自定义扩展通过扩展接口可深度定制客户端行为支持超时配置、事件监听、消息处理等。支持两类扩展接口McpSyncClientCustomizer同步客户端扩展McpAsyncClientCustomizer异步客户端扩展可自定义能力请求超时配置LLM 采样处理器文件根路径权限管理交互式信息收集处理器各类事件监听器工具/资源/提示词变更、进度通知、日志通知等同步客户端扩展示例ComponentpublicclassCustomMcpSyncClientCustomizerimplementsMcpSyncClientCustomizer{Overridepublicvoidcustomize(StringserverConfigurationName,McpClient.SyncSpecspec){// 自定义超时时间spec.requestTimeout(Duration.ofSeconds(30));// 根路径配置spec.roots(roots);// 采样请求处理spec.sampling(request-{/* 业务逻辑 */});// 信息收集请求处理spec.elicitation(request-{/* 业务逻辑 */});// 进度通知监听spec.progressConsumer(progress-{/* 业务逻辑 */});// 工具列表变更监听spec.toolsChangeConsumer(tools-{/* 业务逻辑 */});// 资源列表变更监听spec.resourcesChangeConsumer(resources-{/* 业务逻辑 */});// 提示词列表变更监听spec.promptsChangeConsumer(prompts-{/* 业务逻辑 */});// 日志消息监听spec.loggingConsumer(log-{/* 业务逻辑 */});}}4.3 传输方式支持传输方式标准启动器WebFlux 启动器STDIO 标准输入输出✅✅HttpClient 版 HTTP/SSE、流式 HTTP✅❌WebFlux 版 HTTP/SSE、流式 HTTP❌✅4.4 工具过滤实现McpToolFilter接口可根据连接信息、工具属性动态过滤工具启用/禁用全局仅允许一个过滤 Bean。ComponentpublicclassCustomMcpToolFilterimplementsMcpToolFilter{Overridepublicbooleantest(McpConnectionInfoconnectionInfo,McpSchema.Tooltool){// 过滤指定客户端的所有工具if(restricted-client.equals(connectionInfo.clientInfo().name())){returnfalse;}// 仅保留名称前缀为 allowed_ 的工具if(tool.name().startsWith(allowed_)){returntrue;}// 过滤标注为实验性的工具if(tool.getDescription()!nulltool.getDescription().contains(experimental)){returnfalse;}returntrue;}}4.5 工具名称前缀生成通过McpToolNamePrefixGenerator为工具名添加前缀解决多服务端工具重名冲突。框架内置实现DefaultMcpToolNamePrefixGenerator默认自动去重、特殊字符转下划线、超长截断保证全局唯一noPrefix()不添加前缀多服务端场景不推荐易引发冲突自定义前缀规则示例ComponentpublicclassCustomToolNamePrefixGeneratorimplementsMcpToolNamePrefixGenerator{OverridepublicStringprefixedToolName(McpConnectionInfoconnectionInfo,Tooltool){StringserverNameconnectionInfo.initializeResult().serverInfo().name();StringserverVersionconnectionInfo.initializeResult().serverInfo().version().replace(.,_);// 格式服务名_版本_工具名returnserverName_vserverVersion_tool.name();}}关闭前缀功能ConfigurationpublicclassMcpConfiguration{BeanpublicMcpToolNamePrefixGeneratormcpToolNamePrefixGenerator(){returnMcpToolNamePrefixGenerator.noPrefix();}}4.6 工具上下文与元数据转换通过ToolContextToMcpMetaConverter将 Spring AI 工具上下文转为 MCP 调用元数据可传递用户ID、令牌、追踪标识等上下文。框架内置默认转换器过滤空值与内置上下文 KeynoOp()禁用上下文转换4.7 关闭工具回调自动配置配置spring.ai.mcp.client.toolcallback.enabledfalse将不再自动创建工具回调实例。5. MCP 客户端注解使用注解以声明式方式处理 MCP 各类事件与请求注解用途McpLogging接收服务端日志通知McpSampling处理 LLM 采样/补全请求McpElicitation向用户收集补充信息McpProgress接收长任务进度通知McpToolListChanged工具列表变更通知McpResourceListChanged资源列表变更通知McpPromptListChanged提示词列表变更通知使用示例ComponentpublicclassMcpClientHandlers{McpLogging(clientsserver1)publicvoidhandleLoggingMessage(LoggingMessageNotificationnotification){System.out.println(日志notification.level() - notification.data());}McpSampling(clientsserver1)publicCreateMessageResulthandleSamplingRequest(CreateMessageRequestrequest){// 处理LLM请求并返回结果returnCreateMessageResult.builder().role(Role.ASSISTANT).content(newTextContent(响应内容)).model(gpt-4).build();}McpProgress(clientsserver1)publicvoidhandleProgress(ProgressNotificationnotification){doublepercentnotification.progress()*100;System.out.printf(执行进度%.2f%% %s%n,percent,notification.message());}}注解同步/异步方法均支持可通过clients属性指定绑定的客户端实例。6. 项目使用示例6.1 完整 YAML 配置spring:ai:mcp:client:enabled:truename:my-mcp-clientversion:1.0.0request-timeout:30stype:SYNCsse:connections:server1:url:http://localhost:8080server2:url:http://otherserver:8081streamable-http:connections:server3:url:http://localhost:8083endpoint:/mcpstdio:root-change-notification:falseconnections:server1:command:/path/to/serverargs:---port8080---modeproductionenv:API_KEY:your-api-keyDEBUG:true6.2 代码注入使用// 同步客户端注入AutowiredprivateListMcpSyncClientmcpSyncClients;// 异步客户端注入AutowiredprivateListMcpAsyncClientmcpAsyncClients;// 工具回调注入AutowiredprivateSyncMcpToolCallbackProvidertoolCallbackProvider;