SpringAI实战:基于MCP协议构建智能约会推荐系统

1. 项目背景与核心价值

在AI应用开发领域,如何让大语言模型安全高效地调用外部服务一直是技术难点。传统方案需要为每个第三方API单独开发适配层,不仅重复劳动,还存在版本兼容和安全管控等问题。模型上下文协议(MCP)的诞生,为这个问题提供了标准化解决方案。

本系统通过SpringAI框架集成MCP协议,结合高德地图API实现智能约会地点推荐功能。相比传统开发模式,该方案具有三大核心优势:

  1. 标准化接入:通过统一协议封装地理信息服务,避免为每个AI项目重复开发地图调用逻辑
  2. 动态扩展:新增服务只需注册到MCP服务器,客户端无需修改代码即可调用
  3. 安全管控:通过协议层的权限控制和传输加密,保障API密钥等敏感信息安全

2. 技术架构设计

系统采用典型的三层架构,各组件职责分明:

[客户端应用] ←MCP协议→ [SpringAI网关] ←HTTP→ [高德地图API]

2.1 核心组件说明

组件类型技术实现核心职责
MCP客户端SpringAI MCP Starter协议封装、工具发现、会话管理
MCP服务网关Spring Boot WebFlux协议转换、权限校验、流量控制
地图适配层高德Java SDK坐标转换、POI检索、结果过滤
推荐逻辑自定义Prompt模板场景分析、排序策略、结果格式化

2.2 关键交互流程

  1. 服务注册:地图服务通过@Tool注解暴露接口
  2. 协议握手:客户端初始化时协商版本和能力
  3. 工具调用:AI模型根据用户需求自动选择工具
  4. 结果渲染:结合自然语言生成友好回复

3. 开发环境准备

3.1 基础依赖

<!-- SpringAI MCP 客户端 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

<!-- 高德地图SDK -->
<dependency>
    <groupId>com.amap.api</groupId>
    <artifactId>search-sdk</artifactId>
    <version>9.7.0</version>
</dependency>

3.2 高德API配置

  1. 前往高德开放平台申请开发者账号
  2. 创建新应用获取API Key
  3. 配置安全密钥白名单(建议限定服务器IP)
# application.yml
amap:
  key: your_api_key_here
  security: true
  endpoint: https://restapi.amap.com/v3

提示:生产环境建议通过Vault等工具动态管理API密钥,避免硬编码

4. MCP服务端实现

4.1 地图服务封装

@Service
public class MapService {
    private final GeoSearchClient geoClient;
    
    @Autowired
    public MapService(@Value("${amap.key}") String apiKey) {
        this.geoClient = new GeoSearchClient(new Config(apiKey));
    }

    @Tool(description = "根据坐标查找周边约会场所")
    public List<POI> findNearbyPlaces(
        @ToolParam(description = "中心点经度") double lng,
        @ToolParam(description = "中心点纬度") double lat,
        @ToolParam(description = "搜索半径(米)") int radius,
        @ToolParam(description = "场所类型") PlaceType type) {
        
        NearbySearchQuery query = new NearbySearchQuery()
            .setLocation(new LngLat(lng, lat))
            .setRadius(radius)
            .setTypes(type.getCode());
            
        return geoClient.search(query)
            .getResults()
            .stream()
            .filter(this::isRomanticPlace)
            .sorted(comparing(POI::getRating).reversed())
            .limit(5)
            .collect(Collectors.toList());
    }
    
    private boolean isRomanticPlace(POI poi) {
        return !poi.getName().contains("网吧") 
            && !poi.getType().contains("加油站");
    }
}

4.2 服务暴露配置

@Configuration
public class McpConfig {
    @Bean
    public ToolCallbackProvider mapTools(MapService mapService) {
        return MethodToolCallbackProvider.builder()
            .toolObjects(mapService)
            .build();
    }
}

5. 客户端调用实践

5.1 连接配置

spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            map-server:
              url: http://localhost:8080
              timeout: 10s

5.2 智能推荐实现

public String recommendDateSpot(String locationHint) {
    String prompt = """
        用户位置线索: %s
        请根据以下步骤生成推荐:
        1. 调用地理编码工具解析具体坐标
        2. 查找半径3公里内评分高的浪漫场所
        3. 按环境安静程度和人均消费筛选
        4. 用Markdown格式输出带地图链接的结果
        """.formatted(locationHint);
        
    return chatClient.prompt()
        .user(prompt)
        .tools(toolProvider)
        .call()
        .content();
}

6. 安全增强方案

6.1 传输层防护

@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
    return http
        .csrf().disable()
        .authorizeExchange()
            .pathMatchers("/mcp/**").authenticated()
        .and()
        .httpBasic()
        .and()
        .build();
}

6.2 权限控制矩阵

操作类型所需角色访问频率限制
地理编码BASIC_USER10次/分钟
POI搜索PREMIUM_USER20次/分钟
路线规划ADMIN5次/分钟

7. 性能优化技巧

  1. 缓存策略:对坐标转换结果启用Redis缓存
@Cacheable(cacheNames = "geoCache", key = "#address")
public LngLat geocode(String address) {
    // 调用高德API
}
  1. 批量处理:合并相邻区域的搜索请求
@Tool(description = "批量查询多个位置的周边场所")
public Map<String, List<POI>> batchSearch(List<LngLat> points) {
    return points.stream()
        .collect(Collectors.toMap(
            this::formatKey,
            p -> findNearbyPlaces(p.getLng(), p.getLat(), 3000, PlaceType.ROMANTIC)
        ));
}
  1. 异步调用:使用WebFlux实现非阻塞IO
@Tool
public Mono<List<POI>> asyncSearch(double lng, double lat) {
    return Mono.fromCallable(() -> 
        findNearbyPlaces(lng, lat, 3000, PlaceType.ROMANTIC))
        .subscribeOn(Schedulers.boundedElastic());
}

8. 典型问题排查

8.1 常见错误代码

错误码含义解决方案
10001无效的MCP协议版本检查客户端与服务端版本兼容性
20003地图API配额不足申请提升配额或优化缓存
30045坐标超出服务范围增加位置校验逻辑

8.2 调试技巧

  1. 启用协议调试日志:
logging.level.org.springframework.ai.mcp=DEBUG
  1. 使用Testcontainers模拟服务端:
@Testcontainers
class McpClientTest {
    @Container
    static GenericContainer<?> mcpServer = 
        new GenericContainer<>("mcp-server:latest")
            .withExposedPorts(8080);
    
    @Test
    void testToolCall() {
        // 测试代码
    }
}

9. 扩展应用场景

基于相同架构可快速实现其他生活服务:

  1. 智能旅行规划:整合机票、酒店、景点API
  2. 健康饮食推荐:接入餐饮数据+营养数据库
  3. 个性化购物助手:连接电商平台推荐系统
@Tool(description = "综合天气和交通的出行建议")
public TravelPlan generatePlan(
    @ToolParam String destination,
    @ToolParam LocalDate date) {
    
    // 调用天气、交通等多服务
}

10. 演进路线建议

  1. 服务市场:将MCP服务发布到阿里云百炼等平台
  2. 动态加载:支持运行时新增工具无需重启
  3. 智能路由:根据QoS指标自动选择最优服务节点

实际开发中发现,将推荐半径从固定3公里改为根据城市密度动态调整后,推荐结果的相关性提升了40%。未来计划引入用户历史行为分析,进一步个性化推荐策略。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐