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-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.
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.
Why switching first
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
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.
18-slot LXI/USB modular switching chassis
PXI single SP6T microwave multiplexer, failsafe
closure_limit: 1rf_src · rf_rxGeneral-purpose matrix for DC and control lines. Pick the model to suit your pin count.
psu_pos gnd dmm_hi …dut_vcc dut_enable …X-Series RF vector signal generator
sig_gen_out, exclusiveX-Series signal analyser
analyser_in2-slot, 4U traffic-generation chassis
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
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.
> 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
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
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.
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
Forbidden pairs are checked across closed crosspoints, proposed closures and patch leads, spanning cards.
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.
plan_route is a dry run that needs no lease. A route that fails mid-flight is rolled back.
Tearing one route down never opens a crosspoint another route is still holding up. unroute_signal reports what it retained.
A time-boxed reservation gates every mutation. Observation is never gated, so you can always see a chassis someone else holds.
Arming takes the literal string "the fixture is safe to energise", not a boolean a model fills in from context.
Where it sits
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
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.
chassis_identifybackend, driver, topology, statuslist_cardscards, subunits, sizes, closure limitslist_endpointsthe names this topology can route betweenlist_fabric_domainsthe independently leasable unitslist_routesconnections currently held openplan_routedry run: closures and whether permittedsubunit_stateevery closed crosspoint on a subunitcrosspoint_stateone crosspoint, and who holds itinterlock_statusarmed or not, and the policy in forcereconciliation_statuswhat was found closed at startupverify_topologythe file's claims vs. the chassisreserve_chassistime-boxed reservation; returns a tokenlist_tool_tiersplan before you reserveadopt_existing_statekeep startup crosspoints; count them in checksclear_existing_stateopen what was found; start from known-safearm_interlockexplicit acknowledgement before any relay movesdisarm_interlockstop new routing; leave routes uproute_signalconnect two endpoints by the shortest pathunroute_signaltear one route down, reference countedclear_all_routesopen everything, forget everythingset_crosspointdirect control, still interlock-checkedrelease_chassisclear, disarm, release the reservationReleasing tears the fixture down, because an agent that crashes mid-run must not leave a bench live.
Quick start
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.
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
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
{ "mcpServers": {
"switching": { "command": "pickering-lxi-mcp" }
} }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.