手搓MCP客户端动态调用多MCP服务,调用哪个你说了算!

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

前两天,有个粉丝朋友咨询MCP服务如何动态调用,动态加载MCP服务的链接?我们都知道MCP客户端可以配置多个MCP服务的地址:

properties 复制代码
spring.ai.mcp.client.sse.connections.server1.url=http://localhost:xxxx
spring.ai.mcp.client.sse.connections.server2.url=http://localhost:xxxx
spring.ai.mcp.client.sse.connections.server3.url=http://localhost:xxxx

这样的MCP服务会自动加载到项目中,类似合成一个服务。一个请求进来,MCP客户端会在所有的服务中寻找合适的方法。

但是,业务中总会有各种不同的场景。例如,粉丝朋友的需求。SpringBoot一直坚持约定大于配置的方式,Spring AI也不例外,既然提供配置文件的配置,那么JavaBean的配置一样可行。指定调用只要需要找到调用的时机,自然可以控制。

本篇就带大家一起解决这份粉丝朋友的问题。

02 MCP动态调用

2.1 代码追踪

在研究动态调用之前,我们需要知道配置用在了哪里?

我们直接在.properties.yml配置文件中,

按住Ctrl,然后用鼠标点击url,就会跳转到下面的文件:

从方法中可以看到:

最终的配置会保存在SseParameters中,最后放到Map集合中。使用或者获取服务链接的时候,肯定也会通过getConnections()来获取,这个也是突破问题的关键。

2.2 探寻使用位置

上面找到了配置文件对用的Java类:

org.springframework.ai.mcp.client.autoconfigure.properties.McpSseClientProperties

我们可以采用上面相同的方法,看看哪里调用了getConnections(),由于我们的源码没有全部下载下来,所以很大程度上可能找不到。所以,断点提示就变成了我们唯一的出路。

断点启动之后,我们看到连接数为0,也就是还没有加载MCP服务的链接呢。如下图:

我们断点继续向下执行(快捷键:F8),中间会经历漫长的Bean的创建过程。我们的目标就是看看是哪一个Bean实例化的时候会获取连接。

我们就需要注意几个注解:@Bean@Component等,终于实例化出来了,找到了关键类:

org.springframework.ai.mcp.client.autoconfigure.McpClientAutoConfiguration

具体如图:

我们可以看到namedTransports里面就包含了我们MCP服务,最终放到mcpSyncClients集合中,如下图:

至此,Spring 启动随后完成。

2.3 修改点

我们可以看到mcpSyncClients集合上有@Bean标签,所以会被Spring管理,所以可以直接从Spring容器中,通过注入的方式获取到。

但是修改点有在哪里呢?我们先看看MCP调用的案例:

从构建ChatClient到发送请求参数,就只有ChatModelToolCallbackProvider两个参数。而ChatMode表示采用的大模型的类型,显然和MCP服务没有关系,就只有ToolCallbackProvider这个类了。

org.springframework.ai.tool.ToolCallbackProvider的实现有四个:

通过注入的ToolCallbackProvider,我们断点可以看到它的实现是:

org.springframework.ai.mcp.SyncMcpToolCallbackProvider

从代码的构造启可以看到,参数就是我们上面注入的mcpSyncClients

2.4 动态调用

我们只要根据MCP服务的名称区分不同的McpSyncClient即可。

我们从截图可以看到:

小编提供了两个MCP服务,分别是csdn-mcp-server(CSDN文章浏览服务)和gzh-mcp-server(种种好推荐服务)。小编没有传递参数,默认会调用gzh-mcp-server

我们看看返回的结果:

请求是一个与csdn-mcp-server匹配度比较高的请求,最终只调用了gzh-mcp-server。所以我们就可以控制调用的MCP服务了。

03 动态加载

动态调用的问题,我们已经解决了。如果这些服务链接想从数据库读取,又该如何处理呢?

最直接的想法,就是直接将配置文件对应的Bean自己new出来交给Spring管理。

3.1 配置McpSseClientProperties

Bean配置

java 复制代码
@Bean
public McpSseClientProperties mcpSseClientProperties() {
    McpSseClientProperties mcpSseClientProperties = new McpSseClientProperties();
    Map<String, SseParameters> connections = mcpSseClientProperties.getConnections();
    connections.put("server1", new SseParameters("http://localhost:8080", null));
    connections.put("server2", new SseParameters("http://localhost:8082", null));
    return mcpSseClientProperties;
}

启动结果

McpSseClientProperties发现了2个,实际只需要一个。也就是说,框架自动实例化McpSseClientProperties和我们的冲突了。

这种方式行不通!!!

既然已经存在,那就直接添加连接呢?因为Spring默认是单例。

3.2 动态加载链接

容器启动后加载链接

测试

已经加载成功了。

04 小结

到这里粉丝朋友的问题就全部解决了。同时,也分享给大家,希望对大家有帮助!

相关推荐
Cloud-Future19 小时前
Spring MVC 处理请求的流程
java·spring·mvc
小扳19 小时前
SpringBootWeb 篇-深入了解 ThreadLocal 存在内存泄漏问题
java·开发语言·spring boot·面试
Villiam_AY19 小时前
使用 chromedp 高效爬取 Bing 搜索结果
后端·爬虫·golang
CryptoPP19 小时前
跨境金融数据对接实践:印度NSE/BSE股票行情API集成指南
开发语言·后端·金融
lxsy19 小时前
spring-ai-alibaba-deepresearch 学习(十三)——ResearcherNode
java·源码分析·deepresearch·ai-alibaba
ShineWinsu19 小时前
对于单链表相关经典算法题:206. 反转链表及876. 链表的中间结点的解析
java·c语言·数据结构·学习·算法·链表·力扣
程序员爱钓鱼19 小时前
Go语言实战案例-实现简易定时提醒程序
后端·google·go
迦蓝叶19 小时前
JAiRouter 配置文件重构纪实 ——基于单一职责原则的模块化拆分与内聚性提升
java·网关·ai·重构·openai·prometheus·单一职责原则
ST.J20 小时前
系统架构思考20241204
java·笔记·系统架构
TDengine (老段)20 小时前
TDengine 时间函数 TIMETRUNCATE 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据