Spring AI Alibaba 集成 MCP:工具异常场景抑制大模型幻觉落地实践

MCP(Model Context Protocol)提供了标准化的外部工具接入协议,让AI Agent可以统一对接各类外部服务。在基于 Spring AI Alibaba 构建 MCP Agent 时,MCP Server 连接失败、工具列表为空、调用超时、鉴权失败等场景下,大模型极易产生幻觉,编造虚假返回结果。MCP 协议本身仅定义工具通信规范,不具备幻觉治理能力。本文结合 Spring AI 原生 MCP 同步客户端 McpSyncClient,给出宿主Host前置硬拦截 + 提示词软约束的组合方案,在工具链路异常时阻断模型脑补,附带适配生产的可运行 Java 代码、Mermaid 时序图,规避只依赖 System Prompt 带来的规则逃逸风险。

前言

随着 AI Agent 开发热度上升,MCP(Model Context Protocol)正在成为 Agent 对接外部能力的标准方案。MCP 采用三层架构划分职责:

  1. MCP Host(宿主):AI 应用本体,本文为 SpringBoot + Spring AI Alibaba 应用,是整个交互流程的控制方,掌握 MCP 连接真实状态;
  2. MCP Client :内嵌在 Host 内部,基于 JSON-RPC 协议和远端 MCP Server 通信,负责拉取工具列表、转发工具调用请求;工具清单不是静态配置,必须远程调用 list_tools RPC 获取;
  3. MCP Server :对外暴露工具与资源,例如 GitHub MCP Server、文件服务 MCP Server,提供 list_tools、call_tool 标准 RPC 方法。

原生标准 MCP 调用链路:

用户提问 → Host 驱动 MCP Client 请求 list_tools → MCP Server 返回工具定义 → Client 聚合工具集合交给 Host → Host 将工具定义送入大模型 → 模型判断是否发起工具调用 → Host 驱动 Client 执行 call_tool → Server 执行业务并返回结果 → 模型基于真实工具结果整理回答。

业务痛点:两类高频幻觉场景

很多开发者接入 MCP 之后,会遇到一个共性问题:协议只规范通信,不负责控幻觉。

  • 场景一: MCP Server 连接失败,list_tools 返回空列表,无任何可用工具。当用户询问依赖外部资源的问题,模型会无视"没有工具"的事实,编造不存在的数据;
  • **场景二:**工具列表拉取成功,模型正常下发工具调用指令,但 MCP Server 超时、鉴权报错、返回空值。如果直接把异常信息丢给大模型,模型经常忽略报错信息,脑补一份虚假结果返回用户。

单纯依靠 System Prompt 约束是不可靠的:大模型属于概率生成,存在规则逃逸。所以我们的核心思路是:Host 作为管控方,持有 MCP 真实状态,前置硬拦截优先,Prompt 仅作为兜底软约束。Host 的判断优先级高于模型自主决策。

一、环境与依赖说明

  • SpringBoot:3.2.x
  • Spring AI Alibaba:1.0.0-M1
  • 底层复用 Spring AI 官方 MCP 同步客户端 McpSyncClient,自动处理 stdio / SSE、JSON-RPC 通信

pom.xml 核心依赖:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springai-mcp-anti-hallucination</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springai-mcp-anti-hallucination</name>

    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M1</spring-ai.version>
    </properties>

    <dependencies>
        <!-- Spring AI Alibaba 核心 -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <!-- Spring AI MCP Client -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp</artifactId>
        </dependency>
        <!-- Web接口 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- JSON序列化 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

二、MCP 状态定义 & 会话上下文

Host 侧维护 MCP 状态,状态由 MCP Client 和远端 Server 交互后更新,模型无法篡改这个状态。

复制代码
package com.example.springaimcp.context;

import lombok.Data;

/**
 * MCP工具可用状态枚举
 */
public enum McpAvailableStatus {
    /**
     * 存在可用MCP工具
     */
    HAS_AVAILABLE_TOOLS,
    /**
     * 无任何可用MCP工具(Server连接失败 / list_tools返回空)
     */
    NO_AVAILABLE_TOOLS
}

/**
 * MCP会话上下文,由Host持有,作为幻觉拦截判断依据
 */
@Data
public class McpSessionContext {
    // MCP工具可用状态
    private McpAvailableStatus mcpAvailableStatus;
    // 当前用户问题是否依赖外部资源查询
    private boolean requireExternalResource;
    // 工具调用异常信息
    private String toolErrorMsg;
}

三、核心业务服务:Spring AI 原生 MCP + 幻觉拦截逻辑

重点说明:摒弃伪代码 parseModelWantCallTool,使用 Spring AI 原生 API:

ChatResponse.getResult().getOutput().getToolCalls() 判断模型是否需要调用工具;

MCP 工具自动包装为标准 ToolCallback,和 Spring AI 工具体系完全打通。

复制代码
package com.example.springaimcp.service;

import com.example.springaimcp.context.McpAvailableStatus;
import com.example.springaimcp.context.McpSessionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.ToolResponseMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.mcp.client.sync.McpSyncClient;
import org.springframework.ai.mcp.tool.McpToolUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

@Slf4j
@Service
public class McpAgentService {

    private final ChatClient chatClient;
    private final McpSyncClient mcpSyncClient;
    private final List<ToolCallback> mcpToolCallbacks;

    /**
     * 系统提示词:软约束兜底,禁止模型编造外部资源数据
     */
    private static final String SYSTEM_PROMPT = """
            你是MCP AI助手,请严格遵守规则:
            1. 如果没有可用MCP工具,你绝对不能编造任何外部数据、文件内容、仓库信息。不知道就如实告知用户,禁止猜测。
            2. 如果调用MCP工具收到错误、空结果,禁止脑补返回数据,直接告知用户工具调用失败。
            3. 你只能使用MCP工具返回的真实结果回答用户,不允许虚构工具返回值。
            """;

    public McpAgentService(ChatClient.Builder builder, McpSyncClient mcpSyncClient) {
        this.chatClient = builder
                .defaultSystem(SYSTEM_PROMPT)
                .build();
        this.mcpSyncClient = mcpSyncClient;
        // 从MCP同步客户端拉取远端所有工具,转为Spring AI标准ToolCallback
        this.mcpToolCallbacks = McpToolUtils.getToolCallbacksFromSyncClients(List.of(mcpSyncClient));
    }

    public String chat(String userInput) {
        // 1. 构建会话上下文,更新MCP可用状态
        McpSessionContext sessionContext = buildSessionContext();

        // ========= Host层硬拦截核心逻辑 =========
        boolean needExternalData = judgeNeedExternalResource(userInput);
        if (needExternalData && sessionContext.getMcpAvailableStatus() == McpAvailableStatus.NO_AVAILABLE_TOOLS) {
            return "【宿主拦截】当前MCP无可用工具,该问题依赖外部资源,禁止模型编造结果。";
        }

        UserMessage userMessage = new UserMessage(userInput);
        Prompt prompt = new Prompt(List.of(userMessage));

        // 把MCP工具注册给ChatClient,模型感知远端MCP Server工具定义
        ChatResponse resp = chatClient.prompt(prompt)
                .tools(mcpToolCallbacks)
                .call()
                .chatResponse();

        // Spring AI原生方式读取模型下发的工具调用指令
        var toolCalls = resp.getResult().getOutput().getToolCalls();
        if (!CollectionUtils.isEmpty(toolCalls)) {
            for (var toolCall : toolCalls) {
                String toolName = toolCall.name();
                String argumentsJson = toolCall.arguments();
                ToolCallback targetCallback = mcpToolCallbacks.stream()
                        .filter(cb -> cb.getToolDefinition().name().equals(toolName))
                        .findFirst()
                        .orElse(null);

                if (targetCallback == null) {
                    log.warn("模型请求调用不存在的MCP工具:{}", toolName);
                    return "MCP工具【" + toolName + "】不存在,无法获取数据,请稍后重试,禁止编造结果";
                }

                try {
                    // 底层自动走MCP JSON-RPC,调用远端MCP Server call_tool
                    String toolResult = targetCallback.call(argumentsJson);
                    // 将工具真实结果回传给模型,整理自然语言回答
                    return chatClient.prompt()
                            .messages(userMessage, new ToolResponseMessage(List.of(new ToolResponse(toolCall.id(), toolResult, toolName))))
                            .call()
                            .content();
                } catch (Exception e) {
                    log.error("MCP工具调用异常,toolName:{}", toolName, e);
                    // 超时、鉴权失败、服务宕机统一在这里拦截,不把异常交给模型解读
                    return "MCP工具调用失败,无法获取数据,请稍后重试,禁止编造结果";
                }
            }
        }
        // 无需调用工具,直接返回模型回答
        return resp.getResult().getOutput().getText();
    }

    /**
     * 构建MCP会话上下文,同步远端工具状态
     */
    private McpSessionContext buildSessionContext() {
        McpSessionContext ctx = new McpSessionContext();
        if (CollectionUtils.isEmpty(mcpToolCallbacks)) {
            ctx.setMcpAvailableStatus(McpAvailableStatus.NO_AVAILABLE_TOOLS);
            log.warn("MCP Client拉取远端工具列表为空,MCP Server不可用");
        } else {
            ctx.setMcpAvailableStatus(McpAvailableStatus.HAS_AVAILABLE_TOOLS);
            log.info("成功加载MCP工具列表 size:{}", mcpToolCallbacks.size());
        }
        return ctx;
    }

    /**
     * 简易分类:判断问题是否依赖外部资源
     * 生产环境可替换为轻量分类模型,提升准确率
     */
    private boolean judgeNeedExternalResource(String query) {
        String[] keywords = {"github", "仓库", "issue", "pr", "搜索代码", "slack消息", "查询文件"};
        for (String kw : keywords) {
            if (query.contains(kw)) {
                return true;
            }
        }
        return false;
    }
}

四、Controller 接口层

复制代码
package com.example.springaimcp.controller;

import com.example.springaimcp.service.McpAgentService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class McpChatController {

    private final McpAgentService mcpAgentService;

    @GetMapping("/ai/mcp/chat")
    public String chat(@RequestParam String query) {
        return mcpAgentService.chat(query);
    }
}

五、启动类 + 配置文件

启动类

复制代码
package com.example.springaimcp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringAiMcpApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAiMcpApplication.class, args);
    }
}

application.yml(DashScope 通义大模型)

复制代码
spring:
  application:
    name: springai-mcp-anti-hallucination
  ai:
    dashscope:
      api-key: sk-xxx你的密钥xxx

六、测试用例说明

测试1:MCP无可用工具,查询外部资源问题

请求:GET http://127.0.0.1:8080/ai/mcp/chat?query=查询github仓库最近issue

逻辑:judgeNeedExternalResource=true + MCP状态 NO_AVAILABLE_TOOLS → Host 直接拦截,请求不送入大模型

返回:【宿主拦截】当前MCP无可用工具,该问题依赖外部资源,禁止模型编造结果。

测试2:MCP无可用工具,纯常识问题

请求:GET http://127.0.0.1:8080/ai/mcp/chat?query=1+1等于几

逻辑:不需要外部资源,放行给模型,正常返回答案 2

测试3:MCP工具列表正常,但是call_tool超时/鉴权失败

逻辑:模型下发工具调用指令,执行 targetCallback.call() 抛出异常,代码捕获异常,直接返回固定提示,异常信息不交给模型二次处理,杜绝模型脑补。

返回:MCP工具调用失败,无法获取数据,请稍后重试,禁止编造结果

七、Mermaid 时序图

时序1:MCP Server连接失败/空工具列表,宿主前置拦截

MCPServer LLM MCPClient Host User MCPServer LLM MCPClient Host User #mermaid-svg-tjXkzzDoC6CS9Vio{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tjXkzzDoC6CS9Vio .error-icon{fill:#552222;}#mermaid-svg-tjXkzzDoC6CS9Vio .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tjXkzzDoC6CS9Vio .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tjXkzzDoC6CS9Vio .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tjXkzzDoC6CS9Vio .marker.cross{stroke:#333333;}#mermaid-svg-tjXkzzDoC6CS9Vio svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tjXkzzDoC6CS9Vio p{margin:0;}#mermaid-svg-tjXkzzDoC6CS9Vio .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tjXkzzDoC6CS9Vio text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tjXkzzDoC6CS9Vio .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tjXkzzDoC6CS9Vio .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tjXkzzDoC6CS9Vio .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tjXkzzDoC6CS9Vio .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tjXkzzDoC6CS9Vio #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tjXkzzDoC6CS9Vio .sequenceNumber{fill:white;}#mermaid-svg-tjXkzzDoC6CS9Vio #sequencenumber{fill:#333;}#mermaid-svg-tjXkzzDoC6CS9Vio #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tjXkzzDoC6CS9Vio .messageText{fill:#333;stroke:none;}#mermaid-svg-tjXkzzDoC6CS9Vio .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tjXkzzDoC6CS9Vio .labelText,#mermaid-svg-tjXkzzDoC6CS9Vio .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tjXkzzDoC6CS9Vio .loopText,#mermaid-svg-tjXkzzDoC6CS9Vio .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tjXkzzDoC6CS9Vio .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tjXkzzDoC6CS9Vio .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tjXkzzDoC6CS9Vio .noteText,#mermaid-svg-tjXkzzDoC6CS9Vio .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tjXkzzDoC6CS9Vio .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tjXkzzDoC6CS9Vio .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tjXkzzDoC6CS9Vio .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tjXkzzDoC6CS9Vio .actorPopupMenu{position:absolute;}#mermaid-svg-tjXkzzDoC6CS9Vio .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tjXkzzDoC6CS9Vio .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tjXkzzDoC6CS9Vio .actor-man circle,#mermaid-svg-tjXkzzDoC6CS9Vio line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tjXkzzDoC6CS9Vio :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户提问 查询github issue list_tools 拉取远端工具 RPC list_tools 返回空列表或连接异常 更新状态 NO_AVAILABLE_TOOLS 判断问题依赖外部资源 宿主拦截 无MCP工具 禁止编造回答

时序2:工具列表拉取成功,工具执行调用异常

MCPServer LLM MCPClient Host User MCPServer LLM MCPClient Host User #mermaid-svg-P65dh7YFOqV0324W{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P65dh7YFOqV0324W .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P65dh7YFOqV0324W .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P65dh7YFOqV0324W .error-icon{fill:#552222;}#mermaid-svg-P65dh7YFOqV0324W .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P65dh7YFOqV0324W .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P65dh7YFOqV0324W .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P65dh7YFOqV0324W .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P65dh7YFOqV0324W .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P65dh7YFOqV0324W .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P65dh7YFOqV0324W .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P65dh7YFOqV0324W .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P65dh7YFOqV0324W .marker.cross{stroke:#333333;}#mermaid-svg-P65dh7YFOqV0324W svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P65dh7YFOqV0324W p{margin:0;}#mermaid-svg-P65dh7YFOqV0324W .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-P65dh7YFOqV0324W text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-P65dh7YFOqV0324W .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-P65dh7YFOqV0324W .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-P65dh7YFOqV0324W .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-P65dh7YFOqV0324W .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-P65dh7YFOqV0324W #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-P65dh7YFOqV0324W .sequenceNumber{fill:white;}#mermaid-svg-P65dh7YFOqV0324W #sequencenumber{fill:#333;}#mermaid-svg-P65dh7YFOqV0324W #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-P65dh7YFOqV0324W .messageText{fill:#333;stroke:none;}#mermaid-svg-P65dh7YFOqV0324W .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-P65dh7YFOqV0324W .labelText,#mermaid-svg-P65dh7YFOqV0324W .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-P65dh7YFOqV0324W .loopText,#mermaid-svg-P65dh7YFOqV0324W .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-P65dh7YFOqV0324W .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-P65dh7YFOqV0324W .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-P65dh7YFOqV0324W .noteText,#mermaid-svg-P65dh7YFOqV0324W .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-P65dh7YFOqV0324W .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-P65dh7YFOqV0324W .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-P65dh7YFOqV0324W .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-P65dh7YFOqV0324W .actorPopupMenu{position:absolute;}#mermaid-svg-P65dh7YFOqV0324W .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-P65dh7YFOqV0324W .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-P65dh7YFOqV0324W .actor-man circle,#mermaid-svg-P65dh7YFOqV0324W line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-P65dh7YFOqV0324W :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户提问 list_tools 拉取远端工具 RPC list_tools 返回工具定义 工具集合转为ToolCallback Prompt和工具定义送入模型 返回ToolCall调用指令 执行call_tool RPC call_tool 超时或鉴权报错 捕获异常 工具调用失败禁止编造结果

八、生产落地要点与优化方向

  1. 硬拦截优先级高于Prompt

    Prompt只是软约束,大模型概率生成天然存在突破规则的可能性。Host作为应用控制层,持有真实的MCP连接状态,在请求送入模型之前阻断,是可靠性最高的防线。

  2. 当前方案局限与优化方向

    • 资源依赖判断:示例是关键词匹配,生产可以接入小模型分类,识别用户意图是否需要外部工具;
    • 多MCP Server聚合:McpToolUtils 支持聚合多个 McpSyncClient 的工具集合;
    • 返回结果校验:增加后置校验,扫描模型输出是否包含虚构外部资源信息;
    • 状态缓存与刷新:list_tools 不需要每次请求都拉取,可以增加定时刷新、失效机制,降低RPC开销。
  3. 避坑清单

    • ❌ 只依靠 System Prompt 限制幻觉:MCP不可用时模型依然编造外部数据;
    • ❌ 将原始异常堆栈直接透传给大模型:模型容易解读报错信息、虚构返回值;
    • ✅ Host维护独立MCP状态,前置拦截优先;Prompt作为兜底;
    • ✅ 工具调用异常由Host接管返回固定提示,不交给模型自由解读错误。

结语

MCP 协议解决了工具接入标准化的问题,但幻觉治理不在协议能力范围内。在 Spring AI Alibaba + MCP 构建 Agent 的场景中,宿主状态管控前置拦截 + 提示词兜底这套组合方案,能够很好抑制工具不可用场景下的模型编造行为。

相关推荐
nice_lcj5204 小时前
【从零理解 Spring AI · 07】对话记忆:让模型记住上一句说了什么
java·spring ai·ai应用开发·chatmemory·对话记忆
nice_lcj5204 天前
【从零理解 Spring AI · 03】Spring AI 在 Spring 生态里的位置
java·spring boot·大模型·spring ai·ai应用开发
Java成神之路-4 天前
Spring AI Alibaba StateGraph 多Agent协作全解:5种编排模式
ai应用开发
Java成神之路-4 天前
Spring AI Alibaba 多Agent避坑:别把StateGraph等同于多Agent协作模式
ai应用开发
前进的李工5 天前
Agent 学习笔记 05:系统提示词应该怎么写
笔记·学习·prompt·agent·个人开发·few-shot·ai应用开发
Java成神之路-8 天前
Java 构建 AI 应用:Spring AI Alibaba Workflow 与 Agent 架构抉择
ai应用开发
一枕槐安48713 天前
多模态RAG实战:PDF图文混合文档检索问答本地跑通
langchain·pdf·milvus·ai应用开发·多模态rag
Java成神之路-14 天前
Jev 和 LLM 到底有什么区别?
ai应用开发
Java成神之路-18 天前
多 Agent 协作别过度设计:A2A(Agent2Agent)协议
ai应用开发