通过 MCP 查询数据库
本示例将 Grok API 通过自行运行的小型 MCP 服务器连接到 SQLite 数据库,让 Grok 能根据你自己的数据回答问题。你可以依此为 Agent 提供对其他数据库的只读访问,由服务器强制执行访问限制。
询问一家小咖啡店的销售情况,Grok 会查询你电脑上的 SQLite 数据库并作答。一个仅用 Node 编写的小型 MCP 服务器提供数据库访问,通过隧道取得公共 URL,供 SpaceXAI 连接。Grok 会列出表、读取表结构并执行 SQL。页面会先展示每条查询及返回的数据行,再展示答案。服务器以只读方式打开文件,无论模型发送什么,除了单条 SELECT 查询一律拒绝。
你将学到什么
编写基于 Streamable HTTP 的精简 MCP 服务器,使用
node:http和四个 JSON-RPC 方法通过 SDK 的
mcp()工具将其连接到 Grok,使用allowed_tools限制可调用的工具,并通过以下参数传入 Bearer token:authorization在服务端实施只读限制:使用只读连接,每次只允许一条 SELECT,限制返回行数,并在超时后终止慢查询
通过 Cloudflare 快速隧道为本机服务器提供公共 URL,因为 SpaceXAI 从其服务器连接 MCP 服务
通过 SDK 的流式事件展示每次 MCP 调用的开始和完成,并在旁边显示服务器实际收到的请求
运行示例
需要 Node.js 22.13 或更高版本,以及 cloudflared。它无需账号即可打开隧道(运行 brew install cloudflared 可在 Mac 上安装)。将 API 密钥写入 .env(位于仓库根目录),或导出环境变量 XAI_API_KEY。
cd examples/database-over-mcp/typescript
npm install
npm run web程序会构建示例数据库,在 3001 端口启动 MCP 服务器,并建立连接到它的隧道,整个过程约需五秒。然后打开 http://localhost:3000,点击示例问题或自行提问。页面类似 SQL 笔记本:Grok 的思考和对 MCP 服务器的每次调用会实时显示为单元格,其中包含实际运行的 SQL 和返回的数据行。之后显示答案、最后一次查询的结果图表和费用。右侧面板显示服务器视角,包括 SpaceXAI 通过隧道连接服务器的路径、Grok 可调用的工具和收到的每个请求。防护测试会让 Grok 发送服务器应拦截或限制的 SQL:UPDATE、一次查询含两条语句、原本需要数分钟但在 5 秒后被终止的查询,以及原本返回所有行但被截断为 100 行的查询。每个答案下方都会重新显示示例,方便连续尝试。点击 Stop 或关闭页面即可取消。
也可从终端传入问题,默认问题是“上个月哪些产品卖得最好?”
npm start -- "Who are our best customers?"终端会打印每次调用和返回的行,并将答案、所有查询及其结果保存到 output/<question>.md。
每个问题约需 10 秒,会向 MCP 服务器发起约五次调用,实测费用为 0.008 至 0.012 美元,约一美分。SpaceXAI 不收取远程 MCP 调用费,只收取 token 费用,其中包括 Grok 读取的工具结果。
应用运行时隧道保持开放,停止后会关闭。即使隧道开放,服务器也只响应带有当前正在生成的某个答案的 token 的请求。要换用 ngrok 等其他隧道,将隧道指向端口 3001,并将 MCP_PUBLIC_URL 设为隧道 URL。使用 PORT 和 MCP_PORT 可更改端口。
工作原理
src/make-store.ts 构建 store.db:模拟一家有 160 位客户、18 种产品的小咖啡店,含过去六个月的约 1,350 笔订单。固定的随机种子使每次启动都生成相同的商店数据,只将时间范围平移至今天结束,因此“上个月”始终有销售记录。40 天前有一款咖啡新品上市,因此上月的畅销品与平常不同。
src/mcp-server.ts 是 MCP 服务器,在 node:http 上自行实现 JSON-RPC 处理程序,未引入 MCP TypeScript SDK。只提供工具的服务器仅需四个方法:initialize、ping、tools/list 和 tools/call。用一个短文件处理它们,可直接看到协议传输的内容,也无需增加依赖。在 Streamable HTTP 中,所有消息都通过 POST 发往同一 URL,本例是 /mcp。请求得到 JSON 响应,通知得到状态码 202 和空响应体。该服务器不会主动发消息,因此 GET 返回 405。普通 JSON 响应也适用于不支持服务器发送事件的 Cloudflare 快速隧道。服务器提供三个工具:
list_tables列出数据表及每张表的行数。describe_table返回该表的CREATE TABLE语句和前三行数据。Grok 还会读取表结构中的注释,以了解订单状态的含义及价格以美分计量。run_query执行 Grok 编写的一条 SELECT 查询。
由于 SQL 由模型编写,服务器自行强制执行只读限制:
每次查询都在
src/query.ts中执行,它通过readOnly: true打开数据库,无论 SQL 要求怎样写入,SQLite 都会拒绝。只运行以 SELECT 或 WITH 开头的 SQL。只读连接本身仍允许使用临时表和 ATTACH,后者可以打开并读取本机上的其他 SQLite 文件。
prepare()会编译第一条语句并静默丢弃后续内容,因此服务器会拒绝第一条语句之后仍有内容的 SQL。最多返回 100 行。如果单行内容较长,返回行数还会减少,因为它们都会进入 Grok 的上下文。
每个查询在独立子进程中运行,超过 5 秒即由服务器终止。SQLite 无法从其他线程停止正在运行的查询,执行查询的工作线程也无法在查询结束前终止。
被拒绝或失败的查询以工具结果返回,其中带有 isError: true,这样 Grok 就能读取原因并修正 SQL。工具还带有 readOnlyHint 注解,但它只是给客户端的提示,实际防护不依赖此注解。
src/ask.ts 串起整个流程。openStore() 构建数据库、启动 MCP 服务器,并通过 src/tunnel.ts 开启隧道。后者会启动 cloudflared 并等待隧道主机名出现在公共 DNS 中,此前 SpaceXAI 无法连接。ask() 向 grok-4.7 发送流式请求,并提供一个工具:
mcp({
server_url: store.url,
server_label: "store",
server_description: "The coffee store's SQLite database, with customers, products, orders, and order items. Read-only.",
allowed_tools: ALLOWED_TOOLS,
authorization: token,
})SpaceXAI 会直接连接服务器并调用工具,无需自己编写工具调用循环。Grok 运行前,SpaceXAI 先打开一个 MCP 会话获取工具列表,再为 Grok 的实际调用开启另一个会话。流在调用开始时通过
response.output_item.added事件报告,调用完成时通过server_tool_call监听器提供参数和服务器响应。authorization在传到服务器时采用的格式为Authorization: Bearer <token>。每个答案都有独立的随机 token,Grok 一旦完成,服务器就会将它移除,因此答案返回后 token 随即失效。allowed_tools列出 Grok 可以调用的工具。向 Grok 展示的名称以服务器标签为前缀,例如store___run_query。即使服务器之后添加新工具,也要将其加入列表后 Grok 才能使用。示例采用较低的推理强度。每次工具调用后 Grok 都会再推理一次。默认强度给出的示例答案相同,但会额外执行查询来复核,耗时最长可达两倍。
费用取自 API 返回的
usage.cost_usd。
src/server.ts 在一个端口提供网页,在另一个端口提供 MCP 服务。隧道只暴露 MCP 服务器,避免公开网页而让任何人消耗你的 API 额度。服务器对每个问题调用 ask(),并以服务器发送事件向网页推送各步骤,同时推送 MCP 服务器收到的携带本次回答 token 的各次请求。它还将 AbortSignal 传给请求,因此关闭网页或点击 Stop 即可取消。public/index.html 使用原生 HTML 和 JavaScript,将这些事件显示为笔记本。src/index.ts 在终端打印相同的步骤。