第7篇:《工具的USB接口:我把Function Calling升级成了MCP》

承上 :上一篇我们给AI装了四个工具,它已经能自动调度查订单、查库存、查物流了。但有个问题让我夜不能寐------这四个工具全是直接用 @Tool 注解写的,跟Spring AI深度绑定。万一哪天老板说"咱们改用LangChain了",所有工具代码都得重写。有没有一种标准,让工具写一次、到处能用?

1. 问题场景:Function Calling的"锁定效应"

先回顾我们现在的工具写法:

less 复制代码
@Component
public class BusinessTools {

    @Tool(description = "查询商品库存")
    public String checkStock(
            @ToolParam(description = "商品名称") String productName) {
        return inventoryService.checkStock(productName);
    }
}

这个写法很爽,但有几个问题:

问题 后果
绑定了Spring AI的 @Tool注解 切换到其他AI框架时,工具全部重写
工具定义和实现耦合 改描述要改代码、重新部署
无法被外部系统复用 其他AI应用想用你的工具,没门
测试困难 必须在Spring容器里跑

后端老鸟的直觉反应:这不就是没有接口标准的年代,每个厂商有自己的驱动吗?JDBC出来之前,连MySQL和Oracle的调用方式都不一样。

我们需要一个"工具的JDBC"------MCP(Model Context Protocol,模型上下文协议)。

2. 核心概念:MCP到底是什么?

MCP(Model Context Protocol,模型上下文协议) 本质上是一个工具调用的标准协议。它定义了AI应用(Client)和工具服务(Server)之间的通信规范。

2.1. 用USB类比

插过U盘吧?不管是金士顿的、闪迪的,还是杂牌的,只要符合USB协议,插上就能用。

MCP就是AI工具的"USB协议":

css 复制代码
Function Calling时代(没有标准):
  AI应用 ←→ 工具A(自己定义的协议)
  AI应用 ←→ 工具B(另一个协议)
  AI应用 ←→ 工具C(又一个协议)
  换个AI应用 → 全部重写

MCP时代(统一标准):
  AI应用 ←→ MCP协议 ←→ 工具A
                   ←→ 工具B
                   ←→ 工具C
  换个AI应用 → 工具不用改,支持MCP就行

2.2. MCP的三大角色

arduino 复制代码
┌─────────────┐      MCP协议      ┌─────────────┐
│  MCP Client │ ◄──────────────► │  MCP Server  │
│  (Spring AI)│                   │  (我们的工具) │
└─────────────┘                   └─────────────┘
       ↑                                 ↑
   消费工具的一方                    提供工具的一方
角色 职责 类比
MCP Server 暴露工具,描述工具的Schema USB设备
MCP Client 发现并调用工具 USB主机(电脑)
MCP协议 定义通信标准 USB协议

2.3. MCP vs @Tool

对比维度 @Tool注解 MCP
复用性 只能在Spring AI里用 任何支持MCP的框架都能用
部署方式 和主应用打包在一起 可以独立部署,远程调用
生命周期 随Spring容器启动/停止 独立进程,随时启停
跨语言 必须用Java 可以用任何语言实现Server
服务发现 只能在同一ApplicationContext里 可以注册到服务注册中心
适用场景 快速开发、内部工具 企业级、多应用共享的工具平台

一句话总结@Tool 是快速开发用的,MCP是企业级工具管理用的。两者可以共存,不是互斥关系。

3. 实战:把天气查询工具从@Tool改成MCP Server

Spring AI支持三种MCP Server

方式 依赖 通信机制 适用场景
STDIO spring-ai-starter-mcp-server 进程间stdin/stdout 本地桌面客户端、IDE插件
WebMVC SSE spring-ai-starter-mcp-server-webmvc HTTP + SSE长链接 传统Spring Boot、同步业务
WebFlux SSE spring-ai-starter-mcp-server-webflux 响应式非阻塞SSE 高并发微服务、云原生

下面逐一实现,每种都包含Server端和Client端

3.1. 方式一:STDIO 传输(本地进程通信)

3.1.1. 什么是STDIO?

不启动HTTP服务器,通过进程的标准输入输出流通信:

arduino 复制代码
MCP Client 启动 Server 进程
  → Client 往 Server 的 stdin 写入 JSON 请求
  → Server 处理后往 stdout 输出 JSON 响应
  → Client 读取 stdout 获取结果

没有网络端口,没有HTTP,纯进程间通信。

3.1.2. Server端实现

依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server</artifactId>
        </dependency>

配置 application.yml

yaml 复制代码
spring:
  main:
    banner-mode: off
    web-application-type: none
  ai:
    mcp:
      server:
        stdio: true
        name: my-weather-server-stdio
        version: 1.0.0
        type: SYNC
logging:
  level:
    root: INFO
    org.springframework.ai: DEBUG
    org.springframework: INFO

工具定义(和之前完全一样):

java 复制代码
package com.yunxi.ai.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class WeatherTools {
    @Tool(description = "查询指定城市在指定日期的天气情况")
    public String getWeather(@ToolParam(description = "城市名称,例如:北京、上海、深圳") String city) {
        // 模拟数据
        Map<String, String> weatherMap = Map.of(
                "北京", "晴,25°C,微风",
                "上海", "多云转阴,28°C,东南风3级",
                "深圳", "雷阵雨,30°C,湿度85%",
                "成都", "阴天,22°C,空气质量优"
        );

        String weather = weatherMap.getOrDefault(city, "数据暂未覆盖该城市");
        return city  + " 天气:" + weather;
    }
}

工具注入

java 复制代码
    @Bean
    public ToolCallbackProvider toolCallbackProvider(WeatherTools weatherTools) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherTools)
                .build();
    }

打包运行

java 复制代码
mvn clean package -DskipTests
java -jar target/chapter-07-mcp-server-stdio.jar

启动后不会监听任何端口,只等待stdin输入。

3.1.3. Client端实现

依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>

配置 application.yml

yaml 复制代码
spring:
  ai:
    mcp:
      client:
        stdio:
          servers-configuration: classpath:mcp-servers-config.json

mcp-servers-config.json:

json 复制代码
{
  "mcpServers": {
    "my-weather-server-stdio": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.stdio=true",
        "-Dspring.main.web-application-type=none",
        "-Dspring.main.banner-mode=off",
        "-Dlogging.pattern.console=",
        "-jar",
        "/Users/yunxi/IdeaProjects/yunxi-spring-ai/chapter-07-mcp-server-stdio/target/chapter-07-mcp-server-stdio-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

Client代码

java 复制代码
package com.yunxi.ai.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class ChatService {

    private final ChatClient chatClient;

    public ChatService(ChatClient.Builder builder, ToolCallbackProvider tools) {
        this.chatClient = builder
                .defaultToolCallbacks(tools)
                .build();
    }

    public String chat(String message) {
        return chatClient.prompt()
                .system("你是一个专业的助手,可以回答用户的问题")
                .user(message)
                .call()
                .content();
    }
}

3.1.4. 测试结果

本地服务测试

Vibe Code工具测试(以Trae为例)

**

**

3.2. 方式二:WebMVC SSE 传输(Servlet同步HTTP-SSE)

3.2.1. 什么是WebMVC SSE?

基于传统Spring MVC的Servlet容器,通过HTTP + SSE长链接通信:

arduino 复制代码
Client发起HTTP连接 → Server保持SSE长链接不关闭
  → Client通过这个长链接持续发送请求
  → Server通过这个长链接持续推送响应

3.2.2. Server端实现

依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

配置 application.yml

yaml 复制代码
server:
  port: 8081
spring:
  ai:
    mcp:
      server:
        stdio: false
        name: my-weather-server-web-sse
        version: 1.0.0
        type: SYNC
        instructions: "本服务提供天气查询相关工具"
        protocol: streamable

工具定义 :和STDIO完全一样,@Tool 注解,不重复贴代码。

3.2.3. Client端实现

依赖不变

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

配置 application.yml

yaml 复制代码
spring:
  ai:
    mcp:
      client:
        type: SYNC                       # 或 ASYNC,根据应用需求选择 [citation:4]
        request-timeout: 60s
        toolcallback:
          enabled: true                  # 必须启用,以便将MCP工具自动注入ChatClient [citation:1][citation:2]
        streamable-http:
          connections:
            my-weather-server: # 自定义连接名称
              url: http://localhost:8081 # 基础地址
              endpoint: /mcp

Client代码 :Client代码和STDIO方式一模一样。传输方式的变化对业务代码完全透明------这就是MCP协议的价值。

适用场景:传统Spring Boot项目、同步阻塞业务、需多客户端远程访问。

3.2.4. 测试结果

本地测试

Vibe Code工具测试(以Trae为例)

json 复制代码
{
  "mcpServers": {
    "my-weather-server": {
      "url": "http://localhost:8081/mcp",
      "headers": {}
    }
  }
}

3.3. 方式三:WebFlux SSE 传输(Reactor异步非阻塞HTTP-SSE)

3.3.1. 什么是WebFlux SSE?

和WebMVC SSE功能一样,但底层用WebFlux响应式引擎 ,非阻塞I/O,更高的并发能力,WebFlux SSE 模式正是为实现异步、流式输出而设计的,不过它背后的含义比单纯的"流式输出"要更丰富一些。

对比 WebMVC SSE WebFlux SSE
线程模型 一个连接一个线程 事件驱动,少量线程
并发能力 受限于线程池大小 轻松支撑万级连接
推荐模式 SYNC ASYNC

根据 Spring AI 官方文档,spring-ai-starter-mcp-server-webflux 这个依赖本身支持多种协议,默认启用的是 SSE 协议。WebFlux 只是一个技术栈选项,它默认适配的 MCP 协议是 SSE,而不是 Streamable-HTTP。

3.3.2. Server端实现

依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>

配置 application.yml (SSE模式)

yaml 复制代码
server:
  port: 8082
spring:
  ai:
    mcp:
      server:
        name: weather-mcp-server-webflux
        version: 1.0.0
        type: ASYNC                       # WebFlux推荐异步模式
        instructions: "本服务提供天气查询相关工具"
        stdio: false

配置 application.yml Streamable-HTTP 模式

yaml 复制代码
server:
  port: 8082
spring:
  ai:
    mcp:
      server:
        protocol: STREAMABLE   # 添加这一行,显式指定协议
        name: weather-mcp-server-webflux
        version: 1.0.0
        type: ASYNC
        instructions: "本服务提供天气查询相关工具"
        stdio: false

工具定义 :依然是一样的 @Tool 注解。

3.3.3. Client端实现

依赖 :同样用 spring-ai-starter-mcp-client

配置 application.yml (SSE模式)

yaml 复制代码
spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            my-webflux-server:
              url: http://localhost:8082

配置 application.yml Streamable-HTTP 模式

yaml 复制代码
spring:
  ai:
    mcp:
      client:
        type: SYNC                       # 或 ASYNC,根据应用需求选择 [citation:4]
        request-timeout: 60s
        toolcallback:
          enabled: true                  # 必须启用,以便将MCP工具自动注入ChatClient [citation:1][citation:2]
        streamable-http: # 使用 Streamable-HTTP 传输
          connections:
            my-webflux-server: # 自定义连接名
              url: http://localhost:8082  # 服务端基础地址
              # endpoint 默认就是 /mcp,可以省略

Client代码三套Client代码完全一样。

适用场景:高并发微服务、云原生部署、大量并发客户端。

3.3.4. 测试结果

本地测试

Vibe Code工具测试(以Trae为例)

json 复制代码
{
  "mcpServers": {
    "my-webflux-weather-server": {
      "url": "http://localhost:8082/sse"
    }
  }
}

3.3.5. /mcp vs /sse:一个核心区别,让你彻底分清

/mcp ****是 Streamable-HTTP 协议的默认端点, /sse ****是 SSE 协议的默认端点。两者是不同协议下的接口,而不是同一个协议的不同路径。

对比维度 /sse /mcp
所属协议 SSE(Server-Sent Events) Streamable-HTTP
协议状态 已弃用(Deprecated) 官方推荐
通信方式 先连 /sse,再通过 /mcp/message发请求 所有请求都走 /mcp
所需端点数量 2 个(/sse+ /mcp/message 1 个(/mcp
配置方式 默认,或 protocol: SSE 需显式配置 protocol: STREAMABLE

为什么会有两个?

在 MCP 协议的早期版本中,SSE 是实现服务器推送的主要方式。它需要客户端先访问 /sse 建立长连接,再通过 /mcp/message 发送请求,响应走 SSE 推送回来。这种设计需要维护两个端点,会话管理也比较复杂。

后来 MCP 协议升级,推出了 Streamable-HTTP 模式。它把通信简化为一个 /mcp 端点,所有请求和响应都通过这个端点完成。因为更简单、更灵活,它已经成为官方推荐的方式。

所以现在大家看到的最多的是/mcp。

4. 本篇小结

这一篇我们完整实现了MCP的三种传输方式:

方式 Server关键配置 Client关键配置
STDIO stdio: true command+ args
WebMVC SSE 引入webmvc依赖 + 配置端口 url: http://host/mcp
WebFlux SSE 引入webflux依赖 + 配置端口 + ASYNC url: http://host/mcp+ ASYNC

核心心法

  • 工具代码零改动@Tool 注解一套代码,三种方式通用
  • Client代码零改动 :注入 McpClient,配置决定传输方式
  • 切换传输方式只需改配置:从本地开发(STDIO)到测试环境(WebMVC)到生产(WebFlux),只改yml

下一篇,我们要更进一步------自己写Server还不够爽,直接接入社区里现成的MCP Server,让AI能读文件、查数据库、甚至操作GitHub仓库。


本文与DeepSeek协作完成

相关推荐
腻害兔2 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析——从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?
java·前端·javascript·vue.js·产品经理·ai编程
大数据点灯人2 小时前
【AI编程】Vibe Coding 模型选型:越贵越好吗?效果/速度/成本平衡指南
编程·ai编程·claude·codex·vibe coding
oort1232 小时前
吃上了自家的细糠,还挺丝滑,用起来手感还行,OortCloud发布新版AI编程平台,下载 OortCodex,Token多,免费薅
大数据·开发语言·人工智能·ai编程
小虎AI生活3 小时前
WorkBuddy 自动化实战,TTS 配音从安装到跑通
ai编程
小虎AI生活5 小时前
workbuddy 短视频获客的终极形态,不是做内容,是建工厂
ai编程
钱六两5 小时前
#5、Spring AI Tool Calling 深度解读(从概念>原理>定义工具>使用工具>核心接口剖析)
ai编程
AI分享猿7 小时前
重度长上下文开发怎么选?先看缓存是否吃额度
缓存·ai编程
亦暖筑序7 小时前
AgentScope-Java 入门:用 Middleware 审计 Agent 调用
java·ai编程·agentscope
子昕7 小时前
Opus 5只要Fable一半价格,我准备把Claude续上了
ai编程