【SpringAIAlibaba新手村系列】(14)MCP 本地服务与工具集成

0 阅读1分钟

第十四章 MCP 本地服务与工具集成

版本标注

  • Spring AI: 1.1.2
  • Spring AI Alibaba: 1.1.2.0

章节定位

  • 第十三章讨论的是 Tool Calling 的工作机制。
  • 这一章转到 MCP Server 视角,重点不再是“模型如何调用工具”,而是“本地能力如何作为服务提供出去”。
  • 下一章再继续写 MCP Client,连接并调用这里提供的本地服务。

s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > s13 > [ s14 ] s15 > s16 > s17 > s18

一、本章要解决的问题

第十三章中,工具调用发生在当前应用内部,链路大致如下:

用户问题 -> ChatClient -> 模型决策 -> 本地 @Tool 方法

这种方式适合在单个项目中快速接入工具,但它仍然属于应用内部调用。到了 MCP 场景,关注点会前移到服务提供侧:如果已经有天气、文件、地图等能力,如何将它们做成一个本地 MCP 服务,供其他客户端统一连接和调用。

因此,这一章的主语是 MCP Server,下一章的主语才是 MCP Client。两章分别对应“提供服务”和“使用服务”两个视角。

二、本地 MCP Server 的含义

本地 MCP Server 指的是运行在本机上的 MCP 服务端。它负责把项目中的业务能力按 MCP 协议暴露出去,使这些能力不再只是当前工程里的内部方法,而是可以被外部客户端发现和调用的标准化工具。

放到这一章里,可以把它理解为三层结构:

  1. 业务能力层:例如天气查询服务、地图查询服务、文件读写服务。
  2. Spring Bean 层:这些能力被交给 Spring 管理。
  3. MCP 暴露层:MCP Server 发现其中可暴露的方法,并将其作为工具提供给客户端。

这里最容易混淆的地方在于,@Tool 本身并不等于 MCP Server。@Tool 只负责描述“这个方法可以作为工具能力”,真正将其暴露为 MCP 服务,还需要 MCP Server 依赖和自动配置参与。

三、代码结构

本章直接相关的代码只有两部分。

3.0 服务端配置

除了业务类和配置类之外,服务端还需要在 application.yml 中显式声明使用 Streamable-HTTP 协议。配置可以写成这样:

server:
  port: 8014

spring:
  application:
    name: mcp-server-demo

  ai:
    mcp:
      server:
        type: async
        protocol: STREAMABLE
        name: customer-define-mcp-server
        version: 1.0.0

这段配置的作用是把当前应用明确切到 Streamable-HTTP 方式。这样下一章客户端侧配置中的:

  • url: http://localhost:8014
  • endpoint: /mcp

就和本章服务端形成了一一对应的关系。

3.1 业务服务类

package cn.edu.nnu.opengms.service;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class WeatherService {
    @Tool(description = "根据城市名称获取天气预报")
    public String getWeatherByCity(String city)
    {
        Map<String, String> map = Map.of(
                "北京", "11111降雨频繁,其中今天和后天雨势较强,部分地区有暴雨并伴强对流天气,需注意",
                "上海", "22222多云,15℃~27℃,南风3级,当前温度27℃。",
                "深圳", "333333多云40天,阴16天,雨30天,晴3天"
        );
        return map.getOrDefault(city, "没有该城市的天气信息");
    }
}

WeatherService 是一个普通的 Spring 业务类,真正有意义的是其中这个 @Tool 方法。它向框架表达的是:getWeatherByCity 可以作为一个工具被暴露出去。

3.2 配置类

package cn.edu.nnu.opengms.config;

import cn.edu.nnu.opengms.service.WeatherService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;

/**
 * MCP Server 配置
 * 作用:把 WeatherService 中的工具方法整理为可对外暴露的工具提供者
 */
@Configuration
public class McpServerConfig
{
    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService)
    {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherService)
                .build();
    }
}

这段配置的含义,比单纯注册一个 WeatherService Bean 更明确。

WeatherService 负责提供业务能力,MethodToolCallbackProvider 负责从这个对象中提取 @Tool 方法,并将它们组织成 ToolCallbackProvider。这样一来,MCP Server 拿到的就不再只是一个普通业务 Bean,而是一组已经整理好的工具定义。

如果项目中已经加入下面这个依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>

那么当前工程就已经具备 MCP Server 的运行基础。此时 WeatherService 中被 @Tool 标记的方法,不再只是本地工具候选,而是会通过 ToolCallbackProvider 进入 MCP Server 的能力暴露链路。

四、这两段代码到底算不算“已经实现了 MCP”

从章节定位和工程意图上看,这已经不是单纯的本地 Tool Calling 示例了。因为项目已经引入了 spring-ai-starter-mcp-serverWeatherService 通过 @Tool 声明了可暴露的工具能力,配置类又通过 ToolCallbackProvider 显式将这些工具交给服务端管理。这说明已经进入了 MCP Server 的实现范围。

但如果从“是否已经完全跑通”这个角度判断,仅凭这两个类还不够。严格来说,还需要满足下面几个条件:

  1. MCP Server 相关自动配置正常生效。
  2. @Tool 方法被服务端正确发现并注册。
  3. 应用启动后,客户端能够通过 MCP 方式连接并调用该工具。

到这里,本地 MCP Server 的基础实现已经具备,天气服务也已经具备作为 MCP 工具对外暴露的条件。是否完全跑通,则以下一章客户端成功连接和调用为最终验证。

五、为什么它看起来仍然像 Tool Calling

这里确实很容易产生一种感觉:@ToolToolCallbackProviderMethodToolCallbackProvider 这些名字,看上去还是和 Tool Calling 很像。

原因并不复杂。MCP Server 在 Spring AI 里的底层工具抽象,本来就是沿着 Tool 回调体系组织起来的。也就是说,@Tool 并不是只服务于上一章的本地 Tool Calling,它同时也是 MCP Server 暴露工具能力时使用的基础描述方式。

两者的区别不在注解长得像不像,而在于这些工具最终交给谁使用:

  1. 如果工具被注册给 ChatClient.defaultTools(...),主语就是当前应用内部的调用方,这更接近第十三章的 Tool Calling。
  2. 如果工具被整理成 ToolCallbackProvider 交给 MCP Server,主语就是服务提供方,这就是第十四章的 MCP Server。

因此,代码形态上会有相似之处,但章节含义已经变了。第十三章关注的是“模型在当前应用中怎么调工具”,第十四章关注的是“服务端怎么把工具能力整理并暴露出去”。

六、MCP Server 暴露工具的原理

这一章如果只停留在“把 WeatherService 包装成 ToolCallbackProvider”,还是不够完整。更重要的是理解:服务端到底是怎么把一个普通业务方法变成可被客户端发现和调用的 MCP 能力的。

Spring AI 对 MCP 的支持,底层依赖的是 MCP Java SDK。官方文档把它拆成三层:

  1. 客户端/服务端层
    这一层由 McpClientMcpServer 负责,前者管理连接和调用,后者负责提供工具、资源和提示模板。

  2. 会话层
    这一层由 McpSession 管理会话状态。服务端不是简单接收一条 HTTP 请求就结束,而是在协议交互过程中维护和客户端之间的会话状态。

  3. 传输层
    这一层负责 JSON-RPC 消息的序列化、反序列化以及具体的传输方式实现。本章使用的是 WebFlux 版 MCP Server,当前场景采用的是 Streamable-HTTP 传输方式,它同样建立在 HTTP 连接之上,相比SSE的核心优势在于它能够提供更高效的全双工通信能力,解决了 SSE 仅支持服务端向客户端单向推送数据的局限性(单工通信)。

6.1 服务端是怎样发现工具的

在本章代码里,真正的业务能力来自:

@Tool(description = "根据城市名称获取天气预报")
public String getWeatherByCity(String city)

这个方法本身还是普通 Java 方法,但 @Tool 给了框架一层额外的语义:它可以被作为工具暴露出去。

随后,MethodToolCallbackProvider 会扫描 WeatherService,提取其中带有 @Tool 的方法,并整理出一组工具定义。这里被整理出来的不只是方法名,还包括:

  1. 工具名称
  2. 工具描述
  3. 参数结构
  4. 调用入口

这些信息会进一步被 MCP Server 注册为对外能力。客户端最终看到的,不再是一个 Java 类,而是一组符合 MCP 协议描述方式的工具元数据。

6.2 服务端是如何对外提供这些能力的

当前工程引入的是:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>

根据 Spring AI 官方文档,WebFlux Server Starter 会自动完成 MCP Server 相关组件的配置,并在当前应用中启用对应的服务端能力。对于本章这种本地服务场景,它做的事情可以概括为:

  1. 初始化 MCP Server
  2. 注册工具能力
  3. 挂载 Streamable-HTTP 端点
  4. 等待客户端连接

也就是说,本章的服务端不是通过手写 Controller 去暴露天气接口,而是通过 MCP Server 统一对外提供工具能力。

6.3 数据是怎样传输的

MCP 协议在消息层使用的是 JSON-RPC。服务端和客户端之间真正交换的,不是 WeatherService 对象本身,而是 JSON-RPC 消息。

这条链路可以理解成这样:

WeatherService 中的 @Tool 方法
  -> MethodToolCallbackProvider 提取工具定义
  -> MCP Server 注册工具能力
  -> 通过 `Streamable-HTTP` 端点对外暴露
  -> 客户端建立连接并发现工具
  -> 客户端发起工具调用消息
  -> 服务端执行本地方法并返回结果

所以服务端真正暴露出去的是:

  • 工具名称
  • 参数 schema
  • 调用能力
  • 返回结果

而不是某个 Java Bean 本身。

6.4 为什么客户端后面可以直接发现这些工具

因为服务端在启动时已经把 @Tool 方法注册成了 MCP 能力。客户端连接上来后,会先完成协议初始化和能力协商,然后拉取服务端已经暴露的工具列表。下一章里看到的 ToolCallbackProvider,本质上就是客户端把这些远端工具整理后的结果。

这一来一回,刚好对应了服务端和客户端的职责分工:

  1. 这一章负责把能力暴露出去
  2. 下一章负责发现这些能力并调用它们

七、代码里还有一个需要说明的细节

当前 WeatherService 同时写了 @Service,又在 McpServerConfig 中通过 @Bean 手动注册了一次。这通常会造成重复注册问题。

也就是说,下面两种方式本质上是在做同一件事:

  1. @Service
  2. @Bean public WeatherService weatherService()

在一般情况下,保留一种即可。对当前这个模块来说,更自然的写法是保留 @Service,删除配置类中的手动 @Bean 注册。这样代码更简单,也更符合 Spring 的常规使用方式。

如果继续保留两者,后续在 Bean 注入、组件扫描或自动配置过程中容易引出不必要的歧义。

对当前这一版写法来说,配置类中已经不需要再手动创建 WeatherService 了。保留 @Service 即可,配置类只负责把它包装成 ToolCallbackProvider

八、和第十三章的边界

为了避免和上一章混淆,这里把两章的侧重点并排列一下。

对比项

第13章 Tool Calling

第14章 MCP Server

主语

当前应用中的调用方

本地服务提供方

重点

模型如何决定并执行工具调用

本地能力如何被组织成 MCP 服务

关键对象

ChatClientToolCallback

WeatherServiceToolCallbackProvider、MCP Server 暴露链路

结果

模型能在当前应用中调用工具

客户端可以连接本地 MCP 服务

第十三章解释的是调用机制,这一章建立的是服务端能力。这也是为什么下一章可以自然接着写 MCP Client,而不是和前两章重复。

九、本章小结

这一章的重点不在 ChatClient,而在服务提供侧。真正关键的是两件事:

  1. 项目已经引入 spring-ai-starter-mcp-server,具备了 MCP Server 的运行基础。
  2. WeatherService 中的 @Tool 方法已经具备被服务端发现并暴露的条件。

从这个意义上说,第十四章已经站在了 MCP Server 的语境里。下一章继续转到 MCP Client 视角,去连接并调用这里提供的本地服务,整个链路就完整了。

本章重点

  1. 明确第14章写的是 MCP Server,而不是本地 Tool Calling 的重复讲解。
  2. 理解 @Tool、Spring Bean 和 MCP Server 暴露之间的层次关系。
  3. 为第15章的 MCP Client 调用本地服务建立前提。

下章剧透(s15):

下一章开始切到使用方视角,重点看 MCP Client 如何连接并调用这一章提供的本地服务。

📝 编辑者:Flittly
📅 更新时间:2026年4月
🔗 相关资源MCP 官方文档 | Spring AI MCP