基于 MCP 构建可查询的 Chatbot

本文是 T 系列的第 02 篇,主要介绍 Chatbot UI 如何基于 MCP 构建可查询的 Chatbot。

T 系列为技术文章翻译系列,加以本人的一些拙见,如有雷同,实属正常,如有不同,欢迎交流。

现代对话式 AI 需要能够与现实世界的数据和服务进行交互。对于许多应用场景而言,这意味着需要查询诸如 SQL 数据库等结构化数据源。传统上,将 Chatbot 连接到数据库通常需要复杂的、易碎的 API 封装或存在风险的提示工程,可能导致 SQL 注入漏洞。模型上下文协议(MCP)提供了一种安全且结构化的替代方案。通过使用支持模式识别的工具和安全的解析模式,开发者可以构建出能够检索并呈现可靠实时数据的助手。本文探讨了基于 MCP 框架实现的数据库连接型 Chatbot 的架构与实现方法。

作者以“查询诸如 SQL 数据库等结构化数据源”为例,本质其实是具备访问外部数据的能力,可以是数据库、文件系统、API 等。

标题中的“可查询”是指从数据库获取数据,并将数据以可视化的方式呈现给用户。

数据库直连弊端

将 AI 连接到数据库并不像提供连接字符串那么简单。直接将自然语言转换为 SQL 语句存在诸多风险:

  • 安全漏洞:设计不当的系统可能导致用户自然语言查询生成并执行恶意 SQL 语句,例如 DROP TABLE 语句或未经授权的数据检索。
  • 模式幻觉:若缺乏对数据库模式的明确理解,模型可能生成包含错误表名或列名的 SQL 查询,从而导致错误和请求失败。
  • 过于脆弱:依赖提示工程将模型输出格式化为有效的 SQL 查询方式存在脆弱性,且难以维护。即使提示或模型发生微小变化,也可能导致整个流程中断。

弊端:破坏数据库结构、数据;易生成错误的查询语句;系统不稳定。

MCP 通过充当安全的中间层来应对这些弊端,防止 Agent 直接访问数据库,而是将其请求通过预先定义并验证的一组工具进行路由。

MCP 充当 Agent 与数据库之间的中间层,通过一系列的安全验证机制,保证请求的合法性和数据的安全性。

模式感知工具和 MCP Server

基于 MCP 的数据库集成的核心是 MCP Server。MCP Server 提供了一组受严格控制的工具,Client(AI Agent)可调用这些工具。在进行数据库查询时,这些工具具备模式识别能力,并专为安全性和可靠性而设计。

MCP Server、MCP Client 是 MCP 中定义的两个概念,另外还有 MCP Host,详见 https://modelcontextprotocol.io/docs/learn/architecture#concepts-of-mcp

典型的数据库相关的 MCP Server 可能会提供两个关键工具:

  1. list_tables:一个只读工具,允许代理对数据库模式进行感知,了解可用的表和列。
  2. execute_read_query:一个工具,接受一个有效且经过预验证的 SQL SELECT 语句,并返回查询结果。

关键在于,Agent 程序并非完全独立生成查询。相反,它会利用自身的推理能力,根据 list_tables 工具提供的模式信息来构建查询。随后,MCP Server 会对该查询进行验证并执行,确保其符合预先定义的安全规则。

LLM 根据数据库模式信息,推理生成查询语句,然后 MCP Server 验证并执行查询语句,最终返回查询结果给 LLM 进行总结。

以下是一个简化示例,展示这些工具可能如何在类似 TypeScript 的数据结构中定义,MCP Client 随后便可读取并理解该结构。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/mcp/db-tools.ts
interface TableSchema {
  table_name: string;
  columns: {
    column_name: string;
    data_type: string;
  }[];
}

/**
 * 列出已连接数据库中的可用表
 */
export function listTables(): TableSchema[] {
  // 连接数据库,获取表结构信息
}

/**
 * 执行读取查询
 */
export function executeReadQuery(sql_query: string): Object[] {
  // 执行读取查询,返回查询结果
}

该模型确保 LLM 的作用不是充当SQL专家,而是作为推理引擎,能够判断何时调用特定的安全工具并传递正确的参数。

幕后:查询生成与执行流程

当用户提出需要数据库访问的问题时,MCP Agent 会启动一个多步骤流程:

  1. 用户查询:“我们目前有多少库存产品?”
  2. 初始工具调用:Agent 判断需要了解数据库的结构才能回答该问题,因此调用 list_tables 工具。
  3. 模式检索:MCP Server 返回相关表的模式信息,并将其添加到工具上下文中。例如:products 表包含 product_idnamestock_count 列。
  4. SQL 生成:根据用户的提问和检索到的模式,代理为 execute_read_query 工具生成参数。此时 Agent 已具备上下文,可以生成正确的 SQL 查询语句:SELECT SUM(stock_count) FROM products
  5. 安全执行:MCP 服务器收到 execute_read_query 工具调用。服务器端逻辑包含验证,确保查询是安全的 SELECT 语句,且不包含被禁止的关键词(如 DELETE、UPDATE、DROP)或可能用于数据外泄的复杂连接操作。
  6. 结果解析与响应:数据库返回查询结果(例如:[{ "sum": 450 }])。MCP Server 负责处理响应,结果将添加到工具上下文中。随后,Agent 会使用这些最终的结构化数据,生成面向用户的自然语言回复,例如:“我们共有 450 种产品在库存中。”
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
┌─────────────────────────────────────────────────────────────┐
│  ① 用户查询                                                  
"我们目前有多少库存产品?"                                
└───────────────────────────────┬─────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│  ② 初始工具调用(Agent 侧)                                   
│     Agent 判断需先了解库结构 → 调用 list_tables 工具          
└───────────────────────────────┬─────────────────────────────┘
                                 │  tools/call: list_tables
┌─────────────────────────────────────────────────────────────┐
│  ③ 模式检索(MCP Server 侧)                                 
│     返回相关表模式 → 写入工具上下文                           
│     e.g. products(product_id, name, stock_count)             
└───────────────────────────────┬─────────────────────────────┘
                                 │  tools/result
┌─────────────────────────────────────────────────────────────┐
│  ④ SQL 生成(Agent 侧)                                      
│     依据提问 + 模式 → 为 execute_read_query 生成参数          
│     SELECT SUM(stock_count) FROM products                    
└───────────────────────────────┬─────────────────────────────┘
                                 │  tools/call: execute_read_query
┌─────────────────────────────────────────────────────────────┐
│  ⑤ 安全执行(MCP Server 侧)                                 
│     校验:仅允许 SELECT,拦截 DELETE/UPDATE/DROP 及危险连接   
│     → 转发数据库执行                                          
└───────────────────────────────┬─────────────────────────────┘
                                 │  DB query result
┌─────────────────────────────────────────────────────────────┐
│  ⑥ 结果解析与响应(Agent 侧)                                
│     DB 返回 [{ "sum": 450 }] → 写入上下文                     
│     Agent 生成自然语言:"我们共有 450 种产品在库存中。"       
└─────────────────────────────────────────────────────────────┘

每次工具调用和工具结果都会记录在工具上下文中,为调试和安全分析提供清晰的追溯路径,使得整个交互过程更加透明和可靠。

有感而发

MCP 数据库连接方法代表了一种根本性的转变,从危险的文本转 SQL 生成方式转向更安全、更可靠的推理到工具参数的模式。通过将 Agent(LLM)的高层推理与底层经过验证的数据库操作分离,我们构建出更加稳健和安全的系统。

尽管像 LangChain 框架也提供了类似的功能,但 MCP 提供了一种标准化的协议驱动架构,其本身具备更强的互操作性,并且在不同平台和模型之间更容易扩展。这种标准化确保了为某一 MCP Client 开发的数据库工具,可以无缝地被另一客户端使用。

MCP 是工具调用的协议,抹平不同平台差的差异,即同一 MCP Server 可以被不同平台的 MCP Client 调用。

最重要的优势在于,从脆弱的、基于即时提示的技巧性操作,转向了明确的、以架构为导向的工具使用。这不仅能够避免幻觉和安全风险,还能使代理的行为更加可预测,便于调试。对于专业开发者和研究人员而言,这是一次重大突破。它让我们能够构建出强大且具备数据意识的助手,而不再需要持续担心查询失控或晦涩的提示导致系统崩溃。关注点也从“如何让模型输出有效的SQL查询?”转变为“我应该为模型提供哪种最高效、最安全的工具来解决这个问题?”

本文描述了基于 MCP 构建可查询的 Chatbot 的细节。这里留一个坑位,决定动手码码代码,实现文中的步骤,后续另开一篇。