跳转至

自己启动 MCP 服务

在 Claude Code 和 Codex 里,Dosi 插件会替你启动 dosi 的 MCP 服务。 用别的 MCP 客户端、不想装插件,或者想让几个客户端共用一个服务时,就自己启动。 所有客户端的注册方式都一样,要么给一条启动命令,要么给一个 URL,所以这一页对所有 客户端都适用。每个工具的入参形状和错误契约见 MCP 参考。

安装 dosi

$ curl -fsSL https://dosi.datus.ai/install.sh | sh

这会把 dosi 和 dosi-server 装到 ~/.local/bin,示例模型装到 ~/.local/share/dosi/examples。在中国大陆,在 sh 前面加上 DOSI_SOURCE=oss。

没有数仓也能跟着做:把自带的 orders 示例灌进一个 DuckDB 文件。

$ export DOSI_EXAMPLES=~/.local/share/dosi/examples
$ duckdb orders.duckdb < $DOSI_EXAMPLES/orders/seed.sql

选传输方式

stdio(--mcp-stdio) Streamable HTTP(POST /mcp)
谁来启动 客户端拉起的子进程,每个会话一个 你自己,长驻进程
适合 单个本地客户端,零配置 共享服务、多客户端、CI、容器
模型 跟随文件:改动会自动加载,启动时模型可以还不存在 启动时编译一次
鉴权 进程边界 --auth-token,每个请求都校验
日志 走 stderr,stdout 只有协议 照常 stdout/stderr

两者提供相同的工具。除非需要共享服务,用 stdio 就行。

用 stdio

由客户端自己启动服务。把它注册到客户端的 MCP 配置里,多数客户端接受这种写法:

{
  "mcpServers": {
    "dosi": {
      "command": "dosi-server",
      "args": ["--mcp-stdio", "--model", "dosi/model.yaml"]
    }
  }
}

这份配置放在哪里、键名具体怎么写,因客户端而异,在它的文档里查“MCP servers”即可。 相对路径按客户端启动服务时所在的目录解析,通常就是项目根目录。若 dosi-server 不在客户端的 PATH 上,command 要写绝对路径;也绝不要指向 cargo run,在 MCP 客户端里,编译看起来就像卡死了。

常用参数:

参数 作用
--model <文件或目录> 模型。在 --mcp-stdio 下服务会跟随它:文件还不存在时也能启动,每次改动后重新加载,出错时返回 no_model 或 model_invalid,而不是退出
--db <文件> 不指定连接的查询用的 DuckDB 文件,比如上面灌好的 orders.duckdb
--connections <文件> 具名的数仓连接配置。不传时依次找 ./dosi-connections.yaml、~/.config/dosi/connections.yaml。见连接数仓
--env-file <文件> KEY=VALUE 格式,用来填连接配置里的 ${VAR}。文件改动后重新读取,值不会出现在任何输出里
--ontology、--mapping 本体和它的映射,同样跟随文件

示例对应的参数是 ["--mcp-stdio", "--model", "<examples>/orders/model.yaml", "--db", "orders.duckdb"], <examples> 要写成绝对路径。

用 HTTP

启动一个服务,让所有客户端都连它:

$ dosi-server --model $DOSI_EXAMPLES/orders/model.yaml --db orders.duckdb
INFO dosi_server::bootstrap: model compiled model=.../orders/model.yaml mode="datus" datasets=3 metrics=5 generation=1
INFO dosi_server: listening on http://127.0.0.1:8081

MCP 挂在顶层的 POST /mcp,不在 /v1 下面。要换监听地址传 --bind;别人能访问到 的服务都要加 --auth-token(环境变量 DOSI_SERVER_TOKEN)。然后注册这个 URL:

{
  "mcpServers": {
    "dosi": {
      "type": "http",
      "url": "http://127.0.0.1:8081/mcp",
      "headers": { "Authorization": "Bearer ${DOSI_SERVER_TOKEN}" }
    }
  }
}

模型在启动时编译一次,模型不合法时服务在开始监听之前就退出。改了模型要重启服务, 或者加上 --lazy-model,让它像 stdio 那样跟随文件。

验证一下

不经过 Agent,用 MCP Inspector 就能列出工具并调用:

$ npx @modelcontextprotocol/inspector dosi-server --mcp-stdio \
    --model $DOSI_EXAMPLES/orders/model.yaml --db orders.duckdb

先调 list_metrics,再用 {"metrics": ["revenue"], "group_by": [{"field": "orders.status"}]} 调 run_query: 在示例数据上,已完成订单是 350,已取消的是 100。在客户端里,这些工具的名字一般是 mcp__dosi__<工具名> 这样的形式。

决定 Agent 能做什么

所有工具都标为只读(readOnlyHint),所以会自动放行只读工具的客户端(比如 Codex 的 exec 模式)可以无人值守地调用它们。dosi 只生成 SELECT 语句,但过滤条件和参数值 由数仓计算,dosi 不检查其中调用的函数有没有副作用。run_query、select 和 attribute_metric 会访问数仓:请给连接配一个只读的数据库账号。

另外还有两个开关,可以叠加:

  • 服务端:--disable-execute。会访问数仓的工具都返回 forbidden 错误,发现和 编译仍然可用。共享的只读部署就该这样。
  • 客户端:工具白名单。评审场景只放行 compile_sql 和 explain_query、不放行 run_query;分析场景全放开。“只用 Dosi”的 Agent,就是放行 dosi 服务的工具, 再禁掉 shell。

让 Agent 优先走指标

光有工具,拦不住一个同时还有 shell 权限的 Agent 自己去写 SQL。下面这几条规则可以。 把它放进 AGENTS.md、CLAUDE.md,或你的客户端对应的系统提示词里:

## Data questions

Answer data questions through the `dosi` MCP tools, never by writing SQL
against raw tables.

1. Discover: `list_metrics`, then `list_dimensions` for the breakdown fields
   (`dataset.field`, plus the reserved `metric_time`). Use `describe_metric`
   when a metric's meaning matters.
2. Preview: `compile_sql` (or `explain_query`) before executing, and show me
   the SQL you are about to run.
3. Execute: `run_query`. Omit `connection` for the server's local database, or
   pick one from `list_connections`.

Rules:
- Never invent a metric or dimension name. If a name is rejected, read the
  error's `candidates` / `suggested_retry` and retry with a valid one.
- Never re-derive a metric by hand — no ad-hoc `SUM`/`COUNT DISTINCT`, no
  hand-written joins or filters. The metric definition is the governed answer,
  and a hand-rolled equivalent will drift.
- If no metric covers the question, say so instead of approximating.
- Report the number the tool returned, plus the metric name and any filter
  applied, so the answer is auditable.
- `run_query` caps rows (`row_limit_applied`). Never present a capped page as
  a complete result — aggregate in the query instead.

这段文字与我们自己评测时用的系统提示词是同一个文件,所以文档写的行为和实测的行为 不会各走各的。

排错

现象 原因 处理
run_query 报表不存在 没传 --db 也没指定连接,本地 DuckDB 是空的内存库 传一个灌好数据的 --db 文件,或者指定连接
每个模型工具都返回 no_model --model 指向的文件还不存在(stdio) 写出模型,下一次调用就能用
客户端显示服务连接失败 路径写成了 /v1/mcp、服务没起、cargo run 还在编译,或者模型不合法(HTTP) MCP 在顶层的 POST /mcp;用安装好的二进制;运行 dosi validate
400 且响应体为空 缺 Host 头(DNS 重绑定防护) 代理或手搓请求时不要剥掉 Host
每次调用都 401 设了 --auth-token,客户端没带 bearer 头 补上;鉴权是每请求的,不是每会话
config 错误:Conflicting lock is held 别的进程以读写方式开着这个 DuckDB 文件(dosi 自己只读打开,多个服务可以共用一个文件) 关掉那个写入的进程,或者让它用自己的副本
list_connections 显示 missing_env 连接配置里有 ${VAR} 没设置 在环境变量或 --env-file 里设置
run_query 返回 forbidden --disable-execute 改用 compile_sql,或起一个不带该参数的服务
busy / timeout 执行信号量占满 / 触发执行超时 调大 --max-concurrent-executions、--execute-timeout-secs,或把查询写轻
validate_model 在大模型上失败 /mcp 请求体上限 1 MiB 大模型改用 dosi validate 校验
结果看起来被截断 默认 LIMIT 500,row_limit 上限 5000 在查询里做聚合;读 row_limit_applied
Agent 编造指标名 没有配提示词规则 加上前面那段规则

下一步

  • MCP 参考


    每个工具、入参形状、协议,以及错误契约。

  • 连接数仓


    从示例 DuckDB 文件换到 Postgres、Snowflake、StarRocks 等。

  • Dosi 插件


    服务、skill 和口令保护一次装好,适用于 Claude Code、Codex 和 DeepSeek Harness。