Open-source MCP server · Pickering PXI / LXI switching

Signal routing an agent can be trusted with.

pickering-lxi-mcp lets an LLM agent route signals through Pickering switching by logical endpoint name — connect the signal generator to the DUT receiver, not close crosspoint (1,2) on card 4 — and refuses, before a single relay moves, any route that would make a forbidden pair of endpoints electrically common.

MITPython 3.10 – 3.13stdio · streamable HTTPbuilt-in chassis simulator
Every model number is real hardware and only the DUT is invented. Cabling, subnets and endpoint names come straight from the shipped rf_bench.json, and the relay state is walkthrough 08 part-way through, with three routes open. This is an illustration drawn from the topology, not a photograph.
22MCP tools: 13 observe, 9 mutate, split structurally
8deterministic tool walkthroughs, run as the hard CI gate
170+tests, with no chassis, no vendor driver and no lab
0relays moved when a route is refused. Routes are refused whole.

Why switching first

Every instrument in the rack is reached through the matrix.

Switching is the routing layer of a test rack. The signal generator, the analyser, the supply and the DMM all reach the DUT through it. That makes it the one server a multi-vendor agent cannot do without.

It is also where a mistake is not a wrong reading. On a switching matrix, a mistake is a short. This server exists to make that impossible to request by accident, whether the requester is an agent or a tired engineer at 2 a.m.

The hardware

Real instruments, real ratings, one contract.

The worked example is an RF, DC and LAN bench. The two numbers the interlock actually turns on are datasheet maximums: what the instrument can do, not what it is set to, because the setting is one SCPI command away from changing.

Pickering Interfaces
60-103D-001

18-slot LXI/USB modular switching chassis

Form
4U, full rack width, takes 3U PXI modules
Control
Gigabit LAN · USB 3 · LXI 1.4
In the model
one server process, one address
Pickering Interfaces · ×2
40-785C-521

PXI single SP6T microwave multiplexer, failsafe

RF
18 GHz, 50 Ω, SMA · 3 slots wide
Physics
one channel at a time → closure_limit: 1
Endpoints
rf_src · rf_rx
Pickering Interfaces
40-series GP matrix

General-purpose matrix for DC and control lines. Pick the model to suit your pin count.

Shape
6 × 12 in the topology
Rows
psu_pos gnd dmm_hi …
Columns
dut_vcc dut_enable …
Keysight
N5182B MXG, Opt 1EA

X-Series RF vector signal generator

Rating
+26 dBm typical max out, 10 MHz – 3 GHz
Endpoint
sig_gen_out, exclusive
Keysight
N9020B MXA

X-Series signal analyser

Rating
+30 dBm avg total power · ±0.2 Vdc DC-coupled
Endpoint
analyser_in
VIAVI · formerly Spirent
TestCenter SPT-N4U

2-slot, 4U traffic-generation chassis

Plane
data. Cabled straight to the DUT
Topology
absent on purpose, because a switch topology contains only what the matrix can switch

The DUT, a radio with an Ethernet management port, is the one invented element. Every rating that comes from a datasheet by way of whoever wrote the file is marked asserted, and verify_topology lists it as unverifiable for exactly that reason. Full cabling schedule and the four network planes: docs/EXAMPLE-BENCH.md.

The problem it actually solves

The dangerous mistake is a graph, not a number.

Nobody ever asks to short the supply to ground. They route the supply to a DUT pin, which is reasonable. Then they route that pin to ground for a continuity check, which is also reasonable. The short is the composition of two correct requests, and neither looks wrong when it is made.

So the server asks a different question. Given every crosspoint closed right now, every crosspoint this route would close and every hard-wired patch lead, does any forbidden pair end up in the same connected component? The server answers that before a relay moves. If the answer is yes, it refuses the route whole.

dut_bench · simulator
> route_signal psu_pos -> dut_pin_a1   ok  matrix_a/sub1(1,1)
> route_signal dmm_hi  -> dut_pin_a1   ok  matrix_a/sub1(6,1)
                 measuring under power is fine
> route_signal gnd     -> dut_pin_a1   REFUSED
    this route would make 'psu_pos' and 'gnd'
    electrically common, which the topology forbids.
    Nothing was switched.
> route_signal gnd     -> dut_pin_a5   ok  matrix_a/sub1(3,5)
                 same route, different pin
walkthroughs/08_rf_bench.json · runs in CI
plan_route   sig_gen_out → dut_rx_direct
             permitted: false +26 dBm into +10, 16 dB over
plan_route   sig_gen_out → dut_rx_padded
             permitted: true  ← the pad is the answer

reserve_chassis owner=engineer-a    token
arm_interlock   confirm="the fixture is safe to energise"

route_signal sig_gen_out  → dut_rx_padded  open
route_signal sig_gen_out  → power_sensor   refused
             'sig_gen_out' is exclusive and already routed
route_signal dut_tx       → analyser_in    open
route_signal noise_source → analyser_in    refused
             2 closures on rf_rx, over the SP6T's limit of 1
route_signal psu_pos      → dut_vcc        open
route_signal gnd          → dut_vcc        refused
             would make psu_pos and gnd electrically common

release_chassis                      clears every route, disarms

On the bench above

Four refusals, four different causes.

Every line is actual behaviour of rf_bench.json on the simulation backend, pinned as a walkthrough. If the server's answers change, the build fails and this page doesn't quietly go stale.

✕power limit ✕exclusive endpoint ✕SP6T closure limit ✕connectivity interlock

None of them depends on the agent being careful, and none switched anything before refusing. The plan answers is this route permitted separately from is the interlock armed, so “you haven't armed yet” never masks “this would destroy the DUT”.

Built to be pointed at a real fixture

The interlocks a relay driver doesn't give you.

01 · connectivity

Short-circuit refusal by graph

Forbidden pairs are checked across closed crosspoints, proposed closures and patch leads, spanning cards.

02 · reconciliation

Reads the chassis before it believes anything

If it finds relays nobody owns, it refuses to switch until you adopt or clear them. It won't adopt a fixture that is already shorted.

03 · planning

Plan first, then switch all or nothing

plan_route is a dry run that needs no lease. A route that fails mid-flight is rolled back.

04 · ownership

Reference-counted routes

Tearing one route down never opens a crosspoint another route is still holding up. unroute_signal reports what it retained.

05 · arbitration

One rack, one truth

A time-boxed reservation gates every mutation. Observation is never gated, so you can always see a chassis someone else holds.

06 · intent

Arming means something

Arming takes the literal string "the fixture is safe to energise", not a boolean a model fills in from context.

Where it sits

One server in a multi-vendor agent stack.

The agent talks to each instrument's MCP server in a star. In the signal plane, every one of those instruments reaches the DUT through the Pickering matrix. Three figures carry the design; each opens full size.

Tool surface

Observe freely. Mutate with a token.

Every tool declares a tier. One dispatch point refuses a mutate tool without a live reservation, and there is no passthrough to the vendor driver. A test asserts there never will be.

observe · 13never gated
  • chassis_identifybackend, driver, topology, status
  • list_cardscards, subunits, sizes, closure limits
  • list_endpointsthe names this topology can route between
  • list_fabric_domainsthe independently leasable units
  • list_routesconnections currently held open
  • plan_routedry run: closures and whether permitted
  • subunit_stateevery closed crosspoint on a subunit
  • crosspoint_stateone crosspoint, and who holds it
  • interlock_statusarmed or not, and the policy in force
  • reconciliation_statuswhat was found closed at startup
  • verify_topologythe file's claims vs. the chassis
  • reserve_chassistime-boxed reservation; returns a token
  • list_tool_tiersplan before you reserve
mutate · 9reservation token required
  • adopt_existing_statekeep startup crosspoints; count them in checks
  • clear_existing_stateopen what was found; start from known-safe
  • arm_interlockexplicit acknowledgement before any relay moves
  • disarm_interlockstop new routing; leave routes up
  • route_signalconnect two endpoints by the shortest path
  • unroute_signaltear one route down, reference counted
  • clear_all_routesopen everything, forget everything
  • set_crosspointdirect control, still interlock-checked
  • release_chassisclear, disarm, release the reservation
connect→reconcile→discover→reserve→arm→route→observe→unroute→release

Releasing tears the fixture down, because an agent that crashes mid-run must not leave a bench live.

Quick start

Clone, test, run. No hardware needed.

The default backend is an in-process chassis simulator, on purpose. The failure mode worth fearing is a server that silently reaches for a rack, not one that refuses to.

1 · Try it on a laptop

shell
git clone https://github.com/testinsightconsulting/pickering-lxi-mcp
cd pickering-lxi-mcp
pip install -e ".[dev]"
pytest -q
pickering-lxi-mcp-walkthrough walkthroughs   # the CI gate
pickering-lxi-mcp                            # MCP on stdio, simulated

2 · Point it at a real rack

shell
pip install -e ".[hardware]"                 # adds pilxi
PICKERING_LXI_ADDRESS=192.168.1.50 pickering-lxi-mcp   # LXI by IP
PICKERING_LXI_ADDRESS=PXI          pickering-lxi-mcp   # local PXI
PICKERING_LXI_TOPOLOGY=./my_bench.json pickering-lxi-mcp
pickering-lxi-mcp-validate ./my_bench.json   # review, no rack

3 · Connect any MCP client

mcp config
{ "mcpServers": {
    "switching": { "command": "pickering-lxi-mcp" }
} }

4 · Share one rack between agents

shell
pickering-lxi-mcp --transport streamable-http \
  --host 0.0.0.0 --port 8000
# clients: { "url": "http://lab-host:8000/mcp" }

There are two kinds of simulation, and you should use both, in this order. SimBackend (the default) answers is the routing logic right. PICKERING_LXI_SIM_CARD=1 runs the real ClientBridge driver against cards that aren't in the rack, and answers is the driver integration right. Where the simulator and a real card disagree, the simulator is wrong and gets fixed.