MCP(Model Context Protocol)服务器是现代AI应用架构中的关键组件,它通过标准化协议接口向AI应用程序暴露特定领域的功能。Spring AI MCP服务器Boot Starter为开发者提供了在Spring Boot应用中快速构建MCP服务器的能力,极大简化了AI服务集成的复杂度。
在实际项目中,我发现这套Starter真正强大的地方在于它完美融合了Spring Boot的自动配置理念和AI服务的特殊需求。开发者不再需要手动处理各种协议适配和组件注册的繁琐工作,只需关注核心业务逻辑的实现。
提示:MCP服务器本质上是一个功能适配层,它将特定领域的AI能力封装成标准化的接口,使上层应用可以统一方式调用不同来源的AI服务。
MCP服务器Boot Starter支持四种主流协议,每种协议都有其特定的适用场景:
| 协议类型 | 适用场景 | 性能特点 | 开发复杂度 |
|---|---|---|---|
| STDIO | 本地进程间通信 | 低延迟 | 低 |
| SSE (Server-Sent Events) | 实时数据推送场景 | 中等吞吐量 | 中 |
| Streamable-HTTP | 需要流式传输的Web场景 | 高吞吐量 | 高 |
| Stateless | 简单请求-响应模式 | 低资源消耗 | 低 |
我在实际项目中最常用的是Streamable-HTTP协议,特别是在需要处理大语言模型(LLM)流式输出的场景下。它的分块传输编码(Chunked Transfer Encoding)机制能有效提升用户体验。
Starter对两种操作模式的支持非常完善:
同步模式示例代码:
java复制@McpTool(name = "weatherQuery")
public String getWeather(String location) {
// 模拟天气查询API调用
return "当前" + location + "天气:晴,25℃";
}
异步模式示例代码:
java复制@McpTool(name = "asyncWeatherQuery")
public CompletableFuture<String> getWeatherAsync(String location) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000); // 模拟网络延迟
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "异步获取:" + location + "天气:多云,22℃";
});
}
实测发现,在IO密集型场景下,异步模式能提升至少30%的吞吐量。但要注意线程池的合理配置,否则可能适得其反。
在application.yml中可以进行细致的传输层配置:
yaml复制spring:
ai:
mcp:
server:
transport:
type: HTTP # 可选 STDIO|SSE|HTTP|STATELESS
http:
port: 8081
path: /mcp-api
sse:
heartbeat-interval: 30s
stdio:
encoding: UTF-8
我曾遇到过一个坑:当同时配置多个传输类型时,需要显式指定主传输类型,否则可能导致端口冲突。
Starter提供了三种核心注解来声明服务器能力:
java复制@McpTool(name = "currencyConverter", description = "货币转换工具")
public BigDecimal convertCurrency(
@Param("amount") BigDecimal amount,
@Param("from") String fromCurrency,
@Param("to") String toCurrency) {
// 实现转换逻辑
}
java复制@McpResource(name = "userManual", contentType = "text/markdown")
public Resource getUserManual() {
return new ClassPathResource("docs/user-guide.md");
}
java复制@McpPrompt(name = "travelSuggestion")
public String getTravelPrompt() {
return """
你是一位资深的旅行顾问。
请为来自{country}的游客推荐{season}季节的{days}天{city}旅行计划。
要求包含当地特色美食和不超过{maxBudget}元的预算。
""";
}
注意:注解的name属性必须全局唯一,否则启动时会抛出Bean冲突异常。建议采用"领域_功能"的命名规范。
Starter的自动配置类McpServerAutoConfiguration主要完成以下工作:
通过查看自动配置报告(启动时添加--debug参数),可以清晰了解Starter的决策过程。
在高并发场景下,我总结了以下优化经验:
yaml复制server:
tomcat:
max-connections: 200
threads:
max: 50
min-spare: 10
java复制@Configuration
public class McpConfig {
@Bean
public McpServerProperties mcpServerProperties() {
McpServerProperties props = new McpServerProperties();
props.setAsyncTimeout(Duration.ofSeconds(30));
return props;
}
}
java复制@McpResource(name = "apiSchema", cacheControl = "max-age=3600")
public Resource getOpenApiSchema() {
return new ClassPathResource("schema/openapi.json");
}
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| MCP-400 | 无效请求参数 | 检查@Param注解与参数类型的匹配 |
| MCP-404 | 工具/资源不存在 | 确认注解name属性值是否正确 |
| MCP-503 | 服务不可用 | 检查线程池是否饱和或依赖服务状态 |
| MCP-504 | 异步操作超时 | 调整async-timeout配置或优化实现 |
启用DEBUG级别日志可以看到详细的协议交互:
yaml复制logging:
level:
org.springframework.ai.mcp: DEBUG
典型的问题排查流程:
通过实现McpProtocolAdapter接口可以支持私有协议:
java复制public class CustomProtocolAdapter implements McpProtocolAdapter {
@Override
public void initialize(McpComponentRegistry registry) {
// 初始化逻辑
}
@Override
public void start() {
// 启动协议服务器
}
}
然后在META-INF/spring.factories中注册:
code复制org.springframework.ai.mcp.server.protocol.McpProtocolAdapter=\
com.example.CustomProtocolAdapter
多个工具可以组合成工作流:
java复制@McpTool(name = "tripPlanner")
public TripPlan planTrip(@Param("destination") String city) {
WeatherInfo weather = weatherTool.getWeather(city);
List<Attraction> attractions = attractionTool.findAttractions(city);
return new TripPlan(weather, attractions);
}
这种模式在实践中非常有用,特别是在需要串联多个AI能力的场景中。
集成Spring Security进行访问控制:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/mcp-api/**").authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
对所有输入参数进行校验:
java复制@McpTool(name = "safeCalculator")
public BigDecimal calculate(
@Param("expression") @Pattern(regexp = "^[0-9+\\-*/() ]+$") String expr) {
// 安全计算逻辑
}
我在金融项目中曾因为没有做好输入验证导致过公式注入漏洞,这个教训让我格外重视参数校验。
Starter自动暴露的健康指标包括:
自定义健康检查:
java复制@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// 实现数据库健康检查
return Health.up().build();
}
}
Micrometer指标自动收集:
java复制@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("application", "mcp-server");
}
关键指标包括:
测试工具组件:
java复制@SpringBootTest
class WeatherToolTest {
@Autowired
@Qualifier("weatherQuery")
private McpTool weatherTool;
@Test
void testWeatherQuery() {
String result = weatherTool.execute(Map.of("location", "北京"));
assertThat(result).contains("北京");
}
}
使用Testcontainers进行协议测试:
java复制@Testcontainers
@SpringBootTest(webEnvironment = RANDOM_PORT)
class McpHttpProtocolTest {
@Container
static GenericContainer<?> mcpClient = new GenericContainer<>("mcp-client:latest")
.withExposedPorts(8080);
@Test
void testProtocolHandshake() {
// 测试协议握手过程
}
}
典型Dockerfile配置:
dockerfile复制FROM eclipse-temurin:17-jdk-jammy
COPY target/mcp-server.jar /app/
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/mcp-server.jar"]
建议的部署架构:
在Kubernetes中的部署示例:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
replicas: 3
template:
spec:
containers:
- name: mcp
image: your-registry/mcp-server:1.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
在application.yml中指定协议版本:
yaml复制spring:
ai:
mcp:
server:
protocol-version: 1.1
实现版本适配器:
java复制public class V1LegacyAdapter implements McpVersionAdapter {
@Override
public Object convertRequest(Object rawRequest) {
// 将旧版请求转换为新版格式
}
@Override
public Object convertResponse(Object rawResponse) {
// 将新版响应转换为旧版格式
}
}
经过多个项目的实践验证,我总结了以下黄金法则:
一个符合所有最佳实践的示例:
java复制@McpTool(name = "sales_taxCalculator")
@Description("计算商品销售税,支持多地区税率")
@Timed(value = "tax.calculator", description = "销售税计算耗时")
public BigDecimal calculateTax(
@Param("amount") @Positive BigDecimal amount,
@Param("region") @Size(min=2, max=2) String regionCode) {
if (!taxRates.containsKey(regionCode)) {
throw new McpException("不支持的地区代码: " + regionCode);
}
return amount.multiply(taxRates.get(regionCode));
}
作为微服务发布:
java复制@EnableDiscoveryClient
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}
批处理工具示例:
java复制@McpTool(name = "batch_processor")
public String processBatch(@Param("jobId") String jobId) {
JobParameters params = new JobParametersBuilder()
.addString("jobId", jobId)
.toJobParameters();
jobLauncher.run(importUserJob, params);
return "Batch job started: " + jobId;
}
使用McpClient调用远程工具:
java复制@Bean
public McpClient mcpClient() {
return McpClient.builder()
.baseUrl("http://mcp-server:8080")
.defaultHeader("Authorization", "Bearer token")
.build();
}
public void useRemoteTool() {
Map<String, Object> params = Map.of("location", "上海");
String weather = mcpClient.executeTool("weatherQuery", params);
}
通过JavaScript调用SSE接口:
javascript复制const eventSource = new EventSource('/mcp-api/sse');
eventSource.onmessage = event => {
console.log('Received update:', event.data);
};
虽然Starter已经非常完善,但在以下方面还有改进空间:
目前社区正在讨论的路线图包括对GraalVM原生镜像的支持,这将进一步提升启动速度和内存效率。