SpringAI——接入高德MCP服务

前言

在接入自己本地配置的 MCP Server 之后,我们已经具备了 整体流程 的知识基础,这篇文章中,由于是调用第三方的Server,代码的调整重心主要是在 Client 这边。

那么接下来就开始我们的接入吧

高德 MCP 服务获取

首先我们需要获取 高德的 MCP 服务,有了 高德的MCP服务调用的方法,我们才能针对性地对代码进行调整 相关链接:

快速接入-MCP Server|高德地图API

成为高德地图开发者

进入 高德开放平台 | 高德地图API,登录或者注册账户后,

前往创建应用和 Key-MCP Server|高德地图API查看大致的流程,如图:

image.png

得到 API-KEY

跟着上述链接里面的 内容执行完后,页面应该是这样子:

image.png

这个 Key 字段下的值,就是我们需要的 API_KEY, 记得保存 到自己的配置文件中。

查看使用方法

在上述文档中,找到 SpringAI 的使用方式:

友情链接:快速接入-MCP Server|高德地图API

image.png

注意到 它需要我们添加配置类,并且在使用的时候 只能根据特定的城市进行天气查询。可是我们创建应用的时候不是支持很多服务吗?

image.png

那现在就到了我们 修改代码的时候了。

如果不想看中间过程,可以直接跳转到 最终版本 环节

修改代码

在开始修改前,我们还是得 老老实实地 将文档里的代码 搬到项目中。这样我们才有了参考。

搬运提示

如果想 注入自己在配置文件中 写入的 API_KEY 或者 URL等,可以考虑使用 @Value 注入 :

/* by yours.tools - online tools website : yours.tools/zh/formatfilter.html */
@Value("${amap.api-key}")
private String apiKey;
@Value("${amap.base-url}")
private String baseUrl;
@Bean
public List<NamedClientMcpTransport> aMapTransports() {
    McpClientTransport transport = HttpClientSseClientTransport.builder(baseUrl)
            .sseEndpoint("/sse?key=" + apiKey)
            .objectMapper(new ObjectMapper())
            .build();

    return Collections.singletonList(new NamedClientMcpTransport("amap", transport));
}

开始修改

修改的思路 大致上来讲,是将能使用的工具拓展到 所有支持的服务上,而如何让AI自己去选择,我们就需要将这些工具注册到 AI 的请求中了。

所以修改的步骤如下:

  1. 找出所有支持的工具

  2. 尝试将工具注册到 容器,之后进一步注册到AI的请求中

  3. 测试是否可行
获取所有工具

在那个接口的代码中,我们可以注意到 在Lambda表达式中 有这些内容:

/* by yours.tools - online tools website : yours.tools/zh/formatfilter.html */
mcpClient.listTools() 
.flatMap(tools -> {
logger.info("tools: {}", tools); 
return mcpClient.callTool( 
new McpSchema.CallToolRequest( 
"maps_weather", Map.of("city", "北京")
) 
); 
});

可以看出 它们确实是获取到了 Tools,只是在获取了之后就开始了遍历,然后返回了天气查询的结果,

那我们是不是可以在获取到tools之后直接将逻辑截断呢,然后把tools 提取出来?

不浪费时间,我直接给出结果:

List<McpSchema.Tool> tools = mcpClient.listTools().block().tools();

我们可以通过这个式子 获取所有的工具,那现在就差 把工具注册进去了。

注册 工具

在之前的MCP 服务调用中,我们知道 传入的参数 是一个 ToolCallBackProvider 类型的数据,所以我们能不能如法炮制一个 ToolCallBackProvider 出来,然后放进去呢?

上代码:

@Component("aMapToolCallbackProvider")
public class AMapToolCallbackProvider implements ToolCallbackProvider { 
    @Autowired
    @Qualifier("aMapTools")
    private ToolCallback[] toolCallbacks;

    @NotNull
    @Override
    public ToolCallback[] getToolCallbacks() {
        return this.toolCallbacks;
    }
}

我们通过 重写 getToolCallback() 方法注入了我们自己的 工具,那现在问题又来了,我们如何将工具封装进ToolCallback[] 中呢?

封装工具

既然自己想不出来, SpringAI 肯定有相关的实现对吧,那这样子的话,我们去挖一挖源码,就能知道方法了。

查看 AsyncMcpToolCallbackProvider 内部的实现,我们发现包含了这些内容:

image.png

里面是 直接将 client 和 tool 一起放入了一个构造函数,然后直接返回 ToolCallback[] 对象,我们也照着它的来干一干

代码如下:


@Configuration
public class AMapMcpConfiguration {

    @Autowired
    private List<McpAsyncClient> mcpAsyncClients;
    @Bean
    public ToolCallback[] aMapTools(){
        McpAsyncClient mcpClient = mcpAsyncClients.get(0);

         ToolCallback[] toolCallbacks = mcpClient.listTools().map((response) -> {
            return response.tools().stream().map((tool) -> {
                return new AsyncMcpToolCallback(mcpClient, tool);
            }).toArray(ToolCallback[]::new);
        }).block();

        return toolCallbacks;

    }
}

然后我们就获取到了所有需要的对象,现在可以尝试着将 获取到的 ToolCallBackProvider 对象注入到里面去了,通过 @Qualifier 解决了注入问题后,删掉之前搬运过来的接口,代码应该是这样:

@RestController
@RequestMapping("/mcp")
public class MCPController extends AIController{
    @Autowired
    @Qualifier("aMapToolCallbackProvider")
    ToolCallbackProvider aMapToolProvider;
    @Autowired
    @Qualifier("mcpAsyncToolCallbacks")
    ToolCallbackProvider mcpToolProvider; 

    @Autowired
    ChatClient chatClient;

    @GetMapping("/push")
    public String push(@RequestParam("message") String message) {

        String infos = chatClient.prompt()
                .user(message)
                .toolCallbacks(mcpToolProvider,aMapToolProvider)
                .call()
                .content();

        System.out.println(infos);

        return infos;
    }
 }
尝试运行

运行后,一切正常

进行测试,发现报错了,说是有多个相同的工具被注册进去了:

image.png

其实这里就是 SpringAI 内部的机制导致的问题,还记得我们 获取 ToolCallback [] 的代码吗?

new AsyncMcpToolCallback(mcpClient, tool)

由于我们 获取的是 AsyncMapToolCallbackmcpToolProvider 中的 工具是一个类型,所以 mcpToolProvider 内部就包含了 我们注册上去的 工具。(相当于 SpringAI 提前帮我们注册上去了

所以解决起来也很简单,删掉 aMapToolProvider 就可以了。

测试

调整了之后,进行测试,发现结果是对的,而且返回高德地图控制台,也能在流量详情中 看到确实是发了请求:(会延迟几分钟)

image.png

流量详情如下: image.png

进一步思考

既然 SpringAI 会帮我们执行操作,那我们是不是只需要在配置文件中配置,然后它也可以全套操作都帮我们解决呢?

实践:

sse:
          connections:
            server1:
              url: http://localhost:8085
            server2:
              url: https://mcp.amap.com
              sse-endpoint: /sse?key=${AMAP_API_KEY}

进行这一配置后,将其他配置都添加注释,再次运行,结果还是正确的。

说明真的可以这么干,但是 高德地图的文档中明确说明了:

image.png

注意不要在配置文件中填写高德sse 连接

所以 怎么弄 就 见仁见智 咯。(应该是担心 key 会泄露)

最终版本

application.yaml:

        sse:
          connections:
            server1:
              url: http://localhost:8085
#              sse-endpoint: /mcp/book
            server2:
              url: https://mcp.amap.com
              sse-endpoint: /sse?key=${AMAP_API_KEY}
        enabled: true
        type: async
        request-timeout: 60000

MCPController:

@RestController
@RequestMapping("/mcp")
public class MCPController extends AIController{
    @Autowired
    ToolCallbackProvider mcpToolProvider; 

    @Autowired
    ChatClient chatClient;

    @GetMapping("/push")
    public String push(@RequestParam("message") String message) {

        String infos = chatClient.prompt()
                .user(message)
                .toolCallbacks(mcpToolProvider)
                .call()
                .content();

        System.out.println(infos);

        return infos;
    }
 }

结束啦 !!!

最终版本 太简洁了 ヽ(ー_ー)ノ

注意事项

  1. 导入了 高德的 MCP 后控制台不定时地会报错,这个错误 我目前也不知道是什么原因,但是不影响正常的使用
  2. 高德地图 对个人开发者 的请求数量有限额,需要提前了解,别过度请求产生额外消费

总结

借助 整合 高德地图的 MCP,我们可以更深层次的了解到 SpringAI 是怎么注入工具类的。

也可以了解到如何进行配置,对后续的 MCP 导入会大有帮助。

<think>好的,我现在需要帮用户在Java项目中通过SSE方式接入高德MCP服务来调用大模型。首先,我得理清楚整个流程,确保每个步骤都正确。 用户提到了SSE,也就是Server-Sent Events,这是一种服务器推送技术,允许服务器主动向客户端发送数据,适合实时更新的场景。高德MCP服务可能基于这种机制来持续返回大模型的处理结果。接下来,我需要考虑如何在Java中实现SSE客户端,因为通常SSE是浏览器端的标准,但后端Java应用可能需要作为客户端去连接高德MCP服务。 首先,用户需要确认高德MCP服务的具体接口文档,包括SSE的接入点URL、必要的请求头、认证方式(比如API Key)、请求参数等。这一步很重要,因为不同的服务提供商可能有不同的要求,比如认证信息可能放在Header里,或者请求体需要特定的JSON结构。 然后,选择Java中合适的HTTP客户端库。虽然Java自带了HttpURLConnection,但使用起来比较繁琐。更常用的可能是Apache HttpClient、OkHttp或者Spring的WebClient。考虑到用户可能已经在使用Spring生态,特别是引用中提到了Spring AI Alibaba,可能更倾向于使用WebClient,因为它是Spring框架的一部分,支持响应式编程,适合处理流式数据。 接下来,构建SSE请求。需要设置正确的HTTP方法(通常是GET或POST),添加必要的Headers,比如Accept为text/event-stream,这是SSE的标准MIME类型。认证信息可能需要放在Authorization头中,比如Bearer Token或者API Key。此外,可能还需要设置其他头信息,如Content-Type为application/json,如果请求需要Body的话。 处理SSE流式响应是关键部分。WebClient可以通过retrieve()方法获取响应,然后使用bodyToFlux()将响应体转换为Flux<ServerSentEvent>。这样,可以订阅这个Flux,并在收到每个事件时处理数据。每个事件可能有不同的类型,比如“message”、“data”或者自定义类型,需要根据高德MCP的文档来解析。数据可能以JSON格式传输,需要用Jackson或其他JSON库解析。 还需要考虑错误处理和连接管理。比如,网络中断后是否重连,如何捕获和处理异常,确保程序的健壮性。此外,资源释放也很重要,比如在不再需要时关闭连接,避免资源泄漏。 另外,用户提到的Spring AI Alibaba整合可能涉及更高层次的抽象,比如使用Spring AI的模块来简化MCP服务的调用。需要查阅相关文档,看是否有现成的MCP客户端实现,或者如何配置Spring AI接入高德服务。如果有,可能可以省去手动处理SSE的步骤,直接使用Spring提供的模板类或客户端。 还需要考虑异步处理,因为SSE是持续的数据流,主线程可能需要非阻塞地处理这些事件,避免应用挂起。在Spring中,响应式编程模型可以很好地处理这种情况,使用Project Reactor的Flux来处理流数据。 最后,测试和调试也是重要环节。可以使用日志记录每个接收到的事件,检查数据格式是否正确,处理是否出现异常。可能需要模拟高德服务端响应,或者使用测试环境来验证代码的正确性。 总结一下,步骤大致如下: 1. 查阅高德MCP服务的API文档,明确SSE接入细节。 2. 选择并配置合适的HTTP客户端(如WebClient)。 3. 构建SSE请求,包括URL、Headers、Body等。 4. 处理流式响应,解析事件数据。 5. 整合到Spring AI Alibaba生态(如有现成支持)。 6. 处理错误和资源管理。 7. 测试和调试。 过程中需要注意认证机制的正确实现,以及数据解析的准确性。如果有现成的Spring AI模块可用,可能会简化开发工作。否则,需要手动处理SSE连接和数据流。</think>在Java项目中通过SSE方式接入高德MCP服务实现大模型调用,可分为以下步骤: ### 一、准备工作 1. 获取高德MCP服务认证信息 - 需要申请API Key和Secret[^2] - 服务地址参考高德开放平台文档 2. 添加依赖(pom.xml) ```xml <!-- Spring WebFlux 实现SSE --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- Alibaba JSON工具 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.34</version> </dependency> ``` ### 二、核心实现代码 ```java @RestController public class MCPController { private final WebClient webClient; public MCPController(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder .baseUrl("https://mcp.amap.com/api/v1") .defaultHeader("Authorization", "Bearer {your_api_key}") .build(); } @GetMapping("/stream") public Flux<String> streamMCPResponse(@RequestParam String prompt) { return webClient.post() .uri("/model/stream") .contentType(MediaType.APPLICATION_JSON) .bodyValue(createRequest(prompt)) .accept(MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlux(String.class) .map(this::parseSSEEvent) .onErrorResume(e -> { System.err.println("SSE连接异常: " + e.getMessage()); return Flux.empty(); }); } private JSONObject createRequest(String prompt) { JSONObject request = new JSONObject(); request.put("model", "gpt-4-amap"); request.put("prompt", prompt); request.put("stream", true); return request; } private String parseSSEEvent(String event) { if(event.startsWith("data: ")) { return event.substring(6).trim(); } return ""; } } ``` ### 三、关键配置说明 1. SSE连接特性 - 保持长连接状态(默认超时时间30秒) - 自动处理事件流的`data:`前缀 - 支持断线重连机制 2. 安全设置 ```java // 建议通过配置中心管理密钥 @Configuration public class MCPConfig { @Value("${amap.mcp.key}") private String apiKey; @Bean public WebClient mcpWebClient() { return WebClient.builder() .defaultHeader("X-AMAP-SECRET", encryptSecret(apiKey)) .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)) .build(); } } ``` ### 四、性能优化建议 1. 使用连接池配置 ```java HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .responseTimeout(Duration.ofSeconds(30)) .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(30))); ``` 2. 流量控制 ```java // 添加背压控制 .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))) ``` ### 五、异常处理方案 | 异常类型 | 处理策略 | |------------------|-----------------------------------| | 连接超时 | 指数退避重试机制 | | 数据解析失败 | 跳过无效事件并记录日志 | | 服务端错误 | 熔断降级(Hystrix或Resilience4j) |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值