二:Multi-Agent 协作架构与 MCP 协议实战:Java 企业级 AI 智能体进阶指南

Multi-Agent 协作架构与 MCP 协议实战:Java 企业级 AI 智能体进阶指南

前言

上一篇文章中,我们用 LangChain4j 构建了一个具备工具调用、RAG 和记忆能力的单体 Agent。但在真实的企业场景中,一个 Agent 很难包揽所有事情------就像一家公司不可能只有一个"全能员工"。你需要一个 数据分析 Agent 、一个 报告撰写 Agent 、一个 运维调度 Agent......它们各司其职,又能协同工作。

与此同时,2026 年 AI 领域最大的标准化事件之一------MCP(Model Context Protocol)协议 的成熟,正在重塑 Agent 与外部工具的集成方式。

本文将从架构设计到代码落地,深入讲解这两项关键技术。


一、为什么需要 Multi-Agent?

1.1 单体 Agent 的三大瓶颈

在开始 Multi-Agent 之前,我们先理解:单体 Agent 到底哪里不够用?

瓶颈一:上下文窗口被"撑爆"

一个全能 Agent 需要挂载大量工具------查询能耗的工具、生成报告的工具、发送通知的工具、调用审批流的工具......每注册一个工具,它的 JSON Schema 描述就会占用数百个 Token。当工具数超过 15~20 个时,光是工具描述就可能占据上万 Token,严重挤压实际对话的上下文空间。

瓶颈二:System Prompt 变得臃肿

工具的调用规范、业务流程的规则、安全约束------所有这些都要写进 System Prompt。当一个 Agent 承担了太多职责,Prompt 会长到几千字,模型的注意力被稀释,指令遵循的准确率显著下降。

瓶颈三:职责耦合导致维护困难

所有逻辑塞在一个 Agent 里,修改一个功能可能影响其他功能。测试也变得困难------你无法单独验证"数据分析能力"是否正常,因为它的 Prompt 里混杂着十几个不同领域的指令。

1.2 Multi-Agent 的核心思想:分而治之

Multi-Agent 的架构思想非常朴素:让每个 Agent 只做一件事,做到极致,然后让它们协作。

这本质上与软件工程中的 单一职责原则(SRP) 一脉相承。我们不是造一个"超级大脑",而是组建一个"专家团队":

复制代码
┌──────────────────────────────────────────────────────┐
│                 Orchestrator Agent                    │
│              (调度中心 / 指挥官)                      │
│                                                      │
│   "理解用户意图,拆解任务,分派给合适的专家Agent"       │
└──────┬──────────────┬──────────────┬─────────────────┘
       │              │              │
  ┌────▼────┐   ┌─────▼─────┐  ┌────▼────┐
  │ 数据    │   │  报告     │  │  运维   │
  │ 分析    │   │  生成     │  │  调度   │
  │ Agent   │   │  Agent    │  │  Agent  │
  └─────────┘   └───────────┘  └─────────┘

每个"专家 Agent"有自己独立的:

  • 精简的 System Prompt:只描述该领域的职责和规则
  • 专用的工具集:只挂载该领域需要的 3~5 个工具
  • 独立的记忆空间:不与其他 Agent 的上下文混淆

1.3 Multi-Agent 的三种协作模式

在架构设计中,Multi-Agent 的协作模式主要有三种,各有优劣:

模式一:中心化调度(Orchestrator Pattern)

一个"调度 Agent"作为中枢,理解用户意图后决定调用哪个专家 Agent。

复制代码
用户 ──→ Orchestrator ──→ 数据分析Agent ──→ 结果
                │
                └───────→ 报告生成Agent ──→ 结果
                │
                └───────→ 运维调度Agent ──→ 结果
  • 优点:控制流清晰,调度逻辑集中,易于调试。
  • 缺点:调度 Agent 成为瓶颈和单点故障。
  • 适用场景:大多数企业应用,任务流程相对固定。

模式二:流水线(Pipeline Pattern)

多个 Agent 按固定顺序依次处理,前一个的输出是后一个的输入。

复制代码
用户 ──→ 数据采集Agent ──→ 数据分析Agent ──→ 报告生成Agent ──→ 最终报告
  • 优点:每个阶段可独立优化和替换。
  • 缺点:灵活性低,不支持条件分支。
  • 适用场景:ETL 数据处理、定时报告生成等流程固定的场景。

模式三:协商式(Debate Pattern)

多个 Agent 对同一问题各自给出方案,然后通过"讨论"达成共识。

  • 优点:适合需要多视角判断的复杂决策。
  • 缺点:Token 消耗大,延迟高。
  • 适用场景:风险评估、投资决策等高价值决策场景。

本文重点实现模式一(中心化调度),因为它在企业项目中最实用、落地成本最低。


二、用 LangGraph4j 实现 Multi-Agent 编排

2.1 什么是 LangGraph4j?

LangGraph4j 是 LangChain4j 生态中的 图状态机 框架,专门用于编排多 Agent 的协作流程。它的核心抽象非常简洁:

  • Node(节点):一个执行单元,可以是一个 Agent、一个工具调用、或一段业务逻辑。
  • Edge(边):定义节点之间的流转关系,支持条件分支。
  • State(状态):在节点间流转的共享数据。

用一个类比来理解:如果单体 Agent 是一个"函数",那 LangGraph4j 编排的就是一个"状态机"------多个函数按照条件逻辑组合执行。

2.2 Maven 依赖

xml 复制代码
<dependency>
    <groupId>org.bsc.langgraph4j</groupId>
    <artifactId>langgraph4j-core</artifactId>
    <version>1.5.0</version>
</dependency>

<!-- 如果需要可视化调试 -->
<dependency>
    <groupId>org.bsc.langgraph4j</groupId>
    <artifactId>langgraph4j-studio</artifactId>
    <version>1.5.0</version>
</dependency>

2.3 定义共享状态

首先定义在 Agent 之间流转的共享状态对象。它就像一个"公文包",每个 Agent 处理完后往里面放入自己的结果:

java 复制代码
import org.bsc.langgraph4j.state.AgentState;
import java.util.Map;

/**
 * Multi-Agent 共享状态
 * 
 * 设计原则:
 * - 每个 Agent 只读取自己需要的字段、写入自己产出的字段
 * - 状态是不可变追加的:通过 Map.copyOf 确保线程安全
 * - 使用 Optional 防御空值
 */
public class EnergyWorkflowState extends AgentState {

    public EnergyWorkflowState(Map<String, Object> initData) {
        super(initData);
    }

    // ========== 输入 ==========

    /** 用户原始提问 */
    public String userQuery() {
        return (String) value("userQuery").orElse("");
    }

    // ========== 各 Agent 的产出 ==========

    /** 数据分析 Agent 的分析结果 */
    public String analysisResult() {
        return (String) value("analysisResult").orElse("");
    }

    /** 报告生成 Agent 的报告内容 */
    public String reportContent() {
        return (String) value("reportContent").orElse("");
    }

    /** 运维调度 Agent 的执行结果 */
    public String operationResult() {
        return (String) value("operationResult").orElse("");
    }

    /** 最终汇总回复 */
    public String finalResponse() {
        return (String) value("finalResponse").orElse("");
    }

    /** 路由决策:Orchestrator 判断需要走哪条路径 */
    public String routeDecision() {
        return (String) value("routeDecision").orElse("unknown");
    }
}

2.4 实现各专家 Agent 节点

每个节点是一个函数:接收当前状态,返回更新后的状态。

java 复制代码
import org.bsc.langgraph4j.action.NodeAction;
import java.util.Map;

/**
 * 数据分析 Agent 节点
 * 
 * 职责:接收用户的数据查询需求,调用能耗查询工具,返回结构化分析
 */
public class DataAnalysisNode implements NodeAction<EnergyWorkflowState> {

    private final EnergyDataAgent dataAgent;  // 上一篇文章中构建的专用 Agent

    public DataAnalysisNode(EnergyDataAgent dataAgent) {
        this.dataAgent = dataAgent;
    }

    @Override
    public Map<String, Object> apply(EnergyWorkflowState state) throws Exception {
        // 构建包含上下文的任务描述
        String taskPrompt = String.format("""
            请分析以下能耗数据查询需求,给出详细的数据分析结果:
            
            用户需求:%s
            """, state.userQuery());

        String result = dataAgent.chat(taskPrompt);

        // 将分析结果写入共享状态
        return Map.of("analysisResult", result);
    }
}

/**
 * 报告生成 Agent 节点
 * 
 * 职责:基于数据分析结果,生成专业的能耗分析报告
 */
public class ReportGenerationNode implements NodeAction<EnergyWorkflowState> {

    private final ReportAgent reportAgent;

    public ReportGenerationNode(ReportAgent reportAgent) {
        this.reportAgent = reportAgent;
    }

    @Override
    public Map<String, Object> apply(EnergyWorkflowState state) throws Exception {
        String taskPrompt = String.format("""
            请基于以下数据分析结果,生成一份专业的能耗分析报告:
            
            原始需求:%s
            数据分析结果:%s
            """, state.userQuery(), state.analysisResult());

        String report = reportAgent.chat(taskPrompt);

        return Map.of("reportContent", report);
    }
}

/**
 * 运维调度 Agent 节点
 * 
 * 职责:执行具体的运维操作(发送通知、调整参数、触发工单等)
 */
public class OperationNode implements NodeAction<EnergyWorkflowState> {

    private final OperationAgent operationAgent;

    public OperationNode(OperationAgent operationAgent) {
        this.operationAgent = operationAgent;
    }

    @Override
    public Map<String, Object> apply(EnergyWorkflowState state) throws Exception {
        String taskPrompt = String.format("""
            请根据以下情况执行相应的运维操作:
            
            原始需求:%s
            数据分析结果:%s
            """, state.userQuery(), state.analysisResult());

        String result = operationAgent.chat(taskPrompt);

        return Map.of("operationResult", result);
    }
}

2.5 实现 Orchestrator:智能路由

Orchestrator 是整个 Multi-Agent 系统的"大脑"。它的职责是:理解用户意图,决定调用哪些专家 Agent、以什么顺序调用。

java 复制代码
/**
 * Orchestrator 节点:意图识别与路由决策
 * 
 * 工作原理:
 * 1. 将用户问题发送给一个轻量级 LLM
 * 2. LLM 返回路由标签:ANALYSIS / REPORT / OPERATION / COMPOSITE
 * 3. 条件边(Conditional Edge)根据标签决定下一步走哪个节点
 */
public class OrchestratorNode implements NodeAction<EnergyWorkflowState> {

    private final ChatLanguageModel routerModel;

    public OrchestratorNode(ChatLanguageModel routerModel) {
        this.routerModel = routerModel;
    }

    @Override
    public Map<String, Object> apply(EnergyWorkflowState state) throws Exception {
        String routePrompt = String.format("""
            你是一个任务路由器。根据用户的问题,判断应该走哪条处理路径。
            
            可选路径:
            - ANALYSIS:纯粹的数据查询和分析(如"A栋今天用了多少电")
            - REPORT:需要生成报告或文档(如"生成本月能耗报告")
            - OPERATION:需要执行运维操作(如"通知张工检修B栋设备")
            - COMPOSITE:需要先分析,再根据结果决定是否生成报告或执行操作
            
            只返回路径标签,不要返回其他内容。
            
            用户问题:%s
            """, state.userQuery());

        AiMessage response = routerModel.generate(
            UserMessage.from(routePrompt)
        ).content();

        String decision = response.text().trim().toUpperCase();

        // 防御性校验:如果 LLM 返回了意外内容,降级为 COMPOSITE
        if (!Set.of("ANALYSIS", "REPORT", "OPERATION", "COMPOSITE").contains(decision)) {
            decision = "COMPOSITE";
        }

        return Map.of("routeDecision", decision);
    }
}

2.6 组装状态图:定义完整的协作流程

这是最核心的部分------用状态图定义节点和边的关系:

java 复制代码
import org.bsc.langgraph4j.GraphStateException;
import org.bsc.langgraph4j.StateGraph;
import static org.bsc.langgraph4j.StateGraph.END;
import static org.bsc.langgraph4j.StateGraph.START;

/**
 * 构建 Multi-Agent 协作状态图
 * 
 * 图的拓扑结构:
 * 
 * START ──→ Orchestrator ──┬──(ANALYSIS)──→ DataAnalysis ──→ END
 *                          │
 *                          ├──(REPORT)────→ DataAnalysis ──→ ReportGen ──→ END
 *                          │
 *                          ├──(OPERATION)──→ Operation ──→ END
 *                          │
 *                          └──(COMPOSITE)──→ DataAnalysis ──→ ReportGen ──→ Operation ──→ END
 */
public class EnergyWorkflowBuilder {

    public StateGraph<EnergyWorkflowState> build(
            OrchestratorNode orchestrator,
            DataAnalysisNode dataAnalysis,
            ReportGenerationNode reportGen,
            OperationNode operation) throws GraphStateException {

        return new StateGraph<>(EnergyWorkflowState::new)
            // ===== 注册节点 =====
            .addNode("orchestrator", orchestrator)
            .addNode("data_analysis", dataAnalysis)
            .addNode("report_generation", reportGen)
            .addNode("operation", operation)

            // ===== 定义边 =====

            // 入口:START → Orchestrator
            .addEdge(START, "orchestrator")

            // 条件分支:Orchestrator 的决策决定下一步走向
            .addConditionalEdges("orchestrator",
                state -> state.routeDecision(),
                Map.of(
                    "ANALYSIS",  "data_analysis",
                    "REPORT",    "data_analysis",      // 报告也需要先做数据分析
                    "OPERATION", "operation",
                    "COMPOSITE", "data_analysis"        // 复合路径先走数据分析
                )
            )

            // 纯分析路径:DataAnalysis → END
            // 注意:这里通过状态判断来决定是否继续
            .addConditionalEdges("data_analysis",
                state -> {
                    // 如果路由决策是 ANALYSIS,直接结束
                    // 如果是 REPORT 或 COMPOSITE,继续走报告生成
                    return switch (state.routeDecision()) {
                        case "ANALYSIS" -> "end";
                        default -> "report_generation";
                    };
                },
                Map.of(
                    "end", END,
                    "report_generation", "report_generation"
                )
            )

            // 报告生成后的分支
            .addConditionalEdges("report_generation",
                state -> {
                    // COMPOSITE 路径还需要执行运维操作
                    return "COMPOSITE".equals(state.routeDecision())
                        ? "operation" : "end";
                },
                Map.of(
                    "operation", "operation",
                    "end", END
                )
            )

            // 运维操作完成后结束
            .addEdge("operation", END);
    }
}

2.7 Spring Boot 配置与运行

java 复制代码
@Configuration
public class MultiAgentConfig {

    @Bean
    public StateGraph<EnergyWorkflowState> energyWorkflow(
            OrchestratorNode orchestrator,
            DataAnalysisNode dataAnalysis,
            ReportGenerationNode reportGen,
            OperationNode operation) throws GraphStateException {

        return new EnergyWorkflowBuilder()
            .build(orchestrator, dataAnalysis, reportGen, operation);
    }

    @Bean
    public CompiledGraph<EnergyWorkflowState> compiledWorkflow(
            StateGraph<EnergyWorkflowState> workflow) throws GraphStateException {
        return workflow.compile();
    }
}

@Service
public class MultiAgentService {

    @Autowired
    private CompiledGraph<EnergyWorkflowState> workflow;

    /**
     * 执行 Multi-Agent 协作流程
     * 
     * @param userQuery 用户问题
     * @return 各阶段的执行结果(用于调试和展示)
     */
    public Map<String, Object> execute(String userQuery) {
        // 初始化状态
        Map<String, Object> initState = Map.of("userQuery", userQuery);

        // 执行状态图(内部自动按拓扑顺序执行各节点)
        var result = workflow.stream(initState)
            .collect(Collectors.toList());

        // 获取最终状态
        EnergyWorkflowState finalState = result.get(result.size() - 1);

        return Map.of(
            "routeDecision", finalState.routeDecision(),
            "analysis", finalState.analysisResult(),
            "report", finalState.reportContent(),
            "operation", finalState.operationResult()
        );
    }
}

@RestController
@RequestMapping("/api/multi-agent")
public class MultiAgentController {

    @Autowired
    private MultiAgentService multiAgentService;

    @PostMapping("/execute")
    public ResponseEntity<Map<String, Object>> execute(
            @RequestBody ChatRequest request) {
        return ResponseEntity.ok(multiAgentService.execute(request.getMessage()));
    }
}

调用示例

bash 复制代码
# 场景1:纯数据查询
curl -X POST http://localhost:8080/api/multi-agent/execute \
  -H "Content-Type: application/json" \
  -d '{"message": "A栋今天用电量是多少"}'
# 路由: ANALYSIS → 只走数据分析节点

# 场景2:生成报告
curl -X POST http://localhost:8080/api/multi-agent/execute \
  -H "Content-Type: application/json" \
  -d '{"message": "帮我生成本月各楼栋的能耗对比报告"}'
# 路由: REPORT → 数据分析 → 报告生成

# 场景3:复合任务
curl -X POST http://localhost:8080/api/multi-agent/execute \
  -H "Content-Type: application/json" \
  -d '{"message": "分析C栋数据中心近一周能耗,如果异常就生成报告并通知运维张工"}'
# 路由: COMPOSITE → 数据分析 → 报告生成 → 运维调度

三、MCP 协议:Agent 工具集成的"USB 标准"

3.1 MCP 是什么?它解决了什么问题?

在 MCP 出现之前 ,每个 Agent 框架集成外部工具都有自己的方式。LangChain4j 用 @Tool 注解,Spring AI 用 FunctionCallback,Semantic Kernel 用 KernelFunction......这意味着:你为 LangChain4j 写的一个工具,在 Spring AI 中完全不能复用。

这就像 USB 标准出现之前,每个品牌的手机都有自己的充电接口------极其碎片化。

MCP(Model Context Protocol) 由 Anthropic 于 2024 年底提出,目标是成为 Agent 与外部工具/数据源交互的 统一协议标准。到 2026 年,它已经获得了 OpenAI、Google、微软等主要厂商的支持。

MCP 的核心思想是:将"工具"抽象为一个独立的服务(MCP Server),任何 Agent 框架(MCP Client)都可以通过标准协议调用它。

复制代码
┌─────────────┐         ┌─────────────────┐         ┌──────────────┐
│  Agent 框架  │ ──JSON──→│   MCP Server    │         │   外部系统    │
│ (MCP Client)│ ←──RPC───│  (工具服务)      │────────→│  DB/API/MQ   │
│             │         │                 │         │              │
│ LangChain4j │         │  ┌───────────┐  │         └──────────────┘
│ Spring AI   │         │  │ Tool定义   │  │
│ 其他框架    │         │  │ (JSON Schema)│ │
└─────────────┘         │  └───────────┘  │
                        └─────────────────┘

类比理解

  • 传统方式:Agent 框架和工具是"硬编码绑定",像焊接在主板上的零件。
  • MCP 方式:工具变成可插拔的"USB 设备",任何支持 MCP 的 Agent 都能即插即用。

3.2 MCP 协议的三大原语

MCP 协议定义了三种核心原语(Primitive),可以理解为"三种能力":

原语 含义 类比
Tools Agent 可调用的函数(有输入参数,有执行结果) REST API
Resources Agent 可读取的数据源(只读,类似文件) GET 接口
Prompts 预定义的提示词模板(Agent 可以检索使用) 配置中心

本文重点实现 Tools 原语,因为它在 Agent 场景中使用最广泛。

3.3 MCP 的传输层:Stdio vs SSE

MCP 定义了两种传输方式,适用于不同场景:

Stdio(标准输入输出)

  • MCP Client 以子进程方式启动 MCP Server
  • 通过 stdin/stdout 交换 JSON-RPC 消息
  • 适合:本地开发、单机部署
  • 优点:零网络开销,启动简单

SSE(Server-Sent Events)+ HTTP

  • MCP Server 作为独立 HTTP 服务运行
  • 客户端通过 SSE 接收流式响应,通过 HTTP POST 发送请求
  • 适合:分布式部署、远程服务调用
  • 优点:天然支持网络,可以独立部署和扩展

企业项目推荐 SSE 方式,因为它与微服务架构天然契合。

3.4 构建一个 MCP Server:将能耗查询工具标准化

下面我们把上一篇文章中的能耗查询工具改造成一个标准的 MCP Server。

依赖配置
xml 复制代码
<dependencies>
    <!-- MCP SDK(Java 官方实现) -->
    <dependency>
        <groupId>io.modelcontextprotocol</groupId>
        <artifactId>mcp-sdk-java</artifactId>
        <version>0.9.0</version>
    </dependency>

    <!-- Spring Boot Web(SSE 传输需要) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
定义 MCP 工具

MCP Server 的核心是定义"工具"。每个工具需要声明名称、描述和参数的 JSON Schema:

java 复制代码
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.*;
import org.springframework.stereotype.Service;

/**
 * 能源管理 MCP Server
 * 
 * 职责:将能耗查询、趋势分析、通知发送等能力封装为标准 MCP 工具
 * 
 * 设计原则:
 * 1. 工具描述要对 LLM 友好------清晰、准确、有调用场景说明
 * 2. 参数定义要完整------每个字段的类型和含义都要明确
 * 3. 返回值要结构化------方便 LLM 解析和引用
 */
@Service
public class EnergyMcpServer {

    private final EnergyDataService energyDataService;
    private final NotificationService notificationService;

    public EnergyMcpServer(EnergyDataService energyDataService,
                          NotificationService notificationService) {
        this.energyDataService = energyDataService;
        this.notificationService = notificationService;
    }

    /**
     * 构建 MCP Server 实例,注册所有工具
     */
    public McpServer buildServer() {
        return McpServer.builder()
            .serverInfo("energy-mcp-server", "1.0.0")

            // ===== 工具1:实时能耗查询 =====
            .tool(
                "query_realtime_energy",
                "查询指定区域的实时能耗数据。" +
                "当用户询问某个区域'当前用了多少电'、'实时能耗'等问题时使用。" +
                "返回数据包含当前能耗值(kWh)和采集时间。",
                // JSON Schema 定义参数
                """
                {
                  "type": "object",
                  "properties": {
                    "areaName": {
                      "type": "string",
                      "description": "区域名称,如'A栋办公楼'、'B栋研发中心'"
                    }
                  },
                  "required": ["areaName"]
                }
                """,
                this::handleQueryRealtimeEnergy
            )

            // ===== 工具2:能耗趋势分析 =====
            .tool(
                "query_energy_trend",
                "分析指定区域在过去N天内的能耗变化趋势。" +
                "当用户想了解'能耗走势'、'最近用电变化'等问题时使用。" +
                "返回趋势描述和关键指标(均值、峰值、变化率)。",
                """
                {
                  "type": "object",
                  "properties": {
                    "areaName": {
                      "type": "string",
                      "description": "区域名称"
                    },
                    "days": {
                      "type": "integer",
                      "description": "分析天数,通常为7或30"
                    }
                  },
                  "required": ["areaName", "days"]
                }
                """,
                this::handleQueryEnergyTrend
            )

            // ===== 工具3:发送预警通知 =====
            .tool(
                "send_alert",
                "向指定负责人发送能耗预警通知。" +
                "仅在能耗确实异常且用户明确要求通知时使用。" +
                "返回发送结果(成功/失败)。",
                """
                {
                  "type": "object",
                  "properties": {
                    "recipient": {
                      "type": "string",
                      "description": "接收人姓名或工号"
                    },
                    "alertLevel": {
                      "type": "string",
                      "enum": ["INFO", "WARNING", "CRITICAL"],
                      "description": "告警级别"
                    },
                    "message": {
                      "type": "string",
                      "description": "告警消息内容"
                    }
                  },
                  "required": ["recipient", "alertLevel", "message"]
                }
                """,
                this::handleSendAlert
            )

            .build();
    }

    // ==================== 工具处理函数 ====================

    private McpSchema.CallToolResult handleQueryRealtimeEnergy(
            Map<String, Object> arguments) {
        String areaName = (String) arguments.get("areaName");

        // 调用实际业务服务
        EnergyData data = energyDataService.getRealtimeConsumption(areaName);

        // 将结果封装为 JSON 字符串返回给 LLM
        String resultJson = String.format("""
            {
              "areaName": "%s",
              "currentConsumption": %.1f,
              "unit": "kWh",
              "collectTime": "%s",
              "status": "%s"
            }
            """,
            data.getAreaName(),
            data.getConsumption(),
            data.getCollectTime(),
            data.getStatus()
        );

        return new McpSchema.CallToolResult(
            List.of(new McpSchema.TextContent(resultJson)),
            false  // isError = false
        );
    }

    private McpSchema.CallToolResult handleQueryEnergyTrend(
            Map<String, Object> arguments) {
        String areaName = (String) arguments.get("areaName");
        int days = ((Number) arguments.get("days")).intValue();

        TrendAnalysis trend = energyDataService.analyzeTrend(areaName, days);

        String resultJson = String.format("""
            {
              "areaName": "%s",
              "period": "%d天",
              "averageConsumption": %.1f,
              "peakConsumption": %.1f,
              "changeRate": "%.1f%%",
              "trend": "%s"
            }
            """,
            areaName, days,
            trend.getAverage(),
            trend.getPeak(),
            trend.getChangeRate(),
            trend.getTrendDescription()
        );

        return new McpSchema.CallToolResult(
            List.of(new McpSchema.TextContent(resultJson)),
            false
        );
    }

    private McpSchema.CallToolResult handleSendAlert(
            Map<String, Object> arguments) {
        String recipient = (String) arguments.get("recipient");
        String alertLevel = (String) arguments.get("alertLevel");
        String message = (String) arguments.get("message");

        boolean success = notificationService.sendAlert(
            recipient, alertLevel, message);

        String resultJson = String.format("""
            {
              "success": %s,
              "recipient": "%s",
              "alertLevel": "%s",
              "message": "%s"
            }
            """, success, recipient, alertLevel, message);

        return new McpSchema.CallToolResult(
            List.of(new McpSchema.TextContent(resultJson)),
            !success  // 发送失败时标记为错误
        );
    }
}
启动 SSE 传输服务
java 复制代码
@Configuration
public class McpTransportConfig {

    @Bean
    public McpSseTransport mcpTransport(EnergyMcpServer energyMcpServer) {
        McpServer server = energyMcpServer.buildServer();

        // 使用 SSE 传输:Agent 通过 HTTP 连接此服务
        McpSseTransport transport = McpSseTransport.builder()
            .server(server)
            .port(8090)                    // MCP Server 独立端口
            .endpointPath("/mcp/sse")      // SSE 连接端点
            .messagePath("/mcp/message")   // 消息发送端点
            .build();

        transport.start();
        log.info("MCP Server 已启动,SSE 端点: http://localhost:8090/mcp/sse");

        return transport;
    }
}

3.5 在 Agent 中接入 MCP Client

MCP Server 启动后,Agent 端只需配置连接地址,就能自动发现并调用所有注册的工具:

java 复制代码
@Configuration
public class AgentWithMcpConfig {

    @Bean
    public EnergyAgent agentWithMcp() {
        // 1. 配置大模型
        OpenAiChatModel model = OpenAiChatModel.builder()
            .apiKey(System.getenv("LLM_API_KEY"))
            .baseUrl(System.getenv("LLM_BASE_URL"))
            .modelName("gpt-4o")
            .temperature(0.3)
            .build();

        // 2. 创建 MCP Client,连接到 MCP Server
        McpClient mcpClient = McpClient.builder()
            .transport(McpSseClientTransport.builder()
                .url("http://localhost:8090/mcp/sse")
                .build())
            .build();

        // 3. MCP Client 会自动发现 Server 上注册的所有工具
        //    并将它们转换为 LangChain4j 的 ToolSpec 格式
        List<Object> mcpTools = mcpClient.listTools().stream()
            .map(tool -> new McpToolWrapper(mcpClient, tool))
            .toList();

        // 4. 构建 Agent,传入 MCP 工具
        return AiServices.builder(EnergyAgent.class)
            .chatLanguageModel(model)
            .tools(mcpTools)  // MCP 工具和普通 @Tool 工具一样使用
            .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
            .build();
    }
}

关键优势 :Agent 代码中 没有任何对具体工具的硬编码引用。MCP Server 新增或删除工具,Agent 端零改动------这就是协议标准化的价值。


四、MCP + Multi-Agent:强强联合

4.1 每个 Agent 连接不同的 MCP Server

在 Multi-Agent 架构中,不同的专家 Agent 可以连接不同的 MCP Server,各自获取专属的工具能力:

复制代码
┌────────────────────────────────────────────────────────────────┐
│                        Orchestrator                            │
│                                                               │
│  ┌─────────────┐  ┌──────────────┐  ┌─────────────────────┐   │
│  │ 数据分析     │  │ 报告生成      │  │ 运维调度            │   │
│  │ Agent       │  │ Agent        │  │ Agent               │   │
│  │             │  │              │  │                     │   │
│  │ MCP Client  │  │ MCP Client   │  │ MCP Client          │   │
│  └──────┬──────┘  └──────┬───────┘  └──────────┬──────────┘   │
│         │                │                     │               │
└─────────┼────────────────┼─────────────────────┼───────────────┘
          │                │                     │
   ┌──────▼──────┐  ┌──────▼───────┐  ┌──────────▼──────────┐
   │ MCP Server  │  │ MCP Server   │  │ MCP Server          │
   │ energy-data │  │ report-gen   │  │ ops-automation      │
   │             │  │              │  │                     │
   │ 工具:       │  │ 工具:        │  │ 工具:               │
   │ · 实时查询   │  │ · 生成PDF    │  │ · 发送通知           │
   │ · 趋势分析   │  │ · 生成Excel  │  │ · 创建工单           │
   │ · 历史对比   │  │ · 邮件发送   │  │ · 调整参数           │
   └─────────────┘  └──────────────┘  └─────────────────────┘

4.2 配置代码

java 复制代码
@Configuration
public class MultiAgentMcpConfig {

    /**
     * 数据分析 Agent ------ 连接能源数据 MCP Server
     */
    @Bean
    public EnergyDataAgent dataAgent() {
        McpClient dataMcpClient = McpClient.builder()
            .transport(sseTransport("http://energy-data-mcp:8090/mcp/sse"))
            .build();

        return AiServices.builder(EnergyDataAgent.class)
            .chatLanguageModel(chatModel())
            .tools(dataMcpClient.listToolWrappers())
            .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
            .build();
    }

    /**
     * 报告生成 Agent ------ 连接报告生成 MCP Server
     */
    @Bean
    public ReportAgent reportAgent() {
        McpClient reportMcpClient = McpClient.builder()
            .transport(sseTransport("http://report-gen-mcp:8091/mcp/sse"))
            .build();

        return AiServices.builder(ReportAgent.class)
            .chatLanguageModel(chatModel())
            .tools(reportMcpClient.listToolWrappers())
            .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
            .build();
    }

    /**
     * 运维调度 Agent ------ 连接运维自动化 MCP Server
     */
    @Bean
    public OperationAgent operationAgent() {
        McpClient opsMcpClient = McpClient.builder()
            .transport(sseTransport("http://ops-mcp:8092/mcp/sse"))
            .build();

        return AiServices.builder(OperationAgent.class)
            .chatLanguageModel(chatModel())
            .tools(opsMcpClient.listToolWrappers())
            .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
            .build();
    }

    private ChatLanguageModel chatModel() {
        return OpenAiChatModel.builder()
            .apiKey(System.getenv("LLM_API_KEY"))
            .baseUrl(System.getenv("LLM_BASE_URL"))
            .modelName("gpt-4o")
            .temperature(0.3)
            .timeout(Duration.ofSeconds(60))
            .maxRetries(3)
            .build();
    }

    private McpSseClientTransport sseTransport(String url) {
        return McpSseClientTransport.builder().url(url).build();
    }
}

五、生产环境的深度考量

5.1 MCP Server 的健康检查与容错

MCP Server 作为独立服务,必须具备生产级的可靠性保障:

java 复制代码
/**
 * MCP 连接管理器
 * 
 * 解决的问题:
 * 1. MCP Server 宕机时自动重连
 * 2. 工具调用超时时的优雅降级
 * 3. 连接池管理(避免每次调用都建立新连接)
 */
@Component
public class McpConnectionManager {

    private final Map<String, McpClient> clientPool = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler =
        Executors.newScheduledThreadPool(2);

    /**
     * 获取或创建 MCP Client 连接
     */
    public McpClient getClient(String serverUrl) {
        return clientPool.computeIfAbsent(serverUrl, url -> {
            McpClient client = McpClient.builder()
                .transport(McpSseClientTransport.builder()
                    .url(url)
                    .timeout(Duration.ofSeconds(30))
                    .build())
                .build();

            // 启动心跳检测:每 30 秒检查连接状态
            scheduler.scheduleAtFixedRate(() -> {
                if (!client.isHealthy()) {
                    log.warn("MCP Server {} 连接异常,尝试重连...", url);
                    client.reconnect();
                }
            }, 30, 30, TimeUnit.SECONDS);

            return client;
        });
    }

    /**
     * 带超时和降级的工具调用
     */
    public CallToolResult callToolWithFallback(
            String serverUrl, String toolName,
            Map<String, Object> args, Duration timeout) {
        try {
            McpClient client = getClient(serverUrl);
            return client.callTool(toolName, args)
                .orTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS)
                .join();
        } catch (TimeoutException e) {
            log.error("MCP 工具调用超时: server={}, tool={}", serverUrl, toolName);
            // 降级:返回错误提示让 Agent 告知用户
            return createErrorResult("工具服务暂时不可用,请稍后重试");
        }
    }

    @PreDestroy
    public void shutdown() {
        clientPool.values().forEach(McpClient::close);
        scheduler.shutdown();
    }
}

5.2 MCP 工具调用的可观测性

在生产环境中,你需要追踪每一次 MCP 工具调用的完整链路:

java 复制代码
/**
 * MCP 工具调用拦截器(AOP 方式)
 * 
 * 记录:调用耗时、参数、返回值、错误信息
 * 上报:Prometheus / Grafana / OpenTelemetry
 */
@Aspect
@Component
public class McpToolCallAspect {

    @Autowired
    private MeterRegistry meterRegistry;

    @Around("@annotation(McpToolCall)")
    public Object monitorToolCall(ProceedingJoinPoint pjp) throws Throwable {
        String toolName = pjp.getSignature().getName();
        long startTime = System.currentTimeMillis();

        try {
            Object result = pjp.proceed();
            long duration = System.currentTimeMillis() - startTime;

            // 记录成功指标
            meterRegistry.timer("mcp.tool.call",
                "tool", toolName, "status", "success")
                .record(duration, TimeUnit.MILLISECONDS);

            log.info("[MCP调用成功] tool={} duration={}ms", toolName, duration);
            return result;

        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;

            // 记录失败指标
            meterRegistry.timer("mcp.tool.call",
                "tool", toolName, "status", "error")
                .record(duration, TimeUnit.MILLISECONDS);

            log.error("[MCP调用失败] tool={} duration={}ms error={}",
                toolName, duration, e.getMessage());
            throw e;
        }
    }
}

5.3 安全:MCP Server 的认证与授权

MCP Server 暴露了系统核心能力,必须做好安全防护:

java 复制代码
@Configuration
public class McpSecurityConfig {

    /**
     * MCP Server 端的认证拦截
     * 
     * 方案:API Key + 工具级权限控制
     */
    @Bean
    public McpServerFilter securityFilter() {
        return new McpServerFilter() {

            @Override
            public void beforeToolCall(ToolCallContext context) {
                // 1. 验证 API Key
                String apiKey = context.getHeader("X-API-Key");
                if (!apiKeyValidator.isValid(apiKey)) {
                    throw new McpUnauthorizedException("无效的 API Key");
                }

                // 2. 工具级权限检查
                String toolName = context.getToolName();
                String callerRole = apiKeyValidator.getRole(apiKey);

                if (isWriteOperation(toolName) && !"ADMIN".equals(callerRole)) {
                    throw new McpForbiddenException(
                        "角色 " + callerRole + " 无权调用写入工具: " + toolName);
                }

                // 3. 审计日志
                auditLog.record(callerRole, toolName, context.getArguments());
            }
        };
    }

    private boolean isWriteOperation(String toolName) {
        return Set.of("send_alert", "update_threshold", "delete_data")
            .contains(toolName);
    }
}

六、架构全景:Multi-Agent + MCP 完整拓扑

复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                           Spring Boot 应用                               │
│                                                                         │
│  ┌──────────┐    ┌──────────────────────────────────────────────────┐   │
│  │ REST API │───→│              Orchestrator Agent                  │   │
│  └──────────┘    │  (意图识别 → 路由决策 → 调度执行 → 结果汇总)      │   │
│                  └────┬──────────────┬──────────────┬───────────────┘   │
│                       │              │              │                    │
│            ┌──────────▼──┐  ┌───────▼──────┐  ┌───▼────────────┐      │
│            │ 数据分析Agent│  │ 报告生成Agent │  │ 运维调度Agent  │      │
│            │  (MCP Client)│  │ (MCP Client)  │  │ (MCP Client)   │      │
│            └──────┬───────┘  └───────┬───────┘  └───────┬────────┘      │
│                   │                  │                  │                │
└───────────────────┼──────────────────┼──────────────────┼────────────────┘
                    │ SSE              │ SSE              │ SSE
        ┌───────────▼──────┐ ┌────────▼───────┐ ┌────────▼───────────┐
        │ MCP Server       │ │ MCP Server     │ │ MCP Server         │
        │ energy-data      │ │ report-gen     │ │ ops-automation     │
        │ :8090            │ │ :8091          │ │ :8092              │
        │                  │ │                │ │                    │
        │ Tools:           │ │ Tools:         │ │ Tools:             │
        │ · query_realtime │ │ · generate_pdf │ │ · send_alert       │
        │ · query_trend    │ │ · generate_xlsx│ │ · create_ticket    │
        │ · query_history  │ │ · send_email   │ │ · adjust_threshold │
        │                  │ │                │ │ · trigger_inspect  │
        └────────┬─────────┘ └───────┬────────┘ └────────┬───────────┘
                 │                   │                   │
        ┌────────▼───────────────────▼───────────────────▼───────────┐
        │                    基础设施层                                │
        │  ┌────────┐  ┌──────────┐  ┌─────┐  ┌──────────────────┐  │
        │  │ 达梦DB  │  │ClickHouse│  │ MQ  │  │ 短信/邮件网关    │  │
        │  └────────┘  └──────────┘  └─────┘  └──────────────────┘  │
        └────────────────────────────────────────────────────────────┘

七、与上一篇文章的对比总结

维度 上篇(单体 Agent) 本篇(Multi-Agent + MCP)
架构 单一 Agent + 多个 @Tool Orchestrator + 多个专家 Agent
工具集成 硬编码在 Agent 内部 MCP 协议标准化,可插拔
扩展性 加工具需改代码 新增 MCP Server 即可,Agent 零改动
工具复用 框架绑定,无法跨项目 MCP Server 独立部署,任何框架可调用
复杂度 低(适合快速验证) 中~高(适合生产级系统)
可维护性 职责耦合 各 Agent 独立开发、测试、部署
故障隔离 一个工具出错影响全局 故障局限在单个 Agent/MCP Server

八、展望:下一步该关注什么?

  1. Agent 的自动评估体系:如何系统性地测试 Agent 的准确率?目前主流方案是构建"评估数据集 + 自动打分"流水线。
  2. A2A 协议(Agent-to-Agent):Google 2025 年提出的 Agent 间直接通信协议,与 MCP 互补------MCP 解决 Agent 与工具的连接,A2A 解决 Agent 与 Agent 的连接。
  3. 长期记忆 + 向量数据库的进阶用法:将用户画像、历史决策向量化存储,让 Agent 具备"经验积累"能力。

相关推荐
老白干14 分钟前
基于枚举 + 注解的 Java 数据脱敏实践(fastjson 序列化场景)
java·开发语言
老林说收银14 分钟前
溯引 GEO 优化系统落地实战指南
大数据·人工智能
SKH.16 分钟前
Linux软件编程(5)线程
java·linux·jvm
罗马尼亚硬拉17 分钟前
MetaInfer:从专用推理框架到 AI Infra 的持续优化
人工智能
小新科研测评17 分钟前
2026 年论文阅读工具横评:Zotero、EndNote、Mendeley、Scholaread 哪个效率更高?
论文阅读·人工智能·ai·pdf·自动翻译
JuiceFS18 分钟前
如何通过 S3 和 WebDAV 协议访问 JuiceFS?
运维·人工智能·后端
IvorySQL19 分钟前
倒计时 6 天!PGConf.Asia 2026 演讲征集即将截止
数据库·人工智能·postgresql
数据知道24 分钟前
Java 安全审计实战:SSRF、反序列化、SpEL 注入
java·开发语言·安全·网络安全
嗝屁小孩纸29 分钟前
通用后端基础能力平台(多模块技术总结与避坑指南)
java