<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      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種方式。

      使用

      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 是指:項目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。

      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也是一樣的原理:

      @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("\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 {
      
          @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();
          }
      }
      

      使用:

      設置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 進行重新授權

      最后:

      “在這個最后的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回復是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮斗。感謝你們,我們總會在某個時刻再次相遇。”

      在這里插入圖片描述

      posted @ 2025-09-30 21:37  Rainbow-Sea  閱讀(297)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 久久夜色精品国产亚洲a| 日本欧美一区二区三区在线播放| av日韩精品在线播放| 开心一区二区三区激情| 蜜臀av性久久久久蜜臀aⅴ麻豆| 人妻 日韩精品 中文字幕| 国产精品99区一区二区三| 欧美极品色午夜在线视频| 国产四虎永久免费观看| 少妇人妻精品无码专区视频| 一本大道无码av天堂| 亚洲国产大片永久免费看| 中文人妻无码一区二区三区在线 | 国产精品普通话国语对白露脸| 国产成人高清精品免费软件| 国产精品第一页中文字幕| japanese无码中文字幕| 亚洲国产精品美日韩久久| 精品人妻中文字幕在线 | 婷婷五月综合丁香在线| 欧美肥老太牲交大战| 人妻精品久久无码区| 精品视频不卡免费观看| 福利一区二区在线视频| 国产伦一区二区三区久久| 韩国18禁啪啪无遮挡免费| 狠狠色丁香婷婷综合尤物| 精品国产亚洲午夜精品a| 欧洲精品码一区二区三区| 欧美亚洲国产成人一区二区三区| 亚欧美日韩香蕉在线播放视频| 性高湖久久久久久久久| 亚洲男人av天堂久久资源| 女人色熟女乱| 中文字幕国产精品av| 精品久久精品久久精品久久| 欧美成人www免费全部网站| 亚洲五月天一区二区三区| 少妇xxxxx性开放| 久久亚洲精品11p| 国产精品久久国产丁香花|