Skip to content

Spring Boot + Spring AIFunction Call、工具链调用与 MCP

当最小聊天接口和流式输出已经跑通后,下一步最自然的问题通常就是:

模型怎么和外部世界发生真实交互?

这一步通常会遇到 3 个词:

  1. Function Call
  2. 工具链调用
  3. MCP

它们经常一起出现,但不是一层东西。

1. 先分清这 3 层

1.1 Function Call

它更偏模型侧能力,回答的是:

模型如何表达“我想调用哪个工具,参数是什么”。

1.2 工具链调用

它更偏应用侧落地,回答的是:

Spring Boot 应用拿到工具调用意图后,具体怎么去调用本地 Bean、第三方 SDK、REST API 或内部系统。

1.3 MCP

它更偏协议层,回答的是:

宿主应用如何用更标准化的方式去发现和接入外部工具 / 资源 / Prompt。

🌟 简单记就是:Function Call 是模型怎么表达意图,MCP 是宿主怎么对接能力,中间真正执行工具的还是你的 Spring 应用。

2. Function Call 在 Spring Boot 项目里怎么落

最常见的思路是:

  1. 用 Spring Bean 承接业务工具
  2. 给方法补工具描述
  3. ChatClient 在调用时感知这些工具
  4. 由模型决定是否调用

真正落地时,最值得先分清的不是“注解怎么写”,而是:

  1. 哪些能力应该暴露成 Tool
  2. Tool 的参数应该收多粗
  3. Tool 的返回值应该返回什么粒度
  4. 有副作用的动作该加哪些保护

2.1 一个最小工具类

java
package com.example.ai.tool;

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

/**
 * 天气工具示例。
 */
@Component
public class WeatherTools {

    /**
     * 根据城市名称查询天气。
     *
     * @param city 城市名
     * @return 天气摘要
     */
    @Tool(description = "根据城市名称查询天气")
    public String getWeather(String city) {
        return city + "今天多云,最高温 31 度。";
    }
}

2.2 一个带工具调用的 Service

java
package com.example.ai.service;

import com.example.ai.tool.WeatherTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

/**
 * 带工具调用能力的 AI 服务。
 */
@Service
public class AiToolService {

    private final ChatClient chatClient;
    private final WeatherTools weatherTools;

    public AiToolService(ChatClient.Builder chatClientBuilder, WeatherTools weatherTools) {
        this.chatClient = chatClientBuilder.build();
        this.weatherTools = weatherTools;
    }

    /**
     * 允许模型按需调用本地天气工具。
     *
     * @param question 用户问题
     * @return 模型最终回答
     */
    public String askWithTool(String question) {
        return chatClient.prompt()
                .user(question)
                .tools(weatherTools)
                .call()
                .content();
    }
}

2.3 Tool 设计时最容易忽略的 3 个点

2.3.1 参数要业务化,不要底层化

模型更适合看到这种参数:

  1. city
  2. orderNo
  3. startDate
  4. docType

而不是这种底层细节:

  1. 原始 SQL
  2. 任意 URL
  3. 任意脚本片段
  4. Redis Key 模式

如果参数太底层,模型虽然“能调”,但很难稳定。

2.3.2 返回值要够用,但不要把无关细节全吐给模型

例如查询订单时,更适合返回:

  1. 订单号
  2. 当前状态
  3. 金额
  4. 关键时间点

而不是直接把整份数据库对象、几十个字段、调试日志全塞回去。

2.3.3 有副作用的工具一定要先分级

可以粗分成 3 类:

等级例子处理建议
只读查天气、查订单、查知识库可直接开放给模型调用
低风险写操作创建草稿、生成报表任务建议补幂等和审计
高风险写操作删除数据、发通知、下单、转账建议加权限、确认、限流和审计

3. 一次工具调用完整链路怎么走

mermaid
sequenceDiagram
    participant U as 用户
    participant C as Controller
    participant S as AiToolService
    participant M as 模型
    participant T as 本地 Tool Bean

    U->>C: 帮我查北京天气
    C->>S: askWithTool(question)
    S->>M: 用户问题 + tools 描述
    M-->>S: 调用 getWeather(city=北京)
    S->>T: 执行本地工具
    T-->>S: 返回天气结果
    S->>M: 回传工具结果
    M-->>S: 生成最终答案
    S-->>C: 返回最终答案

4. 工具链调用不一定都是本地 Bean

真实项目里,工具链往往不只一种来源。

更常见的 4 类来源是:

  1. 本地 Spring Bean
  2. 第三方 SDK
  3. 内部 REST / RPC 接口
  4. 数据库、缓存、搜索系统

4.1 一个更贴近工程的包装方式

不要让模型直接“接触一堆基础设施细节”。更自然的做法是:

  1. 把业务语义包成 Tool
  2. Tool 再去调用真正的 Repository / SDK / API Client

例如:

java
package com.example.ai.tool;

import com.example.ai.client.OrderClient;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

/**
 * 订单工具示例。
 */
@Component
public class OrderTools {

    private final OrderClient orderClient;

    public OrderTools(OrderClient orderClient) {
        this.orderClient = orderClient;
    }

    /**
     * 根据订单号查询订单摘要。
     *
     * @param orderNo 订单号
     * @return 订单摘要
     */
    @Tool(description = "根据订单号查询订单摘要")
    public String getOrderSummary(String orderNo) {
        return orderClient.queryOrderSummary(orderNo);
    }
}

这样写的好处是:

  1. 模型看到的是业务语义化工具
  2. 具体调用 REST / RPC / DB 的细节被收在应用内部

4.2 在项目里更稳的分层方式

如果工具能力准备长期维护,通常建议至少拆成下面几层:

  1. Tool Bean:直接暴露给模型的业务语义入口
  2. Application Service:组织业务主流程
  3. Client / Repository:访问外部系统或数据源
  4. Audit / Permission:负责审计和权限控制
mermaid
flowchart TD
    A[模型输出工具调用意图] --> B[Tool Bean]
    B --> C[Application Service]
    C --> D[Client / Repository / SDK]
    C --> E[Permission Guard]
    C --> F[Audit Log]

🌟 这样拆的关键价值是:
模型看到的是稳定业务语义,基础设施细节和风控逻辑仍然留在应用内部。

4.3 一个更贴近工程的工具骨架

java
package com.example.ai.tool;

import com.example.ai.service.OrderQueryService;
import com.example.ai.service.ToolAuditService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

/**
 * 暴露给模型的订单查询工具。
 */
@Component
public class OrderQueryTools {

    private final OrderQueryService orderQueryService;
    private final ToolAuditService toolAuditService;

    public OrderQueryTools(
            OrderQueryService orderQueryService,
            ToolAuditService toolAuditService) {
        this.orderQueryService = orderQueryService;
        this.toolAuditService = toolAuditService;
    }

    /**
     * 根据订单号查询订单摘要。
     *
     * @param orderNo 订单号
     * @return 适合返回给模型的订单摘要
     */
    @Tool(description = "根据订单号查询订单状态、金额和关键时间点")
    public String getOrderSummary(String orderNo) {
        String result = orderQueryService.querySummary(orderNo);
        toolAuditService.recordReadTool("getOrderSummary", orderNo);
        return result;
    }
}

这段代码最想表达的不是“写法花样”,而是:

  1. Tool Bean 自己不要承担太多业务逻辑
  2. 审计不要等到后面出问题才补
  3. 工具返回值要围绕模型真正需要的上下文来组织

5. MCP 在 Spring Boot 项目里落在哪一层

这一步最容易混。

更自然的理解是:

  1. Spring AI 是应用内 AI 编程模型
  2. MCP 是外部能力接入协议

5.1 方案一:Spring Boot 应用是 AI 宿主

这时更常见的是:

  1. 应用自己接模型
  2. 同时通过 MCP Client 去发现和调用外部 MCP Server

5.2 方案二:Spring Boot 应用本身暴露 MCP 能力

这时更常见的是:

  1. 应用内部已有一批业务能力 Bean
  2. 再把这些能力封成 MCP Server 对外给别的 AI 宿主使用

5.2.1 这时最常见的注解和 API 是什么

如果这一层准备写成真正可运行的 MCP Server,光知道“有 Host / Client / Server”还不够,通常还要认识下面这些 API:

名称更常见的用途
@McpTool把一个方法暴露成 MCP Tool
@McpToolParam给 Tool 参数补名称、描述、必填信息
McpSyncRequestContext在 Tool 执行时拿请求上下文、记录日志或进度
@McpResource暴露只读资源
@McpPrompt暴露可复用的 Prompt 模板
SyncMcpToolCallbackProvider / AsyncMcpToolCallbackProvider把远程 MCP 工具转成 Spring AI 可用的 ToolCallback
SyncMcpToolCallback / AsyncMcpToolCallback把单个 MCP Tool 适配成 Spring AI 的工具接口
McpToolUtils在 ToolCallback 和 MCP Tool 规格之间做转换

🌟 这里最容易混的点是:

  1. @McpTool@McpToolParam 更偏 把能力暴露成 MCP Server
  2. SyncMcpToolCallbackProviderAsyncMcpToolCallbackProvider 更偏 消费远程 MCP Server 的工具

也就是说,这不是一组“并列注解”,而是两侧不同角色会用到的 API。

5.2.2 @McpTool@McpToolParam 怎么用

如果你准备把 Spring Boot 应用本身暴露成 MCP Server,最常见的写法会长这样:

java
package com.example.ai.mcp;

import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.ai.mcp.annotation.McpToolParam;
import org.springframework.ai.mcp.McpSyncRequestContext;
import org.springframework.stereotype.Component;

/**
 * 暴露给 MCP Client 的工具服务。
 */
@Component
public class DocumentTools {

    /**
     * 根据关键字搜索文档。
     *
     * @param keyword 查询关键字
     * @param limit 返回条数
     * @param context MCP 请求上下文
     * @return 搜索结果摘要
     */
    @McpTool(name = "search-documents", description = "根据关键字搜索知识库文档")
    public String searchDocuments(
            @McpToolParam(description = "用户输入的搜索关键字", required = true) String keyword,
            @McpToolParam(description = "最多返回多少条结果") Integer limit,
            McpSyncRequestContext context) {

        context.info("search-documents called with keyword=" + keyword);
        int size = limit == null ? 5 : Math.min(limit, 10);

        return "共找到 " + size + " 条与 `" + keyword + "` 相关的文档结果";
    }
}

这段代码里最值得注意的不是注解本身,而是 3 个边界:

  1. @McpTool 标的是“这个方法可以被当成 MCP Tool 调用”
  2. @McpToolParam 负责把参数信息补进 Tool 的描述和 Schema
  3. McpSyncRequestContext 这种上下文参数是框架注入的,不是给模型看的业务参数

把这一层摊开看:

  1. 模型或宿主真正会看到的是 Tool 名、描述和参数 Schema
  2. McpSyncRequestContext 这类参数更像服务端执行时的辅助上下文
  3. 如果参数没有描述,模型往往更难稳定地用对工具

5.2.3 McpSyncRequestContext 这类上下文参数是干什么的

这类参数更适合用来做:

  1. 记录工具执行日志
  2. 报告执行进度
  3. 读取请求上下文信息
  4. 在长耗时任务里给客户端回传状态

它不适合承载业务入参本身,因为业务入参应该仍然写在 @McpToolParam 上。

5.2.4 除了 Tool,还会用到哪些注解

如果场景继续变复杂,常见还会遇到:

  1. @McpResource:暴露只读资源,更适合“把内容拿给模型看”
  2. @McpPrompt:暴露 Prompt 模板,更适合“给宿主复用提示结构”

把分工拉开:

  1. @McpTool 偏执行动作
  2. @McpResource 偏读取内容
  3. @McpPrompt 偏复用提示模板

5.2.5 消费远程 MCP 工具时,为什么更常见的是 Provider/Callback API

如果你的 Spring Boot 应用是宿主,要去消费别人的 MCP Server,更常见的入口反而不是 @McpTool,而是:

  1. SyncMcpToolCallbackProvider
  2. AsyncMcpToolCallbackProvider
  3. SyncMcpToolCallback
  4. AsyncMcpToolCallback

它们解决的问题是:

怎么把远程 MCP Server 上发现到的工具,转成 Spring AI 的 ToolCallback,让 ChatClient 可以继续使用。

例如:

java
package com.example.ai.mcp;

import io.modelcontextprotocol.client.McpSyncClient;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 把远程 MCP Server 的工具适配成 Spring AI 可用的 ToolCallback。
 */
@Configuration
public class McpClientToolConfig {

    @Bean
    public ToolCallbackProvider remoteToolProvider(McpSyncClient mcpSyncClient) {
        return SyncMcpToolCallbackProvider.builder()
                .mcpClients(mcpSyncClient)
                .build();
    }

    @Bean
    public ToolCallback[] remoteToolCallbacks(ToolCallbackProvider remoteToolProvider) {
        return remoteToolProvider.getToolCallbacks();
    }
}

这段代码最想说明的是:

  1. 远程 MCP 工具要先适配成 ToolCallback
  2. 适配完之后,它才能进入 Spring AI 的工具调用体系
  3. 所以 MCP Client 这一侧,重点通常不是注解,而是 Provider / Callback

5.2.6 这些远程 MCP Tool 最后怎么挂到 ChatClient

继续沿着上面的 ToolCallbackProvider 往下走,常见会像这样:

java
package com.example.ai.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.stereotype.Service;

/**
 * 使用远程 MCP Tool 的 AI 服务。
 */
@Service
public class RemoteMcpToolService {

    private final ChatClient chatClient;
    private final ToolCallbackProvider remoteToolProvider;

    public RemoteMcpToolService(
            ChatClient.Builder chatClientBuilder,
            ToolCallbackProvider remoteToolProvider) {
        this.chatClient = chatClientBuilder.build();
        this.remoteToolProvider = remoteToolProvider;
    }

    public String ask(String question) {
        return chatClient.prompt()
                .user(question)
                .tools(remoteToolProvider)
                .call()
                .content();
    }
}

这时可以把整条链路压成一句话:

  1. Server 侧用 @McpTool@McpToolParam 把能力暴露出去
  2. Client 侧用 SyncMcpToolCallbackProvider / AsyncMcpToolCallbackProvider 把远程工具接进来
  3. 最后再把这些 ToolCallback 挂给 ChatClient

5.3 什么时候更适合直接用本地 Tool,什么时候适合上 MCP

这个判断很重要,因为很多团队会过早把问题做重。

场景更合适的做法原因
只有当前 Spring Boot 应用自己要用本地 Tool / Service接入最直接,排障也最简单
能力已经沉淀成共享服务,未来多个宿主要复用MCP协议统一后更容易复用
只是简单调用一个内部 REST API先包成 Tool没必要为了标准化过早引入额外协议层
需要同时暴露工具、资源、PromptMCP 更合适这类场景更接近 MCP 的原生定位

落到选型上,通常就是这 3 句:

  1. 本地能力优先本地 Tool
  2. 开始跨宿主复用时再考虑 MCP
  3. MCP 适合做“共享能力层”,不适合当所有 AI 项目的必选起点

5.4 Spring Boot 作为 MCP Client 时,通常怎么组织

当你的 Spring Boot 应用是 AI 宿主时,更常见的组织方式是:

  1. 应用内仍然保留 ChatClient
  2. 再加一层 McpCapabilityService
  3. 由这层统一负责连接外部 MCP Server
  4. 对模型来说,它拿到的仍然是“可调用工具”
java
package com.example.ai.mcp;

import java.util.List;
import org.springframework.stereotype.Service;

/**
 * 负责统一管理外部 MCP 能力。
 *
 * 这里用语义化占位接口表达职责边界,
 * 重点是说明 Spring Boot 项目里应该把 MCP 连接管理单独收一层。
 */
@Service
public class McpCapabilityService {

    private final McpSessionRegistry mcpSessionRegistry;

    public McpCapabilityService(McpSessionRegistry mcpSessionRegistry) {
        this.mcpSessionRegistry = mcpSessionRegistry;
    }

    /**
     * 列出当前可用的远程工具。
     *
     * @return 工具描述列表
     */
    public List<String> listRemoteTools() {
        return mcpSessionRegistry.listTools();
    }

    /**
     * 调用指定 MCP 工具。
     *
     * @param toolName 工具名
     * @param arguments 参数
     * @return 工具结果
     */
    public String callRemoteTool(String toolName, String arguments) {
        return mcpSessionRegistry.callTool(toolName, arguments);
    }
}

这层最重要的意义是:

  1. MCP 连接和会话管理不要散落在业务 Controller 里
  2. 业务方拿到的是“能力服务”,不是协议细节
  3. 后面无论切换 stdio 还是 HTTP,影响面都会更小

5.5 Spring Boot 作为 MCP Server 时,更适合暴露什么

如果你的 Spring Boot 应用准备把能力对外开放成 MCP Server,更适合暴露的是:

  1. 业务语义明确的工具
  2. 只读或低风险的资源
  3. 可复用的 Prompt 模板

不太适合直接暴露的是:

  1. 原始数据库查询能力
  2. 任意 HTTP 代理能力
  3. 无边界的文件系统写入能力
  4. 高风险且没有审计的写操作

5.6 一个更务实的 MCP Server 暴露思路

可以按下面顺序做:

  1. 把应用内已有 Tool 整理成稳定业务能力
  2. 再挑出跨宿主复用价值高的那一批
  3. 把它们重新包装成 MCP Server 暴露能力
  4. 最后再补鉴权、审计、调用额度和可观测性

🌟 要注意的是:
MCP Server 不是“把系统里的所有能力都开放出去”,而是挑选那些适合标准化复用的能力。

6. Function CallMCP 到底是什么关系

mermaid
flowchart TD
    A[用户问题] --> B[Spring Boot 宿主把上下文发给模型]
    B --> C{模型是否需要外部能力}
    C -- 否 --> D[直接生成答案]
    C -- 是 --> E[模型输出 Function Call 意图]
    E --> F[宿主解析工具名和参数]
    F --> G{工具是否通过 MCP 暴露}
    G -- 是 --> H[通过 MCP Client 调 MCP Server]
    G -- 否 --> I[直接调 Spring Bean / SDK / REST API]
    H --> J[拿到工具结果]
    I --> J
    J --> K[把结果回传模型]
    K --> L[模型生成最终答案]

这张图最想说明的是:

  1. Function Call 不等于 MCP
  2. 模型先表达“要调什么”
  3. Spring Boot 宿主再决定“具体怎么调”
  4. MCP 只是其中一种更标准化的接入方式

6.1 在 Spring 项目里,一次“模型调 MCP 工具”通常会多出哪一层

如果换成项目视角,这条链路通常更像这样:

  1. Controller 收到用户请求
  2. AI Service 组织 Prompt
  3. 模型表达工具调用意图
  4. 宿主应用判断这个工具是本地 Tool 还是远程 MCP Tool
  5. 如果是远程 MCP Tool,就交给 McpCapabilityService
  6. 拿到结果后,再继续回传模型生成最终答案

这也正是为什么:

  1. MCP 更像“能力接入层”
  2. 业务 Service 不应该直接堆协议处理代码
  3. 模型、工具、协议三层最好保持边界清楚

7. 一个更完整的应用骨架

java
package com.example.ai.web;

import com.example.ai.service.AiToolService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 工具调用示例接口。
 */
@RestController
public class AiToolController {

    private final AiToolService aiToolService;

    public AiToolController(AiToolService aiToolService) {
        this.aiToolService = aiToolService;
    }

    @GetMapping("/demo/ai/tool")
    public String tool(@RequestParam String q) {
        return aiToolService.askWithTool(q);
    }
}

7.1 一个把本地 Tool 和 MCP Tool 收在一起的 Service 骨架

java
package com.example.ai.service;

import com.example.ai.mcp.McpCapabilityService;
import com.example.ai.tool.OrderQueryTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

/**
 * 统一组织本地工具和远程 MCP 能力的服务。
 */
@Service
public class AiCapabilityService {

    private final ChatClient chatClient;
    private final OrderQueryTools orderQueryTools;
    private final McpCapabilityService mcpCapabilityService;

    public AiCapabilityService(
            ChatClient.Builder chatClientBuilder,
            OrderQueryTools orderQueryTools,
            McpCapabilityService mcpCapabilityService) {
        this.chatClient = chatClientBuilder.build();
        this.orderQueryTools = orderQueryTools;
        this.mcpCapabilityService = mcpCapabilityService;
    }

    /**
     * 统一处理需要外部能力的问题。
     *
     * @param question 用户问题
     * @return 最终答案
     */
    public String ask(String question) {
        // 演示里只保留主线:本地工具直接挂到 ChatClient;
        // 远程 MCP 能力则由独立服务统一收口。
        String remoteToolSummary = String.join(", ", mcpCapabilityService.listRemoteTools());

        return chatClient.prompt()
                .system("""
                        你是一名企业助手。
                        本地可用工具包括订单查询;
                        远程 MCP 工具包括:%s。
                        只有在确实需要时才调用工具。
                        """.formatted(remoteToolSummary))
                .user(question)
                .tools(orderQueryTools)
                .call()
                .content();
    }
}

这段代码故意没有把所有远程 MCP 调用细节都展开,因为它最想说明的是:

  1. 本地 Tool 和远程 MCP 能力可以共存
  2. 它们最好收在同一层能力编排服务里
  3. Controller 依旧保持轻,只负责暴露接口

8. 如果要落到真实项目,第二阶段最该补什么

比起继续堆更多 demo,第二阶段更值得先补的是下面这些能力:

  1. 工具权限控制
  2. 工具调用审计
  3. 超时与重试
  4. 幂等与确认
  5. 工具返回值裁剪
  6. MCP 连接稳定性和降级策略

8.1 一张“能跑”到“能稳”的补齐清单

能力为什么需要更常见的落点
权限控制防止模型越权调用敏感工具Tool 前置校验 / Spring Security
审计日志方便追查谁调了什么工具Tool Service / Audit Service
超时控制防止远程工具卡住整条链路Client / MCP 调用层
幂等防止重试导致重复写入写操作服务层
二次确认控制高风险动作Agent / Tool 编排层
降级外部能力不可用时保持主流程可用Capability Service

9. 第二阶段最容易踩的坑

9.1 把 Tool 直接写成“底层能力暴露”

例如直接把:

  1. SQL 执行器
  2. Redis 通用操作器
  3. HTTP 通用客户端

裸暴露给模型,这通常并不稳。

更自然的做法是:

  1. 对模型暴露“业务语义工具”
  2. 把底层细节藏在工具内部

9.2 有副作用的工具没有幂等和确认

像发消息、下单、删数据这类工具,一定要继续补:

  1. 权限控制
  2. 幂等设计
  3. 二次确认
  4. 审计日志

9.3 把 MCP 当成“必须先上”的能力

大多数项目并不是第一天就要上 MCP
更常见的节奏是:

  1. 先用本地 Tool 或服务内调用跑通
  2. 工具能力开始跨宿主复用时,再考虑 MCP

9.4 把 MCP Server 当成“万能适配层”

这样很容易出现两个问题:

  1. 不该开放的底层能力也被顺手暴露出去
  2. 真正需要权限控制和审计的动作没有被单独建边界

所以更稳的做法通常是:

  1. 先收敛业务能力
  2. 再决定哪些值得开放成 MCP
  3. 最后才谈协议化和共享复用

9.5 远程工具失败时没有降级策略

如果 MCP Server 临时不可用,而宿主完全没有降级,就会出现:

  1. 单个工具故障拖垮整条问答链路
  2. 模型反复重试导致成本上涨
  3. 用户只看到“系统坏了”,却不知道是哪个外部能力出了问题

至少可以补下面几件事:

  1. 超时
  2. 熔断或失败快速返回
  3. 清晰的错误信息
  4. 无工具时的降级回答

10. 推荐怎么读这一阶段

如果你是按项目落地顺序来看,读这一篇时可以重点盯住下面 4 件事:

  1. 先分清 Function Call、工具链调用和 MCP 不是一层
  2. 把本地 Tool 的业务语义收稳
  3. 再决定哪些能力值得升级成 MCP
  4. 最后再补权限、审计、幂等和降级

11. 这一篇最重要的结论

第二阶段最重要的不是把所有外部能力都标准化,而是把“模型意图 -> Spring 应用执行 -> 结果回传模型”这条工具调用主线跑顺。Function Call 负责表达意图,工具链负责真正执行,MCP 则是在工具开始跨宿主复用时更有价值的一层协议化能力。

基于 VitePress 构建的个人技术笔记。