instrument_mcp
by Xingwen37
README.md
# 仪器 MCP
通过 MCP(Model Context Protocol)让 AI 控制两台台式仪器:RIGOL DHO/HDO 示波器与
SIGLENT SDG1000X 信号发生器。主入口是一个本地 stdio MCP server,AI 客户端
(Claude Desktop、Cursor 等)可直接调用;同时保留薄 CLI 便于人工排错和硬件验证。
## 测试环境
- 电脑通过 USB 分别连接示波器(RIGOL DHO/HDO)和信号发生器(SIGLENT SDG1000X)。
- 信号发生器 CH1 → **低通滤波器衰减网络** → 示波器 CH1,构成闭环验证回路。
- 三本编程手册(PDF)随仓库提供,SCPI 语法以手册为准。
## 架构
```
仪器MCP/
├── instruments/ 共享核心库(包)
│ ├── __init__.py 导出 Scope / Generator / SessionManager / InstrumentError
│ ├── base.py VISA 底层 + SessionManager(按厂商ID懒加载、缓存连接)
│ ├── validation.py 通道/枚举/数值范围校验 + InstrumentError
│ ├── scope.py Scope 类:RIGOL 方言(:CHANnel / :MEASure:ITEM? / :AUToset)
│ └── generator.py Generator 类:SIGLENT 方言(C{n}:BSWV / C{n}:OUTP)
├── server.py FastMCP stdio server(@mcp.tool() 包装 instruments/*)
├── scope_cli.py 示波器薄 CLI(替换原 scope_control.py)
├── generator_cli.py 信号发生器薄 CLI(替换原 generator_control.py)
├── scope_idn.py 连通性冒烟测试 CLI;VISA 底层迁入 instruments.base 后改为再导出
├── requirements.txt pyvisa>=1.14, mcp>=1.27,<2
├── README.md / AGENTS.md
└── *.pdf 三本仪器手册
```
两台仪器使用不同 SCPI 方言,切勿混用:
| | RIGOL DHO/HDO 示波器 | SIGLENT SDG1000X 信号发生器 |
|---|---|---|
| 脚本 | `scope_cli.py` | `generator_cli.py` |
| USB 厂商 ID | `0x1AB1` | `0xF4EC` |
| 自动选择 | `choose_scope_resource()` | `choose_generator_resource()` |
| 通道前缀 | `:CHANnel{n}`(前导冒号),通道 1–4 | `C{n}`(无前导冒号),通道 1–2 |
| 输出使能 | 不适用 | **需显式 `C{n}:OUTP ON`**,仅设置波形参数不会出信号 |
## 前置条件
1. 示波器与信号发生器后面板的 `USB DEVICE` 口分别连接到电脑。
2. 安装 RIGOL Ultra Sigma,或直接安装 NI-VISA(提供 VISA runtime)。
3. 重新插拔 USB 线,确认 Windows 不再把仪器设备显示为错误状态。
4. 安装 Python 依赖(Python 3.10+):
```powershell
python -m pip install -r requirements.txt
```
## 故障排查
检查 PyVISA 是否能找到已安装的 VISA runtime:
```powershell
pyvisa-info
```
若 `Backends > ivi > Binary library` 显示 `Not found`,说明 Python 依赖就绪但
NI/IVI VISA runtime 未安装。请安装 RIGOL Ultra Sigma,重新连接仪器,再运行
`python scope_idn.py --list`。
检查 Windows 是否识别到仪器:
```powershell
Get-PnpDevice -PresentOnly | Where-Object {
$_.FriendlyName -match 'RIGOL|DHO|HDO|SIGLENT|SDG|USBTMC|VISA|Instrument' -or
$_.InstanceId -match '1AB1|F4EC|RIGOL|SIGLENT'
} | Select-Object Status,Class,FriendlyName,InstanceId
```
## 作为 MCP server 接入 AI 客户端
`server.py` 默认以 stdio 传输启动:
```powershell
python server.py
```
### Claude Desktop
在 Claude Desktop 配置文件(`claude_desktop_config.json`)中添加:
```json
{
"mcpServers": {
"bench-instruments": {
"command": "D:\\anaconda3\\python.exe",
"args": ["D:\\project\\仪器MCP\\server.py"]
}
}
}
```
### Cursor
在 Cursor 的 `~/.cursor/mcp.json`(或项目级 `.cursor/mcp.json`)中:
```json
{
"mcpServers": {
"bench-instruments": {
"command": "D:\\anaconda3\\python.exe",
"args": ["D:\\project\\仪器MCP\\server.py"]
}
}
}
```
请按本机实际 Python 路径替换 `command`,并把 `args` 里的路径改成本机
`server.py` 的实际位置。路径分隔符在 JSON 里需转义为 `\\`(Windows)或用
正斜杠 `/`。
### Claude Code(CLI)
Claude Code 用命令行注册 stdio server:
```powershell
claude mcp add bench-instruments -- "D:\anaconda3\python.exe" "D:\project\仪器MCP\server.py"
```
注册后用 `claude mcp list` 确认。Claude Code 会以 stdio 方式拉起该进程,
工具自动出现在会话中。
### opencode / 其他 stdio MCP 客户端
任何遵循 MCP stdio transport 的客户端,原理一致:告诉客户端用哪个命令
启动 server 进程。以 opencode 的 `opencode.json` 为例:
```json
{
"mcp": {
"bench-instruments": {
"type": "local",
"command": ["D:\\anaconda3\\python.exe", "D:\\project\\仪器MCP\\server.py"]
}
}
}
```
若客户端按项目级配置识别,把文件放在工程根目录;若按全局识别,放在其
用户配置目录。具体字段名以该客户端文档为准,核心都是 `command` + `args`
(或等价的命令数组)指向 `python server.py`。
### 用 MCP Inspector 调试(不依赖 AI 客户端)
无需任何 AI 客户端即可验证工具契约:Inspector 会以 stdio 拉起 server,
列出 15 个工具并手动调用。
```powershell
uv run mcp dev server.py
```
然后在浏览器打开 Inspector 提示的地址,连接 → List Tools → 逐个 Call Tool
查看返回。这最适合在接入客户端前先排查工具签名/返回结构问题。
### 注意事项
- server 进程**首次调用某仪器时**才打开并缓存 USB 连接(懒加载),所以
第一个工具调用会比后续慢。AI 看到 `list_resources` 能列出两台仪器即说明
VISA runtime 正常。
- VISA runtime / USB 驱动缺失时,工具会返回 `InstrumentError`(把 setup
提示作为工具错误结果报告给 AI),而非让 server 进程崩溃。AI 应据此
提示用户安装驱动/重插线缆。
- server 是**长驻进程**,两台仪器连接跨工具调用复用;客户端关闭时
lifespan 自动 `close_all()`。
### 可用 MCP 工具
发现类:
- `list_resources` — 列出主机可见的 USB VISA 资源
- `idn(instrument)` — 查询 `*IDN?`,`instrument` 取 `scope` 或 `generator`
示波器类:
- `scope_status` — IDN、触发状态、各通道设置
- `scope_autoset` / `scope_run` / `scope_stop` / `scope_single` / `scope_force_trigger`
- `scope_channel_config(channel, display?, scale?, offset?, coupling?)`
- `scope_measure(channel, item)` — 单项测量,item 如 `VPP`/`FREQUENCY`/`VRMS` 等
- `scope_measure_all(channel)` — 默认项集 VPP/FREQUENCY/VRMS/VMAX/VMIN
信号发生器类:
- `generator_status` — IDN、两通道波形/输出状态、系统错误
- `generator_set_waveform(channel, wave?, freq?, amp?, offset?, phase?)` — 至少给一个参数;**不会自动开输出**
- `generator_output(channel, state)` — `state` 取 `on`/`off`,需显式开启才会出信号
- `generator_query_channel(channel)`
所有工具返回结构化 dict,便于 AI 解析。参数越界(通道号、枚举、频率/幅度范围)会在
发往硬件前被 `instruments.validation` 拦截并报错。
## CLI 排错用法
### 示波器 `scope_cli.py`
```powershell
python scope_cli.py list
python scope_cli.py idn
python scope_cli.py status
python scope_cli.py auto
python scope_cli.py run
python scope_cli.py stop
python scope_cli.py single
python scope_cli.py force
python scope_cli.py ch 1 status
python scope_cli.py ch 1 on
python scope_cli.py ch 1 --scale 0.5 --offset 0 --coupling DC
python scope_cli.py measure 1 VPP
python scope_cli.py measure-demo
```
### 信号发生器 `generator_cli.py`
```powershell
python generator_cli.py list
python generator_cli.py idn
python generator_cli.py status
python generator_cli.py ch 1
python generator_cli.py set 1 --wave SINE --freq 1000 --amp 2 --offset 0
python generator_cli.py output 1 on
python generator_cli.py output 1 off
```
### 连通性冒烟测试
```powershell
python scope_idn.py --list
python scope_idn.py
```
多仪器时可用 `-r` 显式指定资源字符串:
```powershell
python scope_cli.py -r "USB0::0x1AB1::0x0610::HDO4A264201814::INSTR" status
python generator_cli.py -r "USB0::0xF4EC::0x1103::SDG1XCAQ2R1765::INSTR" status
```
## 端到端验证回路
利用 generator CH1 → 低通滤波衰减网络 → scope CH1 的闭环,可让 AI 跑一个验证任务:
1. `generator_set_waveform(1, wave="SINE", freq=1000, amp=2)` 设 1kHz 正弦 2Vpp
2. `generator_output(1, "on")` 开启输出
3. `scope_autoset` 自动设置示波器
4. `scope_measure_all(1)` 测 CH1 的 VPP/FREQUENCY/VRMS/VMAX/VMIN
5. 比对测量值与设定值(受衰减网络影响,幅度应小于设定值),确认链路正常
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues