SpacetimeDB MCP 指南:通过 Model Context Protocol 以工具调用方式操作运行中的数据库
【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB
SpacetimeDB 主机本身支持 MCP(Model Context Protocol)协议,MCP 感知的客户端(AI Agent、编辑器等)可以直接通过工具调用(tool call)操作一个正在运行的数据库,而无需在 Shell 中敲击命令。本文以仓库内的 MCP Skill 文档(codex-plugin/plugins/spacetimedb/skills/mcp/SKILL.md)为骨架,结合 CLI 与 HTTP 层的源码实现,完整讲解 MCP 工具的形态、host-wide 与 database-scoped 两种服务器模式、权限规则、常见错误排查,以及它们与spacetimeCLI 的分工边界。读完本文,你将掌握如何用spacetimedb.list_databases、spacetimedb.get_schema、spacetimedb.sql、spacetimedb.call、spacetimedb.ping五个工具安全地检查与变更线上数据。
概览:主机即 MCP 服务器
SpacetimeDB 的主机(host)直接说 MCP 协议,因此一个支持 MCP 的客户端可以用工具调用来操作一个正在运行的数据库,而不是使用 Shell 命令。如果客户端暴露了spacetimedb工具,那么凡是"读取或修改一个运行中数据库"的任务,都应当优先使用这些工具。
一个关键事实是:这些工具是"被动"注册的——它们以spacetimedb.list_databases、spacetimedb.get_schema、spacetimedb.sql、spacetimedb.call和spacetimedb.ping的形式出现在客户端的工具列表中,但没有任何主动的提示或广播来宣告它们的存在。因此在使用前,务必先查看客户端暴露的工具列表(tools/list),确认工具确实存在,而不是想当然地认为它们缺失。
从源码看,这一能力在服务端有两个落地位置:
- HTTP 路由层:
crates/client-api/src/routes/mcp.rs实现了完整的 MCP JSON-RPC 处理逻辑(initialize、ping、tools/list、tools/call四个方法),并分别挂载在主机级路由POST /v1/mcp(见 crates/client-api/src/routes/mod.rs)和数据库级路由POST /database/:name_or_identity/mcp(见 crates/client-api/src/routes/database.rs)上; - CLI 桥接层:
spacetime mcp命令(crates/cli/src/subcommands/mcp.rs)在 stdio 上循环读取 MCP 客户端发出的 JSON-RPC 行,转发给主机,并把主机的 JSON 响应逐行写回 stdout,从而把"任意支持 stdio MCP 的客户端"接驳到 SpacetimeDB 主机。
何时使用 MCP 工具,何时使用 CLI
MCP 工具与spacetimeCLI 各司其职,二者是互补关系而非替代关系:
| 任务 | 使用 |
|---|---|
| 列出数据库、读取 schema、运行 SQL、调用 reducer | MCP 工具 |
| init、build、publish、generate、start、logs | spacetimeCLI(参见 codex-plugin/plugins/spacetimedb/skills/cli/SKILL.md) |
MCP 工具只能操作一个已经存在的数据库:它们无法搭建项目骨架、编译模块、发布(publish)或生成绑定(bindings)。所以两者的边界非常清晰——脚手架与生命周期管理交给 CLI,运行时数据检查与变更交给 MCP 工具。
在两者都可用的场景下,优先使用 MCP 工具,原因有三:
- 类型化:每个工具都有明确的参数 schema(
inputSchema); - 返回 JSON:结果结构化,便于客户端解析与后续处理;
- 可门控:客户端可以根据工具的
annotations(如readOnlyHint、destructiveHint)对破坏性工具进行权限门控,降低误操作风险。
当然,如果没有连接任何 MCP 客户端,那么使用等价的 CLI 命令(spacetime list、spacetime describe、spacetime sql、spacetime call等)是完全正确的选择。
五个工具的完整参考
无论服务器处于哪种模式,核心工具集都是同一套。下表汇总了工具、参数与返回内容:
| 工具 | 参数 | 返回 |
|---|---|---|
list_databases | 无 | 你自己拥有的数据库,包含 identity 与名称 |
get_schema | database | 表与 reducer 的 JSON 描述 |
sql | database、sql、可选confirmed | 以 JSON 返回的行 |
call | database、reducer、可选args(JSON 数组) | reducer 的执行结果 |
ping | 可选message | 健康检查 |
在服务端源码 crates/client-api/src/routes/mcp.rs 的tools_list函数中可以看到每个工具携带的annotations:
list_databases、get_schema标记为readOnlyHint: true、destructiveHint: false(只读);sql与call标记为readOnlyHint: false、destructiveHint: true(可能变更数据,客户端应谨慎对待);- 所有工具均设置
openWorldHint: false。
list_databases只列出你自己拥有的数据库,因此对于匿名身份(anonymous identity)来说,它返回的结果为空。当你不知道数据库名称时,从这里开始排查是最稳妥的第一步——先用list_databases {}拿到身份与名称,再用名称去查询 schema 或执行 SQL。
两种服务器形态:先读tools/list,再决定参数
一个 MCP 服务器要么是host-wide(主机级),要么是scoped(绑定到单个数据库)。不要假设是哪一种,先读取工具列表来确认。源码 crates/client-api/src/routes/mcp.rs 中的host_wide = scope.is_none()正是这一区分的核心逻辑,它同时决定了tools_list与target_database的行为。
Host-wide(主机级)形态
通过spacetime mcp(不带数据库参数)启动,或直接对POST /v1/mcp发起请求。此时每个数据工具都必须携带一个必填的database参数,其值可以是数据库名称或 identity,并且list_databases会被提供:
{ "name": "sql", "arguments": { "database": "mydb", "sql": "SELECT * FROM message" } }从源码的target_database逻辑(crates/client-api/src/routes/mcp.rs)可以看到,host-wide 模式下如果database参数缺失,会返回INVALID_PARAMS级别的协议错误database argument must be a string;如果提供了参数,则既可以是名称(NameOrIdentity::Name),也可以是 64 位十六进制的 identity(NameOrIdentity::Identity)。
Scoped(数据库级)形态
通过spacetime mcp <database>启动,或对POST /v1/database/<db>/mcp发起请求。此时连接已经固定了目标数据库,因此没有database参数,也没有list_databases工具:
{ "name": "sql", "arguments": { "sql": "SELECT * FROM message" } }即使工具调用中多传了database参数,target_database也会优先采用 URL 中已经解析好的数据库(Target::Resolved),忽略参数中的值——源码中的target_database_prefers_the_url_scope_then_the_argument测试用例明确验证了这一行为。
如何启动这两种形态
CLI 参考(docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md)与 MCP 参考文档(docs/docs/00300-resources/00200-reference/00150-mcp.md)给出了标准启动方式:
spacetime mcp --server local # host-wide,每个工具需带 database 参数 spacetime mcp my-database --server local # scoped,固定到 my-database- 省略数据库参数 → host-wide 模式;
- 传入数据库名称或 identity → scoped 模式;
- 数据库参数也可以来自
SPACETIMEDB_DB_NAME环境变量(见 crates/cli/src/subcommands/mcp.rs 中Arg::new("database").env("SPACETIMEDB_DB_NAME")); - 命令使用你已保存的 SpacetimeDB 身份,除非传入
--anonymous; --server参数接受服务器昵称、主机名或 URL。
CLI 桥接的实现细节(crates/cli/src/subcommands/mcp.rs)是:在 stdio 上逐行读取 JSON-RPC,通过POST转发到主机的mcp端点;若收到 HTTP 202 状态码(表示这是一个无 id 的 notification,无需应答)则继续;否则把 JSON 响应写回 stdout。对应的 HTTP 端点说明如下:
POST /v1/mcp # host-wide POST /v1/database/<name-or-identity>/mcp # database-scopedHTTP 方式使用与其他 SpacetimeDB HTTP API 相同的 bearer token 进行鉴权;也可以省略授权头,此时使用匿名身份。需要注意,spacetime mcp目前标记为UNSTABLE(命令行工具会打印WARNING: This command is UNSTABLE and subject to breaking changes.,见 crates/cli/src/util.rs 的UNSTABLE_WARNING常量),可能尚未出现在已发布的 CLI 中。因此,如果客户端无法启动它,就回退到cliskill 中的等价 CLI 命令。
不变的四条规则
无论使用哪种形态,MCP 工具都以你的身份运行,与 HTTP API 的行为完全一致。权限与数据模型遵循 codex-plugin/plugins/spacetimedb/skills/concepts/SKILL.md 中描述的 SpacetimeDB 核心概念,具体到 MCP 场景有四点需要始终牢记:
Reducer 是写路径。用
call来变更数据。reducer 在一个事务中运行,要么整体提交(Committed),要么整体回滚(Failed/BudgetExceeded)。源码 crates/client-api/src/routes/mcp.rs 的tool_call_reducer会以你的 identity 建立连接上下文、调用 reducer、再断开连接,最终把结果翻译为可读文本(如reducer 'send_message' committed),失败时则返回带错误文本的 HTTP 错误。SQL 写入需要所有权。
sql工具可以读取公开表(public tables);但通过 SQL 写入需要你拥有该数据库。通常应优先使用call——把授权与校验逻辑保留在模块的 reducer 中,客户端无需直接面对写 SQL。私有表对客户端不可读。
get_schema仍然会显示私有表(private tables)的声明,因此当sql报出no such table时,通常意味着该表是私有表,而不是表不存在。可读性取决于你的身份,不要假设某个私有表一定可读。工具错误是带内返回的。一个失败的 reducer 或一条错误的查询,返回的是一个带
isError: true的结果,错误消息以文本形式出现在content里,而不是传输层失败。重试之前,务必先读取错误文本。源码中execution_error_to_tool_result(crates/client-api/src/routes/mcp.rs)正是把所有执行期错误转换为这种带内工具结果,其测试用例failed_reducer_surfaces_in_band_with_message与execution_errors_become_in_band_tool_results都验证了isError: true与错误文本的传递。
实战:检查一个数据库的完整流程
下面是针对一个名为mydb的数据库从"发现"到"查询"再到"写入"的完整工具调用序列(host-wide 模式):
list_databases {} get_schema { "database": "mydb" } sql { "database": "mydb", "sql": "SELECT * FROM message" } call { "database": "mydb", "reducer": "send_message", "args": ["hello"] }几点实操细节:
get_schema返回的 JSON 包含类型的 typespace、所有表(含私有表的声明)以及 reducer 列表。服务端实现(tool_get_schema)会等待数据库 leader 的模块就绪(超时上限 10 秒,见源码常量MODULE_WAIT_TIMEOUT),再把模块定义序列化为 JSON。拿到 schema 后,可以据此确定要查询的表名、可调用的 reducer 名及其参数顺序。sql的confirmed参数:传入"confirmed": true可以让读取等待持久化确认(durably confirmed)后再返回,适用于对一致性要求较高的读取场景(服务端SqlQueryParams { confirmed }会透传给sql_direct)。call的args是位置参数:按 reducer 声明的参数顺序传入 JSON 数组。无参时省略args或传空数组[]均可——源码reducer_args_json明确将None、null都规范化为[],而args必须是 JSON 数组,传字符串或对象会被拒绝(args must be a JSON array)。- scoped 模式下所有调用去掉
database参数、list_databases不可用,其余完全一致。
常见错误排查速查表
| 消息 | 含义 |
|---|---|
database argument must be a string | 服务器是 host-wide 形态,而你省略了database参数(或传了非字符串值) |
unknown tool: list_databases | 服务器已 scoped 到某一个数据库,list_databases不可用 |
`x` not found | 此服务器上不存在名为x的数据库,或调用时把名称用在了需要 identity 的位置 |
no such table: x | 表是私有表(当前身份不可读),或你查询了错误的数据库 |
完全没有spacetimedb工具 | 没有连接 MCP 服务器。此时应改用 CLI 命令(参见cliskill) |
注意第一个错误在服务端表现为 JSON-RPC 协议错误(INVALID_PARAMS,错误码 -32602),而非带内工具错误——因为它发生在参数解析阶段;其余运行期错误则统一走带内isError: true返回。这点区别可以在排查时帮你快速定位问题出在"参数形态"还是"数据库/表"层面。
与官方参考文档的关系
本文对应的完整官方参考见 docs/docs/00300-resources/00200-reference/00150-mcp.md,其中包含相同的工具表、权限说明与错误对照,并额外给出了 HTTP 端点的鉴权细节(bearer token 或匿名身份)。CLI 命令的详细参数(spacetime mcp [OPTIONS] [database]、--server、--anonymous、SPACETIMEDB_DB_NAME)可在 docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md 中查阅。如果要在自己的 Agent 工作流中复用本文的能力,可直接参考本文开篇引用的 Skill 文件 codex-plugin/plugins/spacetimedb/skills/mcp/SKILL.md。
需要再次强调的是:MCP 支持目前仍处于 unstable 阶段,接口可能随版本变化;在实际环境中操作前,先通过tools/list确认工具形态,再按本文的规则发起调用,是稳妥的做法。
【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考