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