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

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-json">{
    <span style="color:#015692">"mcpServers"</span>: {
        <span style="color:#015692">"baidu-map"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"cmd"</span>,
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"/c"</span>,
                <span style="color:#54790d">"npx"</span>,
                <span style="color:#54790d">"-y"</span>,
                <span style="color:#54790d">"@baidumap/mcp-server-baidu-map"</span>
            ],
            <span style="color:#015692">"env"</span>: {
                <span style="color:#015692">"BAIDU_MAP_API_KEY"</span>: <span style="color:#54790d">"LEyBQxG9UzR9C1GZ6zDHsFDVKvBem2do"</span>
            }
        },
        <span style="color:#015692">"filesystem"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"cmd"</span>,
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"/c"</span>,
                <span style="color:#54790d">"npx"</span>,
                <span style="color:#54790d">"-y"</span>,
                <span style="color:#54790d">"@modelcontextprotocol/server-filesystem"</span>,
                <span style="color:#54790d">"C:/Users/tuling/Desktop"</span>
            ]
        },
        <span style="color:#015692">"mcp-server-weather"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"java"</span>,
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"-Dspring.ai.mcp.server.stdio=true"</span>,
                <span style="color:#54790d">"-Dlogging.pattern.console="</span>,
                <span style="color:#54790d">"-jar"</span>,
                <span style="color:#54790d">"D:\\ideaworkspace\\git_pull\\tuling-flight-booking_all\\mcp-stdio-server\\target\\mcp-stdio-server-xs-1.0.jar"</span>
            ]
        }
    }
}
</code></span></span>
  1. 开启cline权限

6.测试:

通过 Spring AI 接入 第三方的 MCP Server
  1. 依赖
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-xml"><span style="color:#656e77"><!--既支持sse\也支持Stdio--></span>
<<span style="color:#b75501">dependency</span>>
    <<span style="color:#b75501">groupId</span>>org.springframework.ai</<span style="color:#b75501">groupId</span>>
    <<span style="color:#b75501">artifactId</span>>spring-ai-starter-mcp-client-webflux</<span style="color:#b75501">artifactId</span>>
</<span style="color:#b75501">dependency</span>>
</code></span></span>

2 配置

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

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

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-json">{
    <span style="color:#015692">"mcpServers"</span>: {
        <span style="color:#015692">"baidu-map"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"cmd"</span>,
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"/c"</span>,
                <span style="color:#54790d">"npx"</span>,
                <span style="color:#54790d">"-y"</span>,
                <span style="color:#54790d">"@baidumap/mcp-server-baidu-map"</span>
            ],
            <span style="color:#015692">"env"</span>: {
                <span style="color:#015692">"BAIDU_MAP_API_KEY"</span>: <span style="color:#54790d">"xxxx"</span>
            }
        },
        <span style="color:#015692">"filesystem"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"cmd"</span>,
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"/c"</span>,
                <span style="color:#54790d">"npx"</span>,
                <span style="color:#54790d">"-y"</span>,
                <span style="color:#54790d">"@modelcontextprotocol/server-filesystem"</span>,
                <span style="color:#54790d">"C:/Users/tuling/Desktop"</span>
            ]
        },
        <span style="color:#015692">"mcp-server-weather"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"java"</span>,
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"-Dspring.ai.mcp.server.stdio=true"</span>,
                <span style="color:#54790d">"-Dlogging.pattern.console="</span>,
                <span style="color:#54790d">"-jar"</span>,
                <span style="color:#54790d">"D:\\xxx\\target\\mcp-stdio-server-xs-1.0.jar"</span>
            ]
        }
    }
}
</code></span></span>
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-json">{
    <span style="color:#015692">"mcpServers"</span>: {
      <span style="color:#656e77">// 外部第三方的</span>
        <span style="color:#015692">"baidu-map"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"cmd"</span>,
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"/c"</span>,
                <span style="color:#54790d">"npx"</span>,
                <span style="color:#54790d">"-y"</span>,
                <span style="color:#54790d">"@baidumap/mcp-server-baidu-map"</span>
            ],
            <span style="color:#015692">"env"</span>: {
                <span style="color:#015692">"BAIDU_MAP_API_KEY"</span>: <span style="color:#54790d">"xxxx"</span>
            }
        },
       <span style="color:#656e77">// 外部第三方的</span>
        <span style="color:#015692">"filesystem"</span>: {
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"cmd"</span>,  <span style="color:#656e77">// 指明使用 cmd 命令执行</span>
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"/c"</span>,
                <span style="color:#54790d">"npx"</span>,
                <span style="color:#54790d">"-y"</span>,
                <span style="color:#54790d">"@modelcontextprotocol/server-filesystem"</span>,
                <span style="color:#54790d">"C:/Users/tuling/Desktop"</span>
            ]
        },
       <span style="color:#656e77">// 自定义的 mcp 服务</span>
        <span style="color:#015692">"mcp-server-weather"</span>: {  <span style="color:#656e77">// 对应的项目名 application的 name</span>
            <span style="color:#015692">"command"</span>: <span style="color:#54790d">"java"</span>, <span style="color:#656e77">// 指明通过 java 命令执行,java 解析可以直接识别到</span>
            <span style="color:#015692">"args"</span>: [
                <span style="color:#54790d">"-Dspring.ai.mcp.server.stdio=true"</span>,
                <span style="color:#54790d">"-Dlogging.pattern.console="</span>, <span style="color:#656e77">// 清空控制台,不然会输入很多信息</span>
                <span style="color:#54790d">"-jar"</span>, <span style="color:#656e77">// -jar 启动 Spring Boot</span>
                <span style="color:#54790d">"D:\\xxx\\target\\mcp-stdio-server-xs-1.0.jar"</span> <span style="color:#656e77">// 自定义的mcp服务的jar路径</span>
            ]
        }
    }
}
</code></span></span>
  1. 绑定到Chatclient
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#656e77">/**
 * <span style="color:#015692">@description</span>: 智能航空助手:
 */</span>
<span style="color:#015692">@RestController</span>
<span style="color:#015692">@CrossOrigin</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">OpenAiController</span> {
    
    <span style="color:#015692">private</span> <span style="color:#015692">final</span> ChatClient chatClient;
    
    <span style="color:#015692">public</span> <span style="color:#b75501">OpenAiController</span>(
            DashScopeChatModel dashScopeChatModel,
                            // 配置引入 外部 mcp tools
                            ToolCallbackProvider mcpTools) {
        <span style="color:#b75501">this</span>.chatClient =ChatClient.builder(dashScopeChatModel)
        .defaultToolCallbacks(mcpTools)  <span style="color:#656e77">// 将外部的 mcop tools 对大模型进行绑定,这里是构造器的绑定,不是单个对话的绑定</span>
        .build();
    }
    

 <span style="color:#015692">@CrossOrigin</span>
<span style="color:#015692">@GetMapping(value = "/ai/generateStreamAsString", produces = MediaType.TEXT_EVENT_STREAM_VALUE)</span>
<span style="color:#015692">public</span> Flux<String> <span style="color:#b75501">generateStreamAsString</span>(<span style="color:#015692">@RequestParam(value = "message", defaultValue = "讲个笑话")</span> String message) {

    Flux<String> content = chatClient.prompt()
            .user(message)
            .stream()
            .content();

    <span style="color:#015692">return</span>  content;

    }
</code></span></span>
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-yaml"><span style="color:#656e77"># 调试日志</span>
<span style="color:#015692">logging:</span>
  <span style="color:#015692">level:</span>
    <span style="color:#015692">io:</span>
      <span style="color:#015692">modelcontextprotocol:</span>
        <span style="color:#015692">client:</span> <span style="color:#54790d">DEBUG</span>
        <span style="color:#015692">spec:</span> <span style="color:#54790d">DEBUG</span>
</code></span></span>
使用 Spring AI 接入 自定义MCP Server

创建一个spring ai项目

  1. 依赖
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-xml"><span style="color:#656e77"><!--mcp-server  --></span>
<<span style="color:#b75501">dependency</span>>
  <<span style="color:#b75501">groupId</span>>org.springframework.ai</<span style="color:#b75501">groupId</span>>
  <<span style="color:#b75501">artifactId</span>>spring-ai-starter-mcp-server</<span style="color:#b75501">artifactId</span>>
</<span style="color:#b75501">dependency</span>>

 <<span style="color:#b75501">dependencyManagement</span>>
        <<span style="color:#b75501">dependencies</span>>
            <span style="color:#656e77"><!--spring ai 包管理依赖 --></span>
            <<span style="color:#b75501">dependency</span>>
                <<span style="color:#b75501">groupId</span>>org.springframework.ai</<span style="color:#b75501">groupId</span>>
                <<span style="color:#b75501">artifactId</span>>spring-ai-bom</<span style="color:#b75501">artifactId</span>>
                <<span style="color:#b75501">version</span>>${spring-ai.version}</<span style="color:#b75501">version</span>>
                <<span style="color:#b75501">type</span>>pom</<span style="color:#b75501">type</span>>
                <<span style="color:#b75501">scope</span>>import</<span style="color:#b75501">scope</span>>
            </<span style="color:#b75501">dependency</span>>
        </<span style="color:#b75501">dependencies</span>>
  </<span style="color:#b75501">dependencyManagement</span>>

<span style="color:#656e77"><!-- 打包 --></span>
<<span style="color:#b75501">build</span>>
        <<span style="color:#b75501">plugins</span>>
            <<span style="color:#b75501">plugin</span>>
                <<span style="color:#b75501">groupId</span>>org.springframework.boot</<span style="color:#b75501">groupId</span>>
                <<span style="color:#b75501">artifactId</span>>spring-boot-maven-plugin</<span style="color:#b75501">artifactId</span>>
                <<span style="color:#b75501">executions</span>>
                    <<span style="color:#b75501">execution</span>>
                        <<span style="color:#b75501">goals</span>>
                            <<span style="color:#b75501">goal</span>>repackage</<span style="color:#b75501">goal</span>>
                        </<span style="color:#b75501">goals</span>>
                    </<span style="color:#b75501">execution</span>>
                </<span style="color:#b75501">executions</span>>
            </<span style="color:#b75501">plugin</span>>
        </<span style="color:#b75501">plugins</span>>
    </<span style="color:#b75501">build</span>>
</code></span></span>
  1. 添加工具
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Service</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">UserToolService</span> {

    Map<String,Double> userScore = Map.of(
        <span style="color:#54790d">"xushu"</span>,<span style="color:#b75501">99.0</span>,
        <span style="color:#54790d">"zhangsan"</span>,<span style="color:#b75501">2.0</span>,
        <span style="color:#54790d">"lisi"</span>,<span style="color:#b75501">3.0</span>);
    <span style="color:#015692">@Tool(description = "获取用户分数")</span>
    <span style="color:#015692">public</span> String <span style="color:#b75501">getScore</span>(String username) { <span style="color:#656e77">// 也可以添加上 @ToolParam(description=“” )告诉大模型这个参数的描述是做什么的</span>
        <span style="color:#015692">if</span>(userScore.containsKey(userName)){
            <span style="color:#015692">return</span> userScore.get(userName).toString();
        }  

        <span style="color:#015692">return</span> <span style="color:#54790d">"未检索到当前用户"</span>+userName;
    }
}
</code></span></span>
  1. 暴露工具
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Bean</span>  <span style="color:#656e77">// 将我们编写的 tools 对外的UserToolService 绑定上去</span>
<span style="color:#015692">public</span> ToolCallbackProvider <span style="color:#b75501">weatherTools</span>(UserToolService userToolService) {
    <span style="color:#015692">return</span> MethodToolCallbackProvider.builder().toolObjects(userToolService).build();
}
</code></span></span>
  1. 配置
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-yaml"><span style="color:#015692">spring:</span>
  <span style="color:#015692">main:</span>
    <span style="color:#015692">banner-mode:</span> <span style="color:#54790d">off</span>
  <span style="color:#015692">ai:</span>
    <span style="color:#015692">mcp:</span>
      <span style="color:#015692">server:</span>
        <span style="color:#015692">name:</span> <span style="color:#54790d">my-weather-server</span>
        <span style="color:#015692">version:</span> <span style="color:#b75501">0.0</span><span style="color:#b75501">.1</span>
</code></span></span>

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

  1. 打包 mvn package

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

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

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

  1. 依赖
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-xml">      <span style="color:#656e77"><!--mcp服务器核心依赖— 响应式--></span>
      <<span style="color:#b75501">dependency</span>>
        <<span style="color:#b75501">groupId</span>>org.springframework.ai</<span style="color:#b75501">groupId</span>>
        <<span style="color:#b75501">artifactId</span>>spring-ai-starter-mcp-server-webflux</<span style="color:#b75501">artifactId</span>>
      </<span style="color:#b75501">dependency</span>>
      <span style="color:#656e77"><!-- 这个 SSE 是需要 Web 的 --></span>
      <<span style="color:#b75501">dependency</span>>
        <<span style="color:#b75501">groupId</span>>org.springframework</<span style="color:#b75501">groupId</span>>
        <<span style="color:#b75501">artifactId</span>>spring-boot-starter-web</<span style="color:#b75501">artifactId</span>>
      </<span style="color:#b75501">dependency</span>>
</code></span></span>
  1. 定义外部工具
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Service</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">UserToolService</span> {

    Map<String,Double> userScore = Map.of(
            <span style="color:#54790d">"xushu"</span>,<span style="color:#b75501">99.0</span>,
            <span style="color:#54790d">"zhangsan"</span>,<span style="color:#b75501">2.0</span>,
            <span style="color:#54790d">"lisi"</span>,<span style="color:#b75501">3.0</span>);
    <span style="color:#015692">@Tool(description = "获取用户分数")</span>
    <span style="color:#015692">public</span> String <span style="color:#b75501">getScore</span>(String username) {
        <span style="color:#015692">if</span>(userScore.containsKey(username)){
            <span style="color:#015692">return</span> userScore.get(username).toString();
        }

        <span style="color:#015692">return</span> <span style="color:#54790d">"未检索到当前用户"</span>;
    }
}
</code></span></span>
  1. 暴露工具
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Bean</span>
    <span style="color:#015692">public</span> ToolCallbackProvider <span style="color:#b75501">weatherToolCallbackProvider</span>(WeatherService weatherService,
                                                            UserToolService userToolService) {
        <span style="color:#015692">return</span> MethodToolCallbackProvider.builder().toolObjects(userToolService).build();
    }
</code></span></span>
  1. 配置(需要用 web 启动)
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-yaml"><span style="color:#015692">server:</span>
  <span style="color:#015692">port:</span> <span style="color:#b75501">8088</span>
</code></span></span>
MCP Client

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

  1. 添加依赖
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-xml"><span style="color:#656e77"><!--既支持sse\也支持Stdio--></span>
<<span style="color:#b75501">dependency</span>>
  <<span style="color:#b75501">groupId</span>>org.springframework.ai</<span style="color:#b75501">groupId</span>>
  <<span style="color:#b75501">artifactId</span>>spring-ai-starter-mcp-client-webflux</<span style="color:#b75501">artifactId</span>>
</<span style="color:#b75501">dependency</span>>
</code></span></span>
  1. 配置
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-yaml"><span style="color:#015692">spring:</span>
  <span style="color:#015692">ai:</span>
    <span style="color:#015692">mcp:</span>
      <span style="color:#015692">client:</span>
        <span style="color:#015692">enabled:</span> <span style="color:#b75501">true</span>
        <span style="color:#015692">name:</span> <span style="color:#54790d">my-mcp-client</span>
        <span style="color:#015692">version:</span> <span style="color:#b75501">1.0</span><span style="color:#b75501">.0</span>
        <span style="color:#015692">request-timeout:</span> <span style="color:#54790d">30s</span>
        <span style="color:#015692">type:</span> <span style="color:#54790d">ASYNC</span>  <span style="color:#656e77"># or SYNC</span>
        <span style="color:#015692">sse:</span> <span style="color:#656e77"># 设置 sse 输出方式</span>
          <span style="color:#015692">connections:</span>
            <span style="color:#015692">server1:</span>
              <span style="color:#015692">url:</span> <span style="color:#54790d">http://localhost:8088</span>
</code></span></span>
  1. 代码
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#656e77">/**
 * <span style="color:#015692">@author</span> wx:程序员徐庶
 * <span style="color:#015692">@version</span> 1.0
 * <span style="color:#015692">@description</span>: 智能航空助手:需要一对一解答关注wx: 程序员徐庶
 */</span>
<span style="color:#015692">@RestController</span>
<span style="color:#015692">@CrossOrigin</span>
<span style="color:#015692">public</span> <span style="color:#015692">class</span> <span style="color:#b75501">OpenAiController</span> {

    <span style="color:#015692">private</span> <span style="color:#015692">final</span> ChatClient chatClient;

    <span style="color:#015692">public</span> <span style="color:#b75501">OpenAiController</span>(
        DashScopeChatModel dashScopeChatModel,
        // 外部 mcp tools
        ToolCallbackProvider mcpTools) {
        <span style="color:#b75501">this</span>.chatClient =ChatClient.builder(dashScopeChatModel)
        .defaultToolCallbacks(mcpTools)
        .build();
    }


    <span style="color:#015692">@CrossOrigin</span>
    <span style="color:#015692">@GetMapping(value = "/ai/generateStreamAsString", produces = MediaType.TEXT_EVENT_STREAM_VALUE)</span>
    <span style="color:#015692">public</span> Flux<String> <span style="color:#b75501">generateStreamAsString</span>(<span style="color:#015692">@RequestParam(value = "message", defaultValue = "讲个笑话")</span> String message) {

        Flux<String> content = chatClient.prompt()
        .user(message)
        .stream()
        .content();

        <span style="color:#015692">return</span>  content;

    }
</code></span></span>
原理
  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里面有一个对象用于执行命令:
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#b75501">ProcessBuilder</span> <span style="color:#54790d">processBuilder</span> <span style="color:#ab5656">=</span> <span style="color:#015692">new</span> <span style="color:#b75501">ProcessBuilder</span>();
        processBuilder.command(<span style="color:#54790d">"java"</span>,<span style="color:#54790d">"-version"</span>);

        <span style="color:#b75501">Process</span> <span style="color:#54790d">process</span> <span style="color:#ab5656">=</span> processBuilder.start();

        process.errorReader().lines().forEach(System.out::println);
</code></span></span>
  1. 所以springAi底层相当于读取到信息后, 会通过processBuilder去执行命令
<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java">String[] commands={<span style="color:#54790d">"java"</span>,
                <span style="color:#54790d">"-Dspring.ai.mcp.server.stdio=true"</span>,
                <span style="color:#54790d">"-Dlogging.pattern.console="</span>,
                <span style="color:#54790d">"-jar"</span>,
                <span style="color:#54790d">"D:\\ideaworkspace\\git_pull\\tuling-flight-booking_all\\mcp-stdio-server\\target\\mcp-stdio-server-xs-1.0.jar"</span>};

        <span style="color:#b75501">ProcessBuilder</span> <span style="color:#54790d">processBuilder</span> <span style="color:#ab5656">=</span> <span style="color:#015692">new</span> <span style="color:#b75501">ProcessBuilder</span>();
        processBuilder.command(commands);
        <span style="color:#656e77">// processBuilder.environment().put("username","xushu");</span>

        <span style="color:#b75501">Process</span> <span style="color:#54790d">process</span> <span style="color:#ab5656">=</span> processBuilder.start();
</code></span></span>

其实你也完全可以自己通过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也是一样的原理:

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-java"><span style="color:#015692">@Test</span>
    <span style="color:#015692">public</span> <span style="color:#015692">void</span> <span style="color:#b75501">test</span>() <span style="color:#015692">throws</span> IOException, InterruptedException {
        String[] commands={<span style="color:#54790d">"java"</span>,
                <span style="color:#54790d">"-Dspring.ai.mcp.server.stdio=true"</span>,
                <span style="color:#54790d">"-Dlogging.pattern.console="</span>,
                <span style="color:#54790d">"-jar"</span>,
                <span style="color:#54790d">"D:\\ideaworkspace\\git_pull\\tuling-flight-booking_all\\mcp-stdio-server\\target\\mcp-stdio-server-xs-1.0.jar"</span>};

        <span style="color:#b75501">ProcessBuilder</span> <span style="color:#54790d">processBuilder</span> <span style="color:#ab5656">=</span> <span style="color:#015692">new</span> <span style="color:#b75501">ProcessBuilder</span>();
        processBuilder.command(commands);
        processBuilder.environment().put(<span style="color:#54790d">"username"</span>,<span style="color:#54790d">"xushu"</span>);

        <span style="color:#b75501">Process</span> <span style="color:#54790d">process</span> <span style="color:#ab5656">=</span> processBuilder.start();

        <span style="color:#b75501">Thread</span> <span style="color:#54790d">thread</span> <span style="color:#ab5656">=</span> <span style="color:#015692">new</span> <span style="color:#b75501">Thread</span>(() -> {
            <span style="color:#015692">try</span> (<span style="color:#b75501">BufferedReader</span> <span style="color:#54790d">processReader</span> <span style="color:#ab5656">=</span> <span style="color:#015692">new</span> <span style="color:#b75501">BufferedReader</span>(<span style="color:#015692">new</span> <span style="color:#b75501">InputStreamReader</span>(process.getInputStream()))) {
                String line;
                <span style="color:#015692">while</span> ((line=processReader.readLine())!=<span style="color:#b75501">null</span>) {
                        System.out.println(line);
                }
            } <span style="color:#015692">catch</span> (IOException e) {
                e.printStackTrace();
            }
        });
        thread.start();


        Thread.sleep(<span style="color:#b75501">1000</span>);

        <span style="color:#015692">new</span> <span style="color:#b75501">Thread</span>(() -> {

            <span style="color:#015692">try</span> {
                <span style="color:#656e77">//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\"}}}";</span>
                <span style="color:#b75501">String</span> <span style="color:#54790d">jsonMessage</span> <span style="color:#ab5656">=</span> <span style="color:#54790d">"{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":\"3b3f3431-1\",\"params\":{}}"</span>;

                jsonMessage = jsonMessage.replace(<span style="color:#54790d">"\r\n"</span>, <span style="color:#54790d">"\\n"</span>).replace(<span style="color:#54790d">"\n"</span>, <span style="color:#54790d">"\\n"</span>).replace(<span style="color:#54790d">"\r"</span>, <span style="color:#54790d">"\\n"</span>);

                <span style="color:#b75501">var</span> <span style="color:#54790d">os</span> <span style="color:#ab5656">=</span> process.getOutputStream();
                <span style="color:#015692">synchronized</span> (os) {
                    os.write(jsonMessage.getBytes(StandardCharsets.UTF_8));
                    os.write(<span style="color:#54790d">"\n"</span>.getBytes(StandardCharsets.UTF_8));
                    os.flush();
                }
                System.out.println(<span style="color:#54790d">"写入完成!"</span>);
            }<span style="color:#015692">catch</span> (IOException e){
                e.printStackTrace();
            }
        }).start();


        thread.join();
        <span style="color:#656e77">/*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]]]*/</span>
    }
</code></span></span>
  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不对, 就没有使用权限。

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-json"><span style="color:#015692">"baidu-map"</span>: {
  <span style="color:#015692">"command"</span>: <span style="color:#54790d">"cmd"</span>,
  <span style="color:#015692">"args"</span>: [
    <span style="color:#54790d">"/c"</span>,
    <span style="color:#54790d">"npx"</span>,
    <span style="color:#54790d">"-y"</span>,
    <span style="color:#54790d">"@baidumap/mcp-server-baidu-map"</span>
  ],
  <span style="color:#015692">"env"</span>: {
    <span style="color:#015692">"BAIDU_MAP_API_KEY"</span>: <span style="color:#54790d">"LEyBQxG9UzR9C1GZ6zDHsFDVKvBem2do"</span>
  }
},
</code></span></span>

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

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

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

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

Logo

更多推荐