引言:为什么企业级 AI 系统需要 MCP
做过企业 AI 落地的团队,很快就会撞上一道现实的墙:模型很聪明,但真正有价值的能力,并不在模型里,而在企业已运转多年的系统里。
以支付平台为例,客服想用自然语言查订单状态,风控想让 AI 自动判断交易风险,运维希望用对话方式看失败率,运营要按时间窗口统计退款率——这些能力分散在订单服务、支付服务、风控服务、数据仓库、监控平台、知识库系统里。
过去,每接一个 AI 应用,往往都要做一轮定制集成,最终形成典型的 N × M 问题:N 个 AI 应用 × M 个业务系统,每一对都得单独做协议适配、鉴权、参数映射、超时控制和可观测埋点。
MCP(Model Context Protocol,模型上下文协议)的核心价值,不是“让模型多调几个函数”,而是把 AI 与企业工具之间的交互标准化。它把“工具发现、参数定义、上下文读取、执行反馈”抽象成统一协议层,使 AI Host、MCP Client、MCP Server 之间职责清晰,让系统从“脚本拼接”升级为“可治理架构”。
这篇文章不做概念演示,而是从架构师视角,完整讲清楚一套基于 Spring Boot + Spring AI + MCP 的生产级落地方案,包括协议原理、业务建模、生产级工具暴露、高并发与安全治理,以及 Kubernetes 部署——一条可执行的从 PoC 到生产的演进路径。
一、MCP 协议到底解决了什么
1.1 从函数调用到标准化工具总线
很多团队第一次接触 MCP 时,会把它理解为“函数调用协议”。这个理解不算错,但远远不够。
函数调用只解决“模型调用某个方法”这一步,而 MCP 解决的是一整条链路:Host 如何发现可用工具;工具的输入输出如何标准化描述;工具之外的上下文资源如何暴露;提示模板如何参数化复用;调用过程中的状态、异常、进度如何反馈;多个 Server、多个实例、多个租户如何治理。
因此,MCP 更像 AI 时代的“工具总线协议”,而不是一个简单的 SDK。
1.2 MCP 的三端协作模型
在工程上,MCP 至少包含三类角色:
Host:承载 AI 应用的运行环境,比如 Claude Desktop、Cursor、自研 Agent 平台
Client:Host 内部或旁路的协议代理,负责与 MCP Server 建立连接、发现工具、发起调用
Server:对外暴露工具、资源、提示模板的服务端,一般由业务系统实现
它们之间的职责分工非常关键:
| 角色 |
关注点 |
典型职责 |
| Host |
用户意图与对话编排 |
Prompt 编排、工具选择、结果融合 |
| Client |
协议通信与连接管理 |
能力发现、协议协商、超时控制、负载路由 |
| Server |
业务能力封装与治理 |
工具实现、参数校验、审计、鉴权、限流 |
这意味着什么?AI 决策与业务能力不再强耦合。 模型负责“决定要不要调用工具”,业务系统负责“安全、稳定、正确地执行工具”。
1.3 MCP 的四类能力模型
真正完整的 MCP 系统,至少涉及四类能力:
Tools:模型可调用的函数型能力,如查询订单状态、统计退款率、评估交易风险、创建退款工单。
Resources:可读取的上下文对象,如风控规则文档、退款政策说明、渠道 SLA 配置、商户信息快照。
Prompts:参数化提示模板,用于把领域经验沉淀为结构化模板,而不是散落在代码中的字符串。
Sampling:Server 反向请求 Host 或模型进行推理,适合复杂推理链、工具内嵌模型判断、审核流等高级协作模式。
很多文章只讲 Tools,但企业级实践里,Resources + Prompts + Tools 的组合,才是做出稳定 AI 系统的关键。
1.4 为什么生产环境优先选择 Streamable HTTP
MCP 的传输方式决定了你的系统上限。常见方案大致如下:
| 传输方式 |
适用场景 |
优点 |
局限 |
| STDIO |
本地开发、IDE 插件 |
接入简单、延迟低 |
无法天然水平扩展 |
| SSE |
轻量远程接入 |
实现直接 |
长连接多、代理链路复杂 |
| Streamable HTTP |
服务化生产部署 |
无状态、易负载均衡、云原生友好 |
需要更完整的服务治理 |
生产环境优先推荐 Streamable HTTP,原因有三:更适合无状态部署,天然适配容器与弹性扩缩容;更容易接入网关、限流、鉴权、链路追踪等企业基础设施;避免大量长连接在高并发场景下占用连接数、线程和代理资源。
一句话总结:PoC 阶段可以用 STDIO 或 SSE,生产阶段优先用 Streamable HTTP。
二、真实业务场景建模:支付平台的 AI 工具化改造
为避免文章只停留在 Demo 层,我们用一个真实的企业场景来建模。
2.1 业务目标
我们要把一个 Spring Boot 支付平台改造成 MCP Server,对外提供如下能力:
getPaymentStatus:查询某笔交易的支付状态
getRefundMetrics:统计指定时间范围退款率
assessFraudRisk:基于规则和画像评估风险等级
createRefundTicket:在异常场景下创建退款处理工单
queryMerchantHealth:按商户查看近 5 分钟支付健康度
2.2 业务域拆分
从架构上,不建议把所有逻辑直接写在 Tool 方法里。更合理的分层是:
MCP 接入层
├─ Tool 定义
├─ Tool 参数校验
├─ Tool 鉴权与审计
└─ Tool 结果封装
应用服务层
├─ PaymentQueryService
├─ RefundAnalysisService
├─ FraudDecisionService
└─ TicketOrchestrationService
领域能力层
├─ 支付领域模型
├─ 风控评分规则
├─ 退款策略规则
└─ 商户运营规则
基础设施层
├─ PostgreSQL / Redis / Kafka
├─ 第三方支付渠道 API
├─ 工单系统 API
└─ 监控与日志系统
这层拆分的好处是:Tool 只是协议适配层,不污染领域逻辑;领域服务可以被 HTTP API、批处理任务、MCP 工具共同复用;后续替换 MCP 框架或增加新 Agent,不需要重写业务核心。
三、生产级架构设计
3.1 单实例能跑,不代表架构成立
很多团队第一版就一行:AI Client -> MCP Server -> 数据库。
这能工作,但有四个明显问题:没有流量治理能力;没有多实例会话策略;没有统一鉴权与审计出口;没有异步削峰和故障隔离。
真正可上线的架构应该至少长这样:
┌───────────────────────────────────────────────────────────────┐
│ AI Host / Agent │
│ Claude Desktop / Cursor / 自研 Copilot / 企业 Agent Hub │
└──────────────────────────┬────────────────────────────────────┘
│ MCP
┌──────────────────────────▼────────────────────────────────────┐
│ MCP Gateway / API Gateway │
│ OAuth2 / 限流 / 负载均衡 / 审计 / Trace 透传 │
└───────────────┬───────────────────────┬───────────────────────┘
│ │
┌───────────────▼──────────────┐ ┌────▼────────────────────────┐
│ Payment MCP Server Cluster │ │ Fraud MCP Server Cluster │
│ Tool 暴露 / 读写分离 / 缓存 │ │ 风控规则 / 特征评分 / 审核 │
└───────────────┬──────────────┘ └────┬────────────────────────┘
│ │
┌─────────▼──────────┐ ┌───────▼────────────────────┐
│ PostgreSQL / Redis │ │ Kafka / Rule Engine / ES │
└─────────────────────┘ └────────────────────────────┘
3.2 高并发设计原则
MCP 工具本质上是“被模型驱动的远程函数调用”。一旦进入生产,你要面对的不是单个用户,而是多个 Agent 同时调用、一个会话里多个工具串行或并行调用、模型重试导致的幂等问题,以及高峰期大量统计查询和聚合计算。
所以在设计时要遵循四条原则:
原则一:同步工具尽量短小。 适合同步返回的工具应该是单表查询、小范围聚合、快速规则判断、毫秒级缓存读取。不要把“大查询、跨系统事务、复杂报表”强行做成同步 Tool。
原则二:重任务异步化。 生成月度商户分析报告、全量风控回放、历史退款聚合计算,这些更适合“提交任务 + 返回任务 ID + 轮询或回调取结果”的模式。
原则三:工具必须幂等。 模型可能因为超时、重试、上下文漂移而重复调用同一工具。任何写操作型工具都必须具备幂等键、去重校验和重复请求保护。
原则四:把状态从连接中解耦。 不要把会话、上下文、任务进度绑死在单机内存里。生产环境必须把关键状态外置到 Redis、数据库或事件流中。
四、项目搭建与依赖选型
4.1 Maven 依赖
下面给出一套偏生产环境的依赖组合:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
4.2 推荐运行时版本
- JDK:
21
- Spring Boot:
3.4.x
- Spring AI MCP Starter:与 Spring Boot 版本保持兼容
- 容器基线:
eclipse-temurin:21-jre
选择 JDK 21 的核心原因不是“追新”,而是虚拟线程对高并发 IO 密集型工具调用更友好,新一代 GC 和运行时诊断能力更成熟,适合作为中长期 LTS 生产基座。
五、领域模型与应用服务设计
在暴露工具之前,先把业务层设计清楚。
5.1 订单查询服务
package com.example.payment.application;
import com.example.payment.domain.Payment;
import com.example.payment.domain.PaymentStatus;
import com.example.payment.infrastructure.PaymentRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
public class PaymentQueryService {
private final PaymentRepository paymentRepository;
public PaymentQueryService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
@Transactional(readOnly = true)
public Optional<Payment> findByTransactionId(String transactionId) {
return paymentRepository.findByTransactionId(transactionId);
}
@Transactional(readOnly = true)
public long countByStatusBetween(PaymentStatus status, LocalDateTime start, LocalDateTime end) {
return paymentRepository.countByStatusAndCreatedAtBetween(status, start, end);
}
@Transactional(readOnly = true)
public long countAllBetween(LocalDateTime start, LocalDateTime end) {
return paymentRepository.countByCreatedAtBetween(start, end);
}
}
5.2 风控评分服务
生产环境里,风控不应只返回一个分数,还要返回解释项,以便 AI 能生成可信回复。
package com.example.payment.application;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class FraudDecisionService {
public FraudAssessment assess(FraudCommand command) {
int score = 0;
List<String> reasons = new ArrayList<>();
if (command.amount() >= 10000) {
score += 30;
reasons.add("高金额交易");
}
if ("NEW".equalsIgnoreCase(command.clientType())) {
score += 20;
reasons.add("新客首单");
}
if (command.hour() >= 1 && command.hour() <= 5) {
score += 20;
reasons.add("凌晨时段交易");
}
if (command.orderCount24h() >= 30) {
score += 15;
reasons.add("24小时内订单数偏高");
}
if (command.successRate7d() < 0.75) {
score += 15;
reasons.add("近7日成功率偏低");
}
String level = score >= 70 ? "HIGH" : score >= 40 ? "MEDIUM" : "LOW";
return new FraudAssessment(score, level, reasons);
}
public record FraudCommand(
double amount,
String clientType,
int hour,
int orderCount24h,
double successRate7d
) {}
public record FraudAssessment(int score, String level, List<String> reasons) {}
}
5.3 异步工单服务
写操作型工具不应直接在请求线程里串行调用多个外部系统,建议事件化。
package com.example.payment.application;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.UUID;
@Service
public class RefundTicketService {
private final KafkaTemplate<String, RefundTicketCreatedEvent> kafkaTemplate;
public RefundTicketService(KafkaTemplate<String, RefundTicketCreatedEvent> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public String submit(RefundTicketCommand command) {
String ticketId = "RT-" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
RefundTicketCreatedEvent event = new RefundTicketCreatedEvent(
ticketId,
command.transactionId(),
command.reason(),
command.operator(),
Instant.now()
);
kafkaTemplate.send("refund-ticket-created", ticketId, event);
return ticketId;
}
public record RefundTicketCommand(String transactionId, String reason, String operator) {}
public record RefundTicketCreatedEvent(
String ticketId,
String transactionId,
String reason,
String operator,
Instant createdAt
) {}
}
这类设计的价值在于:峰值写入先落 Kafka,降低同步链路压力;下游工单系统短暂故障时不阻塞 MCP 主链路;可以做失败重试、死信队列、审计追踪。
很多 Demo 里 Tool 返回一个字符串就结束了,但生产环境里还得考虑:返回结构是否稳定?是否便于模型消费?是否便于日志审计?是否便于未来版本演进?
建议内部使用强类型 DTO,必要时在 Tool 层转换成模型更易理解的结构化文本或 JSON。
package com.example.payment.mcp.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
public record FraudRiskRequest(
@NotNull @Min(1) double amount,
@NotBlank @Pattern(regexp = "NEW|RETURNING") String clientType,
@Min(0) @Max(23) int hour,
@Min(0) int orderCount24h,
@NotNull @Min(0) @Max(1) double successRate7d
) {}
参数对象化有三个价值:校验逻辑集中,不散落在 Tool 方法中;接口契约更清晰,方便模型理解;后续可复用于 HTTP API、测试、审计对象。
package com.example.payment.mcp;
import com.example.payment.application.FraudDecisionService;
import com.example.payment.application.PaymentQueryService;
import com.example.payment.application.RefundTicketService;
import com.example.payment.domain.PaymentStatus;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Component
public class PaymentMcpTools {
private final PaymentQueryService paymentQueryService;
private final FraudDecisionService fraudDecisionService;
private final RefundTicketService refundTicketService;
public PaymentMcpTools(PaymentQueryService paymentQueryService,
FraudDecisionService fraudDecisionService,
RefundTicketService refundTicketService) {
this.paymentQueryService = paymentQueryService;
this.fraudDecisionService = fraudDecisionService;
this.refundTicketService = refundTicketService;
}
@Tool(description = """
根据交易ID查询支付状态、金额、时间和失败原因。
当用户询问某一笔订单是否支付成功、为什么失败、是否已退款时调用。
如果用户只是询问整体趋势或统计概览,不要调用该工具。""")
public String getPaymentStatus(
@ToolParam(description = "交易ID,例如 TXN-20260430-000123") String transactionId) {
return paymentQueryService.findByTransactionId(transactionId)
.map(payment -> """
transactionId=%s
status=%s
amount=%.2f
createdAt=%s
failureReason=%s
"""
.formatted(
payment.getTransactionId(),
payment.getStatus(),
payment.getAmount(),
payment.getCreatedAt(),
payment.getFailureReason() == null ? "NONE" : payment.getFailureReason()))
.orElse("transactionId=%s\nresult=NOT_FOUND".formatted(transactionId));
}
@Tool(description = """
统计指定日期范围内的退款率。
当用户询问最近退款多不多、某个时间段退款率是多少时调用。
如果用户需要查询单笔订单,请改用支付状态查询工具。""")
public String getRefundMetrics(
@ToolParam(description = "开始日期,格式 yyyy-MM-dd") String startDate,
@ToolParam(description = "结束日期,格式 yyyy-MM-dd") String endDate) {
LocalDateTime start = LocalDate.parse(startDate).atStartOfDay();
LocalDateTime end = LocalDate.parse(endDate).plusDays(1).atStartOfDay();
long total = paymentQueryService.countAllBetween(start, end);
long refunded = paymentQueryService.countByStatusBetween(PaymentStatus.REFUNDED, start, end);
double ratio = total == 0 ? 0D : refunded * 100D / total;
return """
startDate=%s
endDate=%s
total=%d
refunded=%d
refundRate=%.2f%%
"""
.formatted(startDate, endDate, total, refunded, ratio);
}
@Tool(description = """
评估一笔交易的欺诈风险等级,并返回评分与触发原因。
当用户询问某笔交易是否存在风险、是否建议人工复核时调用。""")
public String assessFraudRisk(
@ToolParam(description = "交易金额") double amount,
@ToolParam(description = "客户类型,只能是 NEW 或 RETURNING") String clientType,
@ToolParam(description = "交易发生的小时,0 到 23") int hour,
@ToolParam(description = "近24小时订单数") int orderCount24h,
@ToolParam(description = "近7日成功率,0 到 1") double successRate7d) {
var result = fraudDecisionService.assess(
new FraudDecisionService.FraudCommand(amount, clientType, hour, orderCount24h, successRate7d));
return """
score=%d
level=%s
reasons=%s
"""
.formatted(result.score(), result.level(), String.join(",", result.reasons()));
}
@Tool(description = """
为退款异常交易创建处理工单。
仅当用户明确要求创建工单、升级处理、人工介入时调用。
如果只是查询状态或解释原因,不要调用该工具。""")
public String createRefundTicket(
@ToolParam(description = "交易ID") String transactionId,
@ToolParam(description = "工单原因") String reason,
@ToolParam(description = "操作人,例如 ai-assistant 或客服工号") String operator) {
String ticketId = refundTicketService.submit(
new RefundTicketService.RefundTicketCommand(transactionId, reason, operator));
return "ticketId=%s\nstatus=SUBMITTED".formatted(ticketId);
}
}
这段代码里,最容易被忽略但最重要的点有两个:
第一,description 不是注释,而是工具路由策略的一部分。 好的 Tool 描述必须明确:什么情况下调用、什么情况下不要调用、输入参数的业务语义、输出能解决什么问题。
第二,写操作型工具必须有明确触发条件。 像 createRefundTicket 这样的工具,如果描述过于模糊,模型就可能在“仅仅解释退款失败原因”时误创建工单。这是生产事故,不是精度问题。
七、工程化升级:并发、超时、熔断、缓存、幂等
7.1 统一超时与隔离策略
MCP Tool 的超时预算,不能照搬传统后台接口。模型在工具链路里一旦等待过久,整轮对话都会被拖慢,用户感知比普通 API 更差。
建议做法:快查询型工具 300ms ~ 2s;中等聚合型工具 2s ~ 5s;写操作型工具同步确认 1s ~ 3s,重任务异步化。
基于 Resilience4j 的包装示例:
package com.example.payment.mcp.support;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.timelimiter.TimeLimiter;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
@Component
public class ToolExecutionTemplate {
private final ExecutorService executorService = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor();
public <T> T execute(String toolName, Duration timeout, Supplier<T> supplier) {
TimeLimiter timeLimiter = TimeLimiter.of(timeout);
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults(toolName);
Supplier<CompletableFuture<T>> futureSupplier =
() -> CompletableFuture.supplyAsync(supplier, executorService);
try {
return circuitBreaker.executeSupplier(
timeLimiter.decorateFutureSupplier(futureSupplier)
).get();
} catch (Exception e) {
throw new ToolExecutionException(toolName, "TOOL_EXECUTION_FAILED", e);
}
}
}
7.2 缓存策略
并不是所有 Tool 都要直查数据库。对于热点查询,建议增加 Redis 缓存:商户健康度缓存 30 秒;日级统计缓存 1 到 5 分钟;配置型资源缓存 10 分钟以上。
但要注意:单笔订单状态如果要求强一致,不要盲目缓存过久;风控结果若依赖实时画像,缓存要谨慎;写后读场景要考虑主动失效。
@Service
public class MerchantHealthService {
@Cacheable(cacheNames = "merchant-health", key = "#merchantId", unless = "#result == null")
public MerchantHealthSnapshot query(String merchantId) {
return loadFromWarehouse(merchantId);
}
private MerchantHealthSnapshot loadFromWarehouse(String merchantId) {
return new MerchantHealthSnapshot(merchantId, 0.982, 120, 3);
}
public record MerchantHealthSnapshot(
String merchantId,
double successRate,
int qps,
int failedCount5m
) {}
}
7.3 幂等控制
只要是“创建、提交、发起、关闭、扣款、退款”类 Tool,都必须引入幂等键。典型做法:调用方传 requestId;服务端用 requestId + toolName + tenantId 做唯一约束;首次成功后缓存结果,重复请求直接返回之前结果。
@Entity
@Table(name = "tool_idempotency_record", uniqueConstraints = {
@UniqueConstraint(name = "uk_tool_request", columnNames = {"tool_name", "request_id", "tenant_id"})
})
public class ToolIdempotencyRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String toolName;
private String requestId;
private String tenantId;
private String responseBody;
}
这一步不是可选项。模型发生重试时,如果没有幂等保护,最轻是重复建单,最重可能是重复退款。
7.4 异步化与削峰
当工具执行涉及多下游串行调用、重计算、报表生成、文件导出时,建议改造为两段式:submitXxxTask 和 queryXxxTaskResult。其本质是把“长事务工具”转为“任务型工具”,这也是大规模 Agent 系统中非常常见的模式。
八、安全体系:不是加个 Token 就结束了
MCP Server 一旦暴露到远程环境,本质上就是“可被模型驱动的业务操作面”。安全要求应高于普通查询接口。
8.1 OAuth2 Resource Server
package com.example.payment.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health/**").permitAll()
.requestMatchers("/mcp/**").authenticated()
.anyRequest().denyAll())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
8.2 工具级授权
认证只解决“你是谁”,不解决“你能调用什么”。建议把权限控制做到 Tool 级别:客服只能查订单、创建普通退款单;风控人员可调用高风险交易分析工具;运维可调用商户健康度与异常指标工具;高危操作工具需要更高权限甚至人工审批。
8.3 审计日志
每次 Tool 调用都应记录 traceId、sessionId、tenantId、toolName、输入参数摘要、执行耗时、调用结果码、调用人或调用主体。
package com.example.payment.mcp.support;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class ToolAuditLogger {
private static final Logger log = LoggerFactory.getLogger("TOOL_AUDIT");
public void log(String traceId, String toolName, String principal, long costMs, String resultCode) {
log.info("traceId={}, toolName={}, principal={}, costMs={}, resultCode={}",
traceId, toolName, principal, costMs, resultCode);
}
}
如果你的系统涉及支付、金融、医疗、政务,审计链路必须从第一天就设计进去。
九、可观测性:看得见,才运维得住
一个 MCP 工具服务上线后,最怕的是“用户说 AI 不稳定,但你根本不知道哪一步出问题”。建议至少建设三类观测能力。
9.1 Metrics
重点指标:Tool 调用次数、调用成功率、P95/P99 延迟、下游依赖耗时、熔断次数、超时次数、缓存命中率。
package com.example.payment.mcp.support;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class ToolMetricsRecorder {
private final MeterRegistry meterRegistry;
public ToolMetricsRecorder(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
public void record(String toolName, long costMs, boolean success) {
meterRegistry.timer("mcp.tool.latency", "tool", toolName, "success", String.valueOf(success))
.record(costMs, TimeUnit.MILLISECONDS);
meterRegistry.counter("mcp.tool.calls", "tool", toolName, "success", String.valueOf(success))
.increment();
}
}
9.2 Trace
要把 Agent 请求 -> Gateway -> MCP Tool -> DB/Redis/Kafka/第三方接口 这条链路打通。这样你才能准确判断到底是模型选错工具、Tool 执行超时、下游数据库抖动、网关限流,还是外部渠道返回异常。
9.3 Structured Logging
不要输出一堆难以检索的自然语言日志。MCP 服务要尽量结构化输出,方便检索与告警聚合。
十、配置中心、服务发现与多实例治理
对于企业内部多团队、多工具集、多环境部署的场景,单体式静态配置很快会失效。
建议引入服务注册中心(如 Nacos、Consul)、配置中心(统一管理工具开关、描述、限流参数)、Gateway(统一处理鉴权、流量治理、灰度发布)。
10.1 为什么要做工具元数据动态化
Tool 描述不是写完就不变的。上线后你常常会发现:模型误调用某个工具,某些参数描述不清晰,某类问题应该路由到另一个工具。如果每改一次描述都要重新发版,迭代成本会很高。更好的方式是把这类“工具元数据”放入配置中心做动态更新。
10.2 多实例下的状态管理
在 Streamable HTTP 模式下,服务可以做到基本无状态,但仍有几类状态要外置:
| 状态类型 |
存储建议 |
| 会话上下文 |
Redis |
| 幂等记录 |
MySQL / PostgreSQL |
| 异步任务状态 |
Redis + DB |
| 配置与元数据 |
Nacos / Apollo |
| 审计日志 |
ELK / ClickHouse / Loki |
十一、Kubernetes 生产部署
11.1 Dockerfile
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /workspace
COPY . .
RUN ./mvnw clean package -DskipTests
FROM eclipse-temurin:21-jre
WORKDIR /app
RUN useradd -r -s /sbin/nologin spring
USER spring
COPY --from=builder /workspace/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseZGC", "-XX:MaxRAMPercentage=70", "-jar", "app.jar"]
11.2 Spring Boot 配置
server:
port: 8080
shutdown: graceful
spring:
application:
name: payment-mcp-server
threads:
virtual:
enabled: true
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:5432/payment
username: ${DB_USER:payment}
password: ${DB_PASSWORD:changeit}
hikari:
maximum-pool-size: 30
minimum-idle: 8
connection-timeout: 3000
validation-timeout: 1000
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
kafka:
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
ai:
mcp:
server:
enabled: true
name: payment-mcp-server
version: 1.0.0
type: async
protocol: STREAMABLE
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
endpoint:
health:
probes:
enabled: true
metrics:
tags:
application: ${spring.application.name}
11.3 Deployment 与 HPA
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-mcp-server
spec:
replicas: 3
selector:
matchLabels:
app: payment-mcp-server
template:
metadata:
labels:
app: payment-mcp-server
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
containers:
- name: app
image: registry.example.com/payment-mcp-server:1.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "2Gi"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 40
periodSeconds: 20
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payment-mcp-server
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-mcp-server
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
生产环境还建议补齐 PodDisruptionBudget、anti-affinity、topologySpreadConstraints、灰度发布策略、Secret 管理和镜像漏洞扫描。
十二、实战案例:一条完整调用链长什么样
下面看一个真实场景。
场景:客服处理“支付成功但用户未到账”
用户输入:订单 TXN-20260430-000123 为什么用户说没到账?如果有风险请帮我创建人工处理工单。
推荐的 Agent 行为链路:先调用 getPaymentStatus,若状态异常或失败原因复杂,再调用 assessFraudRisk,若判断需要人工介入,再调用 createRefundTicket。
一次典型输出可能是:
transactionId=TXN-20260430-000123
status=SUCCESS
amount=299.00
createdAt=2026-04-30T10:12:31
failureReason=NONE
再结合风控输出:
score=78
level=HIGH
reasons=高金额交易,新客首单,凌晨时段交易
最后触发工单:
ticketId=RT-a8b1c92d2e11
status=SUBMITTED
这一整条链路的关键价值不只是“AI 能回答问题”,而是结果来自真实业务系统,关键动作经过工具级控制,整个过程可审计、可回放、可治理。
十三、常见生产问题与应对策略
13.1 工具描述写得太泛,导致误调用
现象:用户问“退款趋势”,模型却调用单笔订单查询;用户只是想解释失败原因,模型却创建了工单。
解决:在 description 中明确 when-to-call 和 when-not-to-call;给关键写操作工具增加显式触发条件;对误调用日志做定期复盘。
13.2 工具执行太慢,拖垮整轮对话
现象:一个 10 秒的聚合查询导致整轮对话超过 15 秒。
解决:聚合任务异步化;增加缓存;限制单次查询时间窗口;细化数据库索引与预聚合表。
13.3 多实例下重复执行写操作
现象:模型重试叠加网关重试,出现重复建单。
解决:引入幂等键;明确网关与客户端重试策略;写操作结果持久化并支持重复返回。
13.4 工具返回内容不稳定,模型消费困难
现象:有时返回自然语言,有时返回散乱字段,下游 Agent 很难稳定解析。
解决:统一返回格式;保持字段语义稳定;给关键 Tool 做契约测试。
十四、从 PoC 到生产的演进路线
建议按四阶段推进,而不是一开始就堆满所有基础设施。
阶段一:本地验证
单实例、本地数据库、STDIO 或简化 HTTP、验证工具定义与业务闭环。
阶段二:团队测试
单实例远程访问、OAuth2 接入、基础日志与指标、初步 Tool 描述调优。
阶段三:生产试点
Streamable HTTP、多实例部署、Redis 缓存与会话外置、熔断、限流、幂等、审计。
阶段四:平台化治理
MCP Gateway、服务注册发现、工具元数据动态化、多租户隔离、工具市场化管理。
这个节奏比“一步到位设计宇宙飞船”更现实,也更符合多数企业技术团队的推进方式。
总结
MCP 在企业里的真正价值,不是“让模型多了一个插件入口”,而是为 AI 与业务系统之间建立了一条可标准化、可治理、可扩展的工程通道。
从架构角度看,这套体系至少有五个关键结论:
- MCP 的本质是标准化能力暴露,而不是简单函数调用。
- Spring Boot 非常适合承载 MCP Server,因为它天然具备分层、治理、监控和安全基因。
- 生产环境要优先考虑高并发与可扩展,而不是只追求 Demo 跑通。
- 写操作工具必须具备幂等、审计、授权和异步化能力。
- 真正稳定的 AI 系统,不取决于模型多聪明,而取决于工具链路是否可靠。
如果你要把一句话带走,那应该是:MCP 不是给 AI 加工具,而是给企业 AI 系统加操作系统级的接口规范。
当你把 Tool、Resource、Prompt、鉴权、审计、可观测性和分布式治理串成一套完整架构后,AI 才真正从“演示能力”走向“生产能力”。