Skip to main content
Glama

open289

English

CI

An open Python package, command-line interface and MCP server for the Fluke 287/289 multimeter, read over a Fluke IR3000 FC infrared-to-Bluetooth-LE adapter, written so that an AI agent (Claude Code, or any MCP client) can read, log and download real measurements by itself, without Fluke Connect. It reads the live value (about 4 readings/s), the full display state (function, range, HOLD / MIN MAX / REL modes, MIN / MAX / AVERAGE), everything saved in the meter (measurements, MIN MAX and peak sessions, trend recordings), logs to CSV or JSONL for hours with automatic reconnect, downloads trend recordings, and can press the meter's front-panel keys.

The meter is reached through the adapter's pass-through to the meter's own serial command set (ID, QM, QDDB, QSRR, ...); the protocol was worked out on real hardware, see docs/PROTOCOL.md.

Status and tested hardware

Version 0.1.0 (beta). What was verified on real hardware on 2026-11-06: an IR3000 FC with radio firmware 01.00.01 and MSP firmware 01.00.15, a Fluke 289 with firmware V1.71, a Windows 11 PC with Intel Bluetooth (Python and bleak over WinRT).

  • Only a 289 was tested. The 287 speaks the same protocol and should work; that is untested.

  • Linux and macOS: bleak supports them and the package has no Windows-only code in the Bluetooth path, but real hardware was never tried there. CI runs the whole test suite on Linux and Windows against the virtual meter only (see Virtual meter).

  • Measured speeds: live readings arrive about 4 times per second; one pass-through command takes about 1.3 s (QDDB display snapshots ran about 1.2 to 1.8 s apart); a trend recording downloads at 0.44 s per record (up to 8 records per round-trip, as many as fit in 87 bytes).

Related MCP server: ScopeProbe

Install

Requirements: Python 3.10+, a Bluetooth LE radio, a Fluke 287/289 and a Fluke IR3000 FC.

pip install open289

This installs the commands open289 and open289-mcp (python -m open289 ... is the same as open289 ...) and the single dependency bleak.

From a clone, with uv or a plain venv:

uv venv --python 3.12 .venv
uv pip install --python .venv\Scripts\python.exe -e .
python -m venv .venv
.venv\Scripts\pip install -e ".[test]"        # Linux / macOS: .venv/bin/pip

Windows notes:

  • Switch Bluetooth on (Settings, Bluetooth and devices). No pairing or bonding is needed.

  • The console may use a legacy code page (GBK, CP1252); measurement strings contain µ, Ω and °. The CLI reconfigures its own stdout and stderr to UTF-8, and the launchers in a clone start Python with -X utf8 for everything else: open289.cmd live -n 10 (Windows) and ./open289.sh live -n 10 (POSIX shell, Git Bash). With a pip install, python -X utf8 -m open289 ... does the same.

  • If pip's Scripts folder is not on PATH, call python -m open289 or give the full path to open289.exe.

Hardware setup

  1. Clip the IR3000 FC onto the meter's infrared port on the back of the 287/289 (the meter must be switched on).

  2. Wake the adapter: press its button for more than 1 s until the LED flashes green. It advertises over Bluetooth only while it is awake and not connected.

  3. Close Fluke Connect on any phone or tablet. The adapter accepts exactly one Bluetooth connection. While another program holds it, open289 reports NOT_FOUND (the adapter does not advertise) or BUSY. open289 itself takes a lock, so two open289 processes never fight over the adapter: the second gets BUSY.

  4. Check: open289 scan lists the adapter; open289 info shows the meter. info reports "meter": {"attached": false} when the adapter is awake but not on a powered meter.

Adapter and meter both go to sleep or off by themselves and cannot be switched on remotely; wake the adapter with its button and the meter with its dial.

Quick start

open289 scan                                # find the adapter
open289 info                                # adapter and meter identity, batteries, clock, saved counts
open289 read                                # the whole display now: function, range, modes, every reading
open289 live -n 20                          # 20 real-time readings, one JSON line each
open289 log --duration 10m --out volts.csv  # log to a file; ends by itself, or: open289 stop / Ctrl+C
open289 stop                                # end a running live / read --every / log cleanly

Every command prints JSON (one object per line) unless --text is given; add --sim to try everything without hardware. Output of the virtual meter (never invented; hardware values differ, the fields are the same):

$ open289 --sim scan
{"ok":true,"cmd":"scan","devices":[{"address":"FA:28:90:00:00:01","name":"IR 3000 FC","rssi":-60}]}

$ open289 --sim info
{"ok":true,"cmd":"info","adapter":{"address":"FA:28:90:00:00:01","name":"IR 3000 FC","radio_fw":"01.00.01","msp_fw":"01.00.15","battery_pct":100},"meter":{"attached":true,"model":"FLUKE 289","firmware":"V1.71","serial":"12345678","battery":"FULL","memory_level":"OK","clock_epoch":1794013461,"clock":"2026-11-07 01:04:21.000"},"saved":{"recordings":1,"minmax":0,"peak":1,"measurements":2}}

$ open289 --sim --text info
adapter : FA:28:90:00:00:01  radio fw 01.00.01  MSP fw 01.00.15  battery 100%
meter   : FLUKE 289  firmware V1.71  serial 12345678
battery : FULL   memory: OK   clock: 2026-11-07 01:04:31.000 (meter local time)
saved   : {'recordings': 1, 'minmax': 0, 'peak': 1, 'measurements': 2}

info.meter.clock is the meter's own clock (local time, as set on the meter), not the PC's.

Real-time acquisition

Command

Source

Rate

Carries

live

the adapter's BLE notifications

~4/s, one JSON line per notification

the primary reading and, when the meter shows one, the secondary reading

read

one QDDB display query through the pass-through

one round-trip, ~1.3 s

the whole display: function, secondary function, range, autorange, modes, every reading

read --every 1s

the same, repeated

about one per interval (at least ~1.3 s)

one snapshot per line; ends with a done line

Use live for fast values and logging, read when you need to know what the meter is doing (HOLD? MIN MAX? REL? which range?), and read --every when you need modes or MIN / MAX / AVERAGE over time. live and read can be combined by log --snapshot-every (next section) on one connection.

$ open289 --sim live -n 3
{"type":"reading","t":1794027861.982,"value":5.0625,"unit":"V DC","display":"5.0625 V DC","state":"NORMAL","function":"VDC"}
{"type":"reading","t":1794027862.227,"value":5.0625,"unit":"V DC","display":"5.0625 V DC","state":"NORMAL","function":"VDC"}
{"type":"reading","t":1794027862.481,"value":5.0625,"unit":"V DC","display":"5.0625 V DC","state":"NORMAL","function":"VDC"}
{"ok":true,"cmd":"live","event":"done","readings":3,"duration_s":0.748,"rate_hz":4.01,"stopped_by":"count"}

A live reading line

Field

Meaning

type

"reading"

t

PC time of the notification, Unix seconds (3 decimals)

value

the number in SI base units (V, A, ohm, F, Hz, s, S; degrees C / F and percent as they are): 1.5 mV is 0.0015. null for an overload (OL) or a blank / invalid display

unit

the base unit (without the SI prefix; display carries the prefix): V DC, V AC, V AC+DC, A DC, Ω, S, Hz, F, s, °C, °F, %, dB, dBV, dBm, CF

display

the text the meter shows, with the SI prefix and decimals of the display (5.0625 V DC, OL V AC)

state

NORMAL, OL_RANGE (overload), BLANK (autorange blanking), INVALID (during a dial change)

function

the live function name (VDC, VAC, mVDC, Ω, Continuity, Diode, Temp, mADC, ...)

attribute

present when the function has one: positive edge for Hz, continuity open / short / glitch, a good diode

capture

true when the reading is a captured value (HOLD or similar)

secondary

present when the meter shows a secondary value: an object with the same value, unit, display, state, function fields

raw

with --raw only: the 16 notification bytes in hex

The primary and secondary readings are what the meter's main and secondary display show. With Hz on a voltage function the Hz value becomes primary and the volts the secondary, as on the meter:

$ open289 --sim live -n 1            # V AC with the Hz secondary function
{"type":"reading","t":1794027906.375,"value":60.002,"unit":"Hz","display":"60.002 Hz  [positive edge]","state":"NORMAL","function":"VAC","attribute":"positive edge","secondary":{"value":null,"unit":"V AC","display":"OL V AC","state":"OL_RANGE","function":"VAC"}}

What live does not carry: no mode flags (HOLD, MIN MAX, REL, RECORD), no MIN / MAX / AVERAGE values, no range, no function name in the display-snapshot vocabulary. In HOLD the primary is the held value, in REL the relative value. Use read (or log --snapshot-every) for those. A live run ends at -n, --for or open289 stop and prints a done line (readings, duration_s, rate_hz, stopped_by: count, duration, stop); Ctrl+C ends it without a summary (exit 130). --timeout (default 10 s) turns a silent link into an error instead of waiting forever.

A display snapshot (read)

$ open289 --sim read
{"ok":true,"cmd":"read","t":1794027861.456,"function":"V_DC","secondary_function":"NONE","autorange":true,"range":5.0,"unit":"VDC","modes":["HOLD"],"readings":[{"id":"LIVE","value":5.0613,"unit":"VDC","display":"5.0613 V DC","state":"NORMAL","attribute":"NONE","decimals":4,"time":"2026-11-07 01:04:21.456","ts":1794013461.4560547},{"id":"PRIMARY","value":5.0625,"unit":"VDC","display":"5.0625 V DC","state":"NORMAL","attribute":"NONE","decimals":4,"time":"2026-11-07 01:03:58.325","ts":1794013438.3251953}],"hazard":false,"primary":{"id":"PRIMARY",...}}

Field

Meaning

t

PC time of the query, Unix seconds

function

the primary function: V_DC, V_AC, MV_DC, OHMS, CONTINUITY, DIODE_TEST, CAPACITANCE, TEMPERATURE, MA_DC, ...

secondary_function

NONE, HERTZ, PEAK_MIN_MAX, ...

autorange, range

autorange on / off and the range's full-scale value (in base units; 5.0 = the 5 V range)

unit

the primary unit code (VDC, OHM, CEL, ...)

modes

active modes, e.g. HOLD, MIN_MAX_AVG, REL, RECORD; empty list = none

readings

every number on the screen, in display order (below)

primary

a copy of the PRIMARY entry of readings

hazard

true when the meter shows its high-voltage warning (bolt)

minmax_start

when MIN MAX was started, local meter time (only when MIN MAX is active)

Each readings entry has: id (LIVE, PRIMARY, SECONDARY, MINIMUM, MAXIMUM, AVERAGE, REL_REFERENCE, TEMP_OFFSET, ...), value (SI base units, null for OL), unit, display, state, attribute, decimals, time (meter local time) and ts (the same meter clock value as a number: local wall-clock time encoded as epoch seconds, no timezone; not comparable with t). LIVE is the instantaneous input; PRIMARY is the main reading, which differs from LIVE in HOLD or REL. With MIN MAX active the snapshot also has MINIMUM, MAXIMUM and AVERAGE:

$ open289 --sim press MINMAX ; open289 --sim --text read
00:05:09.566  V_DC   range AUTO 5   modes MIN_MAX_AVG
    LIVE           5.0626 V DC            2026-11-07 01:05:09.567
    PRIMARY        5.0626 V DC            2026-11-07 01:05:09.567
    MINIMUM        5.0612 V DC            2026-11-07 01:05:09.099
    MAXIMUM        5.0631 V DC            2026-11-07 01:05:07.500
    AVERAGE        5.0621 V DC            2026-11-07 01:05:09.567

read --every 1s [-n N] [--for 5m] prints one snapshot per line (ok, cmd, t, ...), an {"ok":false,...} line when one query fails (for example METER_BUSY) without ending the run, and a final {"event":"done","snapshots":N, "stopped_by":...}.

Logging

log writes live readings to a file, in the foreground, until --duration, open289 stop or Ctrl+C. It never stops because of a lost link as long as --reconnect-for allows retries.

open289 log --duration 10m --out volts.csv
open289 log --duration forever --out run.jsonl --snapshot-every 10s     # until: open289 stop
open289 log --every 1s --duration 8h --out night.csv                    # one reading per second

Option

Default

Meaning

--out, -o

data/log_<time>.csv

output file (.csv or .jsonl; relative to the current folder; the default is in the data folder)

--format csv|jsonl

from the --out extension, else csv

file format

--duration, -d

forever

logging time, counted from the first connection: 30s, 10m, 8h, 1.5h, 2d, forever

--every

0

one reading per interval (the most recent reading at each tick); 0 = every notification, ~4/s

--snapshot-every

0 (off)

also record the full display (modes, MIN / MAX / AVG) at this interval, e.g. 10s

--status-every

10s

status line on stdout; 0 = off

--reconnect-for

5m

keep retrying this long after the link is lost; 0 = end the log at the first loss

CSV columns: pc_time (Unix seconds), local_time (PC local time with milliseconds), value, unit, display, state, function, attribute, secondary_value, secondary_unit, secondary_display:

pc_time,local_time,value,unit,display,state,function,attribute,secondary_value,secondary_unit,secondary_display
1794027865.921,2026-11-07 00:04:25.921,5.062,V DC,5.0620 V DC,NORMAL,VDC,,,,
1794027866.168,2026-11-07 00:04:26.167,5.062,V DC,5.0620 V DC,NORMAL,VDC,,,,

value is in SI base units and empty for an overload. With --snapshot-every, a CSV log puts the snapshots in a sidecar file <file>.display.jsonl, one JSON object per line: t (Unix seconds), ack, hex (the raw QDDB answer) and display (the same object as a read snapshot). A failed snapshot query is written as {"t":..., "ack":"ERROR", "error":{...}} and the log goes on.

JSONL has one object per line, with a type:

type

Content

ble

a reading: the fields of a live line (t, value, unit, display, state, function, attribute, secondary) plus hex, the 16 notification bytes

qddb

a display snapshot (t, ack, hex, display), only with --snapshot-every

event

link_lost (with reason) and reconnected, with t

{"t": 1794027869.307, "value": 5.0612, "unit": "V DC", "display": "5.0612 V DC", "state": "NORMAL", "function": "VDC", "type": "ble", "hex": "b4c50008020c00000000000000000000"}

Decimation. --every 1s writes one row per second: the latest reading at that moment. The statistics in the summary then cover only the written rows. Use --every 0 when you need every notification (or to see transients), --every for long runs.

Status lines on stdout while it runs (a started line first, then connected once the adapter is found and armed, then a status line every --status-every):

{"ok":true,"cmd":"log","event":"started","file":"...x.csv","format":"csv","duration_s":3.0,"every_s":0.0,"snapshot_every_s":1.0}
{"ok":true,"cmd":"log","event":"connected","connect_s":2.41,"model":"FLUKE 289","adapter":"FA:28:90:00:00:01"}
{"ok":true,"cmd":"log","event":"status","elapsed_s":3.5,"records":4,"last":"5.0627 V DC"}

Connecting takes time. Every command first has to see the adapter advertise. The IR3000 FC advertises only every few seconds, and right after a previous connection ends it can take about 20 s before it advertises again (measured on hardware). --duration therefore counts from the first connection (connect_s says how long finding the adapter took), so a --duration 30s log really holds about 30 s of readings.

Reconnect and gaps. When the Bluetooth link drops (meter off, adapter asleep, out of range, "no live reading for 10 s") the log prints link_lost (with retry_for_s), writes a link_lost event into a JSONL file, retries every 3 s, and on success prints reconnected (with gap_s) and carries on appending to the same file. If the link does not come back within --reconnect-for, the log ends with stopped_by: "link_lost" and the data written so far is kept. It fails with LINK_LOST only when not one reading was logged; a log that never connected at all fails at once with the real error (NOT_FOUND, DETACHED, ...).

Stopping. open289 stop (from another terminal or process) ends it within about a second; so do Ctrl+C, the duration, and an unrecoverable link loss. The file is flushed after every row, so a killed process loses at most the last row.

Summary (the last line, "event":"done"):

{"ok":true,"cmd":"log","event":"done","file":"...x.csv","format":"csv","records":12,"packets":12,"snapshots":2,"started":1794027863.2,"ended":1794027868.7,"duration_s":5.535,"connect_s":2.41,"logging_s":3.125,"stopped_by":"duration","gaps":[],"stats":{"V DC":{"n":12,"min":5.0614,"max":5.0629,"mean":5.0622}},"meter":{"model":"FLUKE 289","adapter":"FA:28:90:00:00:01"},"display_file":"...x.csv.display.jsonl"}

records rows written, packets notifications received, snapshots display snapshots taken, duration_s the whole command, connect_s the time until the first connection, logging_s the time from then on, stopped_by (duration, stop, link_lost, interrupted), gaps (a list of {from, to, reason}, Unix seconds), stats (n, min, max, mean of the primary value per unit; overloads are left out) and display_file (CSV with snapshots only). --text prints a two-line version with the same numbers.

Saved data

open289 memory                       # everything saved in the meter (--text --detail lists every reading)
open289 recording 0 --out rec0.csv   # download trend recording 0
open289 recording 0 --out rec0.csv --start 40   # resume an interrupted download (appends)

memory reads the counts and then every saved item, and prints one JSON object: counts (measurements, minmax, peak, recordings) and items. Each item has kind (measurement, minmax, peak, recording), index (the index within its kind: this is what recording INDEX takes), name, seq, the same fields as a display snapshot (function, modes, readings, primary, ...), start / end for sessions, and for recordings interval_s, event_threshold, records, reading_index and duration_s. If some items cannot be read the result has "ok":false and an errors list, and the exit code is 6 (the readable items are still listed). Every saved item is one query, so memory has a 600 s limit instead of 120 s.

$ open289 --sim --text memory
saved items: {'recordings': 1, 'minmax': 0, 'peak': 1, 'measurements': 2}
measurement #0   'Save 1'         2026-09-29 03:15:52.785  TEMPERATURE  34.7 °C
measurement #1   'Save 2'         2026-11-01 01:56:42.182  CONTINUITY  OL Ω [OPEN_CIRCUIT]
peak        #0   'Save 3'         2026-11-06 21:29:01.471  V_DC  0.005 V DC
recording   #0   'Save 4'         2026-11-06 21:40:19.248  V_DC  1.9874 V DC  72 records, interval 1 s, 1.0 min

Recording download. recording INDEX fetches the trend records of one recording (the meter's RECORD mode) and writes one row per record. It batches up to 8 queries per round-trip and takes 0.44 s per record on the hardware (--no-batch: one per round-trip, about 1.4 s). A record that fails is retried (up to 3 more times). If the meter shows a menu, the download stops with METER_BUSY and a hint with the record to resume from; so does a failing record (PROTOCOL_ERROR). Resume with the same --out and --start K: the file is appended to, without a second header. --limit K stops before record K (an end index, not a count). Events: started, progress every 10 s (done, of), and done (records, duration_s, s_per_record, retries, file).

CSV columns (JSONL has the same keys): index, start, end (meter local time), unit, primary, primary_state, minimum, minimum_state, average, average_state, maximum, maximum_state, samples, record_type (INTERVAL, ...), stable, transient. Values are in SI base units; an _state of OL means the value is empty.

index,start,end,unit,primary,primary_state,minimum,minimum_state,average,average_state,maximum,maximum_state,samples,record_type,stable,transient
0,2026-11-06 21:40:19.248,2026-11-06 21:40:19.752,VDC,1.4904,NORMAL,1.4903,NORMAL,1.49032,NORMAL,1.4904,NORMAL,5,INTERVAL,STABLE,NON_T

Front-panel keys

press KEY [KEY ...] simulates the keys in order and returns the display state after each one. Keys: F1 F2 F3 F4 UP DOWN LEFT RIGHT HOLD MINMAX RANGE INFO BACKLIGHT ONOFF.

open289 press HOLD                    # toggle HOLD
open289 press RANGE                   # step the range (manual)
open289 press RANGE --held            # back to autorange
open289 press MINMAX                  # start MIN MAX
open289 press MINMAX F4               # stop it (opens the result page), then close the page
open289 press ONOFF F1 --allow-off    # switch the meter off for good

Options: --held sends a long press (HELD_DOWN) for every key; --settle S (default 1.0) waits that long before the state is read back; --allow-off permits ONOFF. Each entry of the result has key, held, ack (OK) and state (a display snapshot; or a text when the meter shows a page, see below). press stops at the first key the meter does not acknowledge. press is the only command that changes the meter's state (apart from raw --unsafe).

$ open289 --sim press HOLD
{"ok":true,"cmd":"press","presses":[{"key":"HOLD","held":false,"ack":"OK","state":{"function":"V_DC","secondary_function":"NONE","autorange":true,"range":5.0,"unit":"VDC","modes":[],...}}]}

What was observed on the hardware:

  • HELD_DOWN is a long press. RANGE held = back to autorange. Held MINMAX behaves like a press.

  • MIN MAX has pages. The first MINMAX starts MIN MAX (modes has MIN_MAX_AVG). The second stops it and opens the result page (F1 restart, F2 save, MINMAX or F4 close). F2 on the result page opens the save page (F1 adds to the name, F2 saves, F4 closes). Closing a page returns to the measurement screen.

  • Why queries return METER_BUSY there. While a menu, result page, save page or setup screen is open, the meter answers every measurement query (QDDB, QSLS, ...) with an execution error. open289 names it METER_BUSY (exit 5). press then shows "state": "menu / result page open (measurement queries blocked)". Close the page with the keys (F4) or on the meter, then retry.

  • BACKLIGHT cycles off, bright, dim, also on the pages.

  • ONOFF is refused (UNSAFE_OPERATION, exit 7) unless --allow-off. With it, the meter asks for confirmation (turning off meter will break communication): F1 confirms, F4 cancels. After F1 the meter is off, the adapter switches itself off a moment later, and neither can be switched on remotely. This cannot be undone from the PC.

Raw commands

raw COMMAND [COMMAND ...] sends meter commands through the pass-through and returns each answer (ack, and text or, for binary replies, binary_hex). Query-only by default: ID, IM and Q* commands (QM, QDDB, QDDA, QBL, QSLS, QMP clock, ...) pass; anything else (MP, CSD = clear memory, RI = reset, OFF, PRESS, ...) is refused with UNSAFE_OPERATION before it is sent. --unsafe lifts the filter; use it only when you know exactly what the command does, because some commands erase saved data or switch the meter off.

$ open289 --sim raw ID QM
{"ok":true,"cmd":"raw","results":[{"command":"ID","ack":"OK","text":"FLUKE 289,V1.71,12345678"},{"command":"QM","ack":"OK","text":"5.0628E0,VDC,NORMAL,NONE"}]}

$ open289 --sim raw MP
{"ok":false,"cmd":"raw","results":[{"command":"MP","error":{"code":"UNSAFE_OPERATION","message":"meter command b'MP' is not a read-only query","hint":"only ID, IM and Q* queries pass by default; add --unsafe for other commands"}}]}

Each command is sent and answered in turn; a refused or failed one is reported in its own result and the exit code is that of the first error (7 for a refusal). A command over 87 bytes is refused before it is sent (see Safety).

Self-test

open289 selftest is a read-only health check of the adapter, the link and the meter (it never changes the meter):

Check

What

S01

adapter advertising

S02

connect and arm the adapter control point

S03

meter attached (FLUKE 287/289, not the detached FLUKE PC3000)

S04

adapter firmware answers

S05

pass-through ID

S06

display snapshot (QDDB) decodes (the meter must be on its measurement screen)

S07

a live reading arrives within 5 s

S08

adapter battery >= 20 %

S09

meter battery not EMPTY / ALMOST_EMPTY

The result lists every check with pass, measured and expected, plus passed and failed counts; the exit code is 1 when a check failed. A failed S01, S02 or S03 ends the run early (the later checks need that link).

$ open289 --sim selftest
{"ok":true,"cmd":"selftest","passed":9,"failed":0,"items":[{"id":"S01","name":"adapter advertising","pass":true,"measured":"FA:28:90:00:00:01","expected":null},...]}

Virtual meter

The package contains a model of an IR3000 FC with a Fluke 289 attached (open289/sim.py), so the whole tool, the MCP server and the test suite run with no Bluetooth and no hardware. Select it with --sim or OPEN289_SIM=1.

It simulates what was measured on the real hardware: the GATT table and the CNX ID write that arms the adapter, the 18-byte control-point write limit, the pass-through framing, the out-buffer overflow (no reply above 128 bytes, a modelled guess; 181 measured) and the adapter hang at 373 bytes and more (open289 itself refuses anything over 87 bytes) when it is overrun, the binary QDDB / QSMR / QMMSI / QPSI / QRSI / QSRR layouts, the 16-byte live notifications at 4/s, the single connection (a second process is refused and the adapter stops advertising), the front-panel keys with their pages (MIN MAX result and save pages, power-off confirmation, backlight cycle), a meter that is switched off and an adapter that goes to sleep, saved memory with two measurements, a peak session and a 72-record recording. Behaviour that was never observed on hardware is marked GUESS in the code. Identity: address FA:28:90:00:00:01, serial 12345678. It is a stand-in for tests and demos, not a measurement instrument: the "readings" are synthetic.

The state of the virtual device is one JSON file shared by every process (state/sim_state.json in the data folder), so open289 press HOLD in one command is seen by the next.

python -m open289.sim show                            # the state as JSON
python -m open289.sim reset                           # factory state
python -m open289.sim power-cycle                     # switch adapter and meter on again (clears an adapter hang)
python -m open289.sim set function=V_AC input=open meter_on=false
python -m open289.sim audit [--clear]                 # every GATT write the tool made (state/sim_writes.jsonl)

set keys: function (V_DC V_AC MV_DC MV_AC OHMS CONTINUITY DIODE_TEST TEMPERATURE MA_DC, and V_AC_HZ / V_DC_HZ for the Hz secondary function), secondary (NONE / HERTZ), input (the input of the current function; open for OL), inputs.<FUNCTION>, hz, meter_on, adapter_on, adapter_hung, autorange, range_index, screen (normal, minmax_result, save_page, power_off_confirm), backlight, modes, seed, clock_offset. Environment: OPEN289_SIM_RATE (live notifications per second, default 4), OPEN289_SIM_DELAY (latency of a pass-through send, default 0.05 s; the hardware takes about 0.6 s), OPEN289_SIM_STATE, OPEN289_SIM_AUDIT (file overrides). OPEN289_NO_BLE=1 makes the real Bluetooth backend refuse to start (tests set it, so a test can never reach hardware).

CLI reference

Global options go before the command:

Option

Meaning

--address, -a ADDRESS

adapter BLE address (default: the first IR3000 FC found)

--sim

use the virtual IR3000 + Fluke 289 (no hardware)

--text

human-readable output instead of JSON

--verbose, -v

log adapter control-point traffic to stderr

--max-wall MAX_WALL

time limit in seconds for one-shot commands (default 120; memory 600). live, log, recording and read --every have no wall limit

--version

print the version

open289 <command> --help lists the options of one command.

Command

Purpose

Options (defaults)

scan

find IR3000 FC adapters

--seconds SECONDS (10)

info

adapter and meter identity, battery, memory level, clock, saved counts

read

full display state now (function, range, modes, all readings)

--every EVERY (one snapshot per interval, e.g. 1s), -n, --count COUNT (with --every: stop after N snapshots), --for FOR_ (with --every: stop after this long, e.g. 5m)

live

real-time readings from BLE notifications (~4/s), one JSON line each

-n, --count COUNT, --for FOR_ (e.g. 30s), --raw (include the raw bytes), --timeout TIMEOUT (10 s without a reading is an error)

log

log readings to a CSV / JSONL file

--out, -o OUT (data/log_<time>.csv), --format {csv,jsonl}, --duration, -d DURATION (forever), --every EVERY (0), --snapshot-every SNAPSHOT_EVERY (0), --status-every STATUS_EVERY (10s), --reconnect-for RECONNECT_FOR (5m)

memory

saved measurements, MIN MAX, peak sessions and recordings

--detail (with --text: list every reading)

recording

download trend recording INDEX to CSV / JSONL

index, --out, -o OUT (data/recording<INDEX>_<time>.csv), --format {csv,jsonl}, --start START (0), --limit LIMIT (end index), --no-batch

press

simulate front-panel keys

KEY [KEY ...], --held, --settle SETTLE (1.0 s), --allow-off

raw

raw meter commands (ID, IM and Q* unless --unsafe)

COMMAND [COMMAND ...], --unsafe

selftest

read-only health check of adapter, link and meter

stop

end a running live / read --every / log cleanly

Durations (--every, --for, --duration, --snapshot-every, --status-every, --reconnect-for) take ms, s, m (or min), h, d; a bare number is seconds: 500ms, 30, 90s, 5m, 1.5h, 2d. forever (also inf, until-stop) means no limit (for --duration).

stop writes a request file that a running live, read --every or log (in any process) sees within about a second; it does not need the adapter. recording and the one-shot commands do not react to it.

Output format

Every command prints JSON on stdout, one object per line, compact. One-shot commands print one object. live, read --every, log and recording print event lines and end with an "event": "done" summary. Event lines carry "ok": true, "cmd": ..., "event": ... (started, connected, status, progress, link_lost, reconnected, done); live readings are {"type":"reading", ...}. The first field of a result is ok. Warnings and --verbose traffic go to stderr. Values are in SI base units; display is the text from the meter. Times: t, pc_time, started, ended are Unix seconds on the PC; time, start, end, info.meter.clock, and the numeric ts / clock_epoch, are the meter's local clock (no timezone).

Errors are one object and a non-zero exit code:

{"ok":false,"cmd":"read","error":{"code":"METER_BUSY","message":"'QDDB' -> EXECUTION_ERROR","hint":"the meter is on a menu, result page or setup screen: close it on the meter (or with open289 press), then retry"}}

--text prints a human-readable form of the same data: one line per reading for live (HH:MM:SS.mmm value unit and the secondary after |), a short table for read, memory, press and selftest, one line per record for recording, and a short summary for log. Errors in --text are error CODE: message plus a hint: line. Scripts and agents should use the JSON.

Errors and exit codes

Every error has a code, a message and (mostly) a hint; the exit code follows the code.

Code

Exit

Meaning and typical fix

INVALID_ARG

2

an argument cannot be parsed or is out of range (duration, key, index): fix the command

NOT_FOUND

3

no IR3000 FC is advertising: wake the adapter (button until the LED flashes green), come closer, close Fluke Connect on phones

BUSY

4

another open289 process holds the adapter (wait, or open289 stop); in the MCP server also a running background log

METER_BUSY

5

the meter answers EXECUTION_ERROR: a menu, result page, save page or setup screen is open; close it on the meter or with press F4

DETACHED

5

the adapter reports no powered meter (per the Fluke app it then reports FLUKE PC3000; not observed on hardware): seat it on the IR port, switch the meter on

NO_DATA

5

the meter has no data for the request, e.g. no saved item with that index: check memory

TIMEOUT

6

a one-shot command passed --max-wall, or no reading for --timeout seconds: move closer, check the meter is on, raise --max-wall

BLE_ERROR

6

scan or connect failed: is Bluetooth switched on? (also: OPEN289_NO_BLE is set)

LINK_LOST

6

the Bluetooth link dropped (a log reconnects by itself within --reconnect-for)

PROTOCOL_ERROR

6

an unexpected or undecodable answer; a recording download names the record to resume from

METER_ERROR

6

the meter did not accept the command (syntax error) or did not answer

WRITE_FAILED

6

reserved (not emitted yet; a failed Bluetooth write currently surfaces as INTERNAL)

UNSAFE_OPERATION

7

refused by the safety rules: a non-query raw without --unsafe, ONOFF without --allow-off, a send over 87 bytes

STOPPED

0

reserved; a stopped run ends normally with stopped_by: "stop"

INTERNAL

1

unexpected exception (the result carries the last trace lines); anything else maps to exit 1

Exit 0 is success; Ctrl+C gives 130. A failed selftest (a check failed, it is not an error object) and a memory where some items could not be read exit 1 and 6 respectively. The MCP server uses the same codes and adds TIMEOUT for tool calls past max_wall, CANCELLED and SELFTEST_FAILED.

MCP server

open289-mcp (or python -m open289.mcp_server) serves the meter over the Model Context Protocol (stdio, standard library only; installed with pip install open289):

claude mcp add open289 -- open289-mcp

If pip's Scripts folder is not on PATH, give the full path to open289-mcp. Other clients: {"mcpServers": {"open289": {"command": "open289-mcp"}}}. Each tool call runs one CLI command in a child process, so the adapter is connected and released per call, a hang never blocks the server, and every rule of the CLI applies (safety filter, adapter lock, error codes). The server's own instructions tell the agent to start with status.

11 tools:

Tool

Runs

Key arguments (defaults)

status

info

max_wall. Adapter address / firmware / battery, meter model / firmware / serial / battery / memory level / clock, saved counts. meter.attached false = not on a powered meter

read

read

max_wall. One display snapshot: function, range, modes, primary and every other reading

live

live

count (20, 1..400), seconds (0.1..120; with count: whichever comes first), max_wall. A summary instead of the lines: count, rate, first / last time, min / max / mean of the primary per unit, functions and units seen, the last 5 readings

memory

memory

max_wall (600). Counts and every saved item

download_recording

recording

index (required), out, format, start (0), limit, max_wall (900). File, number of records, seconds per record, retries, the recording header. A failed result carries resume with the start and out to continue with

press

press

keys (required, 1..20 of F1 F2 F3 F4 UP DOWN LEFT RIGHT HOLD MINMAX RANGE INFO BACKLIGHT ONOFF), held (false), allow_off (false), max_wall. The display state after each key

query

raw

commands (required, 1..10, e.g. ["ID","QM"]), max_wall. Read-only: ID, IM, Q*; anything else is refused with UNSAFE_OPERATION before it reaches the meter (there is no --unsafe in MCP)

selftest

selftest

max_wall. The checks S01-S09; a failure returns SELFTEST_FAILED with the failed checks

log_start

log in the background

duration (10m; forever = until log_stop; must be > 0), every (0), snapshot_every (0), out, format, reconnect_for (5m). Returns a job id and the file

log_status

none (reads the job's output)

job (the latest). State (running / done / failed), records so far, the last status line, the file, and when ended its summary

log_stop

stop

job (the latest). Stops cleanly and returns the summary: file, records, duration, min / max / mean per unit, link gaps. Without job it also stops a log or live started elsewhere (a terminal, another client); already_ended: true = it had ended before

status, read, live, memory, query, selftest, log_status are marked read-only. All durations are strings ("30s"), all numbers are SI.

Background logging. log_start returns at once (after the first reading, at most 30 s) with a job id; the log keeps running when the client disconnects, and its output and metadata are kept in state/mcp_jobs/<job>/, so a restarted server, or another client's server, still sees it. Follow it with log_status, end it with log_stop (or it ends after duration, or when the link stays lost longer than reconnect_for). The file holds the data; the tool results are summaries and never hundreds of readings.

BUSY rules. The adapter accepts one connection, so tools run one at a time, and while a background log runs, every other meter tool (status, read, live, memory, press, query, ...) returns BUSY until log_stop (log_status and log_stop still work). BUSY also means another open289 (a terminal running live/log, another client) holds the adapter. Fluke Connect on a phone shows up as NOT_FOUND (the adapter stops advertising). METER_BUSY is different: a menu or result page is open on the meter; close it (press F4) and retry. Safety for agents: press changes the meter (HOLD, MIN MAX, range); ONOFF needs allow_off: true and should be used only when the user asked for it: it switches the meter off for good, and the adapter goes to sleep with it.

CLI commands with no MCP tool: scan (the server finds the adapter itself), stop (used internally by log_stop), and raw --unsafe (deliberately not exposed). An agent that has a shell can use the CLI for all of them.

Data folders

open289 keeps its files in one folder with two subfolders:

  • state/: the adapter lock (open289.lock), the stop request (stop.request), MCP background jobs (mcp_jobs/) and the virtual meter's state (sim_state.json, sim_writes.jsonl).

  • data/: the default place of logs (log_<time>.csv) and recording downloads (recording<INDEX>_<time>.csv). --out writes anywhere else; relative paths are relative to the current folder.

Where the folder is: OPEN289_HOME if set; else, in a source checkout (a pyproject.toml next to the package), the checkout itself; else, for an installed package, %LOCALAPPDATA%\open289 on Windows and ~/.local/share/open289 elsewhere, so nothing is written into site-packages. The MCP server's child processes use the same folder.

Development

Layout:

  • open289/ package: cli.py (commands, output), transport.py (backend selection real or virtual, discovery, connection), adapter.py (IR3000 service client, pass-through, the opcode allowlist, query filter and the 87-byte limit), meter.py (the meter's remote commands and the binary record decoders), reading.py (live notification decoder), records.py (JSON-ready dictionaries), logger.py (long-running log with reconnect), lock.py (single client lock), errors.py, paths.py, sim.py (virtual meter) and mcp_server.py (MCP server).

  • tests/ offline tests against the virtual meter: decoders (against real captures), the transport and its safety rules, the CLI, the MCP server (JSON-RPC, jobs, BUSY rules) and the simulator itself.

  • docs/PROTOCOL.md (and PROTOCOL.zh-CN.md): the protocol, with evidence levels.

  • tools/ diagnostic scripts for the hardware.

  • .github/workflows/: ci.yml and release.yml.

python -m pip install -e ".[test]"
OPEN289_SIM=1 OPEN289_NO_BLE=1 python -m pytest tests -m "not hw" -q

The tests set the simulator environment themselves (tests/conftest.py: a private OPEN289_HOME per test, OPEN289_SIM=1, OPEN289_NO_BLE=1, a fast simulator), so a test can never reach Bluetooth. Tests marked hw need a real adapter and meter and are skipped by default (pytest -m hw).

CI (ci.yml, on pushes to main, pull requests and by hand): the tests on Windows and Linux with Python 3.10, 3.12 and 3.14, all against the virtual meter; a check that tracked files carry no local paths; a package build with twine check --strict and a smoke test of the built wheel (open289 info, open289 live -n 5 on the virtual meter).

Release (release.yml): push a tag vX.Y.Z equal to open289.__version__ (v0.1.0 for 0.1.0). The workflow runs the CI checks, refuses a tag that does not match the version, builds, creates a GitHub release with the files, and publishes to PyPI by trusted publishing (no token). One-time setup on pypi.org: add a (pending) trusted publisher for the project open289 with owner FrostFlops, repository open289, workflow release.yml and environment pypi, and create the environment pypi in the repository's settings.

Protocol

How the adapter and the meter talk (GATT table, the CNX ID write that arms the control point, the pass-through framing, buffer limits, the 16-byte live packet layout, the binary saved-data records, key behaviour), with every statement tagged VERIFIED (seen on the hardware) or DERIVED (not exercised): docs/PROTOCOL.md (中文). Observed live encodings per function: section 4.8 of docs/PROTOCOL.md.

What is NOT here

Fluke's application (Fluke Connect), anything decompiled from it, and the private reverse-engineering workspace are kept out of this repository. The firmware images and updater of the adapter are not used.

Safety

  • The tool never writes the adapter's firmware-update characteristics (the MSP updater and the radio-image updater; writing the wrong value there erases flash) and never sends the adapter opcodes 0x90 to 0x94 (the 1550 / 1555 insulation-test commands, which switch a high voltage).

  • The adapter's out-buffer is capped at 87 bytes per send: larger sends got no reply or hung the adapter until it was power-cycled (373 bytes and more), so open289 refuses them.

  • raw is query-only (ID, IM, Q*) unless --unsafe. MP can change settings, CSD erases saved data, RI resets the meter, OFF switches it off.

  • press ONOFF needs --allow-off, and the meter asks for confirmation (F1). OFF and ONOFF cannot be undone remotely: neither the meter nor the adapter can be switched on again from the PC.

  • press otherwise changes meter state (HOLD, MIN MAX, range, saving a measurement on the save page): it is not read-only, and an agent should only use it on request.

  • The adapter accepts one Bluetooth connection: close Fluke Connect on phones. open289 holds a lock so that only one open289 process connects at a time.

  • This is a measurement reader, not a safety device. Respect the meter's category rating and its own safety rules; open289 does not make a measurement safer.

License

GPL-3.0-only (see LICENSE). Fluke, Fluke Connect and IR3000 are trademarks of Fluke Corporation; this project is not affiliated with Fluke Corporation.

Available Tools

11 tools
download_recordingDownload a trend recordingA

Download trend recording index (see memory) to a CSV or JSONL file, one row per record (start/end time, primary, minimum, average, maximum, unit, states). Long recordings take minutes over Bluetooth. Returns the file, the number of records, seconds per record, retries and the recording header. An interrupted download resumes with start and the same out (a failed result names both under resume).

NameRequiredDescriptionDefault
outNooutput file (default data/recording<index>_<time>.csv in the open289 folder)
indexYesrecording index (0 = the first)
limitNostop before this record index (an end index, not a count)
startNofirst record (resume an interrupted download into the same out file)
formatNofile format (default: from the out extension, else csv)
max_wallNotime limit for the whole call in seconds (default 900); past it the command is stopped and TIMEOUT is returned

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does a good job: it warns that long recordings take minutes over Bluetooth, describes resume-after-interruption semantics, and enumerates the return payload (file, record count, seconds per record, retries, header). It omits file-overwrite behavior when 'out' already exists and does not discuss permissions or failure modes beyond timeouts.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core action and target format lead the description, followed by operational caveats and return shape — a sensible ordering. The parenthetical column list is slightly dense but earns its place by telling the agent what a row contains.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a six-parameter, file-writing tool with no output schema, the description covers the essentials: what is written, what is returned, latency expectations, and resume behavior. The main residual gaps are overwrite semantics for an existing 'out' path and interaction between `limit` and resume.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so every parameter is already documented, and the description's resume note largely restates what the `start` field says. It adds no semantics for `limit`, `max_wall`, or the default-path behavior of `out`, so the schema is doing the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Download trend recording `index`') plus the output format (CSV/JSONL) and row shape. It also routes the agent to the sibling `memory` tool to obtain the index, so it is distinguishable from `read`, `query`, and `live` without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear context: use this to pull a whole recording to a file, and use `memory` for indices. It also documents the resume path ('start' plus the same 'out'). It does not explicitly state when to prefer a sibling such as `read` or `query` for lighter-weight access, so it stops short of full when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

liveReal-time readingsA
Read-only

Collect real-time readings from the adapter (about 4 per second) for count readings or seconds, and return a summary instead of the readings: count, rate, first/last time, min/max/mean of the primary value per unit, the function(s) and unit(s) seen and the last 5 readings. Default: 20 readings. For longer runs or a file use log_start.

NameRequiredDescriptionDefault
countNostop after this many readings (1..400)
secondsNostop after this long (0.1 to 120 s); with count: whichever comes first
max_wallNotime limit for the whole call in seconds (default: 120, or longer for a large count / seconds)

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true; the description adds meaningful behavior: the ~4 Hz sampling rate, that results are aggregated not streamed, the exact summary fields returned (count, rate, first/last time, min/max/mean, functions/units, last 5 readings), and the default window. It does not discuss adapter error behavior or what happens if the adapter is disconnected.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core action, sampling rate, stop conditions, and return shape are front-loaded in one dense sentence, with the routing hint to log_start placed last. The enumeration of summary fields is long but earns its place since there is no output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the return-value burden and does so thoroughly, listing every summary field. Combined with the sampling rate, entry conditions, defaults, and the log_start alternative, an agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so all three parameters are already documented, including the count/seconds 'whichever comes first' semantics. The description restates the default of 20 readings and the count-or-seconds choice but never mentions max_wall and adds no syntax or interaction detail beyond the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Collect real-time readings from the adapter'), the sampling rate (~4/second), and crucially that it returns a summary rather than raw readings. The contrast with log_start makes it distinguishable from the closest sibling without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes long-running or file-persisting cases to log_start, which is the key disambiguation for this tool, and gives the default of 20 readings. It does not differentiate from other siblings such as read, query, or status, so the guidance is clear but not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

log_startStart a background logA

Start logging live readings to a CSV or JSONL file in the background and return the job id and the file. The log ends after duration, at log_stop, or when the Bluetooth link stays lost longer than reconnect_for; it keeps running if this client disconnects. One log at a time, and meanwhile the other meter tools return BUSY. Follow it with log_status, end it with log_stop.

NameRequiredDescriptionDefault
outNooutput file (default data/log_<time>.csv in the open289 folder)
everyNowrite one reading per interval, e.g. '1s'; '0' = every reading (about 4/s)0
formatNofile format (default: from the out extension, else csv)
durationNoe.g. '30s', '10m', '8h' or 'forever' (no limit: it ends at log_stop); must be longer than 010m
reconnect_forNokeep retrying this long after the link is lost (default '5m'; '0' = end the log)
snapshot_everyNoalso save the full display (modes, MIN/MAX/AVG) at this interval, e.g. '10s' (a CSV log puts them in <file>.display.jsonl); '0' = off0

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so well: it discloses background execution, return values, termination triggers (duration, log_stop, prolonged Bluetooth loss), client-disconnect persistence, and concurrency BUSY behavior. These are exactly the operational traits an agent needs before invoking it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with purpose, then lifecycle, then constraints. Every sentence adds operational value; there is no repetition or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a background logging tool with six optional parameters and no output schema, the description covers what is started, what is returned, how it ends, concurrency limits, and sibling follow-up tools. Nothing essential for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already defines each parameter including defaults and examples; the description adds only high-level lifecycle context for duration and reconnect_for. It does not add syntax or format details beyond the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource ('Start logging live readings...') and names output formats, background execution, and return values (job id and file). It also distinguishes this tool from log_status and log_stop by defining their roles in the lifecycle.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear lifecycle guidance: follow with log_status, end with log_stop, one log at a time, and other meter tools return BUSY while active. It also states termination conditions. It does not explicitly say when to use this instead of live/read for one-off readings, so it stops short of full alternative routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

log_statusBackground log statusA
Read-only

State of a background log (running / done / failed), the records written so far, the last status line, the file, and once it has ended its summary (min/max/mean per unit, link gaps).

NameRequiredDescriptionDefault
jobNojob id from log_start (default: the latest log)

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

readOnlyHint=true already covers the safety profile, so the description earns credit for adding genuinely new behavior: the summary fields (min/max/mean per unit, link gaps) only appear once the log has ended, which is non-obvious timing information an agent needs. It stops short of saying whether the call blocks, whether state transitions are push or poll, or what happens for an unknown or expired job id.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One dense but well-ordered sentence that front-loads the core state before the trailing detail. No filler, though the long parenthetical list makes it slightly harder to scan than a two-sentence split would be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description takes on the burden of describing return values and does so field by field, including the conditional post-completion summary. The main residual gap is that nothing describes the failed case beyond a label (error text, exit codes) or how to retrieve the referenced file.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single `job` parameter has full schema description coverage including its default ('the latest log'), so the schema carries the semantics. The description adds nothing about the parameter, and 100% coverage makes 3 the correct baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource (a background log) and enumerates the exact facets returned: state, records written, last status line, file, and post-completion summary. An agent can tell this is a read-only progress/inspection call, but the description never names the sibling `status`, `live`, or `log_start` tools, so differentiation from `status` is left to inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: 'background log' plus the schema note 'job id from log_start' hints this is the follow-up to log_start for polling progress. There is no explicit statement of when to prefer this over `status` or `live`, nor whether polling repeatedly is expected.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

log_stopStop the background logA

Stop the background log cleanly and return its summary: file, records, duration, min/max/mean per unit, link gaps. Without job it also stops a log or live started elsewhere (a terminal, another client); already_ended true = the log had ended before this call (the summary is its old one).

NameRequiredDescriptionDefault
jobNojob id from log_start (default: the latest log)

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses a non-obvious cross-session side effect (stopping a log or live started in a terminal or another client when `job` is omitted) and explains the already_ended flag semantics, including that the summary is stale in that case. It does not cover failure modes, whether the log file is retained, or permission requirements, keeping it short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and return payload, then the conditional behavior and edge case. The parentheticals are dense but each clause carries information; the second sentence runs long and could be split for readability, but there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and no annotations, the description supplies the return fields inline and covers the default-path behavior and the already-ended edge case, which is what an agent needs to call it correctly. Missing only error behavior when no log exists and whether data is preserved after stopping.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100% for the single `job` parameter, so the baseline is 3. The description earns above baseline by explaining the consequence of omitting `job` — it will reach out and stop a log started elsewhere, not merely default to the latest one — which is meaning the schema does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Stop the background log') and immediately enumerates what comes back (file, records, duration, min/max/mean per unit, link gaps). This is clearly distinguishable from log_start, log_status, and live, which would start, inspect, or stream rather than terminate and summarize.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the primary use case clear (terminate a running log and get its summary) and adds a useful conditional: without `job` it will also stop a log or live started elsewhere. It stops short of explicit routing guidance such as 'use log_status to inspect without stopping' or stating what happens when nothing is running, so there is context but no exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

memorySaved itemsA
Read-only

What is saved in the meter's memory: the counts and every saved measurement, MIN MAX session, peak session and trend recording (name, time, function, primary value and the other readings; recordings also interval, number of records and duration). download_recording fetches the records of a recording; the index is the item's index within its kind.

NameRequiredDescriptionDefault
max_wallNotime limit for the whole call in seconds (default 600); past it the command is stopped and TIMEOUT is returned

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The readOnlyHint annotation already establishes that this is a safe read operation. The description adds meaningful context beyond that: it lists the kinds of saved items, the fields each item exposes, and clarifies that recordings include interval, number of records, and duration. It does not describe pagination, error behavior, or response format, but it substantially improves behavioral understanding.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded and dense, with no filler sentences. The long parenthetical listing fields is somewhat hard to parse, but every clause conveys relevant structure. It is appropriately sized for the amount of return-shape information it needs to convey.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description carries the burden of explaining return values, and it does so thoroughly by enumerating item kinds and fields. It also explains the index relationship to download_recording. Minor gaps remain around pagination and error responses, but the definition is complete enough for an agent to understand what memory returns.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single parameter max_wall has complete schema description coverage, including its default and timeout behavior. The description does not mention max_wall or add any meaning beyond the schema. With 100% coverage, the schema carries parameter semantics adequately, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that this tool reports what is saved in the meter's memory, enumerating counts, measurements, MIN MAX sessions, peak sessions, and trend recordings with their fields. It distinguishes the index from the record-fetching behavior of download_recording. The purpose is clear, though it reads more as a data inventory than a crisp verb-resource statement.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage by explaining that download_recording fetches the records of a recording, while this tool provides the index and item metadata. It does not explicitly state when to call memory versus status, read, or query, nor does it give exclusions or prerequisites. The guidance is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

pressPress meter keysA

Simulate front-panel keys in order (soft keys F1-F4 under the display, the arrows, HOLD, MINMAX, RANGE, INFO, BACKLIGHT, ONOFF) and return the display state after each key. held: a long press (RANGE held = back to autorange). ONOFF switches the meter OFF for good (it cannot be switched on remotely, and the adapter goes to sleep with it): it is refused unless allow_off is true; the meter then asks for confirmation (F1 confirms, F4 cancels).

NameRequiredDescriptionDefault
heldNolong press (HELD_DOWN) for every key
keysYeskeys, pressed in order
max_wallNotime limit for the whole call in seconds (default 120); past it the command is stopped and TIMEOUT is returned
allow_offNopermit ONOFF, which switches the meter off; only when the user asked for it

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses that display state is returned after each key, explains held behavior including RANGE autorange, and warns that ONOFF permanently powers off the meter, cannot be reversed remotely, puts the adapter to sleep, is refused unless allow_off is true, and requires F1/F4 confirmation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a compact, front-loaded paragraph: main action first, then held semantics, then the ONOFF safety behavior. Every sentence adds useful information without repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation-style tool with no output schema and no annotations, the description covers the important return value, the sequential key behavior, long-press semantics, and the irreversible ONOFF flow. No critical invocation detail is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds meaningful semantics beyond the schema: held is a long press with special RANGE behavior, and allow_off controls the ONOFF safety gate and confirmation flow. max_wall is left to the schema, which is acceptable.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (simulate) and resource (front-panel keys), lists the supported keys, and says it returns the display state after each key. The purpose is clear and distinct from sibling tools such as status, read, and live.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives important invocation constraints for held presses and ONOFF, but it does not explicitly say when to use this tool instead of reading state through siblings. Usage is implied by the purpose and special-key rules.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

queryRead-only meter queryA
Read-only

Send read-only remote commands to the meter and return each answer (text, or hex for binary replies): ID (model, firmware, serial), IM, and Q* queries such as QM (primary measurement), QDDB (display data), QBL (battery), QSLS (saved counts), QMP . Anything else (PRESS, MP, CSD, RI, OFF ...) is refused with UNSAFE_OPERATION before it reaches the meter.

NameRequiredDescriptionDefault
commandsYese.g. ['ID', 'QM']
max_wallNotime limit for the whole call in seconds (default 120); past it the command is stopped and TIMEOUT is returned

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so the safety profile is covered structurally. The description does add real behavior beyond that: the UNSAFE_OPERATION refusal happens before the command reaches the meter, and binary replies come back as hex. It does not discuss per-command failure modes or what a partial multi-command failure looks like, so it adds useful but not complete context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and result, then the whitelist, then the refusal rule. Dense but nearly every clause carries information; the parenthetical command examples are slightly list-heavy but functional.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description correctly takes on the job of explaining responses (text, or hex for binary). Combined with the schema's timeout semantics, an agent has enough to call this correctly; only multi-command failure ordering is unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not: it defines which command strings are legal and shows the QMP <property> sub-syntax, well past the schema's bare example ['ID', 'QM']. Timing semantics for max_wall are already in the schema and are not repeated unnecessarily.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb plus resource: 'send read-only remote commands to the meter and return each answer'. It enumerates the accepted command vocabulary (ID, IM, Q*, QMP <property>), which lets an agent distinguish this from siblings like press or memory without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete when-to-use scope via the command whitelist and states the exclusion case: anything else (PRESS, MP, CSD, RI, OFF) is refused. It stops short of naming the specific sibling tool (press) an agent should call instead for those operations, leaving that routing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

readRead the displayA
Read-only

One snapshot of the meter display: function (V_DC, OHMS ...), secondary function, range and autorange, modes (HOLD, MIN_MAX_AVG, REL, RECORD ...), the primary reading and every other reading on screen (MINIMUM, MAXIMUM, AVERAGE, SECONDARY, REL_REFERENCE ...). value is in SI base units, display is the text the meter shows. METER_BUSY = a menu or result page is open.

NameRequiredDescriptionDefault
max_wallNotime limit for the whole call in seconds (default 120); past it the command is stopped and TIMEOUT is returned

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds valuable behavior beyond annotations by explaining the returned display contents, the SI-base-unit meaning of value, the display text field, and the METER_BUSY error condition.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but front-loaded around the snapshot concept and then systematically lists returned components. The parenthetical enumerations are somewhat long, but every clause earns its place by clarifying return content or error behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the burden of explaining return values, and it does so well by naming the display fields, units, and METER_BUSY state. It is not fully complete on sibling selection or the max_wall parameter, but the core read behavior is sufficiently covered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the only parameter, max_wall, is fully documented in the input schema including its default, bounds, and timeout behavior. The description adds no additional parameter meaning beyond what the schema already provides, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the resource and scope: 'One snapshot of the meter display,' then enumerates what the snapshot includes. It is specific enough for an agent to understand the tool, but it does not explicitly contrast itself with siblings such as live or status.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'One snapshot' implies this is for a single sample rather than continuous streaming, which is useful context against the live sibling. However, the description never explicitly states when to use this tool instead of live, status, or memory, nor does it name alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

selftestSelf-testA
Read-only

Read-only health check of adapter, link and meter: adapter advertising, connection, meter attached, adapter firmware, ID, display snapshot, a live reading within 5 s, adapter and meter battery. Never changes the meter. SELFTEST_FAILED lists the failed checks (measured vs expected).

NameRequiredDescriptionDefault
max_wallNotime limit for the whole call in seconds (default 120); past it the command is stopped and TIMEOUT is returned

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only supply readOnlyHint=true; the description independently reinforces safety ('Never changes the meter') and adds behavior an agent cannot get elsewhere: the check includes a live reading that can take up to 5 s, and failures are surfaced as SELFTEST_FAILED with measured vs expected values. It does not quantify total runtime or describe the success-path return shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences, front-loaded with the read-only nature and then the concrete check list. The enumeration is dense but every item earns its place by telling the agent what a failure could mean; no filler or restated title beyond 'Self-test'.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-required-parameter, no-output-schema tool, the description covers what is validated and how failure is reported, which is the information an agent needs. The only soft spot is no guidance on reading/acting on the result or on choosing max_wall, minor given the 100% schema coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the sole parameter max_wall (default 120 s, TIMEOUT on expiry) is already fully documented in the schema. The description says nothing about the time limit or how a 5 s live reading interacts with it, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and scope ('Read-only health check of adapter, link and meter') and then enumerates exactly what is verified — advertising, connection, meter attached, firmware, ID, display snapshot, live reading within 5 s, batteries. This is clearly distinguishable from data-reading siblings like read, live, status, and query.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'health check' framing implies when to reach for it (diagnosing connectivity/setup problems), but there is no explicit when-to-use versus status/live, and no stated prerequisites or exclusions. An agent must infer that this is the diagnostic path rather than the data-retrieval path.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

statusAdapter and meter statusA
Read-only

Is the IR3000 FC adapter reachable and a Fluke 287/289 attached and on? Adapter address, firmware and battery %; meter model, firmware, serial, battery, memory level and clock; the number of saved items. meter.attached false = the adapter is not on a powered meter. NOT_FOUND = the adapter is not advertising (press its button until the LED flashes green; close Fluke Connect on any phone).

NameRequiredDescriptionDefault
max_wallNotime limit for the whole call in seconds (default 120); past it the command is stopped and TIMEOUT is returned

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses the exact returned information, defines meter.attached false as no powered meter, and explains NOT_FOUND as the adapter not advertising with a concrete recovery action. That is unusually rich behavioral context for a read-only status tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but front-loaded: the core status question comes first, followed by returned fields and failure semantics. It is slightly run-on in listing adapter and meter fields, but nearly every phrase carries useful information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the return-value burden and does so by naming adapter address, firmware, battery, meter model, serial, memory, clock, saved-item count, and key failure states. It omits response formatting details, but is otherwise complete for this read-only status probe.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The only parameter, max_wall, is fully documented in the schema with its default, bounds, and TIMEOUT behavior. The description adds no additional meaning about this parameter, so the schema baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a precise question and resource: whether the IR3000 FC adapter is reachable and a Fluke 287/289 meter is attached and powered. It also enumerates the adapter and meter fields returned, so an agent can tell this is a connectivity/status probe rather than a measurement read or self-test.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It implies the tool is used to verify adapter and meter presence before other operations, and gives troubleshooting guidance for NOT_FOUND. However, it does not explicitly say when to prefer this over selftest, log_status, or query, nor does it state prerequisites beyond the NOT_FOUND remedy.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 11 tool updatesv0.1.0
    • First observeddownload_recording
    • First observedlive
    • First observedlog_start
    • First observedlog_status
    • First observedlog_stop
    • First observedmemory
    • First observedpress
    • First observedquery
    • First observedread
    • First observedselftest
    • First observedstatus

TDQS

A3.9/5.0

Scored across 11 tools

Disambiguation4/5

Most tools map to distinct actions such as read, press, logging, memory inventory, and recording download. However, status and selftest both report adapter/link/meter health and battery state, and query can duplicate display data available through read, so an agent may occasionally choose the wrong diagnostic tool.

Naming Consistency3/5

All names use lowercase snake_case, but the pattern is mixed: some are single-word noun-like names (memory, status), some are verbs (read, press, query), and the logging tools use a log_* prefix. It remains readable, but there is no predictable verb_noun convention across the set.

Tool Count5/5

Eleven tools is well within the expected range for a meter-control server and each tool covers a meaningful workflow. The set divides cleanly into reading, logging, memory, control, and diagnostics without obvious filler tools.

Completeness4/5

The surface covers connection/health checks, display snapshots, live capture, background logging, keypress control, memory inventory, recording download, and diagnostics. Minor gaps remain, such as no direct retrieval or clearing of saved measurement items beyond trend recordings, but common workflows are supported.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to control Bluetooth audio devices via MCP tools, including battery status, connect/disconnect, find-my, and snoop decoding.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants to automatically discover and control supported laboratory instruments over USB, LAN, and serial connections, including power-supply output, setpoint, and protection management plus multimeter measurements.
    2
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Enables AI clients to scan for and connect to compatible BLE devices, control suction, vibration, linear, and rotary outputs, and safely stop or disconnect through MCP tools.
    22
    1
    MIT