oscilloscope-mcp
Click on "Install 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., "@oscilloscope-mcpMeasure frequency on channel 1"
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.
English | 日本語
oscilloscope-mcp
An MCP server that lets an AI agent (Claude Code, Cursor, custom) operate a real bench oscilloscope over the network.
There are two ways to use it:
1. Interactive: you ask, the agent drives the scope. Instead of reaching over to the bench to turn knobs and click through the scope's menus, you say what you want in plain language ("trigger on the 2nd falling edge of CH1", "show me CH1–CH3", "what's the frequency?"), and the agent sets it up on the instrument, reads it back, and shows you an interactive waveform view.
2. As a feedback channel when an AI agent does the work. When you hand FPGA development to an AI agent, this gives it feedback on how the design behaves on real hardware: the agent captures the real signals and checks automatically whether they match what the RTL simulation predicted, so nobody has to read the scope by eye to close the "RTL sim PASS / HW FAIL" gap. It also works as a general input path: feed the state of various instruments to the AI and let it act on what it sees.
Either way, you get the results in whatever form fits the job: structured data the agent can use directly as its next input (measurements with caveats, edge / run streams), a PNG screenshot for a quick human look, or a self-contained HTML viewer that draws the measured samples in the browser.
The scope is controlled under the hood over its standard SCPI interface, and you never write it or even need to know it exists.

One MCP-driven capture: an AI agent set the timebase, V/div, per-channel
offset, channel labels, and trigger on a RIGOL DS1104Z, then pulled the
screenshot back to the user. The four traces are GPIO 26 / 19 / 13 / 17 on a
Raspberry Pi 4 driven at 50 / 100 / 200 / 500 Hz via pigpio DMA waveform
output, and the scope reads each one back exactly, confirming the whole loop
end to end. Every setting was made by the agent through this MCP server's
tools, with no hands on the front panel.
What makes it different
The agent is given everything the instrument can do, as data, not a thin wrapper around a few commands. The full set of capabilities (for example, all 15 DS1000Z trigger types and ~95 parameters) is declared in a machine-readable profile, taken from the official programming guide and verified on real hardware. The agent can ask "what can I set here?" and get back the parameters it can actually set, so it isn't limited to the few operations the tool's author happened to think of.
Natural language in, validated settings out. Every request is checked against what the instrument actually supports; an impossible setting is rejected before it reaches the hardware. A bad request fails loudly instead of leaving the scope in a surprising state.
Every operation is also a reproducible, typed command. The same thing you asked for interactively can be scripted, shared, and run in CI.
Structured facts, plus screenshots you can check. The agent pulls out the numbers and caveats, and PNG screenshots come back for review (with optional cursor / channel-label annotation). Never a black box.
Self-contained HTML viewer. One file, no dependencies, opens in any browser. It reads the measured RAW samples from the scope and draws them in the browser, up to the scope's full memory. The controls match the scope itself: per-channel V/div, drag the GND offset, a cursor that snaps to samples, channel on/off.
Sim-vs-hardware diff. Give it a reference edge sequence (for example, from your RTL simulation) and it compares that against what the scope actually measured, returning structured added / missing / shifted edges.
Plug-in architecture. Add a new vendor / model with two files: a SCPI-dialect driver and a capability profile YAML. Capability is declared data, not code.
Tested. ~560 unit tests plus live conformance against real hardware.
Drivers ship for the RIGOL DS1000Z series (DS1054Z / DS1074Z / DS1104Z); only DS1104Z is verified on real hardware so far. The others use the same code path and profile, but are not conformance-tested yet.
Related MCP server: siglent-sds-mcp
Install
git clone https://github.com/masahiro-999/oscilloscope-mcp.git
cd oscilloscope-mcp
pip install -e .Run as MCP server
# Set the scope's network address.
export SCOPE_MCP_HOST=<your-scope-ip> # e.g. 192.168.1.42
# Start the server (stdio transport, default).
python -m oscilloscope_mcp
# Or use the console script.
oscilloscope-mcpRegister with Claude Code
claude mcp add oscilloscope -- python -m oscilloscope_mcpThen the scope_query, scope_screenshot, scope_trigger,
scope_measure, scope_measure_stat, scope_channel, scope_timebase,
scope_waveform, scope_acquire, scope_capture, scope_compare,
and scope_viewer tools become available to any session that connects
to this MCP server.
Tools exposed
MCP tool | Purpose |
| Raw SCPI command passthrough. Returns the response plus a |
| Save a PNG of the current scope screen for user review. Optionally place manual cursors (Δt / ΔV) and channel labels (DIR / STP / NXT / CLK) to make the image easy to read. |
| Read or configure the trigger; it covers every trigger type the instrument supports. Call with no arguments to read the current state (returns |
| Read scope-side automatic measurements ( |
| Read measurement statistics ( |
| Read or configure an analog channel's vertical setup. |
| Read or configure the horizontal (timebase) setup. Pass any subset of |
| Capture one channel and return a compact threshold-quantized event stream, not raw ADC samples. Raw volts pass through a Schmitt-trigger comparator ( |
| Read or configure the acquisition mode: |
| Declarative single-shot capture: configure, arm, wait, and read in one call. Composes trigger/channel/timebase/acquire setup, arms |
| Sim vs HW reference diff: the project's core goal. Takes a golden edge/run list from your RTL simulator and compares it against hardware (live capture or provided data). Returns |
| Generate a self-contained interactive HTML waveform viewer. Reads RAW waveform data (scope must be stopped) and embeds the measured analog voltage samples into an HTML file. Features: per-channel ON/OFF toggle, per-channel V/div dropdown (10mV–100V), draggable GND offset markers (▶), T/div dropdown with 1-2-5 auto-stepping on scroll-zoom, drag-to-pan, vertical cursor snapped to sample points with fixed voltage readout, trigger position marker (▼T at t=0, derived from |
Layout
oscilloscope-mcp/ # repo (kebab-case)
├── README.md
├── LICENSE
├── pyproject.toml
├── src/oscilloscope_mcp/ # Python package (snake_case)
│ ├── __init__.py
│ ├── __main__.py # `python -m oscilloscope_mcp` entry
│ ├── server.py # FastMCP server: tool registration + dispatch
│ ├── transport/
│ │ └── scpi_lan.py # raw TCP SCPI client (zero third-party deps)
│ ├── instruments/
│ │ ├── _base.py # Scope ABC, vendor-agnostic interface
│ │ ├── __init__.py # MODEL_REGISTRY + open_scope() dispatch
│ │ ├── rigol_ds1000z.py # RIGOL DS1054Z / DS1104Z driver
│ │ └── profiles/
│ │ ├── _ds1000z_family.yaml # shared trigger + acquisition schema
│ │ ├── rigol_ds1104z.yaml
│ │ └── rigol_ds1054z.yaml
│ └── helpers/
│ ├── caveat_calc.py # capability + current setting → caveats[]
│ ├── trigger.py # trigger profile: validate / normalize / caveats
│ ├── measure.py # measurement profile: validate / normalize / parse
│ ├── acquisition.py # channel + timebase profile validation
│ ├── quantize.py # raw volts → 0/1 stream (Schmitt hysteresis)
│ ├── rle.py # digital stream → runs[(t_us, level, dur_us)]
│ ├── edges.py # runs → edges[(t_us, ch, RISE/FALL)]
│ ├── bus.py # multi-channel runs → bus_runs[(t_us, value, dur_us)]
│ ├── reference_diff.py # sim vs HW edge diff (the whole point of the project)
│ ├── viewer.py # self-contained HTML waveform viewer generator
│ ├── glitch_list.py # runs < min_width (runt/glitch detection)
│ ├── edge_interval_stats.py # jitter / clock stability stats
│ ├── pattern_search.py # RLE bit-pattern template matching
│ ├── voltage_histogram.py # voltage distribution / bimodal detection
│ ├── fft_peaks.py # top-N frequency peaks (EMI / switching noise)
│ ├── causality_check.py # cross-channel "B follows A within N µs"
│ └── envelope_downsample.py # min/max decimation for visualization
└── tests/ # unit + live conformanceEnvironment
Env var | Required | Meaning |
| yes | scope IP or hostname |
| no (default 5555) | SCPI TCP port |
| no | model key (e.g. |
Env-var prefix SCOPE_MCP_* is project-specific so it does not collide
with other shells driving unrelated tools.
Adding a new instrument
Two new files plus one registry line:
profile (
src/oscilloscope_mcp/instruments/profiles/<model>.yaml): capability (analog BW, sample rate per channel count, memory depth,idn_matchregex).driver (
src/oscilloscope_mcp/instruments/<vendor>_<series>.py): subclassoscilloscope_mcp.instruments._base.Scope, implement each abstract method using the vendor's SCPI dialect.registry line: add an entry to
MODEL_REGISTRYinsrc/oscilloscope_mcp/instruments/__init__.py.conformance test: run
SCOPE_MCP_HOST=<ip> \ SCOPE_MCP_CONFORMANCE_MODEL=<MODEL_KEY> \ pytest tests/test_instrument_conformance.py -v
No other module changes are required.
Limits (tool warnings)
All structured outputs include a caveats[] field. Going outside a limit the
profile declares (for example, 4-channel mode forces 250 MSa/s on a DS1000Z,
which drops practical observation to ~25 MHz) automatically adds a caveat. The
agent must read caveats[] before trusting any number.
Verified on real hardware
Everything below was tested against a live RIGOL DS1104Z (the
tests/test_instrument_conformance.py suite): connection / IDN, raw SCPI,
screenshot, channel / timebase / acquire read+set, measurements and statistics,
waveform capture in NORMal and RAW (full memory), declarative scope_capture,
run control, trigger mode switching across all 15 types, and the validation
(rejection) paths.
Built from the official programming guide and unit-tested, but not yet confirmed on real hardware. Treat these as based on the spec until they are verified:
Per-trigger-type parameters beyond EDGE (pulse width, slope time, the RS232 / IIC / SPI fields, …). Every type's parameter ranges / enums come from the guide and are validated before anything is sent, and the mode switch is hardware-checked for all 15 types, but the individual parameters of the non-EDGE types are not each tested on hardware.
Acquire types other than
NORMal(AVERages / PEAK / HRESolution) and explicitmemory_depthvalues.scope_compareagainst a real simulation. The diff engine is unit-tested and verified on real captured edges shifted by a known amount, but a genuine RTL-simulation-output vs hardware-capture run has not been done yet.Models other than DS1104Z (DS1054Z / DS1074Z): same code path + profile, not conformance-tested.
Security / trust model
This is a local-only stdio MCP server. It speaks to an MCP client over its parent process's stdin/stdout, and it does not open any network port. The client (Claude Code, Cursor, your own agent) launches it as a subprocess, and the trust boundary is the local user account.
Within that boundary:
scope_queryis a raw SCPI passthrough with no command validation. Any SCPI the client sends is forwarded to the scope. That is intentional: SCPI on a bench scope is mostly read operations, and even changes that alter the setup (timebase, channel scale, trigger) are easy to undo. But it does mean an agent with this MCP server enabled can fully reconfigure your scope and read everything on its screen. Use it accordingly.The SCPI connection to the scope itself is unauthenticated TCP on the scope's LAN port (RIGOL DS1000Z uses 5555). Anything on the same network can already talk to the scope. Installing this MCP server does not change that; it just gives the AI agent the same access you already have.
If you want to lock down the scope further, do it on the scope's network (VLAN, firewall) rather than at this layer. The MCP server is not the right place to police what an agent on the same machine can ask the scope to do.
Tests
# Unit tests (no hardware).
pytest tests/ -v
# Live conformance against a real scope.
SCOPE_MCP_HOST=<your-scope-ip> \
pytest tests/test_instrument_conformance.py -vAdding support for your instrument
Any oscilloscope with a publicly documented control interface (SCPI or otherwise) can be supported. Thanks to the plug-in architecture above, a new vendor / model is just a driver subclass plus a capability profile, with no changes to the core. See Adding a new instrument.
Forks that add instrument support are welcome. Requests are too: open an issue for an instrument you'd like supported (a link to its programming guide helps) and we'll consider it.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/masahiro-999/oscilloscope-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server