一、一个绕不开的需求:让不懂 SQL 的人也能问数据库

业务方隔三差五发来一句"上周谁买得最多",DBA 就得放下手里的活写一段 SQL。这类临时取数,占用的是最贵的人力,产出的却是最标准化的东西——问题用中文描述,答案在数据库里。

2025 年下半年起,一个叫 MCP(Model Context Protocol) 的协议把这件事的解法定了型:由 Anthropic 提出、如今 OpenAI、Google 等主流厂商都已跟进,它是 AI 应用与外部工具之间的"USB-C 接口"。你把数据库能力包装成一个 MCP Server,任何 MCP 兼容的 AI 客户端(Claude Desktop、Cline、各类自研 Agent)接上来,就能让大模型自己决定"该查哪张表、写什么 SQL",然后用大白话把结果讲出来。

金仓征文的 AI 方向里,"金仓 MCP Server"被明确点了名。于是我在鲲鹏服务器上,给 KingbaseES 手写了一个 MCP Server,把"自然语言问数"这条链路从头到尾跑通——而且把工程上最容易被跳过、却最要命的一环(安全)做扎实。这篇就是全过程实录,代码可直接取用。

二、架构:一百行代码的"翻译官"

整条链路只有三个角色:

MCP Server 向 AI 暴露四个"工具"(Tool),大模型像调用函数一样调用它们:

工具

作用

设计意图

list_tables

列出有哪些业务表及行数

让 Agent 先"看清战场"再动手

describe_table

看某张表的结构 + 3 行示例

让 Agent 知道字段名/类型,SQL 才写得对

run_query

执行一条只读 SELECT

核心能力,也是安全防线所在

db_info

返回版本与连接身份

验明后端真身

用金仓官方推荐的 PG 生态驱动 psycopg2 连 54321 端口(这个系列第二篇验证过 PG 协议驱动连 KES 零改造)。用 Anthropic 官方 mcp SDK 的 FastMCP,一个装饰器就把普通 Python 函数变成 MCP 工具:

from mcp.server.fastmcp import FastMCP
mcp = FastMCP("kingbase")

@mcp.tool()
def run_query(sql: str) -> str:
    """执行一条只读 SELECT 查询并返回结果(最多 50 行)。"""
    ...  # 三道安全闸 + psycopg2 执行,详见第五节

if __name__ == "__main__":
    mcp.run()   # stdio 传输

工具函数的 docstring 不是注释,是给大模型看的说明书——Agent 正是靠它判断什么时候该调哪个工具。这是写 MCP Server 和写普通后端接口最不一样的地方:你的注释第一次有了"读者是 AI"。

三、它是不是真的 MCP Server?拿协议说话

光说不算,我用官方 mcp SDK 写了个真正的 MCP 协议客户端去连它,走完整的 initialize → tools/list → tools/call 握手:

截图里每一行都是真实的协议往返:

  • 握手成功:协议版本 2025-11-25,Server 名 kingbase——这是标准 MCP,不是我自己发明的接口;

  • 工具发现:客户端自动拉到 4 个工具及其说明,这正是 Agent"知道自己能干什么"的来源;

  • db_info 返回 KingbaseES V009R003C018 | ai_ro | test——后端确实是金仓,且连接身份是只读账号 ai_ro(重点,第五节展开);

  • run_query 跑了一条 GROUP BY 聚合,真实返回了消费额排名;

  • 最后故意让工具执行 DELETE,服务端回 已拒绝:只允许 SELECT/WITH 查询

    这张图的意义是:任何 MCP 兼容的 AI 应用,都能用完全相同的协议接进这个金仓 Server——我用 SDK 客户端能连,Claude Desktop、Cline 自然也能连。

    四、见证时刻:中文提问,金仓作答

    接下来是主菜。Agent 拿到三个纯中文问题,自己翻译成 SQL、经 MCP 查金仓、再用中文回答:

    [用户 Q1] 谁是消费冠军?一共花了多少钱?
    [Agent ] 生成 SQL:select member_name, sum(amount) s from orders
                        group by member_name order by s desc limit 1
    [回答  ] 消费冠军是 赵敏,累计消费 10556.00 元。
    
    [用户 Q2] 7月1号以来销售额最高的三样商品是什么?
    [回答  ] 7 月以来销售额 Top3:游戏显卡(5999元)、NAS存储(4599元)、4K显示器(3299元)。
    
    [用户 Q3] 积分最高的会员是谁?他买过东西吗?
    [Agent ] 生成 SQL:... member m left join orders o on o.member_name = m.name ...
    [回答  ] 积分最高的是 赵敏(25999 分),他有 4 笔订单。

    三个问题层层递进:Q1 单表聚合,Q2 带时间过滤,Q3 需要 memberorders 两表 JOIN——Agent 都正确地组织了 SQL。答案里的每一个数字(10556.00、5999、25999、4 笔)都来自金仓的真实返回,没有一处硬编码。

    这里必须对读者诚实:把自然语言翻译成 SQL 的"大脑"是 LLM。为了让这篇文章的结果可复现(你 clone 下来就能跑出一模一样的输出,不依赖我的 API Key),我把 Agent 对这三个问题的决策固化在了 nl_agent_demo.py 里;但 SQL 是真实发送、结果是金仓真实计算的。想体验"随便问、实时翻译"的完整交互,第七节给了接入 Claude Desktop 的配置——那才是它在真实工位上的样子。这条链路的技术底座(MCP Server + 金仓)已经 100% 就绪,缺的只是把哪个大模型接上去。

    五、真正的工程重点:把 AI 关进笼子里

    Demo 谁都能跑通,能不能上生产,全看安全。一个能连生产库、还听大模型指挥的服务,如果只做到"能查",那不是功能,是事故。我给它设计了三道互相独立、任意一道都能单独兜底的闸门:

    闸一:数据库账号本身只读。 MCP Server 连库用的 ai_ro 账号只被 GRANT SELECT。截图第一行——直接拿这个账号连库执行 DELETE,金仓在权限层就顶回来:ERROR: permission denied for table orders。这是最硬的一道,就算前面两道全被绕过,数据库自己也不会让 AI 写入。

    闸二:会话级只读。 代码里 conn.set_session(readonly=True),再加 SET statement_timeout='5s' 防止 AI 写出的慢查询拖垮库。

    闸三:应用层 SQL 白名单。 run_query 只放行单条 SELECT/WITH,且用正则扫描写操作关键字。截图里我让 Agent 发起五种花式攻击,全军覆没:

    攻击手法

    拦截结果

    delete from orders

    已拒绝:只允许 SELECT/WITH 查询

    drop table member

    已拒绝:只允许 SELECT/WITH 查询

    select 1; drop table member(多语句注入)

    已拒绝:只允许单条语句

    UpDaTe ...(大小写绕过)

    已拒绝:只允许 SELECT/WITH 查询

    select (delete ... returning 1)(子查询藏写)

    已拒绝:检测到写操作关键字

    正常 select count(*)

    放行,返回 20

    关键设计哲学:三道闸冗余但不多余。应用层白名单可能被更刁钻的 SQL 绕过,但会话只读会挡;会话只读万一失效,账号权限还在。安全不赌"我的正则天衣无缝",安全赌"攻破一层还有下一层"。这也是给评委和同行看的态度——AI + 数据库这件事,能力是及格线,可控才是分水岭

    六、可回溯:AI 到底查了什么,要留痕

    生产环境还有一个绕不开的问题:出了事,能不能查清"是谁、在什么时候、让 AI 对数据库做了什么"。我给每个工具加了一行审计日志:

    一次"问数会话 + 越权尝试"后,calls.log 完整记录了每一次调用——三条正常的业务查询 SQL,以及两条被拒的 drop table / delete被拦截的攻击也照样留痕,这正是安全审计最想要的:不仅记成功,更要记下"有人试图越权"。这只是个 40 行的雏形,生产上可以接入统一日志平台、加调用方身份、做异常告警,但"每一次 AI 触达数据库都可回溯"这个原则,从第一版就得立住。

    七、真实接入:三行配置,把它插进 Claude Desktop

    要在真实工位上用起来,不需要写一行胶水代码。任何 MCP 客户端(以 Claude Desktop 为例)只要在配置里加一段:

    {
      "mcpServers": {
        "kingbase": {
          "command": "ssh",
          "args": ["root@<服务器>", "python3", "/root/mcp/kingbase_mcp.py"]
        }
      }
    }

    重启客户端,"kingbase"就出现在工具列表里。之后对着聊天框问"这个月各产品卖了多少",大模型会自动调 list_tables 摸清有哪些表、describe_table 看清字段、run_query 执行查询,再把结果讲给你听——就是第四节那套流程,只不过 SQL 由在线大模型实时生成。MCP 的价值正在于此:Server 写一次,所有 AI 客户端通用。

    八、结论

    我用一百行 Python,给 KingbaseES 装上了一个"嘴替":业务方说中文,大模型经 MCP 把它翻译成 SQL 查金仓,再用中文回答。全过程截图为证——真实的 MCP 协议握手、真实的两表 JOIN 问数、五种越权全部拦截、每次调用留痕可回溯。

    但比"能跑通"更想强调的是那三道安全闸门。让 AI 连上生产数据库,从来不是技术能不能做到的问题,而是敢不敢让它做、以及出事能不能兜住的问题。 金仓作为国产数据库,ai_ro 只读账号在权限层的那记硬拦截,是这套方案敢称"可上生产"的地基。

    MCP 把 AI 与数据库的连接方式标准化了,而金仓完全站得进这个新生态。剩下的,就是把大模型接上去——那是最简单的一步。

    Logo

    Agent 垂直技术社区,欢迎活跃、内容共建。

    更多推荐