9. Spring AI 当中对应 MCP 的操作

9. Spring AI 当中对应 MCP 的操作

@

MCP

问题:

  1. 当有服务商需要将tools提供外部使用(比如高德地图提供了位置服务tools, 比如百度提供了联网搜索的tools...)
  2. 或者在企业级中, 有多个智能应用,想将通用的tools公共化

怎么办?

可以把tools单独抽取出来, 由应用程序读取外部的tools。 那关键是怎么读呢? 怎么解析呢? 如果每个提供商各用一种规则你能想象有多麻烦! 所以MCP就诞生了, 他指定了标准规则, 以jsonrpc2.0的方式进行通讯。

那问题又来了, 以什么方式通讯呢? http? rpc? stdio? mcp提供了sse和stdio这2种方式。

9. Spring AI 当中对应 MCP 的操作

使用

Streamable http目前springai1.0版本不支持(因为Streamable http 是 spring ai 1.0 之后说明的) 我们先掌握SSE和STDIO

分别说下STDIO和SSE的方式:

  • STDIO更适合客户端桌面应用和辅助工具
  • SSE更适合web应用 、业务有关的公共tools

9. Spring AI 当中对应 MCP 的操作

MCP STDIO 输出配置实操

MCP Server
现成共用MCP Server

现在有很多MCP 服务 给大家提供一个网站:MCP Server(MCP 服务器)

9. Spring AI 当中对应 MCP 的操作

那MCP有了, 怎么调用呢? 这里介绍2种使用方式:

MCP Client
通过工具

CherryStudio、Cursor 、Claude Desktop、Cline 等等很多, 这里不一一演示, 不会的话自己找个文章, 工具使用都很简单!

9. Spring AI 当中对应 MCP 的操作

以Cline为例: 他是Vscode的插件

  1. 安装VSCode

  2. 安装插件:

9. Spring AI 当中对应 MCP 的操作

  1. 配置cline的模型:

9. Spring AI 当中对应 MCP 的操作

  1. 配置cline的mcpserver9. Spring AI 当中对应 MCP 的操作
{     "mcpServers": {         "baidu-map": {             "command": "cmd",             "args": [                 "/c",                 "npx",                 "-y",                 "@baidumap/mcp-server-baidu-map"             ],             "env": {                 "BAIDU_MAP_API_KEY": "LEyBQxG9UzR9C1GZ6zDHsFDVKvBem2do"             }         },         "filesystem": {             "command": "cmd",             "args": [                 "/c",                 "npx",                 "-y",                 "@modelcontextprotocol/server-filesystem",                 "C:/Users/tuling/Desktop"             ]         },         "mcp-server-weather": {             "command": "java",             "args": [                 "-Dspring.ai.mcp.server.stdio=true",                 "-Dlogging.pattern.console=",                 "-jar",                 "D:\ideaworkspace\git_pull\tuling-flight-booking_all\mcp-stdio-server\target\mcp-stdio-server-xs-1.0.jar"             ]         }     } } 
  1. 开启cline权限

9. Spring AI 当中对应 MCP 的操作9. Spring AI 当中对应 MCP 的操作

6.测试:

9. Spring AI 当中对应 MCP 的操作

通过 Spring AI 接入 第三方的 MCP Server
  1. 依赖
<!--既支持sse也支持Stdio--> <dependency>     <groupId>org.springframework.ai</groupId>     <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> </dependency> 

2 配置

spring:   ai:     mcp:       client:       # 连接超时时间设置         request-timeout: 60000          stdio: # 设置 sse 输出方式         # 配置Mcp 方式2: 将 mcp的配置 单独放在一个 Json 文件当中读取,推荐,利用维护         # classpath 是指:项目resources           servers-configuration: classpath:/mcp-servers-config.json         # 配置MCP 方式2: 直接将 mcp 配置全局配置文件中(mcp 配置太多不利于维护)           # connections:           #   server1:           #     command: /path/to/server           #     args:           #       - --port=8080           #       - --mode=production           #     env:           #       API_KEY: your-api-key           #       DEBUG: "true" 
  1. mcp-servers-config.json:

获取Baidu地图key: 控制台 | 百度地图开放平台

{     "mcpServers": {         "baidu-map": {             "command": "cmd",             "args": [                 "/c",                 "npx",                 "-y",                 "@baidumap/mcp-server-baidu-map"             ],             "env": {                 "BAIDU_MAP_API_KEY": "xxxx"             }         },         "filesystem": {             "command": "cmd",             "args": [                 "/c",                 "npx",                 "-y",                 "@modelcontextprotocol/server-filesystem",                 "C:/Users/tuling/Desktop"             ]         },         "mcp-server-weather": {             "command": "java",             "args": [                 "-Dspring.ai.mcp.server.stdio=true",                 "-Dlogging.pattern.console=",                 "-jar",                 "D:\xxx\target\mcp-stdio-server-xs-1.0.jar"             ]         }     } } 
{     "mcpServers": {       // 外部第三方的         "baidu-map": {             "command": "cmd",             "args": [                 "/c",                 "npx",                 "-y",                 "@baidumap/mcp-server-baidu-map"             ],             "env": {                 "BAIDU_MAP_API_KEY": "xxxx"             }         },        // 外部第三方的         "filesystem": {             "command": "cmd",  // 指明使用 cmd 命令执行             "args": [                 "/c",                 "npx",                 "-y",                 "@modelcontextprotocol/server-filesystem",                 "C:/Users/tuling/Desktop"             ]         },        // 自定义的 mcp 服务         "mcp-server-weather": {  // 对应的项目名 application的 name             "command": "java", // 指明通过 java 命令执行,java 解析可以直接识别到             "args": [                 "-Dspring.ai.mcp.server.stdio=true",                 "-Dlogging.pattern.console=", // 清空控制台,不然会输入很多信息                 "-jar", // -jar 启动 Spring Boot                 "D:\xxx\target\mcp-stdio-server-xs-1.0.jar" // 自定义的mcp服务的jar路径             ]         }     } } 
  1. 绑定到Chatclient
/**  * @description: 智能航空助手:  */ @RestController @CrossOrigin public class OpenAiController {          private final ChatClient chatClient;          public OpenAiController(             DashScopeChatModel dashScopeChatModel,                             // 配置引入 外部 mcp tools                             ToolCallbackProvider mcpTools) {         this.chatClient =ChatClient.builder(dashScopeChatModel)         .defaultToolCallbacks(mcpTools)  // 将外部的 mcop tools 对大模型进行绑定,这里是构造器的绑定,不是单个对话的绑定         .build();     }        @CrossOrigin @GetMapping(value = "/ai/generateStreamAsString", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> generateStreamAsString(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {      Flux<String> content = chatClient.prompt()             .user(message)             .stream()             .content();      return  content;      } 
# 调试日志 logging:   level:     io:       modelcontextprotocol:         client: DEBUG         spec: DEBUG 
使用 Spring AI 接入 自定义MCP Server

创建一个spring ai项目

  1. 依赖
<!--mcp-server  --> <dependency>   <groupId>org.springframework.ai</groupId>   <artifactId>spring-ai-starter-mcp-server</artifactId> </dependency>   <dependencyManagement>         <dependencies>             <!--spring ai 包管理依赖 -->             <dependency>                 <groupId>org.springframework.ai</groupId>                 <artifactId>spring-ai-bom</artifactId>                 <version>${spring-ai.version}</version>                 <type>pom</type>                 <scope>import</scope>             </dependency>         </dependencies>   </dependencyManagement>  <!-- 打包 --> <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>                 <executions>                     <execution>                         <goals>                             <goal>repackage</goal>                         </goals>                     </execution>                 </executions>             </plugin>         </plugins>     </build> 
  1. 添加工具
@Service public class UserToolService {      Map<String,Double> userScore = Map.of(         "xushu",99.0,         "zhangsan",2.0,         "lisi",3.0);     @Tool(description = "获取用户分数")     public String getScore(String username) { // 也可以添加上 @ToolParam(description=“” )告诉大模型这个参数的描述是做什么的         if(userScore.containsKey(userName)){             return userScore.get(userName).toString();         }            return "未检索到当前用户"+userName;     } } 
  1. 暴露工具
@Bean  // 将我们编写的 tools 对外的UserToolService 绑定上去 public ToolCallbackProvider weatherTools(UserToolService userToolService) {     return MethodToolCallbackProvider.builder().toolObjects(userToolService).build(); } 
  1. 配置
spring:   main:     banner-mode: off   ai:     mcp:       server:         name: my-weather-server         version: 0.0.1 

# 注意:您必须禁用横幅和控制台日志记录,以允许 STDIO 传输!!工作 banner-mode: off

  1. 打包 mvn package

此时target/生成了jar则成功!

  1. 在我们需要的用到我们自定义的 mcp 的项目当中,加上我们自行定义的 MCP 服务。如下,我们是将其统一放到了一个配置的 json 文件当中。去了
{     "mcpServers": {       // 外部第三方的         "baidu-map": {             "command": "cmd",             "args": [                 "/c",                 "npx",                 "-y",                 "@baidumap/mcp-server-baidu-map"             ],             "env": {                 "BAIDU_MAP_API_KEY": "xxxx"             }         },        // 外部第三方的         "filesystem": {             "command": "cmd",  // 指明使用 cmd 命令执行             "args": [                 "/c",                 "npx",                 "-y",                 "@modelcontextprotocol/server-filesystem",                 "C:/Users/tuling/Desktop"             ]         },        // 自定义的 mcp 服务         "mcp-server-weather": {  // 对应的项目名 application的 name             "command": "java", // 指明通过 java 命令执行,java 解析可以直接识别到             "args": [                 "-Dspring.ai.mcp.server.stdio=true",                 "-Dlogging.pattern.console=", // 清空控制台,不然会输入很多信息                 "-jar", // -jar 启动 Spring Boot                 "D:\xxx\target\mcp-stdio-server-xs-1.0.jar" // 自定义的mcp服务的jar路径             ]         }     } } 

MCP SSE 输出配置实操(推荐 Web)

MCP Server

这种方式需要将部署为Web服务

  1. 依赖
      <!--mcp服务器核心依赖— 响应式-->       <dependency>         <groupId>org.springframework.ai</groupId>         <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>       </dependency>       <!-- 这个 SSE 是需要 Web 的 -->       <dependency>         <groupId>org.springframework</groupId>         <artifactId>spring-boot-starter-web</artifactId>       </dependency> 
  1. 定义外部工具
@Service public class UserToolService {      Map<String,Double> userScore = Map.of(             "xushu",99.0,             "zhangsan",2.0,             "lisi",3.0);     @Tool(description = "获取用户分数")     public String getScore(String username) {         if(userScore.containsKey(username)){             return userScore.get(username).toString();         }          return "未检索到当前用户";     } } 
  1. 暴露工具
@Bean     public ToolCallbackProvider weatherToolCallbackProvider(WeatherService weatherService,                                                             UserToolService userToolService) {         return MethodToolCallbackProvider.builder().toolObjects(userToolService).build();     } 
  1. 配置(需要用 web 启动)
server:   port: 8088 
MCP Client

将上面 通过 SSE 方式创建的自定义 MCP Server 配置进来

  1. 添加依赖
<!--既支持sse也支持Stdio--> <dependency>   <groupId>org.springframework.ai</groupId>   <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> </dependency> 
  1. 配置
spring:   ai:     mcp:       client:         enabled: true         name: my-mcp-client         version: 1.0.0         request-timeout: 30s         type: ASYNC  # or SYNC         sse: # 设置 sse 输出方式           connections:             server1:               url: http://localhost:8088 
  1. 代码
/**  * @author wx:程序员徐庶  * @version 1.0  * @description: 智能航空助手:需要一对一解答关注wx: 程序员徐庶  */ @RestController @CrossOrigin public class OpenAiController {      private final ChatClient chatClient;      public OpenAiController(         DashScopeChatModel dashScopeChatModel,         // 外部 mcp tools         ToolCallbackProvider mcpTools) {         this.chatClient =ChatClient.builder(dashScopeChatModel)         .defaultToolCallbacks(mcpTools)         .build();     }       @CrossOrigin     @GetMapping(value = "/ai/generateStreamAsString", produces = MediaType.TEXT_EVENT_STREAM_VALUE)     public Flux<String> generateStreamAsString(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {          Flux<String> content = chatClient.prompt()         .user(message)         .stream()         .content();          return  content;      } 

原理

  1. STDIO 是基于标准输入输出流的方式, 需要在MCP 客户端安装一个包(可以是jar包、python包、npm包等..). 它是“客户端”的MCP Server。

9. Spring AI 当中对应 MCP 的操作

  1. SSE 是基于Http的方式进行通讯, 需要将MCP Server部署为一个web服务. 它是服务端的MCP Server
STDIO原理

9. Spring AI 当中对应 MCP 的操作

很多人不理解stdio到底什么意思, 为什么一定要把stdio server的banner关掉, 还要清空控制台?

9. Spring AI 当中对应 MCP 的操作

  1. 首先SpringAi底层会读取到mcp-servers-config.json的信息
  2. 然后执行命令(其实聪明的小伙伴早就发现了,mcp-servers-config.json文件中就是一堆shell命令)
    1. 怎么执行? 熟悉java的同学应该知道,java里面有一个对象用于执行命令:
ProcessBuilder processBuilder = new ProcessBuilder();         processBuilder.command("java","-version");          Process process = processBuilder.start();          process.errorReader().lines().forEach(System.out::println); 
  1. 所以springAi底层相当于读取到信息后, 会通过processBuilder去执行命令
String[] commands={"java",                 "-Dspring.ai.mcp.server.stdio=true",                 "-Dlogging.pattern.console=",                 "-jar",                 "D:\ideaworkspace\git_pull\tuling-flight-booking_all\mcp-stdio-server\target\mcp-stdio-server-xs-1.0.jar"};          ProcessBuilder processBuilder = new ProcessBuilder();         processBuilder.command(commands);         // processBuilder.environment().put("username","xushu");          Process process = processBuilder.start(); 

其实你也完全可以自己通过mcd去执行命令

9. Spring AI 当中对应 MCP 的操作

  1. 运行jar -jar mcp-stdio-server.jar
  2. 输入{"jsonrpc":"2.0","method":"tools/list","id":"3b3f3431-1","params":{}}
  3. 输出tools列表

这就是标准输入输出流! 看到这里你应该知道, 为什么需要-Dlogging.pattern.console= 完全是为了清空控制台,才能读取信息!

所以利用java也是一样的原理:

@Test     public void test() throws IOException, InterruptedException {         String[] commands={"java",                 "-Dspring.ai.mcp.server.stdio=true",                 "-Dlogging.pattern.console=",                 "-jar",                 "D:\ideaworkspace\git_pull\tuling-flight-booking_all\mcp-stdio-server\target\mcp-stdio-server-xs-1.0.jar"};          ProcessBuilder processBuilder = new ProcessBuilder();         processBuilder.command(commands);         processBuilder.environment().put("username","xushu");          Process process = processBuilder.start();          Thread thread = new Thread(() -> {             try (BufferedReader processReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {                 String line;                 while ((line=processReader.readLine())!=null) {                         System.out.println(line);                 }             } catch (IOException e) {                 e.printStackTrace();             }         });         thread.start();           Thread.sleep(1000);          new Thread(() -> {              try {                 //String jsonMessage="{"jsonrpc":"2.0","method":"initialize","id":"3670122a-0","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"spring-ai-mcp-client","version":"1.0.0"}}}";                 String jsonMessage = "{"jsonrpc":"2.0","method":"tools/list","id":"3b3f3431-1","params":{}}";                  jsonMessage = jsonMessage.replace("rn", "\n").replace("n", "\n").replace("r", "\n");                  var os = process.getOutputStream();                 synchronized (os) {                     os.write(jsonMessage.getBytes(StandardCharsets.UTF_8));                     os.write("n".getBytes(StandardCharsets.UTF_8));                     os.flush();                 }                 System.out.println("写入完成!");             }catch (IOException e){                 e.printStackTrace();             }         }).start();           thread.join();         /*JSONRPCRequest[jsonrpc=2.0, method=initialize, id=5d83d0d1-0, params=InitializeRequest[protocolVersion=2024-11-05, capabilities=ClientCapabilities[experimental=null, roots=null, sampling=null],         clientInfo=Implementation[name=spring-ai-mcp-client, version=1.0.0]]]*/     } 
  1. 通过ProcessBuilder执行命令
  2. 通过子线程轮询 process.getInputStream 获取输出流
  3. 通过process.getOutputStream(); 进行写入流

所以整个过程是这样的:再回顾上面的图

启动程序--->读取mcpjson--->通过ProcessBuilder启动命令---> 写入初始化jsonrpc---->写入获取tools列表jsonrpc---->请求大模型(携带tools)---->写入请求外部tool的jsonrpc---->获取数据--->发送给大模型---->响应。

STDIO源码

9. Spring AI 当中对应 MCP 的操作

MCP鉴权

在做MCP企业级方案落地时, 我们可能不想让没有权限的人访问MCP Server, 或者需要根据不同的用户返回不同的数据, 这里就涉及到MCP Server授权操作。

那MCP Server有2种传输方式, 实现起来不一样:

STDIO

这种方式在本地运行,它 将MCP Server作为子进程启动 我们称为标准输入输出, 其实就是利用运行命令的方式写入和读取控制台的信息,以达到传输。

9. Spring AI 当中对应 MCP 的操作

通常我们会配置一段json,比如这里的百度地图MCP Server :

  • 其中command和args代表运行的命令和参数。
  • 其实env中的节点BAIDU_MAP_API_KEY就是做授权的。

如果你传入的BAIDU_MAP_API_KEY不对, 就没有使用权限。

"baidu-map": {   "command": "cmd",   "args": [     "/c",     "npx",     "-y",     "@baidumap/mcp-server-baidu-map"   ],   "env": {     "BAIDU_MAP_API_KEY": "LEyBQxG9UzR9C1GZ6zDHsFDVKvBem2do"   } }, 

所以STDIO做授权的方式很明确, 就是通过env【环境变量】,实现步骤如下:

  1. 服务端发放一个用户的凭证(可以是秘钥、token) 这步不细讲,需要有一个授权中心发放凭证。
  2. 通过mcp client通过env传入凭证
  3. mcp server通过环境变量鉴权

所以在MCP Server端就可以通过获取环境变量的方式获取env里面的变量:

也可以通过AOP的方式统一处理

@Tool(description = "获取用户余额")     public String getScore() {         String userName = System.getenv("API_KEY");          // todo .. 鉴权处理         return "未检索到当前用户"+userName;     } 

这种方式要注意: 他不支持动态鉴权, 也就是动态更换环境变量, 因为STDIO是本地运行方式,它 将MCP Server作为子进程启动, 如果是多个用户动态切换凭证, 会对共享的环境变量造成争抢, 最终只能存储一个。 除非一个用户对应一个STDIO MCP Server. 但是这样肯定很吃性能! 如果要多用户动态切换授权, 可以用SSE的方式;

SSE
说明

不过,如果你想把 MCP 服务器开放给外部使用,就需要暴露一些标准的 HTTP 接口。对于私有场景,MCP 服务器可能并不需要严格的身份认证,但在企业级部署下,对这些接口的安全和权限把控就非常重要了。为了解决这个问题,2025 年 3 月发布的最新 MCP 规范引入了安全基础,借助了广泛使用的 OAuth2 框架

9. Spring AI 当中对应 MCP 的操作

本文不会详细介绍 OAuth2 的所有内容,不过简单回顾一下还是很有帮助。

在规范的草案中,MCP 服务器既是资源服务器,也是授权服务器。

  • 作为资源服务器,MCP 负责检查每个请求中的 Authorization请求头。这个请求头必须包括一个 OAuth2access_token(令牌),它代表客户端的“权限”。这个令牌通常是一个 JWT(JSON Web Token),也可能只是一个不可读的随机字符串。如果令牌缺失或无效(无法解析、已过期、不是发给本服务器的等),请求会被拒绝。正常情况下,调用示例如下:
curl https://mcp.example.com/sse -H "Authorization: Bearer <有效的 access token>" 
  • 作为授权服务器,MCP 还需要有能力为客户端安全地签发access_token。在发放令牌前,服务器会校验客户端的凭据,有时还需要校验访问用户的身份。授权服务器决定令牌的有效期、权限范围、目标受众等特性。

用 Spring Security 和 Spring Authorization Server,可以方便地为现有的 Spring MCP 服务器加上这两大安全能力。

9. Spring AI 当中对应 MCP 的操作

给 Spring MCP 服务器加上 OAuth2 支持

这里以官方例子仓库的【天气】MCP 工具演示如何集成 OAuth2,主要是让服务器端能签发和校验令牌。

首先,pom.xml里添加必要的依赖:

<dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> </dependency> 

接着,在application.properties配置里加上简易的 OAuth2 客户端信息,便于请求令牌:

spring.security.oauth2.authorizationserver.client.oidc-client.registration.client-id=xushu spring.security.oauth2.authorizationserver.client.oidc-client.registration.client-secret={noop}xushu666 spring.security.oauth2.authorizationserver.client.oidc-client.registration.client-authentication-methods=client_secret_basic spring.security.oauth2.authorizationserver.client.oidc-client.registration.authorization-grant-types=client_credentials 

这样定义后,你可以直接通过 POST 请求和授权服务器交互,无需浏览器,用配置好的/secret作为固定凭据。 比如 最后一步是开启授权服务器和资源服务器功能。通常会新增一个安全配置类,比如SecurityConfiguration,如下:

import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer.authorizationServer;  @Configuration @EnableWebSecurity class SecurityConfiguration {      @Bean     SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {         return http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())         .with(authorizationServer(), Customizer.withDefaults())         .oauth2ResourceServer(resource -> resource.jwt(Customizer.withDefaults()))         .csrf(CsrfConfigurer::disable)         .cors(Customizer.withDefaults())         .build();     } } 

这个过滤链主要做了这些事情:

  • 要求所有请求都要经过身份认证。也就是访问 MCP 的接口,必须带上 access_token。
  • 同时启用了授权服务器和资源服务器两大能力。
  • 关闭了 CSRF(跨站请求伪造防护),因为 MCP 不是给浏览器直接用的,这部分无需开启。
  • 打开了 CORS(跨域资源共享),方便用 MCP inspector 测试。

这样配置之后,只有带 access_token 的访问才会被接受,否则会直接返回 401 未授权错误,例如:

curl http://localhost:8080/sse --fail-with-body # 返回: # curl: (22) The requested URL returned error: 401 

要使用 MCP 服务器,先要获取一个 access_token。可通过client_credentials授权方式(用于机器到机器、服务账号的场景):

curl -XPOST http://localhost:8080/oauth2/token --data grant_type=client_credentials --user xushu:xushu666 # 返回: # {"access_token":"<YOUR-ACCESS-TOKEN>","token_type":"Bearer","expires_in":299} 

把返回的 access_token 记下来(它一般以 “ey” 开头),之后就可以用它来正常请求服务器了:

curl http://localhost:8080/sse -H"Authorization: Bearer YOUR_ACCESS_TOKEN" # 服务器响应内容 

你还可以直接在MCP inspector工具里用这个 access_token。从菜单的 Authentication > Bearer 处粘贴令牌并连接即可。

为MCP Client设置请求头

目前, mcp 的java sdk 没有提供api直接调用, 经过徐庶老师研究源码后, 你只能通过2种方式实现:

重写源码

扩展mcp 的sse方式java sdk的源码, 整个重写一遍。 工作量较大, 并且我预计过不了多久, spring ai和mcp协议都会更新这块。 看你的紧急程度, 如果考虑整体扩展性维护性,可以整体重写一遍:

提供一个重写思路

重写McpSseClientProperties

MCPSse客户端属性配置:新增请求头字段

package org.springframework.ai.autoconfigure.mcp.client.properties;  @ConfigurationProperties("spring.ai.mcp.client.sse") public class McpSseClientProperties {     public static final String CONFIG_PREFIX = "spring.ai.mcp.client.sse";     private final Map<String, SseParameters> connections = new HashMap();          private final Map<String, String> headersMap = new HashMap<>();     private String defaultHeaderName;     private String defaultHeaderValue;     private boolean enableCompression = false;     private int connectionTimeout = 5000;      public McpSseClientProperties() {     }      public Map<String, SseParameters> getConnections() {         return this.connections;     }      public Map<String, String> getHeadersMap() {         return this.headersMap;     }      public String getDefaultHeaderName() {         return this.defaultHeaderName;     }      public void setDefaultHeaderName(String defaultHeaderName) {         this.defaultHeaderName = defaultHeaderName;     }      public String getDefaultHeaderValue() {         return this.defaultHeaderValue;     }      public void setDefaultHeaderValue(String defaultHeaderValue) {         this.defaultHeaderValue = defaultHeaderValue;     }      public boolean isEnableCompression() {         return this.enableCompression;     }      public void setEnableCompression(boolean enableCompression) {         this.enableCompression = enableCompression;     }      public int getConnectionTimeout() {         return this.connectionTimeout;     }      public void setConnectionTimeout(int connectionTimeout) {         this.connectionTimeout = connectionTimeout;     }      public static record SseParameters(String url) {         public SseParameters(String url) {             this.url = url;         }          public String url() {             return this.url;         }     } } 

重写SseWebFluxTransportAutoConfiguration

自动装配添加请求头配置信息

package org.springframework.ai.autoconfigure.mcp.client;  @AutoConfiguration @ConditionalOnClass({WebFluxSseClientTransport.class}) @EnableConfigurationProperties({McpSseClientProperties.class, McpClientCommonProperties.class}) @ConditionalOnProperty(         prefix = "spring.ai.mcp.client",         name = {"enabled"},         havingValue = "true",         matchIfMissing = true ) public class SseWebFluxTransportAutoConfiguration {     public SseWebFluxTransportAutoConfiguration() {     }      @Bean     public List<NamedClientMcpTransport> webFluxClientTransports(McpSseClientProperties sseProperties, WebClient.Builder webClientBuilderTemplate, ObjectMapper objectMapper) {         List<NamedClientMcpTransport> sseTransports = new ArrayList();         Iterator var5 = sseProperties.getConnections().entrySet().iterator();         Map<String, String> headersMap = sseProperties.getHeadersMap();         while(var5.hasNext()) {             Map.Entry<String, McpSseClientProperties.SseParameters> serverParameters = (Map.Entry)var5.next();             WebClient.Builder webClientBuilder = webClientBuilderTemplate.clone()                     .defaultHeaders(headers -> {                         if (headersMap != null && !headersMap.isEmpty()) {                             headersMap.forEach(headers::add);                         }                     })                     .baseUrl(((McpSseClientProperties.SseParameters)serverParameters.getValue()).url());             WebFluxSseClientTransport transport = new WebFluxSseClientTransport(webClientBuilder, objectMapper);             sseTransports.add(new NamedClientMcpTransport((String)serverParameters.getKey(), transport));         }          return sseTransports;     }      @Bean     @ConditionalOnMissingBean     public WebClient.Builder webClientBuilder() {         return WebClient.builder();     }      @Bean     @ConditionalOnMissingBean     public ObjectMapper objectMapper() {         return new ObjectMapper();     } } 

使用:

9. Spring AI 当中对应 MCP 的操作

设置WebClientCustomizer

在用Spring-ai-M8版本的时候, 发现提供了WebClientCustomizer进行扩展。 可以尝试:

  1. 根据用户凭证进行授权
curl -XPOST http://localhost:8080/oauth2/token --data grant_type=client_credentials --user xushu:xushu666 
  1. 根据授权后的token进行请求:
@Bean public WebClientCustomizer webClientCustomizer() {     // 认证 mcp server  /oauth?username:password   --> access_token     return (builder) -> {         builder.defaultHeader("Authorization","Bearer eyJraWQiOiIzYmMzMDRmZC02NzcyLTRkYTItODJiMy1hNTEwNGExMDBjNTYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ4dXNodSIsImF1ZCI6Inh1c2h1IiwibmJmIjoxNzQ2NzE4MjE5LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJleHAiOjE3NDY3MTg1MTksImlhdCI6MTc0NjcxODIxOSwianRpIjoiM2VhMzIyODctNTQ5NC00NWZlLThlZDItZGY1MjViNmIwNzkxIn0.Q-zWBZxa2CeFZo2YinenyaLb8KBMMua40X8YSs4n2fez7ODihtoVuCeJQnd2Q6qV2Pa8Z3cfH4QcMUuxMJ-_sLtZaSXpbCThH5q3KoQZ8C4MLJRTpuRqv4z1n7uLNXiVG2rya5hGwjTxu5qzHuBa2ri9pamRwmsjTz4vLHBJ1ILxDJcTkZUFuV1ExQJViewGt_7KMYcFqzGyRPiS4mm4wVvJTDjqcEGwMelu51L44K1DDYgt29vVLRVQEmnUtbBzePAxRqfw_HWJdhRSeQNiqRYCYhdAlPr3QZUFJa54GpuZn3CNyaXFoL7mENSR7wCYWx6wi--_REw6oaIfeSm-Xg");     }; } 

SSE是支持动态切换token的, 因为一个请求就是一个新的http请求, 不会出现多线程争抢。

但是需要动态请求:

curl -XPOST http://localhost:8080/oauth2/token --data grant_type=client_credentials --user xushu:xushu666 进行重新授权

最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

9. Spring AI 当中对应 MCP 的操作

发表评论

评论已关闭。

相关文章