Skip to content

Multi-Backend Setup

A single backend controls a set of instruments — one GPIB bus through an AR488 adapter, a group of LAN instruments through pyvisa-py, or everything visible to NI-VISA. When your equipment spans multiple transports or physical locations, mcpyvisa manages them all from one server. This tutorial shows you how to configure multiple backends and run cross-backend operations.

Common scenarios:

  • Mixed instrument buses — GPIB instruments on an AR488 adapter and newer USB-TMC or LAN instruments connected directly. Each bus type needs its own backend.
  • Separate test benches — one bench for analog measurements, another for RF or power. Each has its own GPIB bus and adapter.
  • Mixed transports — a USB-connected AR488 adapter on the bench in front of you, and a WiFi adapter on a rack across the lab.
  • Isolation — sensitive measurement equipment on one bus, noisy power supplies on another, to avoid bus contention and ground loops.
  • Scale — more than 30 GPIB instruments. Each GPIB bus supports 30 addresses, so a second backend doubles your GPIB capacity. USB-TMC and LAN backends have no such limit.

Each backend operates independently. The server talks to them concurrently, and serializes commands within each backend using per-backend locks.

Create or update your configuration file with multiple [[backend]] sections. The double brackets in TOML denote an array — each [[backend]] block defines one backend.

~/.config/mcpyvisa/config.toml
[server]
log_level = "INFO"
auto_discover = true
auto_identify = true
# AR488 adapter on the local bench (GPIB instruments)
[[backend]]
name = "bench-a"
type = "ar488"
transport = "serial"
port = "/dev/ttyUSB0"
baudrate = 115200
auto_discover = true
auto_identify = true
read_timeout_ms = 3000
inter_command_delay_ms = 10
# pyvisa-py for LAN and USB-TMC instruments on the rack
[[backend]]
name = "lan-rack"
type = "pyvisa-py"
auto_discover = true
auto_identify = true
read_timeout_ms = 5000
# Instrument aliases -- each specifies which backend it lives on
[instruments.dmm]
resource = "GPIB0::22::INSTR"
backend = "bench-a"
[instruments.psu]
resource = "GPIB0::5::INSTR"
backend = "bench-a"
[instruments.scope]
resource = "TCPIP::192.168.1.10::INSTR"
backend = "lan-rack"
[instruments.bench-dmm]
resource = "USB0::0x0957::0x0607::MY12345::INSTR"
backend = "lan-rack"

A few things to note:

  • Each backend must have a unique name. This is how you reference them in connect_backend and disconnect_backend.
  • Backend type determines the driver. ar488 uses the pyvisa-ar488 backend, pyvisa-py uses the pure-Python implementation, system uses NI-VISA.
  • Instrument aliases specify their backend. The backend field in each [instruments.*] section tells mcpyvisa which backend to route commands through. This means "dmm" and "scope" resolve to different backends automatically.
  • Timeouts are independent. LAN instruments might need longer timeouts than local USB connections.
  1. Check server status

    Start a Claude Code session and verify the server sees all configured backends:

    > Show me the server status

    Claude calls server_status():

    Server: mcpyvisa v0.2.0
    Backends: 2 configured
    bench-a (ar488, serial /dev/ttyUSB0): disconnected
    lan-rack (pyvisa-py): disconnected
    Instruments: 4 aliases configured
    dmm -> GPIB0::22::INSTR (bench-a)
    psu -> GPIB0::5::INSTR (bench-a)
    scope -> TCPIP::192.168.1.10::INSTR (lan-rack)
    bench-dmm -> USB0::0x0957::0x0607::MY12345::INSTR (lan-rack)

    Both backends are configured but not yet connected. Aliases are listed with their target resource and backend.

  2. Connect the first backend

    > Connect to bench-a

    Claude calls connect_backend("bench-a"):

    Connected to bench-a (ar488, serial)
    Firmware: AR488 GPIB controller 0.05.99
    Discovered 2 instrument(s):
    GPIB0::5::INSTR — KEITHLEY INSTRUMENTS INC. 2000 (alias: dmm)
    GPIB0::22::INSTR — Agilent Technologies E3631A (alias: psu)
  3. Connect the second backend

    > Connect to lan-rack too

    Claude calls connect_backend("lan-rack"):

    Connected to lan-rack (pyvisa-py)
    Discovered 2 instrument(s):
    TCPIP::192.168.1.10::INSTR — Tektronix TDS 2024B (alias: scope)
    USB0::0x0957::0x0607::MY12345::INSTR — Agilent 34461A (alias: bench-dmm)
  4. View everything at once

    > Show me all instruments

    Claude calls server_status():

    Server: mcpyvisa v0.2.0
    Backends: 2 configured, 2 connected
    bench-a (ar488, serial): connected
    GPIB0::5::INSTR — KEITHLEY INSTRUMENTS INC. 2000 (alias: dmm)
    GPIB0::22::INSTR — Agilent Technologies E3631A (alias: psu)
    lan-rack (pyvisa-py): connected
    TCPIP::192.168.1.10::INSTR — Tektronix TDS 2024B (alias: scope)
    USB0::0x0957::0x0607::MY12345::INSTR — Agilent 34461A (alias: bench-dmm)

    The backend name prefix tells you which bus each instrument is on. Aliases let you forget the details.

With multiple backends connected, you can orchestrate workflows that span buses. The alias system routes each command to the correct backend automatically — you do not need to specify which backend an instrument is on when using aliases.

Here is a practical example: read a voltage with the bench DMM while adjusting a power supply on the GPIB bus. Suppose you are characterizing how a device under test responds to supply voltage changes.

  1. Set the power supply output

    The Agilent E3631A (alias "psu") on bench-a has three outputs. Set the +6V output to 3.3V:

    > Set the psu to output 3.3V on the +6V channel

    Claude calls:

    • instrument_write("psu", "INST P6V") — select the +6V output
    • instrument_write("psu", "VOLT 3.3") — set voltage to 3.3V
    • instrument_write("psu", "OUTP ON") — enable the output
    Sent to psu: INST P6V
    Sent to psu: VOLT 3.3
    Sent to psu: OUTP ON
  2. Read the resulting voltage on the other backend

    The Agilent 34461A (alias "bench-dmm") on lan-rack is probing the device under test via USB-TMC:

    > Read DC voltage on the bench-dmm

    Claude calls:

    • instrument_write("bench-dmm", "CONF:VOLT:DC")
    • instrument_query("bench-dmm", "READ?")
    Sent to bench-dmm: CONF:VOLT:DC
    +3.29120E+00

    3.291V measured at the DUT — reasonable for a 3.3V supply with some drop across the circuit. The power supply was controlled over GPIB (via AR488) and the measurement was taken over USB-TMC (via pyvisa-py), all through the same mcpyvisa server.

  3. Sweep and measure

    You can ask Claude to automate the full sequence:

    > Sweep the psu voltage from 3.0V to 5.0V in 0.5V steps.
    > At each step, wait 500ms for settling, then read the voltage on
    > the bench-dmm. Show me a table of set voltage vs measured voltage.

    Claude will interleave instrument_write calls to "psu" with instrument_query calls to "bench-dmm", building a table as it goes:

    | Set (V) | Measured (V) |
    |---------|-------------|
    | 3.0 | 2.987 |
    | 3.5 | 3.491 |
    | 4.0 | 3.993 |
    | 4.5 | 4.488 |
    | 5.0 | 4.982 |

mcpyvisa includes a bus_health_check prompt that audits all backends in a single pass. This is useful for verifying your multi-backend setup is healthy.

> Run a bus health check

Claude activates the bus_health_check prompt and executes a systematic sequence:

  1. Calls server_status() to enumerate all configured backends
  2. For each connected backend, calls discover_instruments() and check_srq() (GPIB backends only)
  3. For disconnected backends, attempts connect_backend()
  4. Calls serial_poll() on GPIB backends to check for instruments requesting service

The resulting report looks something like:

Infrastructure Health Report
==================================
Backends: 2 configured, 2 connected, 0 failed
bench-a (ar488, serial /dev/ttyUSB0):
Firmware: AR488 GPIB controller 0.05.99
Instruments: 2 discovered, 2 identified
GPIB0::5::INSTR — KEITHLEY INSTRUMENTS INC. 2000 (dmm) — status 0x00 (idle)
GPIB0::22::INSTR — Agilent Technologies E3631A (psu) — status 0x00 (idle)
SRQ: not asserted
lan-rack (pyvisa-py):
Instruments: 2 discovered, 2 identified
TCPIP::192.168.1.10::INSTR — Tektronix TDS 2024B (scope)
USB0::0x0957::0x0607::MY12345::INSTR — Agilent 34461A (bench-dmm)
(SRQ not applicable for this backend type)
Recommendations:
- All instruments responding normally. No action needed.

Different instruments and transports need different timing. For AR488 backends, you can adjust settings at runtime without restarting the server using configure_ar488.

  1. Increase timeout for a slow instrument

    Some instruments — particularly older HP meters or anything doing an internal self-test — take several seconds to respond. If you are getting timeout errors:

    > Set the read timeout on bench-a to 10 seconds

    Claude calls configure_ar488("bench-a", read_timeout_ms=10000):

    Configured bench-a: read_timeout_ms=10000

    This takes effect immediately for all subsequent operations on that backend. Other backends are not affected.

  2. Tune EOS settings for a specific AR488 backend

    Some instruments expect line endings different from the default CRLF. The eos parameter controls what the AR488 adapter appends when sending data:

    ValueLine ending
    0CR+LF (default)
    1CR only
    2LF only
    3None
    > Set bench-a to use LF line endings

    Claude calls configure_ar488("bench-a", eos=2):

    Configured bench-a: eos=2
  3. View current server status

    To check the state of all backends and instruments:

    > Show me the server status

    Claude calls server_status() which returns the full picture: all backends, their connection state, and all discovered instruments with aliases.

Understanding how mcpyvisa handles concurrent access helps you reason about what is safe to do in multi-backend setups.

Across backends: concurrent. Each backend has its own asyncio lock and its own transport. Operations on bench-a and lan-rack can proceed simultaneously without blocking each other.

Within a backend: serialized. GPIB is a shared bus — only one device can talk at a time. USB-TMC and LAN transports also serialize per-backend to avoid interleaved responses. The per-backend lock ensures that if two tool calls target the same backend, one waits for the other to complete.

Practical implication: If Claude needs to read a voltage on the "dmm" (bench-a) and capture a waveform on the "scope" (lan-rack), those two operations can overlap. But if Claude needs to write a command and then read a response from two instruments on the same backend, those are always sequential.