一、前言:为什么需要 MCP 工具调用?

普通的对话问答模式下,大模型存在几个天生短板:

  1. 知识存在时间断层,无法获取最新实时数据;
  2. 无法访问业务数据库、业务接口,不能查询订单、用户、业务数据;
  3. 不会执行精确计算、代码运算,容易出现算术错误;
  4. 不能调用外部服务,无法完成联网查询、文件读取、天气查询等任务。

想要让 AI 真正落地业务,就必须赋予它调用外部工具的能力,而支撑这套能力的行业标准就是 MCP 模型上下文协议(Model Context Protocol)

简单理解:

MCP 就是一套标准化的规则,让大模型知道自己 “有哪些工具可用、工具入参是什么、执行完如何带回结果”,并自动完成「思考→选工具→填参数→调用→整合结果回答用户」全流程。

Spring AI 基于 MCP 协议做了完整封装,我们不需要手动拼接工具描述、不需要解析工具调用格式,通过简单注解即可完成工具注册,让大模型自动调用。

本篇从概念到实战,完整拆解 MCP 协议、本地工具调用、第三方接口调用、多工具混合编排,带你打通 AI 智能体的核心能力。

二、MCP 模型上下文协议核心概念

2.1 什么是 MCP?

MCP(Model Context Protocol,模型上下文协议),是用于大模型与外部工具之间通信的通用标准化协议。 它定义了一套通用 JSON 格式,用来描述:

  • 工具名称、功能说明
  • 工具入参类型、是否必填、参数说明
  • 工具调用格式规范
  • 工具返回结果格式规范

主流大模型(智谱、通义、文心、OpenAI)全部兼容 MCP 协议,是当前工具调用的行业通用标准。

2.2 工具调用完整执行流程(MCP 标准流程)

整个流程分为 4 步,也是所有智能体的通用逻辑:

  1. 思考阶段(Reason):大模型分析用户问题,判断是否需要调用工具、需要调用哪个工具;
  2. 工具调用(Act):大模型按照 MCP 协议,生成标准化工具调用参数;
  3. 本地执行:Spring AI 框架解析 MCP 格式,调用我们注册的业务工具;
  4. 结果回填:将工具执行结果重新塞回对话上下文,大模型整合信息后给出最终回答。

整个过程全自动完成,用户无感知,这就是经典的 ReAct 智能体模式

2.3 Spring AI 中工具相关核心注解

Spring AI 对 MCP 做了高度封装,核心依靠两个注解完成工具定义:

  • @Tool:标记一个方法为 AI 可调用工具,配置工具名称、功能描述;
  • @ToolParam:对方法入参进行说明,告诉大模型参数含义、是否必填。

框架会自动根据注解,生成符合 MCP 规范的工具描述,交给大模型进行决策调用。

三、实战一:基础本地工具调用(入门必学)

先通过最简单的天气查询工具,演示完整的 MCP 工具调用流程,理解整体运行逻辑。

3.1 编写工具类,注册 AI 可调用方法

创建 WeatherTool 工具类,通过注解定义工具功能和参数:

package demo.ai.tool;

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

/**
 * 天气查询工具
 * 遵循 MCP 协议,可供大模型自动调用
 */
@Component
public class WeatherTool {

    /**
     * 根据城市名称查询天气
     * @param city 城市名称
     * @return 天气信息
     */
    @Tool(description = "根据城市名称,查询该城市当前实时天气状况")
    public String getWeather(@ToolParam(description = "用户需要查询的城市名称,例如:北京、上海、广州") String city) {
        // 模拟第三方天气接口调用
        return city + " 今日天气:多云,气温 18~26℃,微风,空气质量良好";
    }
}

3.2 ChatClient 注册工具,开启 MCP 调用能力

在全局配置类ChatClientConfiguration中,将工具注入 ChatClient,框架会自动生成 MCP 工具描述:

package demo.ai.conf;

import demo.ai.tool.WeatherTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import jakarta.annotation.Resource;

@Configuration
public class ChatClientConfiguration {

    @Resource
    private WeatherTool weatherTool;

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                // 注册工具,开启 MCP 工具调用能力
                .defaultTools(weatherTool)
                .build();
    }
}

3.3 编写对话测试接口

package demo.ai.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import jakarta.annotation.Resource;

@RestController
@RequestMapping("/mcp")
public class McpController {

    @Resource
    private ChatClient chatClient;

    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatClient.prompt()
                .system("你是智能助手,遇到需要查询天气的问题,自动调用天气工具获取数据,再整理回答用户")
                .user(msg)
                .call()
                .content();
    }
}

3.4 测试验证

访问接口:

http://localhost:8080/mcp/chat?msg=帮我查一下成都的天气

执行流程:

  1. 大模型识别用户需要查天气,触发 MCP 工具调用;
  2. 按照 MCP 协议生成调用参数:city=成都
  3. Spring AI 解析 MCP 数据,执行 getWeather("成都")
  4. 将结果回填上下文,大模型整合后给出最终回答。

四、实战二:多工具混合编排(业务常用)

真实业务场景中,通常存在多个工具,大模型会根据用户问题,自动选择对应工具,甚至连续调用多个工具

我们新增两个工具:日期计算工具、汇率换算工具,实现多工具智能调度。

4.1 新增两个业务工具

package demo.ai.tool;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

@Component
public class BusinessTool {

    /**
     * 计算两个日期之间相差的天数
     */
    @Tool(description = "计算两个日期之间相差的天数,日期格式为 yyyy-MM-dd")
    public long calcDay(
            @ToolParam(description = "开始日期,格式yyyy-MM-dd") String startDate,
            @ToolParam(description = "结束日期,格式yyyy-MM-dd") String endDate
    ) {
        LocalDate start = LocalDate.parse(startDate);
        LocalDate end = LocalDate.parse(endDate);
        return ChronoUnit.DAYS.between(start, end);
    }

    /**
     * 人民币兑美元汇率换算
     */
    @Tool(description = "人民币兑换美元,返回换算后的金额")
    public String rmbToUsd(@ToolParam(description = "人民币金额") Double money) {
        double rate = 0.138;
        return "换算后美元金额:" + String.format("%.2f", money * rate);
    }
}

4.2 配置类注册多个工具

@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
    return builder
            .defaultTools(weatherTool, businessTool)
            .build();
}

4.3 多工具场景测试

测试问题:

http://localhost:8080/mcp/chat?msg=现在是2025-01-01,距离国庆节还有多少天?另外帮我把5000元人民币换成美元

大模型会自动识别,连续调用日期计算工具、汇率工具,再整合结果统一回答,无需人工干预。

五、实战三:结合记忆上下文,实现多轮工具调用

MCP 工具调用可以和前面学习的对话记忆无缝结合,实现多轮对话连续调用工具,这是智能客服的核心模式。

5.1 配置记忆 + 工具

新增ScoreTool工具类

package demo.ai.tool;

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

@Component
public class ScoreTool {
    /**
     * 返回学生的成绩列表
     */
    @Tool(description = "返回学生的成绩列表")
    public String getScoreList() {
        return "张三:90分,李四:85分,王五:95分,赵六:88分,王二:92分";
    }

    @Tool(description = "生成学生成绩报告")
    public String report(@ToolParam(description = "最高分") Integer max,
                         @ToolParam(description = "最低分你") Integer min,
                         @ToolParam(description = "平均分") Integer avg) {
        String grade = "";
        if (avg > 90) grade = "优";
        else if (avg > 80) grade = "良";
        else if (avg > 70) grade = "中";
        else if (avg > 60) grade = "及格";
        else grade = "不及格";
        return "本次考试,最高分:" + max + ",最低分:" + min + ",平均分:" + avg + ",等级:" + grade;
    }
}

沿用第 之前的 内存记忆配置,在 ChatClient 中同时注入记忆顾问、工具列

    @Bean
    public ChatClient chatClient(ChatClient.Builder chatClientBuilder, MessageWindowChatMemory chatMemory) {
        return chatClientBuilder
                .defaultTools(weatherTool, businessTool, scoreTool)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }

5.2 多轮对话测试

第一轮提问:

http://localhost:8080/mcp/chat?msg=我要查询学生的成绩列表

第二轮接着提问:

http://localhost:8080/mcp/chat?msg=统计最高分、最低分、平均分

第二轮接着提问:

http://localhost:8080/mcp/chat?msg=统计学生成绩报告

模型会保留上一轮对话上下文,自动连续调用不同工具,完成连贯问答。

六、MCP 底层原理:Spring AI 如何生成工具描述?

很多同学好奇:为什么只加注解,大模型就能知道工具信息?

核心流程:

  1. 启动项目时,Spring AI 扫描所有带 @Tool 的 Bean;
  2. 读取方法注解、参数注解,自动生成符合 MCP 协议的 JSON 工具描述
  3. 每次对话请求时,框架自动把工具描述拼接到 Prompt 头部,告诉大模型可用工具;
  4. 大模型思考后,返回 MCP 格式的工具调用指令;
  5. Spring AI 解析 MCP JSON,反射执行对应工具方法,回填结果。

简化后的 MCP 工具描述结构:

json

{
  "type": "function",
  "function": {
    "name": "getWeather",
    "description": "根据城市名称查询实时天气",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {
          "type": "string",
          "description": "城市名称"
        }
      },
      "required": ["city"]
    }
  }
}

所有兼容 MCP 的大模型,都能识别这套格式,这也是 Spring AI 跨模型通用的底层基础。

七、生产级最佳实践 & 避坑指南

✅ 推荐规范

  1. 工具职责单一:一个工具只做一件事,不要在一个方法里混合多个业务逻辑;
  2. 描述尽量详细@Tool@ToolParam 的描述越清晰,大模型调用准确率越高;
  3. 参数尽量使用简单类型:优先使用 String、Integer、Double,复杂对象容易解析失败;
  4. 工具统一归类:按业务模块划分工具类,例如订单工具、用户工具、支付工具;
  5. 结合会话记忆使用:智能客服场景必须开启记忆,保证对话连贯性;
  6. 工具执行加日志:结合前面的日志 Advisor,记录工具调用入参、出参,方便问题排查。

❌ 高频踩坑点

  1. 描述过于简略:大模型无法理解工具用途,导致不调用或错误调用;
  2. 参数命名不规范:参数名和业务含义不匹配,MCP 解析后传参错误;
  3. 工具抛出异常未处理:工具内部异常会导致整个对话中断,建议内部 try‑catch 并返回错误文本;
  4. 大量工具一次性注册:工具过多会造成 Prompt 过长,Token 消耗暴增,建议按场景动态注册;
  5. 混淆工具调用和普通问答:必须在 system 提示词中引导大模型 “需要时自动调用工具”。

八、完整可运行控制器代码

package demo.ai.tool;

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

/**
 * 天气查询工具
 * 遵循 MCP 协议,可供大模型自动调用
 */
@Component
public class WeatherTool {

    /**
     * 根据城市名称查询天气
     * @param city 城市名称
     * @return 天气信息
     */
    @Tool(description = "根据城市名称,查询该城市当前实时天气状况")
    public String getWeather(@ToolParam(description = "用户需要查询的城市名称,例如:北京、上海、广州") String city) {
        // 模拟第三方天气接口调用
        return city + " 今日天气:多云,气温 18~26℃,微风,空气质量良好";
    }
}
package demo.ai.tool;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

@Component
public class BusinessTool {

    /**
     * 计算两个日期之间相差的天数
     */
    @Tool(description = "计算两个日期之间相差的天数,日期格式为 yyyy-MM-dd")
    public long calcDay(
            @ToolParam(description = "开始日期,格式yyyy-MM-dd") String startDate,
            @ToolParam(description = "结束日期,格式yyyy-MM-dd") String endDate
    ) {
        LocalDate start = LocalDate.parse(startDate);
        LocalDate end = LocalDate.parse(endDate);
        return ChronoUnit.DAYS.between(start, end);
    }

    /**
     * 人民币兑美元汇率换算
     */
    @Tool(description = "人民币兑换美元,返回换算后的金额")
    public String rmbToUsd(@ToolParam(description = "人民币金额") Double money) {
        double rate = 0.138;
        return "换算后美元金额:" + String.format("%.2f", money * rate);
    }
}
package demo.ai.tool;

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

@Component
public class ScoreTool {
    /**
     * 返回学生的成绩列表
     */
    @Tool(description = "返回学生的成绩列表")
    public String getScoreList() {
        return "张三:90分,李四:85分,王五:95分,赵六:88分,王二:92分";
    }

    @Tool(description = "生成学生成绩报告")
    public String report(@ToolParam(description = "最高分") Integer max,
                         @ToolParam(description = "最低分你") Integer min,
                         @ToolParam(description = "平均分") Integer avg) {
        String grade = "";
        if (avg > 90) grade = "优";
        else if (avg > 80) grade = "良";
        else if (avg > 70) grade = "中";
        else if (avg > 60) grade = "及格";
        else grade = "不及格";
        return "本次考试,最高分:" + max + ",最低分:" + min + ",平均分:" + avg + ",等级:" + grade;
    }
}
package demo.ai.conf;

import demo.ai.tool.BusinessTool;
import demo.ai.tool.ScoreTool;
import demo.ai.tool.WeatherTool;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfiguration {
    @Resource
    private WeatherTool weatherTool;
    @Resource
    private BusinessTool businessTool;
    @Resource
    private ScoreTool scoreTool;

    /**
     * 注册内存存储仓库(默认存储方案)
     */
    @Bean
    public InMemoryChatMemoryRepository chatMemoryRepository() {
        return new InMemoryChatMemoryRepository();
    }

    /**
     * 注册滑动窗口记忆策略
     * maxMessages:最大保留消息条数,超出自动裁剪旧消息,保留系统消息
     */
    @Bean
    public MessageWindowChatMemory chatMemory(InMemoryChatMemoryRepository repository) {
        return MessageWindowChatMemory.builder()
                .chatMemoryRepository(repository)
                .maxMessages(10)   //最多保留10条对话消息
                .build();
    }

    @Bean
    public ChatClient chatClient(ChatClient.Builder chatClientBuilder, MessageWindowChatMemory chatMemory) {
        return chatClientBuilder
                .defaultTools(weatherTool, businessTool, scoreTool)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }
}
package demo.ai.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import jakarta.annotation.Resource;

@RestController
@RequestMapping("/mcp")
public class McpController {

    @Resource
    private ChatClient chatClient;

    /**
     * http://localhost:8080/mcp/chat?msg=帮我查询重庆的天气
     * http://localhost:8080/mcp/chat?msg=现在是2026-05-01,距离国庆节还有多少天?另外帮我把5000元人民币换成美元
     * http://localhost:8080/mcp/chat?msg=我要查询学生的成绩列表
     * http://localhost:8080/mcp/chat?msg=统计最高分、最低分、平均分
     * http://localhost:8080/mcp/chat?msg=统计学生成绩报告
     */
    @GetMapping("/chat")
    public String chat(@RequestParam String msg, HttpServletRequest request) {
        return chatClient.prompt()
                .system("你是智能助手,遇到需要查询天气的问题,自动调用天气工具获取数据,再整理回答用户")
                .user(msg)
                .advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, request.getSession().getId()))
                .call()
                .content();
    }
}

九、本篇学习总结

本篇完整讲解了 MCP 模型上下文协议 + Spring AI 工具调用 核心能力,核心收获:

  1. 理解 MCP 是大模型调用外部工具的通用行业协议,是智能体的底层基础;
  2. 掌握 @Tool@ToolParam 注解的使用方式,快速定义 AI 工具;
  3. 理解 ReAct 思考 + 执行流程,大模型全自动完成工具调用;
  4. 掌握单工具、多工具、结合记忆的实战用法;
  5. 看懂 MCP 底层 JSON 结构,理解 Spring AI 自动生成工具描述的原理;
  6. 掌握生产环境下工具调用的最佳实践与常见坑点。

掌握 MCP 工具调用,意味着你已经从普通问答开发,正式进入 AI Agent 智能体开发 阶段,是 Spring AI 进阶的核心分水岭。

十、结语

普通大模型只能回答知识类问题,而具备 MCP 工具调用能力的 AI,才能真正落地业务场景

无论是智能客服、数据分析、自动化办公、企业知识库,底层全部依靠 MCP 工具调用实现。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐