内容
# bait_mcp
MCP 服务器,用于暴露 ophyd 设备,供 Bluesky/BITS 仪器使用。该服务器重新暴露了一个 **OAS** (Ophyd-as-a-Service)设备读写界面,作为两个 MCP 工具(`read_device`、`set_device`)供 LLM 代理使用。
OAS 被集成在此软件包中(`src/bait_mcp/ophyd_websocket/`),因此只需运行一个 `bait-mcp` 命令,就可以启动所有功能:它会启动 OAS,针对您的 BITS 软件包的 `startup.py`,然后启动工作线程和 MCP 前端。`bait_mcp` 是 **BITS 仪器的前端** —— 它需要一个已安装的 BITS 软件包,并且如果没有安装,则拒绝启动。没有任何依赖于 tomo-bait 或任何一个仪器;它适用于您安装和选择的任何 BITS 软件包。
设计上采用两个进程:一个 asyncio MCP 前端和一个同步 ZMQ 工作线程,负责所有 OAS WebSocket I/O。镜像布局与 `control_suite_mcp_aps_12id` 相同。
## 先决条件
`bait_mcp` 需要一个 **已安装、可导入的 BITS 软件包** 在您启动的环境中。它不提供自己的设备,也没有模拟/独立模式:它暴露的设备正是该软件包的 `startup.py` 加载到其 `oregistry`(BITS/Guarneri 设备注册表)中的设备。那些注册名称正是您传递给 `read_device` / `set_device` 的内容。
启动时,集成的 OAS 运行 `startup.py` —— 与队列服务器运行的同一个文件 —— 因此它需要的一切(EPICS 访问、目录配置)都适用。连接失败的设备仍然会加载;如果 `startup.py` 导入失败,则 `bait_mcp` 也会失败,按照设计。
**环境要求:** 集成的 OAS 在与 `bait-mcp` 相同的解释器下运行(`python -m bait_mcp.ophyd_websocket.server`)。启动器通过导入软件包名称来解析 `<package>/startup.py`,因此 BITS 软件包(及其依赖项,例如 `apsbits`)必须在那里可导入。这几乎总是意味着从具有您的仪器的 conda 环境中启动 —— 请参阅 [设置](#设置)。
## 设置
```bash
uv sync
```
因为 BITS 软件包必须在启动时可导入,所以请在已经具有您的仪器软件包(和 EPICS/ophyd 支持)的 conda 环境中安装 `bait_mcp`:
```bash
conda run -n <your_env> pip install -e .
```
复制示例配置并在运行前编辑:
```bash
cp configs/example.yaml configs/local.yaml
```
YAML 值首先加载;显式 CLI 标志覆盖 YAML 中传递给该进程的值。
## 指向您的仪器
设置 `bits.package` 为您的 BITS 软件包的 **可导入名称**。启动器从中解析 `<package>/startup.py` 并针对该文件启动 OAS,因此暴露的设备正是 `startup.py` 注册的设备。该软件包必须在启动环境中安装 —— 如果不可导入,则 `bait_mcp` 拒绝启动(没有回退):
```yaml
bits:
package: "my_beamline" # 可导入的 BITS 软件包名称(不是路径)
```
在不编辑 YAML 的情况下,在启动时覆盖:
```bash
uv run bait-mcp --config configs/local.yaml --bits-package my_beamline
```
## 启动(OAS + 工作线程 + 前端)
一个命令启动所有三个进程(OAS 始终从选定的 BITS 软件包启动):
```bash
uv run bait-mcp \
--config configs/local.yaml \
--bits-package my_beamline \
--worker-endpoint tcp://127.0.0.1:5556 \
--worker-startup-timeout-s 10 \
--request-timeout-ms 30000 \
--mcp-host 0.0.0.0 \
--mcp-port 8051 \
--mcp-path /mcp
```
使用这些默认值,MCP 端点是:
```text
http://127.0.0.1:8051/mcp
```
## 调试启动
仅工作线程:
```bash
uv run bait-mcp-worker \
--config configs/local.yaml \
--bind tcp://127.0.0.1:5556
```
仅 MCP 前端:
```bash
uv run bait-mcp-server \
--config configs/local.yaml \
--worker tcp://127.0.0.1:5556 \
--timeout-ms 30000 \
--host 0.0.0.0 \
--port 8051 \
--path /mcp
```
## 停止后台服务器
启动器 **自动清理启动时**:在启动之前,`bait-mcp` 终止任何预先存在的 `bait_mcp` 服务器(先前的启动器、MCP 前端、工作线程或启动的 OAS),因此孤立的进程无法占用端口 8051/8002。因此,正常的重启会自行清除残留进程。(注意:这意味着您不能一次运行两个仪器 —— 第二次启动会终止第一次启动。它们无论如何都会在相同的端口上冲突。)
要手动终止它们而不启动(使用相同的精确范围):
```bash
./scripts/kill_bait.sh
```
两者都限定在 `bait_mcp` 自己的进程中,不会触及无关工具,这些工具碰巧从 `bait_mcp/.venv` 运行(例如编辑器的语言服务器)。
## MCP 客户端配置
```json
{
"mcpServers": {
"bait_mcp": {
"url": "http://127.0.0.1:8051/mcp",
"transport": "http"
}
}
}
```
这是 `eaa_core.tool.mcp_client.MCPTool` 期望的形状 —— `bait_mcp` 可以开箱即用作为 EAA 工具。
## 暴露的工具
| 工具 | 用途 |
|---|---|
| `read_device(name, timeout=None)` | 订阅,获取第一个带有值的消息,关闭。成功时返回 `{ok, value, timestamp, connected, signal}`,失败时返回 `{ok: false, error}`。 |
| `set_device(name, value, timeout=None)` | 订阅,设置,等待“成功设置”确认,关闭。返回 `{ok, result}` 或 `{ok: false, error}`。立即执行 —— 没有 HITL 门控。 |
`name` 是 OAS 设备注册表已知的设备名称 —— 即您的 BITS 软件包的 `startup.py` 在 `oregistry` 下注册的名称。`value` 是数字或字符串;通过 WebSocket 协议不支持组件访问(对于这种情况,请使用 OAS REST `PUT /devices`)。
## 重要选项
- `worker.endpoint` —— MCP 前端和工作线程之间使用的 ZMQ 端点。
- `worker.request_timeout_ms` —— 前端对工作线程调用的请求超时。
- `launcher.worker_startup_timeout_s` —— 启动器在启动 MCP 之前的健康检查超时。
- `launcher.shutdown_timeout_s` —— 关闭时子进程被终止前的宽限期。
- `mcp.host`、`mcp.port`、`mcp.path` —— HTTP 传输设置。
- `oas.host`、`oas.port` —— 启动器绑定集成的 OAS 的位置(与 `oas.url` 保持同步)。
- `oas.url` —— 工作线程连接到的基准 URL(它附加 `/api/v1/device-socket`)。
- `oas.request_timeout_s` —— 默认的每次调用 OAS WebSocket 超时。
- `oas.workdir` —— **必需**,绝对路径。OAS 进程的工作目录;请参阅 [写入数据的位置](#写入数据的位置)。
- `bits.package` —— 可导入的 BITS 软件包名称;启动器加载 `<package>/startup.py`。使用 `--bits-package` 覆盖。
## 写入数据的位置
OAS 进程运行 BITS 软件包的 `startup.py`,它构建了一个 RunEngine。BITS 保留 RunEngine 元数据(`.re_md_dict.yml`),并在启用时保留日志和 NeXus/SPEC 数据文件到 `apsbits` 解析的 **相对工作目录** 的路径。为了将数据排除在 `bait_mcp` 仓库之外(以及您启动的位置),`oas.workdir` 是 **必需的**:启动器运行 OAS 进程,以该目录作为其 CWD,如果需要则创建它。它没有默认值 —— `bait_mcp` 在您设置它之前拒绝启动:
```yaml
oas:
workdir: "/abs/path/to/instrument/data"
```
指向您的仪器的數據区域(例如,在 bits 仓库内,或专用數據目录) —— 任何 `bait_mcp` 仓库以外的区域。
## 安全/HITL
MCP 服务器 **不** 实现写入的人机审批门控。`set_device` 是工具调用时实际写入的内容。如果您的代理在写入设备之前需要确认,请在客户端控制工具调用。