rigol-dho-mcp
This server provides programmatic control and data acquisition from Rigol DHO800/DHO900 oscilloscopes over LAN via the Model Context Protocol (MCP).
Identify & Status: Query device identity (
*IDN?) and retrieve comprehensive status including trigger state, sample rate, memory depth, timebase, and per-channel settings.Run Control: Start/stop acquisition, arm single-shot, autoset, clear waveforms, and force trigger.
Configure Channels: Enable/disable channels, set vertical scale, offset, coupling (AC/DC/GND), probe ratio, bandwidth limit, and invert.
Configure Timebase: Set horizontal scale (s/div) and offset.
Configure Trigger: Set edge trigger source, slope (positive/negative/either), level, and sweep mode.
Configure Acquisition: Set memory depth, acquisition type (Normal/Average/Peak/Ultra), and average count.
Measurements: Perform automatic measurements (VPP, VRMS, VAVG, frequency, period, rise/fall time, duty cycle, etc.) on any channel.
Waveform Capture: Retrieve scaled voltage/time data from screen (~1000 points) or deep memory (scope must be stopped), with summary statistics and optional decimated sample arrays.
Screenshot: Capture the scope's display as a PNG image.
Raw SCPI Commands: Send arbitrary SCPI commands as an escape hatch for any functionality not covered by dedicated tools (opt-in via
RIGOL_ENABLE_SCPI_RAW=1).Deployment: Runs locally via stdio or as a Docker container with HTTP or stdio transport, compatible with standard MCP clients.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@rigol-dho-mcp Identify the oscilloscope"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Rigol DHO800/DHO900 MCP Server
An MCP (Model Context Protocol) server for controlling and reading Rigol DHO800/DHO900 series oscilloscopes over LAN, built on the SCPI command set from the official programming guide. It talks directly to the scope's raw SCPI socket on port 5555, so there's no VISA install to deal with.
Note: This server is currently pinned to
mcp<2.0.0. The official MCP Python SDK's v2.0 release renamesFastMCPtoMCPServerand moves it out ofmcp.server.fastmcp, which breaks this server's imports as written. The pin inpyproject.tomlkeeps deploys working untilserver.pyis migrated to the v2 API.
Tools
Tool | Purpose |
|
|
| Trigger state, sample rate, memory depth, timebase, per-channel settings |
| run / stop / single / autoset / clear / force_trigger |
| Enable, V/div, offset, coupling, probe ratio, BW limit, invert |
| Main timebase scale and offset |
| Edge trigger source, slope, level, sweep mode |
| Memory depth, acquisition type, averages |
| Automatic measurements (VPP, VRMS, FREQuency, RTIMe, etc.) |
| Scaled voltage/time data from screen or deep memory, with stats |
| PNG of the scope's display |
| Raw SCPI escape hatch for anything else in the guide |
| Set cursor mode (OFF/MANual/TRACk), type, source, and positions |
| Read cursor positions and delta/frequency readouts |
| Delay or phase between two channels (RRDelay, FFPHase, etc.) |
Related MCP server: SkippyMCP
Scope setup
Connect the scope to your LAN and grab its IP address under
Utility > IOon the scope.That's really it. The raw SCPI socket on port 5555 is open by default.
Run locally (stdio)
pip install .
RIGOL_HOST=192.168.1.100 rigol-dho-mcpThis starts the server on stdio, ready for any MCP client to spawn and talk to it directly.
Testing SCPI commands locally (CLI)
rigol-dho-cli talks straight to the scope over the same SCPI client the MCP server uses — no MCP client required. Handy for checking a command works, or debugging the connection before wiring it up as a tool.
pip install .
# one-shot: run one or more commands and print the result, then exit
RIGOL_HOST=192.168.1.100 rigol-dho-cli "*IDN?" ":CHANnel1:SCALe?"
# interactive REPL: omit the commands
RIGOL_HOST=192.168.1.100 rigol-dho-cli
scpi> *IDN?
RIGOL TECHNOLOGIES,DHO814,...
scpi> :RUN
OK (system error queue: 0,"No error")
scpi> :DISPlay:DATA? PNG
binary response (34521 bytes) -> saved to capture_00001.png
scpi> quitIt reads the same RIGOL_HOST / RIGOL_PORT / RIGOL_TIMEOUT env vars as the server (or pass --host / --port / --timeout directly). Queries (commands ending in ?) print the response; writes are followed by a :SYSTem:ERRor? check so a typo shows up immediately. Binary responses (screenshots, waveform data) are saved to a file in the current directory instead of being dumped to the terminal.
Run with Docker
HTTP transport (recommended for containers)
docker build -t rigol-dho-mcp .
docker run -d --name rigol-dho-mcp \
-p 8000:8000 \
-e RIGOL_HOST=192.168.1.100 \
rigol-dho-mcpThis exposes the MCP endpoint at http://<docker-host>:8000/mcp (streamable HTTP).
Using Docker Compose
Alternatively, you can use docker-compose.yml:
# Copy the example environment file and edit it with your scope's IP address:
cp .env.example .env
# Edit .env to set your scope's IP address under RIGOL_HOST
# Then start the service:
docker compose up -d
# View logs:
docker compose logs -f
# Stop the service:
docker compose downstdio inside Docker
docker run -i --rm \
-e RIGOL_HOST=192.168.1.100 \
-e MCP_TRANSPORT=stdio \
rigol-dho-mcpThe container needs to be able to reach the scope's IP. On Linux the default bridge network usually works fine; if your scope only sits on the host's LAN segment and bridge routing doesn't reach it, add
--network host. For docker-compose, you can uncomment thenetwork_mode: "host"line indocker-compose.yml.
Using it with an MCP client
This is a standard MCP server, so any client that speaks MCP over stdio or streamable HTTP can use it. The config shape is basically the same everywhere: point the client at the rigol-dho-mcp command (stdio) or the running HTTP endpoint, and pass RIGOL_HOST.
stdio:
{
"mcpServers": {
"rigol-dho800": {
"command": "rigol-dho-mcp",
"env": { "RIGOL_HOST": "192.168.1.100" }
}
}
}Streamable HTTP (pointing at the Dockerized server from above):
{
"mcpServers": {
"rigol-dho800": {
"url": "http://localhost:8000/mcp"
}
}
}If your client doesn't support remote MCP servers natively, use mcp-remote as a bridge instead:
{
"mcpServers": {
"rigol-dho800": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8000/mcp"]
}
}
}Check your client's docs for exactly where this config goes; the values themselves don't change.
Security & Network Configuration
HTTP Endpoint Access Restrictions
When running with HTTP transport (Docker or MCP_TRANSPORT=streamable-http), the MCP server exposes an endpoint at http://<host>:8000/mcp.
⚠️ Important: This endpoint has no authentication mechanism. Anyone who can reach this port can control your oscilloscope and read waveform data/screenshots.
Because of that, compose.yml publishes the port on loopback only (MCP_BIND_ADDRESS=127.0.0.1) by default. The intended deployment is behind a reverse proxy that adds authentication.
To expose it on your LAN anyway, set
MCP_BIND_ADDRESS=0.0.0.0in.env— and be aware that this makes the scope controllable by anyone who can reach the port.To put it behind Traefik, uncomment the labels block in
compose.ymland drop theports:block.
⚠️ Authentik/SSO note: this is a pure-API service. MCP clients can't complete an interactive browser login, so forward-auth SSO middleware (
authentik_domain@file) will break every client. Use a non-interactive credential — abasicauthor bearer-token middleware — instead.
CORS is not access control. MCP_ALLOWED_ORIGINS constrains browsers only; curl, a script, or any non-browser MCP client is unaffected by it regardless of how it's set.
DNS Rebinding Protection
The HTTP transport includes DNS rebinding protection by default (MCP_ENABLE_DNS_REBINDING_PROTECTION=1). This validates Origin and Host headers on incoming requests to prevent malicious websites from accessing your MCP server through a browser. It is a useful control, but it is not authentication — a direct client that sets an allowed Host header passes it trivially.
Both allowlists default to empty, which rejects everything:
Allowed Hosts: set
MCP_ALLOWED_HOSTSto the exacthost:portyou connect to (e.g.localhost:8000,scope.home.lab:8000). With protection enabled and this unset, every request is rejected with421 Misdirected Request— if the server appears to reject all traffic, this is why.Allowed Origins: set
MCP_ALLOWED_ORIGINSonly if a browser-based client needs access (e.g.http://localhost:6274for MCP Inspector).
The container healthcheck endpoint /health is intentionally exempt from these checks and reports HTTP liveness only — it doesn't probe the scope and returns no information about it.
⚠️ Warning: Setting
MCP_ENABLE_DNS_REBINDING_PROTECTION=0or usingMCP_ALLOWED_ORIGINS=*disables this protection entirely and should only be done on trusted, isolated networks for local testing.
SCPI Input Handling
Every tool parameter that gets interpolated into a SCPI command is an enumerated type or a bounded number, and the SCPI client rejects any command containing control characters or non-ASCII. This is what keeps RIGOL_ENABLE_SCPI_RAW=0 meaningful: SCPI is newline-delimited, so without both checks an embedded newline in a parameter would reach the scope as a second, arbitrary command.
If you add a tool, do not interpolate a free-form str into a command string — give the parameter a Literal type.
Raw SCPI Escape Hatch Risks
The scpi_command tool is opt-in via the RIGOL_ENABLE_SCPI_RAW=1 environment variable. When enabled, it accepts arbitrary SCPI commands from the MCP client.
⚠️ Warning: Arbitrary SCPI can leave the scope in any state or perform destructive actions (e.g.,
*RSTto reset the scope, changing critical settings). Ensure your MCP client has appropriate access controls and that you understand the risks before enabling this feature.
Variable | Default | Meaning |
| — (required) | Scope IP address or hostname |
|
| SCPI socket port |
|
| I/O timeout, seconds |
|
| Transport |
|
| HTTP bind address/port inside the container |
|
| Host interface compose publishes the port on. |
|
| Validate |
| — (empty) | Allowed |
| — (empty) | Allowed browser origins; also configures CORS |
|
| Container memory ceiling (deep-memory reads need ~2 GB) |
|
| Set to |
Notes
Deep-memory reads (
get_waveformwithmode="memory") need the scope in the STOP state, so callrun_control("stop")first. Data comes back in chunks and gets decimated tomax_pointsbefore returning.Waveform samples are scaled to volts using the preamble:
V = (raw − YORigin − YREFerence) × YINCrement.A measurement value near
9.9e37just means it's invalid for the current signal.get_measurementflags this for you.scpi_commandis opt-in (setRIGOL_ENABLE_SCPI_RAW=1). It checks:SYSTem:ERRor?after write-only commands, so a typo in raw SCPI shows up right away instead of failing silently.
Known issue: pending MCP v2 migration
The official MCP Python SDK's v2.0 release (stable as of late July 2026) replaces FastMCP with MCPServer and relocates it out of mcp.server.fastmcp. server.py still imports the old path (from mcp.server.fastmcp import FastMCP, Image), so an unpinned install pulls v2 and crashes on startup with ModuleNotFoundError: No module named 'mcp.server.fastmcp'. The mcp<2.0.0 pin in pyproject.toml avoids this for now. Migrating server.py to the v2 API is planned but not yet done.
Available Tools
11 toolsconfigure_acquisitionB
Set acquisition memory depth, mode, and average count.
Inputs: acq_type, averages, memory_depth.
configure_channelA
Configure an analog channel. Only the parameters you pass are changed; the tool returns the channel's resulting settings.
Inputs: channel (required), scale, invert, offset, +4 more.
configure_timebaseB
Set the main horizontal timebase scale and/or offset.
Inputs: scale, offset.
configure_trigger_edgeA
Configure edge triggering (source, slope, level, sweep mode). Sets trigger mode to EDGE, then applies only the parameters given.
Inputs: level, slope, sweep, source.
get_measurementA
Perform an automatic measurement on a channel and return its value.
Common items: VPP (peak-to-peak), VAVG, VRMS, VMAX, VMIN, FREQuency, PERiod, RTIMe (rise time), FTIMe (fall time), PDUTy (duty cycle). Values are in SI units (V, s, Hz). A value near 9.9e37 means the measurement is invalid for the current signal.
Inputs: item (required), channel.
get_screenshotA
Capture the scope's current display as a PNG image.
Useful for visually inspecting waveforms, menus, and measurement readouts exactly as shown on the instrument's screen.
Inputs: no named parameters.
get_statusB
Get an overview of the scope's current state: trigger status, sample rate, memory depth, timebase, and per-channel settings.
Inputs: no named parameters.
get_waveformA
Capture waveform data from a channel, scaled to volts and seconds.
Returns summary statistics (vmin/vmax/vpp/vavg/vrms) plus decimated time/voltage arrays. In 'memory' mode the scope must be stopped first (use run_control 'stop'); the full memory depth is read and decimated.
Inputs: mode, channel, max_points, include_data.
identifyA
Query the instrument identity (*IDN?) to verify the connection.
Returns manufacturer, model, serial number, and firmware version. Use this first to confirm the scope is reachable.
Inputs: no named parameters. Returns: result
run_controlA
Control acquisition state.
run: start continuous acquisition (:RUN)
stop: stop acquisition (:STOP) — required before reading deep memory
single: arm a single-shot acquisition (:SINGle)
autoset: auto-configure vertical/horizontal/trigger for the applied signal
clear: clear all waveforms on screen (:CLEar)
force_trigger: force a trigger event (:TFORce)
Inputs: action (required). Returns: result
scpi_commandA
Send an arbitrary SCPI command from the DHO800/900 programming guide.
Commands ending in '?' are treated as queries and their response is returned; others are write-only. Use for anything not covered by the dedicated tools (cursors, math, decoding, mask tests, DVM, etc.).
Inputs: command (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 11 tools
Each tool targets a distinct aspect of the oscilloscope: configuration (acquisition, channel, timebase, trigger), measurement, screenshot, status, waveform capture, identification, run control, and generic SCPI. No overlap.
Tools use snake_case with verb_noun pattern (configure_*, get_*), but 'identify' is a lone verb and 'run_control' combines multiple actions. Minor inconsistency, but overall clear.
11 tools is well-scoped for an oscilloscope MCP, covering essential operations without being excessive.
Covers core functionality: configuration, measurement, waveform, screenshot, status, run control. Missing dedicated cursor/math tools but covered by the generic scpi_command, so minor gap.
Maintenance
Related MCP Connectors
Documentation for the Spektralwerk spectrometer SCPI API as a streamable HTTP MCP Server
QuLab MCP remote server (Streamable HTTP) for computational science and lab tools.
MCP server for network documentation, generated by doc2mcp.
Related MCP Servers
- AlicenseAqualityCmaintenanceAn MCP server for controlling Rigol DS1000Z series oscilloscopes over LAN using natural language. It enables users to take measurements, configure instrument settings, and capture screenshots directly through an MCP client.1745MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server for controlling Rigol oscilloscopes from an AI assistant. It translates MCP tool calls into SCPI commands over PyVISA.1MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for controlling Hantek DSO2D15 and other DSO2000-family oscilloscopes via USB and SCPI, enabling waveform acquisition, measurements, and screen captures.1MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server that connects AI assistants to the Owon DS1102 oscilloscope, enabling waveform capture, measurement retrieval, and remote control of scope settings.GPL 3.0