连接多个MCP服务
本节的任务是:连接多个MCP服务,打造全能AI助理。
在之前的章节中,我们成功接入了百度地图的 MCP 服务,让 AI 拥有了查路线、看天气的能力。但在真实的业务场景中,用户的需求往往是复合的:
显然,单独一个 MCP 服务无法满足所有需求。我们需要让 AI 同时连接多个 MCP 服务端,变成一个全能助理。
但在实际操作中,这会带来两个致命问题:
1. 工具重名冲突:不同服务之间极容易出现工具重名(比如百度有 geocode,高德也有 geocode,如果直接注册给 AI,会导致冲突报错)。
2. 路由分发困难:AI 调用工具时只会告诉你工具名,不会告诉你它属于哪个服务,客户端必须把请求转发给正确的 MCP 服务器。
解决这两个问题的标准方案是:“前缀命名法 + 路由字典”。
核心架构设计
1. 拉取工具时加前缀:在注册工具时,给来自同一个 MCP 的工具名加上统一的前缀。例如来自百度的工具加前缀"baidu",来自高德的工具加前缀"gaode"
2. 建立路由字典:用一个字典记录这些工具前缀对应的 MCP 服务器地址。
3. 转发时剥离前缀:在 ToolExecutor 拦截到调用时,根据下划线切分出前缀和真实的工具名。去字典查出目标 URL,并向目标MCP发送剥离了前缀的真实工具名。
如何给MCP工具指定前缀
用LLMCaller的AddMCPTools方法注册MCP工具时,可用第二个参数指定前缀,例如:
caller.AddMCPTools(mcptools,"baidu_")
注册时会自动给工具名加上前缀"baidu_",例如"map_directions"会变为"baidu_map_directions"
完整参考代码
以下代码演示了如何通过路由字典,同时接入百度地图、高德地图和千问的MCP服务。
注意不同的MCP服务,鉴权方式可能不同,例如千问使用请求头鉴权。
以下代码可以直接在 Foxtable 命令窗口执行:
Dim
deepseekKey
As
String
=
"sk-a734b83536da45e9aedc3e42605bfb3a"
'
你的DeepSeek密钥
Dim
caller
As
New
LLMCaller("https://api.deepseek.com/chat/completions",
"deepseek-chat",
deepseekKey)
caller.Messages =
New
JArray()
caller.SystemPrompt =
"你是一个智能出行助手。你可以使用地图工具来查询地点、规划路线、获取天气等。请根据用户需求自主选择合适的工具。"
' ==================== 1.创建MCP路由字典
====================
Dim
mapRouters
As
New
Dictionary(Of
String,
String)
mapRouters("baidu")
=
"https://mcp.map.baidu.com/mcp?ak=bYDdADrPUCNkSAID9bJU70EF8Iy5htGu"
'请替换为自己的百度地图AK
'mapRouters("gaode") = "https://mcp.amap.com/mcp?ak=你的高德AK"
'注意:高德实际地址可能需查阅官方文档,这里仅作演示
'mapRouters("qwen") =
"https://maas.qianwenaiapi.com/api/v1/mcps/EnhancedSearch/mcp" '注意:千问使用请求头鉴权,实际地址请查阅官方文档
' ==================== 2.循环拉取注册每个MCP服务的工具
====================
For
Each
key
As
String
In
mapRouters.Keys
'
用Try捕获错误,避免个别MCP连接出错影响整体
Try
Dim
mcpUrl
As
String
= mapRouters(key)
'
获取MCP服务的地址
Dim
hc
As
New
HttpClient(mcpUrl)
hc.SkipError =
True
hc.ContentType =
"application/json"
hc.Headers.Add("Accept",
"application/json, text/event-stream")
'
核心必填项:声明客户端支持的数据格式
If
key =
"qwen"
Then
hc.Headers.Add("Authorization",
"Bearer
你的千问API_Key")
End
If
hc.Content =
<![CDATA[
{"jsonrpc":"2.0", "id":1, "method":"tools/list", "params":{}} ]]>.Value
Dim
listResp
As
String
= hc.GetData()
Dim
listJson
As
JObject = JsonConvert.DeserializeObject(listResp)
Dim
mcpTools
As
JArray = listJson("result")("tools")
caller.AddMCPTools(mcptools, key &
"_")
'将MCP工具注册到LLMCaller,注意要指定前缀,前缀和工具名用符号"_"隔开
Catch
ex
As
Exception
MessageBoxA.Show(key &
"MCP连接失败")
End
Try
Next
' ==================== 4.
设置工具执行器(自动路由转发)
====================
caller.ToolExecutor =
Function(ttoolName
As
String,
targs
As
JObject)
As
String
Dim
realToolName
As
String
= ttoolName
Dim
prefix
As
String
=
""
Dim
spidx
As
Integer
= ttoolName.IndexOf("_")
If
spidx > 0
Then
prefix = ttoolName.Substring(0, spidx)
'获取前缀
realToolName = ttoolName.Substring(spidx + 1)
'获取去掉前缀后的真实工具名
End
If
If
prefix <>
""
AndAlso
mapRouters.ContainsKey(prefix)
Then
'
如果路由表包括这个前缀,表示这是来自某个MCP的工具
Dim
callReq
As
New
JObject
From
{ {"jsonrpc",
"2.0"},
{"id",
Rand.NextString(10)}, {"method",
"tools/call"},
{"params",
New
JObject
From
{{"name",
realToolName}, {"arguments",
targs}}} }
Dim
url
As
String
= mapRouters(prefix)
'从路由表获取MCP地址
Dim
hc
As
New
HttpClient(url)
hc.SkipError =
True
hc.ContentType =
"application/json"
hc.Headers.Add("Accept",
"application/json, text/event-stream")
If
prefix =
"qwen"
Then
hc.Headers.Add("Authorization",
"Bearer
你的千问API_Key")
End
If
hc.Content = callReq.ToString()
Dim
resp
As
String
= hc.GetData()
Dim
respJson
As
JObject = JsonConvert.DeserializeObject(resp)
If
respJson("result")
IsNot
Nothing
AndAlso
respJson("result")("content")
IsNot
Nothing
Then
Dim
content
As
JArray = respJson("result")("content")
If
content.Count > 0
Then
Return
content(0)("text").ToString()
End
If
End
If
Return
"MCP调用失败:"
& vbCrLf & ttoolName & vbCrLf & targs.ToString() & vbCrLf & resp
Else
'如果不是来自MCP的工具,那么就是自定义的工具,需要自己写执行逻辑,这里直接返回错误:
Return
"工具["
& ttoolName &
"]执行错误!"
End
If
End
Function
' ==================== 5.
发起提问
====================
caller.UserMessage =
"帮我查一下从北京天安门到故宫的步行路线,大概需要走多久?"
If
caller.GetResult()
Then
MessageBoxA.Show(caller.Result)
Else
MessageBoxA.Show(caller.Error)
End
If