Skip to main content
Glama
JDS2800 MCP
by ikatkov

JDS2800 MCP

MCP server and JSON scripting CLI for a JUNTEK JDS2800 over USB serial.

Uses the existing Kristoff Bonne Python driver with per-operation serial ownership, line framing, parameter checks and verified readback. The legacy source and MIT license are vendored, so the server does not require another checkout or computer. Built with the official MCP Python SDK, pinned to version 1.30.0. Supports macOS and Linux with Python 3.11 or newer.

Install and run

git clone https://github.com/ikatkov/jds2800-mcp.git
cd jds2800-mcp
uv sync --locked
./jds2800 info
./jds2800 state

Run these commands from the checkout. The jds2800 and run-mcp launchers use the virtual environment created by uv sync. An installed package also provides jds2800 and jds2800-mcp console commands.

No port setting is required. The driver tries available USB serial ports, prioritizing JDS descriptions and CH340 adapters. It identifies a compatible generator by reading its model, serial number and channel states, without changing output settings. Probes have short timeouts and use the same serial lock as normal operations. Bluetooth/headset and debug-console ports are excluded.

If no generator answers, the CLI exits with status 2 and an error listing the ports it tried. If multiple generators answer, it asks you to select one explicitly using --port or JDS2800_PORT. An explicit port always overrides discovery.

Related MCP server: dwf-mcp-server

Manual reference for agents

Read JDS2800-reference.md before controlling the instrument. It summarizes the supplied manufacturer's 18-page English manual (Rev 1.0, June 2018), with page references, specifications, operating procedures, documented ambiguities, 50-ohm power calculations, and separately labeled protocol details and measured hardware findings.

The same Markdown is bundled in the Python package and available without a generator connection through the MCP get_manual tool or the jds2800://manual resource (text/markdown). Server instructions direct agents to it. The CLI ./jds2800 manual returns it as a JSON string; ./jds2800 call get_manual also works.

MCP connection

Install the console commands so an MCP client can launch the server from any directory:

uv tool install .

Copy .mcp.example.json into the client's MCP settings:

{
  "mcpServers": {
    "jds2800": {
      "command": "jds2800-mcp"
    }
  }
}

Ensure the installed console command is on the client's PATH. For development, ./run-mcp launches the checkout's environment; configure the client with the path to your own checkout rather than a path from someone else's machine.

Server startup and tool discovery do not open the generator or enable outputs. The server implements 17 tools:

Tool

Purpose

get_manual

Bundled specifications, operating notes and verified limitations

get_calibration

Separate measured 50-ohm SINE/SQUARE power tables and valid ranges

preview_power

Check device identity and calculate voltage for requested dBm

set_power

Configure calibrated SINE/SQUARE AC RMS power into a physical 50-ohm load

list_ports

Find USB serial ports without opening them

list_waveforms

Built-in names/IDs and arbitrary slots

get_device_info

Model code, serial, port and frequency limit

get_state

Read both channels or one channel, mode and phase

configure_channel

Frequency, waveform, amplitude, offset, duty and enable

set_outputs

Independently enable/disable either output

set_phase

Inter-channel phase; negative values normalized modulo 360

set_mode

Explicitly select mode and stop active actions

configure_sweep

Sweep parameters, readback, optional start

stop_outputs

Stop actions and disable both outputs

read_register

Read protocol registers, including counter/measurement data

read_arbitrary

Read 2048 samples from a stored waveform

upload_arbitrary

Overwrite a stored waveform and verify all samples

Scripting CLI

Successful commands print one JSON value to stdout. Failures print JSON to stderr and exit with status 2. Help/argument parsing uses the standard argparse interface. The port is discovered automatically. To override it, supply --port before the subcommand or set JDS2800_PORT.

# 1 kHz triangle, 2 Vpp, +1 V offset on channel 1.
./jds2800 configure 1 --waveform TRIANGLE --frequency-hz 1000 \
  --amplitude-vpp 2 --offset-v 1 --enabled on

# 2 kHz, 1 Vpp, 30% duty on channel 2.
./jds2800 configure 2 --waveform PULSE --frequency-hz 2000 \
  --amplitude-vpp 1 --offset-v 0 --duty-percent 30 --enabled on

./jds2800 outputs --ch1 off
./jds2800 phase -90
./jds2800 stop

# Every MCP tool is also available through `call`.
./jds2800 call get_state --args '{"channel":2}'
./jds2800 call configure_sweep --args \
  '{"channel":1,"start_hz":1000,"end_hz":2000,"time_s":1,"start":false}'
./jds2800 mode WAVE_CH1

# Multiple operations, one serial lease. This example enables both outputs.
./jds2800 batch examples/dual-channel.json
./jds2800 stop

batch also accepts - to read a JSON array from stdin. Entries contain an operation (the MCP tool name) and an optional arguments object. Operations run sequentially and stop on the first failure. Earlier operations are not rolled back; the error includes their results and the failing entry's zero-based index.

Device behavior and units

  • Frequency is in Hz, amplitude in V peak-to-peak, offset in V, duty in percent, and phase in degrees. Frequency readback uses the actual quantized device value.

  • The connected unit reports model code 15, serial 1816400000. Its sine limit is 15 MHz. The driver detects the limit instead of assuming the original JDS6600 library's 60 MHz maximum.

  • Pulse/CMOS/arbitrary frequency is limited to 6 MHz. Other non-sine waveforms are limited to the smaller of the model limit and 25 MHz.

  • Amplitude and offset limits are checked together. Maximum amplitude decreases above 10 MHz and 30 MHz; non-sine amplitude is conservatively capped at 5 Vpp above 10 MHz. The allowed offset range also depends on amplitude. The actual load affects physical voltage. The tested wiring used direct coax into the scope's high-impedance inputs, with probe attenuation 1×.

  • Device synchronization can make selected CH2 parameters follow CH1 (manual p. 18). This server does not read or change synchronization settings; check the front-panel SYS menu before relying on independent channel parameters.

  • SQUARE is physically 50% duty on this unit. The duty register can say 30% while the square wave stays at 50%. Use PULSE for adjustable duty. Requests to set a non-50% SQUARE duty are rejected before writing.

  • Channel configuration requires WAVE_CH1 or WAVE_CH2 mode. It validates the complete target configuration, temporarily disables the target channel, writes and reads back all its parameters, then restores its previous enable state unless enabled was supplied. It preserves the other channel's enable state.

  • A failed configuration requests the target channel off and reports an error. Unplugging can prevent that request from reaching the device. There are no automatic write retries or claims of rollback; read state before continuing.

  • Each operation releases the serial connection after completion. CLI commands can coexist with a running MCP server. A shared file lock serializes operations across processes, including a complete batch. External legacy programs do not participate in this lock and should not run against the same port concurrently.

  • configure_sweep selects sweep mode and stops prior actions. It preserves output enable state; start=true starts the action. Return explicitly to wave mode before ordinary configuration. Arbitrary uploads overwrite device storage; they do not select the slot or enable output.

Calibrated sine output in dBm

Calibrated square output

get_calibration, preview_power, and set_power also accept waveform="SQUARE". The default remains SINE. Square output uses its own measured per-channel profile, never the sine table or a theoretical +3 dB conversion. For example, an MCP call is:

{"channel":1,"frequency_hz":10000000,"power_dbm":7,"waveform":"SQUARE","enabled":true}

Square power means AC RMS power including harmonics within the scope's bandwidth, with the measured DC component removed. It is not fundamental-only RF power. The output is configured for 50% duty and zero requested DC offset. Physical 50-ohm termination and the measured cable setup are required. get_calibration(waveform="SQUARE") reports the supported range and verification results; out-of-range requests fail before writes. CMOS/PULSE and other duty cycles do not have calibrated dBm controls.

./jds2800 calibration --waveform SQUARE
./jds2800 preview-power 1 --frequency-hz 10000000 --dbm 7 --waveform SQUARE
./jds2800 power 1 --frequency-hz 10000000 --dbm 7 --waveform SQUARE --enabled on

The square profile is bundled separately as data/square-calibration.json and exposed at jds2800://calibration/square. A measured override uses JDS2800_SQUARE_CALIBRATION; JDS2800_CALIBRATION continues to select the sine profile. See the square measurement guide. Existing MCP clients must reconnect to discover the new waveform argument.

Existing sine profile

The bundled power calibration is for this 15 MHz generator, serial 1816400000, with separate tables for CH1 and CH2. It uses external 50-ohm terminations at Rigol CH1/CH3 through the existing direct coax cables. These loads must be present at the receiving end when using dBm settings. Scope-referenced power includes the measured cable loss.

./jds2800 calibration
./jds2800 preview-power 1 --frequency-hz 5500000 --dbm 0
./jds2800 power 1 --frequency-hz 5500000 --dbm 0 --enabled on
./jds2800 power 2 --frequency-hz 14500000 --dbm -10 --enabled on
./jds2800 stop

set_power / power explicitly sets SINE, zero DC offset, 50% duty, frequency, and calibrated amplitude. It preserves output enable state unless enabled is supplied. It requires wave mode, verifies the generator serial and register readback, and refuses requests outside the measured frequency/level ranges. It does not remeasure power on every call. The returned predicted_power_dbm accounts for the generator's 1 mV amplitude increments.

Calibration covers 1–15 MHz, at every integer MHz plus 10.01 MHz, with ten amplitude settings per frequency on each channel: 320 points, each measured three times. The table supports approximately -29 to +11 dBm across the full frequency range, with precise per-frequency bounds reported by get_calibration. The ordinary voltage controls retain the manufacturer's larger amplitude range; the power controls require measured coverage. No frequency or level extrapolation is performed. Interpolation is linear in dBm versus log amplitude and log frequency.

MCP get_calibration(include_points=true) returns the full measured table. The resource jds2800://calibration returns metadata and supported ranges. An alternate calibration file can be selected using JDS2800_CALIBRATION; it must match the actual device and use the same sine/zero-offset/50-ohm convention. The table is bundled in the installed package. Published evidence omits private paths and connection addresses while preserving numeric readings and provenance.

The bundled profile applies only to serial 1816400000; other units require their own measured profile, selected with JDS2800_CALIBRATION. Calibration collection currently targets 15 MHz units.

To repeat calibration, attach both external 50-ohm loads, supply the scope server and address as described below, and run:

uv run jds2800-calibrate --scope-host "$RIGOL_HOST" --rigol-server "$RIGOL_SERVER" \
  --out artifacts/new-power-calibration.json --confirm-50ohm

This procedure changes channel settings, uses the Rigol MCP server, saves partial measurements as it runs, and disables both outputs on completion or a handled failure. --resume continues an incomplete run with the same measurement grid. The checked-in data are validated against independent frequency/level requests by scripts/verify_power.py. This is a scope-referenced correction; absolute accuracy still depends on the scope, terminations, cables, and operating conditions. The RMS measurement includes harmonics and noise rather than isolating the RF fundamental. Sine power calibration does not apply to square/pulse/noise outputs; square output has the separate measured profile described above.

Scope readings come from numeric measurement queries. The collector clears previous measurements before each reading, retains Vpp, and derives AC power from RMS with the measured DC component removed. The display scale only keeps signals in range. The original 256 points used 16-acquisition averaging; 64 refinement points and the independent checks use normal acquisition. See the guide for the raw evidence.

Independent verification passed 72 checks from -25 to +10 dBm, including intermediate frequencies, the 10 MHz boundary, and both outputs together. The largest difference from requested power was 0.195 dB relative to this scope. See the results. Both outputs were left off.

What was tested

On October 3, 2026, both connected channels were verified through the actual stdio JDS2800 MCP server and the existing Rigol scope MCP server. Wiring: generator CH1 → scope CH1; generator CH2 → scope CH3. Scope: DS1104Z, serial DS1ZA225013504. See the measured values and screenshot in artifacts/hardware-verification.json and artifacts/scope-triangle-pulse.png.

The hardware test covers both sine outputs, the original triangle/offset setup, 30% pulse duty, independent output switching, negative-phase register readback, model-limit rejection without mutation, and CLI access while MCP is alive. The scope readings verify control behavior; this is not a calibration of absolute amplitude. Both generator outputs are off at the end of the test.

Sweep parameter/mode readback and reading all 2048 samples from arbitrary slot 1 were also checked on the device. Sweep progression, physical inter-channel phase, arbitrary uploads, and external counter/measurement inputs have not been verified on the scope. The tests do not overwrite stored arbitrary waveforms.

uv run pytest -q
uv run ruff check src tests scripts
uv run ruff format --check src tests scripts

# Handshake and discovery without hardware.
uv run python scripts/check_mcp.py

# Read the device through MCP without changing its settings.
uv run python scripts/check_mcp.py --hardware

# Changes both channels for testing, captures scope data, then disables outputs.
uv run python scripts/verify_hardware.py --rigol-server "$RIGOL_SERVER" \
  --scope-host "$RIGOL_HOST"

Supply RIGOL_SERVER as the path to a compatible Rigol stdio MCP server script and RIGOL_HOST as the scope hostname or IP address. The hardware checks use the active Python environment and USB discovery unless --port is supplied. The scope server must be able to run in that environment. Scope setup writes are followed by *OPC? through its scpi MCP tool to wait for completion.

Library comparison

Two drivers were exercised against the same 15 MHz instrument:

Driver

Observed result

Assessment for this server

Python on1arf/jds6600_python v0.1.0

Read both channels and changed frequency, waveform, amplitude, offset and enables

Compact API, same language as MCP, and compatible with the reproduced automation sequence

Rust signal-gen-cjds66 v0.1.10

Read identity, frequency/amplitude, and changed settings in individual commands

Partly compatible, but combined reads failed with exit status 19

The Rust reproduction --gg --gn successfully read CH1's offset, then failed on CH2 with a missing-equals error. Its fixed-size read() calls do not consume a complete variable-length CRLF reply or check the number of bytes returned. A leftover byte is parsed as the next response. See artifacts/rust-offset-read.txt and artifacts/rust-frequency-write.txt.

The Python library is the better starting point, but its original class-level serial handle and loose validation also needed improvement. This server bypasses that constructor, replaces the transport, verifies readback, prevents negative waveform indexes, and fixes the original negative-phase conversion. The vendored Python driver makes ordinary operation independent of the comparison drivers.

Configuration

Setting

Default

JDS2800_PORT / --port

Probe USB serial ports and select a single matching JDS generator

JDS2800_TIMEOUT / --timeout

1 second per serial reply/write

JDS2800_LOCK_TIMEOUT

5 seconds waiting for another operation

Protocol reference: upstream register map. Hardware ranges follow the supplied manufacturer's manual, summarized in the bundled reference, which records the document edition, page references, and checksum. MIT license; the vendored library retains its original copyright and license.

Available Tools

17 tools
configure_channelA
Destructive

Set channel parameters in wave mode and verify them. Unspecified settings are preserved. Output is gated during changes; its previous enable state is restored unless enabled is supplied. Limits follow the detected model. On communication/readback failure the target output is requested off. SQUARE is physically 50% duty; use PULSE for adjustable duty.

Inputs: channel (required), enabled, offset_v, waveform, +3 more.

configure_sweepB
Destructive

Switch to sweep mode, configure and read back limits. start=true starts the sweep. Output enable state is preserved; use set_outputs separately.

Inputs: end_hz (required), time_s (required), channel (required), start_hz (required), +3 more.

get_calibrationA
Read-onlyIdempotent

Read measured power calibration metadata/ranges without opening a port. include_points=true also returns the full amplitude/frequency measurement table. Applies to zero-offset SINE into a physical 50-ohm load through measured cables.

Inputs: channel, include_points.

get_device_infoA
Read-onlyIdempotent

Read the model code, serial number, port and detected sine frequency limit.

Inputs: no named parameters.

get_manualA
Read-onlyIdempotent

Read the bundled manual reference: model/frequency/voltage limits, 50-ohm power estimates, modes, synchronization, protocol notes, and tested behavior. Includes source page numbers and discrepancies. No serial port is opened.

Inputs: no named parameters. Returns: result

get_stateA
Read-onlyIdempotent

Read mode, phase and channel settings. Omit channel to read both.

Inputs: channel.

list_portsA
Read-onlyIdempotent

List USB serial ports without opening the generator.

Inputs: no named parameters. Returns: result

list_waveformsA
Read-onlyIdempotent

List built-in waveform names/IDs and the 60 arbitrary waveform slots.

Inputs: no named parameters. Returns: result

preview_powerA
Read-onlyIdempotent

Check generator identity and calculate calibrated voltage for sine power into 50 ohms. Interpolates measured levels/frequencies; no extrapolation or writes. Returns predicted power after 1 mV amplitude quantization, not a live measurement.

Inputs: channel (required), power_dbm (required), frequency_hz (required).

read_arbitraryA
Read-onlyIdempotent

Read all 2048 samples from an arbitrary waveform slot, numbered 1..60.

Inputs: slot (required).

read_registerA
Read-onlyIdempotent

Read a protocol register 0..89 for diagnostics or measurement/counter data. Unsupported registers may time out; this does not select a mode.

Inputs: register (required).

set_modeA
Destructive

Stop current actions, select the operating mode and verify it.

Inputs: mode (required).

set_outputsA
Destructive

Enable or disable outputs independently, preserving unspecified output state.

Inputs: ch1, ch2.

set_phaseA
Destructive

Set inter-channel phase, normalized to 0..359.9 degrees, with readback.

Inputs: phase_deg (required).

set_powerA
Destructive

Set calibrated sine power into a physical 50-ohm load using the measured table. Sets waveform=SINE, DC offset=0, duty=50, and frequency/amplitude. Requires wave mode and matching serial number. Preserves enabled state unless supplied. Predicted dBm includes measured cable loss; other loads/waveforms are unsupported. Uses register readback; it does not remeasure the signal with the scope.

Inputs: channel (required), power_dbm (required), frequency_hz (required), enabled.

stop_outputsA
Destructive

Stop sweep/counter/pulse/burst actions and disable both output channels.

Inputs: no named parameters.

upload_arbitraryA
Destructive

Overwrite slot 1..60 with 2048 integer samples (0..4095) and verify readback. This changes device waveform storage; it does not select the slot or enable output.

Inputs: slot (required), samples (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 17 tools

    Disambiguation4/5

    Most tools have a clear primary purpose, but there are overlapping boundaries around output control (stop_outputs vs set_outputs), power/amplitude setting (set_power vs configure_channel vs preview_power), and sweep/mode selection (configure_sweep vs set_mode). The detailed descriptions mostly resolve these, so misselection is possible but not severe.

    Naming Consistency5/5

    All tools use snake_case verb_noun names (get_/set_/list_/read_/configure_/stop_/upload_/preview_) with no mixed conventions. The pattern is predictable throughout.

    Tool Count4/5

    17 tools is slightly above the typical 3-15 range, but the complex signal-generator domain (calibration, power, modes, arbitrary waveforms, diagnostics) justifies most tools. It is not excessive but feels a bit heavy.

    Completeness4/5

    Core signal generation workflows are covered: ports/info/state, channel config, outputs, phase, mode/sweep, calibrated sine power, arbitrary waveform read/upload, and manual/diagnostics. Minor gaps exist for configuring pulse/burst/counter modes despite stop_outputs referencing them, but agents can handle the main use cases.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      B
      maintenance
      Enables control of MHS-5200A series signal generators via serial connection, including frequency, amplitude, waveform, and sweep settings.
      16
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables control of Digilent WaveForms instruments (oscilloscope, AWG, logic analyzer) over USB, supporting devices like Analog Discovery 2/3 and Digital Discovery.
      3
      MIT
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables remote control of Siglent SDG series function/arbitrary waveform generators via SCPI over TCP/IP. Provides tools for configuring waveforms, modulation, sweep, burst, and other instrument functions.
      1
      -