支持MCP的AI对话框
本节内容可以参考文件"CaseStudy\LLMCaller.foxdb"的窗口"使用MCP的AI对话框"。
在前面的章节中,我们通过 LLMCaller 和 WebViewer
成功打造了一个能连续对话、支持流式输出、且上下文内容可持久化的AI对话框,参考:上下文内容的持久化。
本节的任务:改造这个的AI对话框,客户端不再在本地定义和执行工具,而是直接连接多个MCP服务。
本节的示例要连接两个MCP服务:
1、一个公共MCP服务,百度地图MCP,让AI能查路线、看天气。
2、一个私有MCP服务,就是上一节设计的,让AI能统计、分析Foxtable的数据表,所以运行本节代码之前,请先打开上一节的示例文件MCPServer.foxdb。
为了区分这些工具,我们在客户端引入了“前缀命名法 +
路由字典”:来自百度的工具,前缀为"baidu_“,来自本地MCP的工具,前缀为"data_",对于在本地定义工具,则工具名不加前缀。
看起来要多很多个修改,其实不不需要,只需修改AfterLoad事件的部分代码即可。
修改AfterLoad事件代码
打开你的 AI 对话框窗口,将原来AfterLoad事件中“初始化 LLMCaller”和“从数据表读取工具”的代码全部删除,替换为以下代码(请保留后面的
WebViewer 初始化和 HTML 加载代码):
'''Async
' ==================== 1.
初始化
LLMCaller ====================
Dim
caller
As
New
LLMCaller("https://api.deepseek.com/chat/completions",
"deepseek-flash",
"sk-a734b83536da45e9aedc3e42605bfb3a")
'请替换为自己的密钥
caller.stream =
True
'开启流式输出
If
FileSys.FileExists(ProjectPath &
"aiMessages.txt")
Then
'
读取存储在本地的上下文
Dim
msgstr
As
String
= FileSys.ReadAllText(ProjectPath &
"aiMessages.txt")
caller.Messages = JsonConvert.DeserializeObject(msgstr)
Else
caller.Messages =
New
JArray()
'开启多轮对话
End
If
caller.SystemPrompt =
"你是一个全能助手。你可以调用本地数据表分析工具、百度地图工具,以及本地专属工具。请根据用户的需求,自主选择合适的工具。"
Vars("caller")
= caller
'将初始化好的LLMCaller存放在Var变量中,方便复用
'
==================== 2.
连接多个
MCP 服务并动态注册工具
====================
Dim
mcpRouters
As
New
Dictionary(Of
String,
String)
mcpRouters("baidu")
=
"https://mcp.map.baidu.com/mcp?ak=bYDdADrPUCNkSAID9bJU70EF8Iy5htGu"
'请替换为自己的百度AK
mcpRouters("data")
=
"http://127.0.0.1:18688/mcp"
For
Each
key
As
String
In
mcpRouters.Keys
'用Try捕获错误,避免个别MCP连接出错影响整体
Try
Dim
mcpUrl
As
String
= mcpRouters(key)
Dim
hc
As
New
HttpClient(mcpUrl)
hc.SkipError =
True
hc.ContentType =
"application/json"
hc.Headers.Add("Accept",
"application/json, text/event-stream")
hc.Content =
"{""jsonrpc"":""2.0"",""id"":1,""method"":""tools/list"",""params"":{}}"
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
'
==================== 3.
注册本地专属工具(演示MCP工具和本地工具混合使用)
====================
'
这里我们可以硬编码一个本地工具,它不依赖
MCP,完全由本地代码处理
Dim
localTool
As
AITool = caller.AddTool("local_hello",
"向指定的人打招呼")
localTool.AddParameter("name",
"string",
"要打招呼的人名",
True)
' ==================== 4.
设置工具执行器(自动路由
+ 本地兜底)
====================
caller.ToolExecutor =
Function(ttoolName
As
String,
targs
As
JObject)
As
String
'
尝试切分前缀
Dim
spidx
As
Integer
= ttoolName.IndexOf("_")
Dim
prefix
As
String
=
""
Dim
realToolName
As
String
= ttoolName
If
spidx > 0
Then
prefix = ttoolName.Substring(0, spidx)
'获得前缀
realToolName = ttoolName.Substring(spidx + 1)
'获得真实工具名
End
If
If
prefix <>
""
AndAlso
mcpRouters.ContainsKey(prefix)
Then
'如果前缀存在于路由字典中,说明这是
MCP 工具
Dim
targetUrl
As
String
= mcpRouters(prefix)
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
hcCall
As
New
HttpClient(targetUrl)
hcCall.SkipError =
True
hcCall.ContentType =
"application/json"
hcCall.Headers.Add("Accept",
"application/json, text/event-stream")
'
如果有特殊鉴权(如千问),在这里补充:If
prefix = "qwen" Then ...
hcCall.Content = callReq.ToString()
Dim
resp
As
String
= hcCall.GetData()
Dim
respJson
As
JObject = JsonConvert.DeserializeObject(resp)
'
提取
MCP 返回的结果
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
工具,说明是本地注册的工具,走本地执行逻辑
Select
Case
ttoolName
Case
"local_hello"
Dim
personName
As
String
= targs("name").ToString()
Return
"你好,"
& personName &
"!这是来自
Foxtable
本地代码的专属问候!"
Case
Else
Return
"工具["
& ttoolName &
"]不存在或执行出错!"
End
Select
End
If
End
Function
'
==================== 5.
初始化
WebViewer ====================
Dim
wv
As
WebViewer = e.Form.Controls("WebViewer1").WebViewer
Await
wv.EnsureCoreWebView2Async(Nothing)
' 6. 注入
FoxLib(用于后续可能的扩展,如调用
Foxtable 功能)
Dim
ft
As
New
FoxLib(wv)
wv.CoreWebView2.AddHostObjectToScript("ft",
ft)
' 加载聊天界面 HTML 和 渲染历史对话 (保持你原有的代码不变)
见证奇迹的时刻
现在,你的对话框已经脱胎换骨。你可以向它连续提出一个复合问题:
“先帮我查一下北京天安门到故宫的步行路线,然后看看咱们项目里有哪些数据表,最后向我(张三)问个好。”
你会看到 AI 在后台发起三次不同的工具调用:
选 baidu_map_directions -> 截取前缀 baidu -> 转发给百度 MCP -> 返回路线。
选 data_list_tables -> 截取前缀 data -> 转发给本地 MCP -> 返回表名。
选 local_hello -> 发现前缀不在字典中 -> 走本地代码 -> 返回专属问候。
架构的终极魅力
无限扩展:如果你明天想让 AI 拥有“查询天气”或“订机票”的能力,你不需要改这行代码。只需要在 Trae
或本地 MCP 商店找一个新的 MCP 地址,往 mcpRouters 字典里一加,就能自动接入。
新旧兼容:你不必抛弃原来写好的那些轻量级本地工具。通过路由字典的前缀匹配,MCP 工具和本地工具可以完美混用,大模型完全无感知。
超级轻量:客户端现在变成了一个纯粹的“路由网关”和“UI壳”。所有复杂的鉴权、数据操作、API 调用,都被安全地转移到了 MCP 服务端。
至此,你不仅学会了如何开发 AI 对话框,还掌握了目前 AI 领域最前沿的 MCP 工程化落地方案。这套架构,足够支撑起企业级的复杂智能体应用了!