支持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 工程化落地方案。这套架构,足够支撑起企业级的复杂智能体应用了!


本页地址:http://www.foxtable.com/webhelp/topics/7302.htm