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

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

@

目录
  • 9. Spring AI 当中对应 MCP 的操作
    • MCP
      • 问题:
      • 使用
        • MCP STDIO 输出配置实操
          • MCP Server
            • 现成共用MCP Server
          • MCP Client
            • 通过工具
            • 通过 Spring AI 接入 第三方的 MCP Server
            • 使用 Spring AI 接入 自定义MCP Server
        • MCP SSE 输出配置实操(推荐 Web)
          • MCP Server
          • MCP Client
        • 原理
          • STDIO原理
        • STDIO源码
        • MCP鉴权
          • STDIO
          • SSE
            • 说明
            • 给 Spring MCP 服务器加上 OAuth2 支持
            • 为MCP Client设置请求头
            • 重写源码
  • 最后:

MCP

问题:

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

怎么办?

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

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

使用

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

分别说下STDIO和SSE的方式:

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

MCP STDIO 输出配置实操

MCP Server
现成共用MCP Server

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

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

MCP Client
通过工具

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

以Cline为例: 他是Vscode的插件

  1. 安装VSCode

  2. 安装插件:

  1. 配置cline的模型:

  1. 配置cline的mcpserver
{"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权限

6.测试:

通过 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 是指:项目resourcesservers-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 toolsToolCallbackProvider 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: DEBUGspec: 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: offai:mcp:server:name: my-weather-serverversion: 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. 暴露工具
@Beanpublic 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: truename: my-mcp-clientversion: 1.0.0request-timeout: 30stype: ASYNC  # or SYNCsse: # 设置 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 toolsToolCallbackProvider 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。

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

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

  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去执行命令

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

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

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

@Testpublic 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("\r\n", "\\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源码

MCP鉴权

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

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

STDIO

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

通常我们会配置一段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 框架

本文不会详细介绍 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 服务器加上这两大安全能力。

给 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 {@BeanSecurityFilterChain 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() {}@Beanpublic 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@ConditionalOnMissingBeanpublic WebClient.Builder webClientBuilder() {return WebClient.builder();}@Bean@ConditionalOnMissingBeanpublic ObjectMapper objectMapper() {return new ObjectMapper();}
}

使用:

设置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_tokenreturn (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 进行重新授权

最后:

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/923286.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析:JVM 内存结构

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

gudao网站建设闸北东莞网站建设

7-13 p070找出全部子串位置 分数 5 作者 吴敏华 单位 首都师范大学 输入两个串s1,s2&#xff0c;找出s2在s1中所有出现的位置。 前后两个子串的出现不能重叠。例如’aa’在 aaaa 里出现的位置只有0,2 输入格式: 第一行是整数n 接下来有n行&#xff0c;每行两个不带空格的字符…

深圳微信网站建设报价九游手游平台app

一、利用webapps文件夹自动部署这是最简单的方式&#xff0c;只要将网站直接拷贝到&#xff1a;tomcat根目录下的webapps文件夹里举例&#xff1a;helloworld文件夹下创建里index.html文件&#xff0c;然后把helloworld文件夹移动到tomcat根目录下webapps文件夹里&#xff0c;重…

9/30

今天上午上了一节工程实训的课,其中小车红外线测距系统用到了C语言,第一次真正的用到了所学知识,感觉不错,四节课很快过去了,明天就是国庆,开心

rhel8无法输入中文问题(红帽8安装中文输入法)

问题:明明在设置里添加了汉语拼音输入法缺无法输入中文解决方法: 通过yum安装中文输入法 [root@server01 ~]# dnf install ibus-libpinyin -y到设置里添加新安装的输入法发现还没有新安装的输入法,这时需要重启系统…

威佐夫博弈(Wythoff‘s Game)

威佐夫博弈(Wythoff‘s Game) 1. Beatty 定理 设 无理数 \(r\) 对应的 Beatty 数列 \(B_r\) 为 \((B_r)_i=\lfloor i\times r\rfloor\)。 定理:若无理数 \(r,s\) 满足 \(\frac 1 r+\frac 1 s=1\),则 \(B_r,B_s\) 为…

外贸网站收到询盘淘客网站建设视频

目录 一、modbus简介 二、功能码01、02 三、modbus解析 四、功能码03、04 五、功能码05 六、功能码06 七、功能码16 一、modbus简介 我们在网上查阅modbus的资料发现很多很杂&#xff0c;modbus-RTU ASCII TCP等等&#xff0c;还有跟PLC结合的&#xff0c;地址还分1开…

PWN手成长之路-05-ROP

与远程环境进行交互,可以进行输入,但是输入之后无任何回显。file 查看文件。64位 ELF 。checksec 查看文件安全属性。IDA 打开文件。查看 main 函数的反编译代码。查看 buf 这个字符数组(栈上的缓冲区),本身 buf …

随机采样研究随笔

\(y=x^n\) 在 O(n) 复杂度随机 \(y=(1-x)^a x^b\) 在 O(n) 复杂度随机 线性时间随机 \(n\) 个有序的 \([0,1]\) 实数。 线性时间随机 \(sum=0\) 的一个 1,-1 序列 \(O(n\sqrt n)\) 时间随机一个合法括号序列

Python 正则表达式实战:一文搞定文本处理

在 Python 中,正则表达式(Regular Expression)是一种强大的文本处理工具,用于匹配、搜索、替换等操作。无论是数据清洗、文本解析还是复杂的文本处理任务,正则表达式都能轻松应对。今天,就让我们一起深入学习 Py…

2025-2026-1 20231301 《信息安全设计》第八周学习总结

View Post2025-2026-1 20231301 《信息安全设计》第八周学习总结2025-2026-1 20231301 《信息安全设计》第八周学习总结 目录作业信息学习内容总结一、TLS协议深度解析二、OpenSSL SSL/TLS编程实战三、OpenSSL命令行TL…

太月星网站建设程序开发一键生成微信小程序

你知道吗&#xff0c;C类是编程世界中的一种强大工具&#xff0c;它可以帮助我们更好地组织和管理代码。接下来&#xff0c;我将为你呈现一篇近万字的C类的教程&#xff0c;希望能帮助你熟悉这个概念。 首先&#xff0c;让我们从C类的定义开始。类是一个模板&#xff0c;它描述…

2025-2026-1 20231301 《信息安全设计》第七周学习总结

View Post2025-2026-1 20231301 《信息安全设计》第七周学习总结2025-2026-1 20231301 《信息安全设计》第七周学习总结 目录作业信息学习内容总结第十章:身份认证和PKI理论基础一、PKI体系架构深度解析二、证书处理实…

springboot+vue心理健康服务小程序(源码+文档+调试+基础修改+答疑) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

肉山谷英雄传说新手任务登录英文网站怎么做装修网站建设网

文章目录 前言UUID 处理的更改正则表达式的更改结束 前言 Android 14 已经出来好久好久了… 今天其他的暂且不论&#xff0c;单纯的讲一下 OpenJDK 17 更新的两点变更&#xff08;扒源代码&#xff09;~ 对正则表达式的更改UUID 处理 首先&#xff0c;正则表达式的更改&…

网站备案查询 工信部wordpress 删除标签页

导语 如果之前的单机版hadoop环境安装满足不了你&#xff0c;集群版hadoop一定合你胃口&#xff0c;轻松入手。目录 集群规划前置条件配置免密登录 3.1 生成密匙 3.2 免密登录 3.3 验证免密登录集群搭建 4.1 下载并解压 4.2 配置环境变量 4.4 修改配置 4.4 分发程序 4.5 初始化…

湛江企业自助建站时尚网站设计案例

1. 模型旋转角度尽量取整数&#xff0c;保证线条不会出现锯齿 2. 修改反锯齿模型为FXAA方式&#xff0c;默认的TemporalAA方式会闪烁 3. 动态更新的纹理尺寸一般都不会是2的N次方&#xff0c;比如401X518。 解决方案是 动态更新一张1024x1024的贴图的其中401X518&#xff0c;…

南宁网站建设招聘建设工程合同备案在什么网站上

2016只剩下不到百分之一的时间了&#xff0c;网上陆续看到各种企业或个人的总结或盘点&#xff0c;公司也必须规定每个员工要做年度工作总结&#xff0c;或许是环境释然&#xff0c;心里也有无数次要做总结的念头&#xff0c;尤其是月末年末这种感觉更重&#xff0c;但却没静下…

没用的博客园页面的要素介绍(待更新)

1. 关于那几行字点击查看"<b style=color:rgb(119, 248, 255)>又是一年雨季</b>","<b style=color:rgb(119, 248, 255)>青苔悄悄爬满缝隙</b>","<b style=color:…

详细介绍:Music Tag Web 怎么安装 ffmpeg?

详细介绍:Music Tag Web 怎么安装 ffmpeg?pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &quo…