本文介绍如何通过MCP协议实现Graph编排层与RAG检索层的串联,外部AI工具通过MCP作为统一入口触发完整链路。涉及代码审查场景的完整实现,包括MCP工具注册、Graph子图编排、RAG知识注入、并行节点执行、HITL人工确认等核心机制。
技术栈:Spring AI 1.1.6 · Spring Boot 3.5.5 · JDK 21 · MCP SDK 0.18.2
前置知识 :建议先阅读MCP协议原理与Java实现、Spring AI Alibaba Graph编排实战等系列文章
一、问题背景
RAG与Graph是AI Agent中两个核心能力:
- Graph:支持多步骤任务编排,具备并行执行、HITL中断、条件分支等能力
- RAG:从知识库检索上下文,为LLM提供领域知识注入
单独使用各有优势,但实际应用中常面临一个问题:外部系统如何触发这条链路?
典型场景:
- Claude Desktop想调用代码审查能力
- Cursor IDE需要接入内部知识库检索
- 第三方系统需要触发Graph编排流程
这些外部系统无法直接调用内部模块,需要一层协议适配------这正是MCP协议的价值所在。
二、架构设计
2.1 三层职责划分

| 层级 | 模块 | 职责 | 协议 |
|---|---|---|---|
| 接入层 | MCP Server | 接收外部请求,协议解析与路由 | MCP/SSE |
| 编排层 | Graph Supervisor | 子图调度,任务流控制 | Java方法调用 |
| 执行层 | RAG + LLM | 知识检索,具体推理执行 | Feign/HTTP |
核心原则:接入层(MCP)做协议翻译,编排层(Graph)做任务调度,执行层(RAG+LLM)做具体推理。
2.2 完整调用链路
bash
外部 MCP Client(Claude Desktop / Cursor / MCP Inspector)
↓ SSE 长连接
McpCodeReviewServer(8091端口)
↓ MCP协议路由
GraphMcpToolContributor.graphCodeReview()
↓ Java方法调用
SupervisorGraphTestService.runSupervisor()
↓ 启动主图
Supervisor 主图 → IntentRouter → CodeReview 子图
↓
RAGRetrievalNode(查编码规范知识库)
↓
┌─ StyleCheckNode(正则规则检查)
└─ CodeCheckNode(LLM 语义审查)
↓ 并行执行后合并
RiskAssessNode(风险评分)
↓
评分 ≥ 阈值 → ReportNode(直接返回报告)
评分 < 阈值 → HumanReviewNode(人工确认)
↓
返回 Markdown 格式审查报告
三、MCP工具注册
3.1 Maven依赖
bash
<!-- dream-ai-graph/pom.xml -->
<dependencies>
<!-- MCP SDK -->
<dependency>
<groupId>io.modelcontextprotocol</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
<version>0.18.2</version>
</dependency>
<!-- Spring AI Alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.1.2.2</version>
</dependency>
</dependencies>
3.2 MCP Server配置
bash
# application.yml
spring:
ai:
mcp:
server:
enabled: true
port: 8091
name: dream-saas-graph
version: 1.0.0
description: Dream-SaaS Graph编排服务
dream-saas:
mcp:
server:
enabled: true
3.3 核心接点:GraphMcpToolContributor
这是串联的关键类,同时实现MCP工具接口和Graph调度逻辑:
bash
package com.zhu.dream.ai.graph.mcp;
@Component
@Lazy
@ConditionalOnProperty(
prefix = "dream-saas.mcp.server",
name = "enabled",
havingValue = "true"
)
public class GraphMcpToolContributor implements McpToolContributor {
private static final Logger log = LoggerFactory.getLogger(GraphMcpToolContributor.class);
private final SupervisorGraphTestService supervisorGraphTestService;
private final ObjectMapper objectMapper;
public GraphMcpToolContributor(
SupervisorGraphTestService supervisorGraphTestService,
ObjectMapper objectMapper) {
this.supervisorGraphTestService = supervisorGraphTestService;
this.objectMapper = objectMapper;
}
@Override
public String moduleId() {
return McpModuleIds.GRAPH;
}
@Override
public Object[] toolObjects() {
return new Object[] {this};
}
/**
* 完整代码审查链路
* 内部调用Graph编排,执行RAG检索→LLM审查→风险评估→报告生成
*/
@Tool(
name = "graph_code_review",
description = "通过Graph编排执行完整代码审查:RAG知识检索 + LLM深度审查 + 风险评估 + Markdown报告生成"
)
public String graphCodeReview(
@ToolParam(description = "待审查的源代码") String code,
@ToolParam(description = "审查说明,如重点关注空指针、并发安全等", required = false) String instruction) {
long start = System.nanoTime();
log.info("[MCP-GRAPH] invoke tool=graph_code_review codeChars={} instruction={}",
code != null ? code.length() : 0,
StringUtils.hasText(instruction) ? instruction : "(none)");
try {
// 核心调用:触发Graph编排
Map<String, Object> result = supervisorGraphTestService.runSupervisor(
StringUtils.hasText(instruction) ? instruction : "",
StringUtils.hasText(code) ? code : "");
String json = toJson(result);
long durationMs = (System.nanoTime() - start) / 1_000_000L;
log.info("[MCP-GRAPH] success tool=graph_code_review durationMs={}", durationMs);
return json;
} catch (Exception ex) {
long durationMs = (System.nanoTime() - start) / 1_000_000L;
log.warn("[MCP-GRAPH] failed tool=graph_code_review durationMs={}: {}",
durationMs, ex.getMessage(), ex);
return toJson(Map.of("error", ex.getMessage(), "type", ex.getClass().getSimpleName()));
}
}
/**
* 意图路由分析
* 仅分析用户意图,不执行具体子图
*/
@Tool(
name = "graph_route_analyze",
description = "分析用户意图路由(ragRoute/reviewCodeRoute/chatRoute),不执行子图"
)
public String graphRouteAnalyze(
@ToolParam(description = "用户输入文本") String query) {
long start = System.nanoTime();
log.info("[MCP-GRAPH] invoke tool=graph_route_analyze query={}", query);
try {
Map<String, Object> result = supervisorGraphTestService.analyzeRoute(
StringUtils.hasText(query) ? query : "");
String json = toJson(result);
long durationMs = (System.nanoTime() - start) / 1_000_000L;
log.info("[MCP-GRAPH] success tool=graph_route_analyze durationMs={}", durationMs);
return json;
} catch (Exception ex) {
long durationMs = (System.nanoTime() - start) / 1_000_000L;
log.warn("[MCP-GRAPH] failed tool=graph_route_analyze durationMs={}: {}",
durationMs, ex.getMessage());
return toJson(Map.of("error", ex.getMessage()));
}
}
private String toJson(Object value) {
try {
return objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
log.error("序列化失败", e);
return "{\"error\":\"序列化失败: " + e.getMessage() + "\"}";
}
}
}
关键设计点:
@ConditionalOnProperty控制是否启用MCP服务@Lazy延迟加载,避免循环依赖- 方法返回值是JSON字符串,MCP协议要求工具返回文本
- 统一错误处理,返回结构化错误信息
四、Graph主图调度
4.1 Supervisor主图入口
bash
@Service
public class SupervisorGraphTestService {
private final ApplicationContext applicationContext;
private final GraphCodeReviewSubGraph codeReviewSubGraph;
public SupervisorGraphTestService(
ApplicationContext applicationContext,
GraphCodeReviewSubGraph codeReviewSubGraph) {
this.applicationContext = applicationContext;
this.codeReviewSubGraph = codeReviewSubGraph;
}
/**
* 主图入口:根据意图路由到对应子图
*/
public Map<String, Object> runSupervisor(String instruction, String code) {
// 1. 意图识别
String intent = analyzeIntent(code, instruction);
// 2. 根据意图路由到子图
switch (intent) {
case "reviewCode":
return runCodeReviewSubGraph(code, instruction);
case "ragRoute":
return runRagRoute(instruction);
default:
return Map.of("error", "未知意图: " + intent);
}
}
/**
* 意图识别:根据code特征判断任务类型
*/
private String analyzeIntent(String code, String instruction) {
// 代码非空 → 代码审查
if (StringUtils.hasText(code)) {
return "reviewCode";
}
// 纯文本 → RAG检索
if (StringUtils.hasText(instruction)) {
return "ragRoute";
}
return "unknown";
}
}
4.2 CodeReview子图执行
bash
@Component
public class GraphCodeReviewSubGraph {
private final RAGRetrievalNode ragRetrievalNode;
private final StyleCheckNode styleCheckNode;
private final CodeCheckNode codeCheckNode;
private final RiskAssessNode riskAssessNode;
private final HumanReviewNode humanReviewNode;
private final ReportNode reportNode;
/**
* 构建CodeReview子图
* 流程:RAG检索 → 并行执行(规则检查+LLM审查) → 风险评估 → 报告生成
*/
@Bean
public Graph codeReviewGraph() {
return Graph.builder()
.id("code-review")
.stateInspector(new JsonStringStateInspector())
// 开始节点:接收用户消息
.start(USER_MESSAGE)
// 第一步:RAG检索注入领域知识
.add(ragRetrievalNode) // RAGRetrievalNode
.add(RAGooterNode.class) // 拼装prompt
// 第二步:并行执行规则检查与LLM审查
.parallel(
styleCheckNode, // StyleCheckNode:正则规则
codeCheckNode // CodeCheckNode:LLM语义
)
// 第三步:风险评估
.add(riskAssessNode)
// 第四步:条件分支
.choice(
// 条件1:风险评分>=阈值 → 直接生成报告
c -> c.if_(state -> state.get("needsHumanReview", Boolean.class))
.then(humanReviewNode), // HITL人工确认
// 条件2:风险评分<阈值 → 直接返回
c -> c.else_()
.then(reportNode) // 生成Markdown报告
)
.end(FINAL_OUTPUT)
.build();
}
/**
* 执行子图
*/
public Map<String, Object> execute(String code, String instruction) {
GraphState state = new GraphState();
state.put("code", code);
state.put("instruction", instruction);
state.put("userMessage", instruction + "\n\n待审查代码:\n" + code);
return codeReviewGraph().execute(state);
}
}
五、核心节点实现
5.1 RAG检索节点
bash
@Component
@Singleton
public class RAGRetrievalNode implements Node {
private static final Logger log = LoggerFactory.getLogger(RAGRetrievalNode.class);
private static final int TOP_K = 5;
private final KnowledgeBaseService knowledgeBaseService;
public RAGRetrievalNode(KnowledgeBaseService knowledgeBaseService) {
this.knowledgeBaseService = knowledgeBaseService;
}
@Override
public GraphState execute(GraphState state) {
String code = state.get("code", String.class);
String instruction = state.get("instruction", String.class);
log.info("[RAG] 开始检索知识库 codeLength={}", code.length());
// 构造检索query
String query = buildRetrievalQuery(code, instruction);
// 调用RAG服务检索编码规范
List<KnowledgeChunk> chunks = knowledgeBaseService.retrieve(query, TOP_K);
// 拼装检索结果作为上下文
String context = chunks.stream()
.map(KnowledgeChunk::getContent)
.collect(Collectors.joining("\n\n---\n\n"));
state.put("ragContext", context);
state.put("ragChunkCount", chunks.size());
log.info("[RAG] 检索完成 chunks={}", chunks.size());
return state;
}
private String buildRetrievalQuery(String code, String instruction) {
return String.format("%s\n\n%s",
StringUtils.hasText(instruction) ? instruction : "",
StringUtils.hasText(code) ? code.substring(0, Math.min(code.length(), 500)) : ""
);
}
}
RAG检索的价值:解决"LLM不知道团队编码规范"的问题。通过检索将规范从知识库查出来,注入后续LLM节点的prompt。
5.2 规则检查节点
bash
@Component
@Singleton
public class StyleCheckNode implements Node {
private static final Logger log = LoggerFactory.getLogger(StyleCheckNode.class);
// 内置规则
private static final List<CheckRule> RULES = List.of(
CheckRule.builder()
.name("命名规范-方法名")
.pattern("\\b[a-z][a-zA-Z0-9]*\\([^(]*\\)")
.message("方法名应使用驼峰命名")
.severity(Severity.WARNING)
.build(),
CheckRule.builder()
.name("空指针风险-equals对比")
.pattern("\"[^\"]*\".equals\\([^)]+\\)")
.message("建议使用Objects.equals()避免空指针")
.severity(Severity.ERROR)
.build(),
// ... 更多规则
);
@Override
public GraphState execute(GraphState state) {
String code = state.get("code", String.class);
log.info("[StyleCheck] 开始规则检查");
List<ReviewIssue> issues = new ArrayList<>();
for (CheckRule rule : RULES) {
Matcher matcher = Pattern.compile(rule.getPattern()).matcher(code);
while (matcher.find()) {
issues.add(ReviewIssue.builder()
.rule(rule.getName())
.message(rule.getMessage())
.severity(rule.getSeverity())
.location(matcher.start() + "-" + matcher.end())
.snippet(matcher.group())
.build());
}
}
state.put("styleIssues", issues);
log.info("[StyleCheck] 检查完成 issues={}", issues.size());
return state;
}
}
规则检查的价值:快速定位格式问题和明显错误,无需LLM调用,毫秒级完成。
5.3 LLM审查节点
bash
@Component
@Singleton
public class CodeCheckNode implements Node {
private static final Logger log = LoggerFactory.getLogger(CodeCheckNode.class);
private final ChatClient chatClient;
private final ObjectMapper objectMapper;
public CodeCheckNode(ChatClient chatClient, ObjectMapper objectMapper) {
this.chatClient = chatClient;
this.objectMapper = objectMapper;
}
@Override
public GraphState execute(GraphState state) {
String code = state.get("code", String.class);
String ragContext = state.get("ragContext", String.class);
log.info("[CodeCheck] 开始LLM审查");
// 构造prompt
String prompt = buildPrompt(code, ragContext);
// 调用LLM
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
// 解析LLM返回的审查结果
List<ReviewIssue> issues = parseIssues(response);
state.put("llmIssues", issues);
log.info("[CodeCheck] LLM审查完成 issues={}", issues.size());
return state;
}
private String buildPrompt(String code, String ragContext) {
return String.format("""
# 代码审查任务
## 编码规范上下文
%s
## 待审查代码
```java
%s
```
## 审查要求
请从以下维度审查代码:
1. 空指针风险
2. 并发安全问题
3. 资源泄漏
4. 逻辑错误
5. 性能问题
返回JSON格式的审查结果:
```json
{
"issues": [
{"severity": "ERROR|WARNING", "message": "...", "line": N}
]
}
```
""", ragContext, code);
}
private List<ReviewIssue> parseIssues(String response) {
try {
// 提取JSON部分
String json = extractJson(response);
Map<String, Object> result = objectMapper.readValue(json, Map.class);
List<Map<String, Object>> issueList = (List<Map<String, Object>>) result.get("issues");
return issueList.stream()
.map(m -> ReviewIssue.builder()
.severity(Severity.valueOf(String.valueOf(m.get("severity")).toUpperCase()))
.message(String.valueOf(m.get("message")))
.line((Integer) m.get("line"))
.build())
.collect(Collectors.toList());
} catch (Exception e) {
log.warn("[CodeCheck] 解析LLM返回失败: {}", e.getMessage());
return Collections.emptyList();
}
}
}
LLM审查的价值:理解上下文语义,发现规则检查抓不到的逻辑漏洞,如业务逻辑错误、安全隐患等。
5.4 风险评估节点
bash
@Component
@Singleton
public class RiskAssessNode implements Node {
private static final Logger log = LoggerFactory.getLogger(RiskAssessNode.class);
private static final int THRESHOLD = 70; // 风险评分阈值
// 严重程度权重
private static final int ERROR_WEIGHT = 10;
private static final int WARNING_WEIGHT = 3;
@Override
public GraphState execute(GraphState state) {
List<ReviewIssue> styleIssues = state.get("styleIssues", List.class);
List<ReviewIssue> llmIssues = state.get("llmIssues", List.class);
// 合并所有问题
List<ReviewIssue> allIssues = new ArrayList<>();
if (styleIssues != null) allIssues.addAll(styleIssues);
if (llmIssues != null) allIssues.addAll(llmIssues);
// 计算风险评分
int riskScore = calculateRiskScore(allIssues);
// 判断是否需要人工确认
boolean needsHumanReview = riskScore < THRESHOLD;
state.put("riskScore", riskScore);
state.put("needsHumanReview", needsHumanReview);
state.put("allIssues", allIssues);
log.info("[RiskAssess] 风险评分={} needsHumanReview={}", riskScore, needsHumanReview);
return state;
}
private int calculateRiskScore(List<ReviewIssue> issues) {
if (issues == null || issues.isEmpty()) {
return 100; // 无问题满分
}
int penalty = issues.stream()
.mapToInt(issue -> {
switch (issue.getSeverity()) {
case ERROR: return ERROR_WEIGHT;
case WARNING: return WARNING_WEIGHT;
default: return 1;
}
})
.sum();
return Math.max(0, 100 - penalty);
}
}
风险评估的价值:量化代码质量,决定是否需要人工介入。评分>=阈值自动放行,<阈值触发HITL中断。
5.5 HITL人工确认节点
bash
@Component
@Singleton
public class HumanReviewNode implements Node {
private static final Logger log = LoggerFactory.getLogger(HumanReviewNode.class);
private final AtomicReference<Map<String, Object>> pendingReview = new AtomicReference<>();
@Override
public GraphState execute(GraphState state) {
List<ReviewIssue> issues = state.get("allIssues", List.class);
Integer riskScore = state.get("riskScore", Integer.class);
// 构造人工确认请求
Map<String, Object> reviewRequest = Map.of(
"issues", issues,
"riskScore", riskScore,
"status", "PENDING"
);
// 保存待确认状态(实际实现中可能存入数据库或消息队列)
pendingReview.set(reviewRequest);
log.info("[HITL] 触发人工确认 riskScore={} issues={}", riskScore, issues.size());
// 设置状态,等待人工确认
state.put("status", "AWAITING_HUMAN_REVIEW");
state.put("reviewRequest", reviewRequest);
return state;
}
/**
* 处理人工确认结果
*/
public GraphState handleHumanDecision(GraphState state, boolean approved, String comment) {
log.info("[HITL] 人工确认结果 approved={} comment={}", approved, comment);
state.put("humanApproved", approved);
state.put("humanComment", comment);
state.put("status", approved ? "APPROVED" : "REJECTED");
return state;
}
}
HITL的价值:AI拿不准时有人类兜底,既保证自动化效率,又不失安全性。适用于高风险操作或低置信度场景。
六、调用验证
6.1 启动服务
bash
# 1. 启动MCP Server(8091端口)
cd dream-ai-code-review && mvn spring-boot:run
# 2. 启动Graph服务(8098端口)
cd dream-ai-graph && mvn spring-boot:run
# 3. 验证MCP Server健康状态
curl http://localhost:8091/actuator/health
6.2 MCP Inspector测试
bash
# 安装MCP Inspector
npx @modelcontextprotocol/inspector
# 或使用CLI直接调用
npx @modelcontextprotocol/cli call graph_code_review \
--arg '{
"code": "public class UserService {\n private String name;\n \n public void setName(String name) {\n this.name = name;\n }\n \n public String getName() {\n return name;\n }\n}",
"instruction": "请重点关注空指针风险和命名规范"
}'
6.3 预期输出
bash
{
"success": true,
"data": {
"riskScore": 75,
"needsHumanReview": false,
"report": "# 代码审查报告\n\n## 问题汇总\n| 严重程度 | 问题描述 | 位置 |\n|---------|---------|------|\n| WARNING | 成员变量name缺少非空校验 | setName() |\n\n## 详细分析\n...(Markdown格式报告)",
"stats": {
"totalIssues": 1,
"errors": 0,
"warnings": 1
}
}
}
6.4 日志观察点
bash
# 启动时日志
[MCP] GraphMcpToolContributor registered, tools: [graph_code_review, graph_route_analyze]
[MCP] Server started on port 8091
# 调用时日志链路
[MCP-GRAPH] invoke tool=graph_code_review codeChars=267
[Supervisor] Intent recognized: reviewCode
[Supervisor] Launching CodeReview sub-graph
[RAG] Retrieving from knowledge base chunks=5
[StyleCheck] Rule check completed issues=2
[CodeCheck] LLM review completed issues=1
[RiskAssess] Score calculated: 75 >= threshold(70), auto-pass
[Report] Generating Markdown report
[MCP-GRAPH] success tool=graph_code_review durationMs=2340
七、MCP串联适用场景分析
7.1 适合场景
| 场景 | 说明 | 收益 |
|---|---|---|
| 外部AI工具接入 | Claude Desktop/Cursor/Zed等支持MCP | 一次实现,多方调用 |
| 跨语言集成 | Java后端 + Python前端 + TS CLI | 统一协议,无需多语言SDK |
| 工具发现与版本管理 | MCP原生支持动态发现 | 新增工具无需更新文档 |
7.2 不适合场景
| 场景 | 替代方案 | 原因 |
|---|---|---|
| 内部微服务调用 | Feign/Dubbo | MCP协议开销不必要 |
| 毫秒级延迟场景 | 直接方法调用 | SSE/HTTP有网络开销 |
| 单体应用内部 | 直接Bean调用 | 无需网络传输 |
7.3 判断标准
bash
问自己:这个调用是"外部系统触发"还是"内部模块协作"?
- 外部 → MCP
- 内部 → Feign/Direct
八、项目结构
bash
dream-ai/
├── dream-ai-graph/ # Graph编排模块
│ └── src/main/java/com/zhu/dream/ai/graph/
│ ├── mcp/
│ │ ├── GraphMcpToolContributor.java # ⭐ MCP→Graph接点
│ │ └── McpModuleIds.java # 模块ID定义
│ ├── subgraph/
│ │ └── codereview/
│ │ ├── GraphCodeReviewSubGraph.java # ⭐ 子图定义
│ │ ├── RAGRetrievalNode.java # RAG检索节点
│ │ ├── StyleCheckNode.java # 规则检查节点
│ │ ├── CodeCheckNode.java # LLM审查节点
│ │ ├── RiskAssessNode.java # 风险评估节点
│ │ ├── HumanReviewNode.java # HITL节点
│ │ └── ReportNode.java # 报告生成节点
│ └── supervisor/
│ └── SupervisorGraphTestService.java # ⭐ 主图调度器
│
├── dream-ai-code-review/ # MCP Server模块
│ └── src/main/java/com/zhu/ai/agent/codeReview/
│ ├── mcp/
│ │ ├── McpCodeReviewServer.java
│ │ └── CodeReviewMcpContributor.java
│ └── service/
│ └── CodeReviewServiceNew.java
│
└── dream-ai-rag/ # RAG知识库模块
└── ...
关键串联文件只有3个:
GraphMcpToolContributor.java--- MCP工具定义,内部调用GraphSupervisorGraphTestService.java--- 主图调度器,路由到子图GraphCodeReviewSubGraph.java--- 子图定义,编排执行流程
九、踩坑记录
9.1 MCP方法返回类型
问题:MCP协议要求工具返回文本,不能直接返回对象
解决方案:
bash
// ❌ 错误:返回了Java对象
public Map<String, Object> graphCodeReview(String code) { ... }
// ✅ 正确:序列化为JSON字符串
public String graphCodeReview(String code) {
return objectMapper.writeValueAsString(result);
}
9.2 循环依赖
问题 :SupervisorGraphTestService注入GraphCodeReviewSubGraph,GraphCodeReviewSubGraph又注入依赖SupervisorGraphTestService的节点
解决方案 :使用@Lazy注解延迟加载
bash
@Component
@Lazy
public class GraphMcpToolContributor { ... }
9.3 Graph状态传递
问题:并行节点执行后,状态合并顺序不确定
解决方案:显式合并结果
bash
// 在并行节点之后显式添加合并节点
.parallel(styleCheckNode, codeCheckNode)
.add(ResultMergerNode.class) // 显式合并allIssues
9.4 HITL超时处理
问题:人工确认可能长时间不响应
解决方案:设置超时,自动拒绝或使用上次结果
bash
@Scheduled(fixedDelay = 300000) // 5分钟超时检查
public void checkPendingReviews() {
// 超时逻辑处理
}
总结
MCP串联Graph与RAG的核心要点:
- 接点在GraphMcpToolContributor:MCP工具实现内部调用Graph调度器,两个模块通过它接在一起
- 分层各司其职:接入层(MCP)协议翻译,编排层(Graph)任务调度,执行层(RAG+LLM)具体推理
- MCP是对外接口:外部系统用MCP接入,内部模块用Feign/Direct调用
- HITL保障安全:AI拿不准时有兜底,既保证自动化效率,又不失安全性
技术栈:
- spring-ai-bom 1.1.6
- spring-ai-alibaba 1.1.2.2
- MCP SDK 0.18.2
- Spring Boot 3.5.5
- JDK 21
