Skip to main content
Glama
zeng-andrew

Instrument MCP Server

by zeng-andrew
README.md
# Instrument MCP Server

A Model Context Protocol (MCP) server for controlling test instruments via SCPI, Modbus-RTU, or direct USB.

Supports Keysight MXA/EXA spectrum analyzers, R&S CMW500 wireless testers, Keysight DC power supplies, Modbus-RTU chambers, and the DreamSourceLab DSLogic U3Pro16 USB logic analyzer.

## Features

- **Natural language instrument control** - Control instruments through conversational AI
- **Auto-discovery** - Automatically identifies instrument models via *IDN?
- **YAML-driven commands** - Easy to add new instruments without code changes
- **Multi-instrument sessions** - Connect and control multiple instruments simultaneously
- **Error handling** - Automatic error queue reading and diagnostics
- **AI self-learning** - AI can explore, learn, and save new instrument commands
- **USB logic analyzer support** - Direct USB control of DSLogic U3Pro16 (no VISA required)

## Prerequisites

Before installing, ensure you have:

- **Python 3.10+**
- **VISA Backend** (one of the following):
  - [NI-VISA](https://www.ni.com/en-us/support/downloads/drivers/download.ni-visa.html) (Windows/Linux)
  - [Keysight IO Libraries Suite](https://www.keysight.com/find/iosuite) (Windows)
  - [pyvisa-py](https://pyvisa-py.readthedocs.io/) (pure Python, limited support)

> **Notes**:
> - Without a VISA backend, the package cannot communicate with VISA instruments.
> - The DSLogic U3Pro16 uses USB directly via `pyusb` + `libusb-1.0.dll` and does not require VISA.

## Supported Instruments

| Instrument | Type | Connection |
|-----------|------|-----------|
| Keysight MXA N9020A / N9010A / EXA | Spectrum Analyzer | VISA (TCP/IP/GPIB/USB) |
| R&S CMW500 | Wireless Communication Tester | VISA (TCP/IP) |
| Keysight 66311B / 66311 | DC Power Supply | VISA (GPIB) |
| DreamSourceLab DSLogic U3Pro16 | USB Logic Analyzer | USB (pyusb + libusb-1.0) |
| Generic SCPI instruments | Any | VISA |
| Modbus-RTU 恒温恒湿试验箱 | Chamber | Serial (RS-232C) |
| 米家智能插座2 (chuangmi.plug.212a01) | Smart Plug | LAN (miIO/MIoT, UDP + token) |

## 米家智能插座2(mi_plug)

通过局域网 miIO/MIoT 协议控制(不经小米云端)。IP/token 默认从 cwd 下
`mi_plug_config.json` 读取(模板 `mi_plug_config.example.json`,token 提取见
`docs/miplug_chuangmi_212a01.md`),也可用 `connect` 的 `token` 参数覆盖。

### Connection

```
connect(address="10.1.200.146", instrument_type="mi_plug", alias="plug")
```

### 命令

| 命令 | 功能 |
|------|------|
| `miplug_info` | miIO 握手 + 设备信息(验证 token) |
| `miplug_status` | 读取全部关键属性(开关/温度/功率/电压/电流/循环配置等) |
| `miplug_on` / `miplug_off` / `miplug_toggle` | 开关控制(写后回读校验) |
| `miplug_loop_start` / `miplug_loop_stop` / `miplug_loop_info` | 设备端循环定时(siid=4) |
| `miplug_get_property` / `miplug_set_property` | 按 MIoT spec 读写任意 siid/piid 属性 |
| `miplug_token_qr_start` / `miplug_token_qr_finish` | 扫码登录小米云端提取设备 token(最简单稳定的方式,无需连接插座;默认自动写入 `mi_plug_config.json`) |

## Keysight 66300 系列直流电源

支持 66311B/D、66321B/D、66319B/D 等 66300 家族移动通信直流电源(共用同一套 SCPI 命令)。

### Connection

```
connect(address="GPIB0::5::INSTR", instrument_type="keysight_ps", alias="ps")
```

### 诊断命令

| 命令 | 功能 |
|------|------|
| `ps_health_check` | 一键只读健康检查:自检 + 错误队列 + 状态寄存器解码 + 保护配置 + 测量有效性判定,返回 `verdict`(USABLE / WARNING / NEEDS_SERVICE) |
| `ps_clear_protection` | 清除输出保护锁存(`*CLS` + `OUTP:PROT:CLE`),可选关闭 OVP,读回 QUES 确认是否清除成功 |

`ps_health_check` 的 `verdict` 判定:
- **NEEDS_SERVICE** — `*TST?` 非零或错误队列含自检错误码(NVRAM 校验和失败等),按手册第 41 页需返修
- **WARNING** — 保护位置位或测量返回无效占位符 `9.91e+37`
- **USABLE** — 一切正常

Questionable 状态位映射依据手册 Table 8-5:bit0=OV, bit1=OCP, bit4=OT(过温), bit5=SD/OS(传感线开路), bit9=RI, bit10=UNR, bit14=MeasOvld。

### 66319D 双通道与耦合(Coupling)

66319D 具有主输出(output 1)与辅助输出(output 2)两路输出,通道命令用后缀 2 区分:
`OUTP/OUTP2`、`VOLT/VOLT2`、`CURR/CURR2`、`MEAS:VOLT?/MEAS:VOLT2?`。
注意:**不支持 `INST:NSEL` 通道选择**(报 -113 Undefined header)。

两路能否独立开关取决于输出耦合状态(`INST:COUP:OUTP:STAT`):
- `ALL`(出厂默认):任意 OUTP 命令同时开关两路——即"关 CH1 连累 CH2"的根因
- `NONE`:`OUTP1`/`OUTP2` 可独立控制;未耦合时裸 `OUTP` 只作用于主输出

耦合设置是易失的(`OUTP:PON:STAT?` 返回 RST 时断电重启恢复默认 ALL),持久化需三步:
`INST:COUP:OUTP:STAT NONE` → `*SAV 0` → `OUTP:PON:STAT RCL0`(有覆盖寄存器/上电带电输出
两个副作用需确认)。完整流程、命令速查与本机实测记录见
`docs/agilent_66319d_coupling_off.md`,实验脚本 `tests/ps_66319d_dual_channel_test.py`。

## Modbus 温箱(恒温恒湿试验箱)

Modbus-RTU 恒温恒湿试验箱,支持定值运行与程式(程序)控制。协议:9600 8N1, CRC-16/MODBUS, 站地址 1。

### Connection

```
connect(address="COM30", instrument_type="modbus_chamber", alias="chamber")
```

### 定值模式命令

| 命令 | 功能 |
|------|------|
| `mc_read_pv` | 读取当前温度 PV |
| `mc_read_sv` / `mc_set_sv` | 读取/设置设定温度 SV |
| `mc_read_status` | 读取完整状态(温度/湿度/输出/运行标志) |
| `mc_read_humidity` / `mc_read_humidity_sv` | 读取湿度 PV/SV |
| `mc_set_humidity_sv` | 设置目标湿度 SV |
| `mc_run` / `mc_stop` | 启动/停止定值运行 |

### 程式控制命令

| 命令 | 功能 |
|------|------|
| `mc_prog_read_table` | 读取任意程式的完整段表(温度/湿度/时间/循环/TS1-4) |
| `mc_prog_read_seg` | 读取指定程式的某一段 |
| `mc_prog_write_seg` | 修改已有段的设定(温度/湿度/时间/TS) |
| `mc_prog_set_cycle` | 设置某段的循环次数 |
| `mc_prog_status` | 读取程式运行状态(程式号/段号/剩余时间) |
| `mc_prog_run` | 启动程式运行 |
| `mc_prog_clear` | 清空段表 |

**程式启动方法**:先写 `0x0064` 选定程式号,再写 `0x0065=1` 启动(与定值运行同值,区别在选程式号)。

**硬件限制**(COM30 实测):段数寄存器(reg41)只读,无法通过 Modbus 新增段或新建程式。新增段/新建程式须在面板操作——推荐在面板上把已有程式**复制**到目标号,再用 `mc_prog_write_seg` / `mc_prog_set_cycle` 远程修改各段参数。

## DSLogic U3Pro16 Logic Analyzer

The DSLogic U3Pro16 is supported as a first-class MCP instrument.

### Prerequisites

- Install a 64-bit `libusb-1.0.dll` and ensure it is discoverable:
  - Placed next to the package (`src/instrument_mcp/libusb-1.0.dll`), or
  - In the current working directory, or
  - In a directory in your `PATH`.
- Windows uses the WinUSB driver for DSLogic (installed by DSView). Close DSView before connecting from MCP.

### Connection

```
connect(address="USB", instrument_type="dslogic", alias="la")
```

### Commands

After connecting:

```
dslogic_get_status(alias="la")
dslogic_set_threshold(alias="la", vth=1.65)
dslogic_capture(alias="la", samplerate_mhz=100, samples=1024, output_path="capture.bin")
dslogic_monitor_level(alias="la", samplerate_mhz=1, duration=30, channel=-1, edge_type="both", debounce_us=0)
```

`dslogic_monitor_level` runs in stream mode on the DSLogic U3Pro16 and reports edge transitions (rising, falling, or both) with optional debouncing. It prints the initial/final state of all 16 channels and a list of detected edge events. This command is specific to the DSLogic U3Pro16 and will not work with other logic analyzers.

### Data Format

- **Stream mode** (`dslogic_monitor_level`): Uses the DSLogic `LA_CROSS_DATA` format. Data arrives as 8-byte blocks per channel, where each block contains 64 consecutive samples for that channel. Channels are interleaved in the order CH0, CH1, ..., CH15, then repeated.
- **Buffer mode** (`dslogic_capture`): Capture files are raw data from the device. With 16 channels enabled in buffer mode, each sample is typically a 2-byte little-endian word where bit `n` represents channel `n` (`CH0` = LSB).

## Installation

### Using uv (recommended)

```bash
# 从 PyPI 安装
uv tool install instrument-mcp

# 或从本地源码安装
uv tool install .

# 或开发模式安装
uv tool install --editable .
```

### Using pip

```bash
pip install instrument-mcp
```

### From source

```bash
git clone https://github.com/yourusername/instrument-mcp.git
cd instrument-mcp
pip install -e .
```

## Usage

### As a standalone MCP Server

```bash
# 启动服务器(stdio 模式,用于 MCP 客户端)
instrument-mcp

# 或使用 Python 模块
python -m instrument_mcp.server

# 使用 uv 运行(无需全局安装)
uv run instrument-mcp
```

### Configure in Claude Desktop

Add to your Claude Desktop config (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "instrument": {
      "command": "instrument-mcp"
    }
  }
}
```

Or with uv (if not globally installed):

```json
{
  "mcpServers": {
    "instrument": {
      "command": "uv",
      "args": ["run", "instrument-mcp"]
    }
  }
}
```

Or with full path:

```json
{
  "mcpServers": {
    "instrument": {
      "command": "python",
      "args": ["-m", "instrument_mcp.server"]
    }
  }
}
```

### Using with Claude Code

```bash
# Add to your project
claude mcp add instrument instrument-mcp
```

## Example Commands

Once connected, you can use natural language:

```
"Connect to the MXA at 192.168.1.100"
"Set center frequency to 2.4 GHz with 100 MHz span"
"Run a peak search and read the marker"
"Configure harmonic measurement with 7 harmonics"
"Fetch the harmonic amplitude values"
```

Or with the CMW500:

```
"Connect to CMW500 at 172.22.1.3"
"Preset the instrument"
"Set LTE Band 7, 20MHz bandwidth"
"Turn on the cell"
"Check UE connection status"
```

## Development

```bash
# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest

# Lint
ruff check .

# Debug with MCP inspector
mcp dev src/instrument_mcp/server.py
```

## Project Structure

```
instrument-mcp/
├── src/instrument_mcp/
│   ├── server.py            # MCP Server entry point
│   ├── instruments.py       # Instrument abstraction layer (VISA, Modbus, USB)
│   ├── dslogic_usb.py       # Low-level DSLogic U3Pro16 USB controller
│   ├── commands/            # YAML command definitions + Python handlers
│   │   ├── __init__.py      # YAML command loader
│   │   ├── keysight_ps.yaml # Power supply commands (incl. diagnostics)
│   │   ├── keysight_ps_handler.py
│   │   └── ...
│   └── diagnostics/         # 诊断与健康检查模块
│       └── keysight_ps_diag.py  # 66300 系列电源健康检查 + 保护清除
├── pyproject.toml
└── README.md
```

## Adding New Instruments

1. Create a new YAML file in `src/instrument_mcp/commands/`
2. Define `instrument_type`, `model_keywords`, and `commands`
3. Restart the server - commands are loaded automatically

Example YAML structure:

```yaml
instrument_type: my_instrument
description: My Test Instrument
model_keywords:
  - "MYMODEL"
  - "MYBRAND"
commands:
  - name: my_command
    description: Does something useful
    params:
      - name: param1
        type: string
        description: A parameter
        default: "default_value"
    scpi_template:
      query: "MY:CMD? {param1}"
```

## Troubleshooting

### "No module named 'pyvisa'" or VISA errors

Install a VISA backend:

```bash
# Option 1: NI-VISA (recommended for Windows)
# Download from https://www.ni.com/en-us/support/downloads/drivers/download.ni-visa.html

# Option 2: pyvisa-py (pure Python, limited support)
pip install pyvisa-py
```

### Connection timeout

- Verify instrument is powered on and network reachable: `ping 192.168.1.100`
- Check instrument is not locked by another software
- Verify VISA address format: `TCPIP::IP::INSTR`, `GPIB0::5::INSTR`

### DSLogic U3Pro16 "Access denied" or USB errors

- **Close DSView** before connecting from MCP. DSView holds the USB device open.
- Ensure a 64-bit `libusb-1.0.dll` is available next to the package, in the working directory, or in `PATH`.
- On Windows, DSLogic uses the WinUSB driver installed by DSView. Reinstall the driver if needed.

### Commands not found after saving

Restart the MCP Server to reload commands from `.instrument_mcp/` directory.

## Release Checklist (for maintainers)

Before publishing to PyPI:

- [ ] Update version in `pyproject.toml`
- [ ] Update `CHANGELOG.md`
- [ ] Run tests: `pytest`
- [ ] Run lint: `ruff check .`
- [ ] Build package: `python -m build`
- [ ] Test installation: `pip install dist/*.whl`
- [ ] Upload to PyPI: `twine upload dist/*`

## License

MIT License

TDQS

C2.3/5.0

Scored across 110 tools

Disambiguation3/5

Tools are generally distinct within each instrument prefix (cmw_, mxa_, ps_), but there are many duplicate generic tools (cls, opc) alongside instrument-specific versions, causing potential confusion. Agent may struggle to choose between 'cls' and 'cmw_cls'.

Naming Consistency3/5

Naming is inconsistent: instrument-specific tools follow a prefix_verb_noun pattern, but generic tools and admin functions (e.g., debug_last_error, explore_scpi) use different styles without prefix. Mix of snake_case and compound words.

Tool Count2/5

110 tools is excessive. Many are redundant (generic vs. instrument-specific commands) or overly granular (e.g., numerous harmonic settings for MXA). The count overwhelms the intended scope of SCPI instrument control.

Completeness4/5

Covers basic SCPI operations for three instrument families, including discovery and custom command saving. Generic scpi_query/scpi_write fill most gaps, though advanced configurations for each instrument are limited.

Maintenance

ActivityMaintained
ResponsivenessNo issues