# EmbeddedCI BenchPod (pypi · embeddedci-mcp)

Drive a BenchPod hardware-in-the-loop bench: power, flash, UART, I2C, CAN, analog and logic.

- Trust score: 36/100 (low)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-23

## Components

- pypi · `embeddedci-mcp`: 36/100 (this document), [markdown](https://verifymcp.io/servers/embeddedci-com-embeddedci-mcp/embeddedci-mcp.md), [page](https://verifymcp.io/servers/embeddedci-com-embeddedci-mcp/embeddedci-mcp)

## Channel facts

- Registry: `pypi`
- Package: `embeddedci-mcp`
- Version: `2.1.1`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-23.

- **Supply Chain Security**: 100/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs hatchling.build at install time, a recognised native-build step with no shell scripting around it.
  - 1 of 31 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 32/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - License check failed: no license is declared.
  - Actively maintained (last published 0 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 0/100
  - Schema quality not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- **Stability & Change Management**: 0/100
  - Stability not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- **Tool Coverage**: 0/100
  - Tool coverage not yet verified: we do not have a sandbox capture of the tool definitions this version of the package serves yet.
- **Tool Safety**: 0/100
  - Tool safety not yet verified: we do not have a sandbox capture of the tool definitions this version of the package serves yet.
- **Capabilities**: 0/100
  - Protocol version not yet verified: we do not have a sandbox capture of the MCP handshake this version of the package performs yet.

**Unverified: 5 categories.** Categories scored 0 because our sandbox run of this package has not given us the schema these checks need to read. That is a gap on our side rather than a finding about the package, and we only credit what we can confirm, so the score stands at 0 until the capture succeeds. We are working through the fleet, so this normally clears without any action from you.

## Install

### How do I install the EmbeddedCI BenchPod MCP server?

EmbeddedCI BenchPod runs locally as a PyPI package, launched with uvx embeddedci-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add embeddedci-com-embeddedci-mcp -- uvx embeddedci-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "embeddedci-com-embeddedci-mcp": {
      "command": "uvx",
      "args": [
        "embeddedci-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "embeddedci-com-embeddedci-mcp": {
      "command": "uvx",
      "args": [
        "embeddedci-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add embeddedci-com-embeddedci-mcp -- uvx embeddedci-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "embeddedci-com-embeddedci-mcp": {
      "type": "local",
      "command": [
        "uvx",
        "embeddedci-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add embeddedci-com-embeddedci-mcp --command uvx --arg embeddedci-mcp
```

### Hermes

```yaml
mcp_servers:
  embeddedci-com-embeddedci-mcp:
    command: "uvx"
    args: ["embeddedci-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "embeddedci-com-embeddedci-mcp": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "embeddedci-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add embeddedci-com-embeddedci-mcp -t stdio -c uvx -a embeddedci-mcp
```

### Other

```json
{
  "mcpServers": {
    "embeddedci-com-embeddedci-mcp": {
      "command": "uvx",
      "args": [
        "embeddedci-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-23 (score 36, −33)

- [security regression] Stability: 0.03 → unverified
- [security regression] Tool safety: pass → unverified
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional] Package version: 2.1.0 → 2.1.1

### 2026-09-22 (score 69, 0)

- [security regression] Tool safety: pass → unverified
- [security] Stability: Stability not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional improvement] Stability: unverified → 0.03
- [functional] Package version: 2.0.0 → 2.1.0

### 2026-09-21 (score 69)

First indexed and scored.

## MCP tools (61)

### `connect` (~159 tokens)

Open a BenchPod connection (closing any previous one and its sessions) and report status.

Over the cloud the device is shared: this takes an exclusive lease, released by `disconnect`
or after the server's idle timeout (the next tool call then reconnects).

Input parameters:

- `connection`: host[:port] (TCP, default port 8080), a serial device path, 'usb' (auto-detect), 'discover' (mDNS) or 'embeddedci:<device-name>' (cloud). Omit for the server default.
- `la_voltage`: Select the LA I/O-bank voltage right after connecting (the DUT's I/O voltage).
- `lease_wait` (number): Cloud only: seconds to wait when another run holds the shared device.

Output parameters:

- `capabilities`
- `connected` (boolean)
- `connection`
- `firmware` (object): The pod's raw status report.
- `kind`: tcp, serial, discover or embeddedci (cloud).
- `la_voltage`: Selected LA bank voltage; null = not set.
- `leased` (boolean)
- `session`
- `warnings` (array)

### `disconnect` (~26 tokens)

Close UART/CAN sessions and the connection, releasing a cloud lease. Safe when not connected.

Output parameters:

- `capabilities`
- `connected` (boolean)
- `connection`
- `firmware` (object): The pod's raw status report.
- `kind`: tcp, serial, discover or embeddedci (cloud).
- `la_voltage`: Selected LA bank voltage; null = not set.
- `leased` (boolean)
- `session`
- `warnings` (array)

### `status` (~44 tokens)

Connection, firmware, capabilities, selected LA voltage and open sessions — with warnings
for anything that will block the next steps (e.g. no LA voltage). Works when not connected.

Output parameters:

- `capabilities`
- `connected` (boolean)
- `connection`
- `firmware` (object): The pod's raw status report.
- `kind`: tcp, serial, discover or embeddedci (cloud).
- `la_voltage`: Selected LA bank voltage; null = not set.
- `leased` (boolean)
- `session`
- `warnings` (array)

### `set_la_voltage` (~62 tokens)

Select the LA I/O-bank voltage to match the DUT's I/O (1.8 V needs a rev3 pod).

Required before flash, UART, LA capture, pull resistors or I2C-sensor emulation.

Input parameters:

- `voltage` (number, required)

Output parameters:

- `readback`
- `voltage`

### `wiring` (~104 tokens)

Which DUT signal is on which LA channel — call this first, before any channel argument.

Returns the bench's effective profile: a 14-row pin table (what is wired to each channel and
its bias resistor), the named signals, the target-power rail, the UART baud and the SWD target,
plus warnings about risky wiring. Every tool whose channel, baud, rail or SWD arguments are
omitted takes them from this profile, and channel arguments accept these names.

Output parameters:

- `efuse` (integer): Target-power rail the tools default to.
- `i2c_address` (integer): Emulated-sensor address as a 7-bit integer.
- `la_voltage` (number): LA I/O-bank voltage the profile asks for, in volts.
- `pins` (array): LA1-LA14 with what is wired to each and its bias resistor.
- `profile` (object): The profile as its JSON object.
- `saved` (boolean): set_wiring stored this profile on embeddedci.com.
- `signals` (array)
- `source` (string): Where the profile came from: defaults, file, server or dict.
- `swd_nreset` (boolean)
- `swd_target` (string)
- `uart_baud` (integer)
- `version` (integer)
- `warnings` (array): Wiring that works but is risky (e.g. an I2C bus with no pull-up).

### `set_wiring` (~201 tokens)

Replace the wiring profile this connection uses (and optionally store it on embeddedci.com).

The profile is validated first: two roles or signals on one LA channel, an out-of-range pin or
an unknown key are rejected with a message naming every problem.

Input parameters:

- `profile` (object, required): The profile as a JSON object (schema version 1): la_mv, efuse, uart_rx/uart_tx/uart_baud, i2c_sda/i2c_scl/i2c_addr, swd_swclk/swd_swdio/swd_nreset/swd_target, spi_*, and signals [{name, la, direction…
- `save` (boolean): Also store the profile for this device on embeddedci.com (cloud devices only), so other runs and the web UI see it. false = use it for this connection only.

Output parameters:

- `efuse` (integer): Target-power rail the tools default to.
- `i2c_address` (integer): Emulated-sensor address as a 7-bit integer.
- `la_voltage` (number): LA I/O-bank voltage the profile asks for, in volts.
- `pins` (array): LA1-LA14 with what is wired to each and its bias resistor.
- `profile` (object): The profile as its JSON object.
- `saved` (boolean): set_wiring stored this profile on embeddedci.com.
- `signals` (array)
- `source` (string): Where the profile came from: defaults, file, server or dict.
- `swd_nreset` (boolean)
- `swd_target` (string)
- `uart_baud` (integer)
- `version` (integer)
- `warnings` (array): Wiring that works but is risky (e.g. an I2C bus with no pull-up).

### `power_on` (~84 tokens)

Switch the target's power rail on. The result says which rail was used.

Input parameters:

- `delay`: Seconds: schedule the power-on pod-side and return at once (e.g. to power on during a capture).
- `efuse`: Target-power eFuse: 1 = internal 5 V, 2 = external supply. Omit for the wiring profile's rail.

Output parameters:

- `delay`
- `efuse` (integer)
- `on` (boolean)

### `power_off` (~70 tokens)

Switch the target's power rail off. The result says which rail was used.

Input parameters:

- `delay`: Seconds: schedule the power-off pod-side.
- `efuse`: Target-power eFuse: 1 = internal 5 V, 2 = external supply. Omit for the wiring profile's rail.

Output parameters:

- `delay`
- `efuse` (integer)
- `on` (boolean)

### `measure_power` (~209 tokens)

Measure the DUT's supply current and voltage for a window: average, minimum and peak current,
voltage, energy and charge (gap-free sampling, so energy is integrated, not estimated).

Use this for a self-contained window (a boot, a sleep interval). To profile across other tool
calls, bracket them with power_profile_start / power_profile_stop.

Input parameters:

- `duration` (number, required): Seconds to sample (blocking).
- `efuse`: Target-power eFuse: 1 = internal 5 V, 2 = external supply. Omit for the wiring profile's rail.
- `points` (integer): Points of current/voltage trace to return alongside the statistics; 0 = statistics only.
- `rate_hz` (number): Samples per second. Accurate to ~200 Hz; above that the pod delivers what its sampling loop allows, flattening near 365 Hz. rate_hz in the result is what you actually got, adc_rate_hz what the sensor…

Output parameters:

- `adc_rate_hz` (number): Conversion rate the current sensor was configured for — the ceiling, not what arrived.
- `avg_current` (number)
- `avg_power` (number): Watts (energy over duration).
- `avg_voltage` (number)
- `charge` (number): Coulombs.
- `duration` (number)
- `efuse` (integer)
- `energy` (number): Joules, integrated over every sample.
- `fault` (boolean): The eFuse tripped (over-current or short) during the profile.
- `max_voltage` (number)
- `min_current` (number)
- `min_voltage` (number)
- `n` (integer): Raw samples the statistics cover.
- `peak_current` (number)
- `rate_hz` (number): Samples per second actually delivered (measured). The pod reads one sensor register per firmware pass, so this lands below what was asked for — roughly 350-450 Hz. Every sample carries its own timest…
- `trace_current` (array): Amps per trace point.
- `trace_step` (number): Seconds covered by each trace point.
- `trace_voltage` (array): Volts per trace point.
- `truncated` (boolean): Sampling stopped at max_duration rather than on request.

### `power_profile_start` (~124 tokens)

Start sampling the target-power rail in the background, then run the steps you want to
profile and call power_profile_stop. Replaces any profile already running.

Input parameters:

- `efuse`: Target-power eFuse: 1 = internal 5 V, 2 = external supply. Omit for the wiring profile's rail.
- `max_duration` (number): Seconds after which the pod stops sampling by itself (the result is then truncated).
- `rate_hz` (number): Samples per second; accurate to ~200 Hz, flattening near 365 Hz above that.

Output parameters:

- `efuse` (integer)
- `max_duration` (number): Seconds after which the pod stops sampling by itself.
- `rate_hz` (number): Sample rate asked for; the achieved rate is in the stop result.
- `running` (boolean)

### `power_profile_stop` (~52 tokens)

Stop the running power profile and return its statistics (and, with points, a trace).

Input parameters:

- `points` (integer): Points of current/voltage trace to return alongside the statistics; 0 = statistics only.

Output parameters:

- `adc_rate_hz` (number): Conversion rate the current sensor was configured for — the ceiling, not what arrived.
- `avg_current` (number)
- `avg_power` (number): Watts (energy over duration).
- `avg_voltage` (number)
- `charge` (number): Coulombs.
- `duration` (number)
- `efuse` (integer)
- `energy` (number): Joules, integrated over every sample.
- `fault` (boolean): The eFuse tripped (over-current or short) during the profile.
- `max_voltage` (number)
- `min_current` (number)
- `min_voltage` (number)
- `n` (integer): Raw samples the statistics cover.
- `peak_current` (number)
- `rate_hz` (number): Samples per second actually delivered (measured). The pod reads one sensor register per firmware pass, so this lands below what was asked for — roughly 350-450 Hz. Every sample carries its own timest…
- `trace_current` (array): Amps per trace point.
- `trace_step` (number): Seconds covered by each trace point.
- `trace_voltage` (array): Volts per trace point.
- `truncated` (boolean): Sampling stopped at max_duration rather than on request.

### `power_status` (~28 tokens)

Both target-power rails: eFuse on/off and tripped state, bus voltage and current draw.

Output parameters:

- `external`
- `internal`

### `reset_target` (~79 tokens)

Drive the DUT's reset line from the pod's reset pin (rev3 pods, DUT header J1 pin 22).

Input parameters:

- `action` (string): pulse = reset once; hold = keep the target in reset; release = let it run; status = read only.
- `pulse` (number): Pulse length in seconds (at most 1).

Output parameters:

- `asserted` (boolean)
- `supported` (boolean)

### `flash` (~326 tokens)

Program the DUT over SWD through the pod's CMSIS-DAP probe (OpenOCD runs on this server's host).

The SWD pins, the reset flag and the target config come from the wiring profile when omitted.
A failed flash is a normal result (ok=false): read target_unreachable (unpowered, mis-wired or
held in reset), stalled, and the log tails to decide what to change.

Input parameters:

- `connect_attempts` (integer)
- `connect_under_reset`
- `extra_args`
- `extra_configs`
- `file` (string): Firmware image path on the machine running this server.
- `load_address` (string): Load address for raw .bin images, e.g. 0x08000000.
- `nreset`: The target's reset line is wired to the pod's reset pin: enables connect-under-reset. Omit for the profile's swd_nreset.
- `reset` (boolean)
- `swclk`: LA channel (or wiring name) wired to the DUT's SWCLK; omit for the profile's swd_swclk.
- `swdio`: LA channel (or wiring name) wired to the DUT's SWDIO; omit for the profile's swd_swdio.
- `target` (string): OpenOCD target config, e.g. target/stm32f4x.cfg; omit for the profile's swd_target.
- `target_power`: Power this eFuse on before flashing.
- `timeout` (number)
- `verify` (boolean)

Output parameters:

- `ok` (boolean)
- `returncode` (integer)
- `stalled` (boolean)
- `stderr_tail` (string)
- `stdout_tail` (string)
- `target_unreachable` (boolean)

### `capture_uart` (~134 tokens)

Record the DUT's UART output for a fixed window (or until until_regex matches).

The channels and baud come from the wiring profile when omitted.

Input parameters:

- `baud`: UART baud rate; omit for the wiring profile's uart_baud.
- `duration` (number, required): Capture window in seconds.
- `rx`: LA channel (or wiring name) wired to the DUT's TX; omit for the profile's uart_rx.
- `tx`: LA channel (or wiring name) wired to the DUT's RX; omit for the profile's uart_tx.
- `until_regex`: Python regular expression; stop as soon as it matches.

Output parameters:

- `bytes` (integer)
- `matched` (boolean)
- `text` (string)
- `truncated` (boolean): text keeps the start and the end when output is long.

### `power_cycle_and_capture` (~205 tokens)

Power the target off, then capture UART while it powers back on — the boot banner lands in the window.

The channels, baud and power rail come from the wiring profile when omitted.

Input parameters:

- `baud`: UART baud rate; omit for the wiring profile's uart_baud.
- `delay` (number): Seconds into the capture the power comes back.
- `duration` (number): Capture window in seconds; must exceed delay.
- `efuse`: Target-power eFuse: 1 = internal 5 V, 2 = external supply. Omit for the wiring profile's rail.
- `off_settle` (number)
- `rx`: LA channel (or wiring name) wired to the DUT's TX; omit for the profile's uart_rx.
- `tx`: LA channel (or wiring name) wired to the DUT's RX; omit for the profile's uart_tx.
- `until_regex`: Python regular expression; stop as soon as it matches.

Output parameters:

- `bytes` (integer)
- `matched` (boolean)
- `text` (string)
- `truncated` (boolean): text keeps the start and the end when output is long.

### `uart_open` (~159 tokens)

Start buffering the DUT's UART in the background (replacing any open session).

Open it BEFORE an action whose output matters (power_on, reset_target), then uart_read; use
uart_write to type into the DUT's console. The channels and baud come from the wiring profile
when omitted; the result says which were used. A channel used as GPIO must be released first
(gpio_release).

Input parameters:

- `baud`: UART baud rate; omit for the wiring profile's uart_baud.
- `rx`: LA channel (or wiring name) wired to the DUT's TX; omit for the profile's uart_rx.
- `tx`: LA channel (or wiring name) wired to the DUT's RX; omit for the profile's uart_tx.

Output parameters:

- `baud`
- `open` (boolean)
- `rx`
- `tx`

### `uart_write` (~41 tokens)

Send text to the DUT's RX through the open UART session.

Input parameters:

- `line_ending` (string): Appended after text.
- `text` (string, required)

Output parameters:

- `written` (integer)

### `uart_read` (~64 tokens)

Return the UART output received since the previous uart_read, optionally waiting for a match.

Input parameters:

- `timeout` (number): Seconds to wait for until_regex (or, without one, for any new output).
- `until_regex`: Python regular expression; stop as soon as it matches.

Output parameters:

- `closed` (boolean)
- `matched`: Whether until_regex matched (null without one).
- `overflowed` (boolean)
- `text` (string): Everything received since the previous uart_read.
- `truncated` (boolean)

### `uart_close` (~20 tokens)

Stop the background UART session (safe when none is open).

Output parameters:

- `baud`
- `open` (boolean)
- `rx`
- `tx`

### `enable_i2c_sensor` (~155 tokens)

Make the pod act as a BMP280 on sda/scl for the DUT to read. Engage pull-ups on both lines first (set_pull).

The channels and address come from the wiring profile when omitted.

Input parameters:

- `address`: 7-bit address (BMP280: 0x76 or 0x77); omit for the profile's i2c_addr.
- `pressure_pa`
- `scl`: LA channel (or wiring name) of the bus's SCL; omit for the profile's i2c_scl.
- `sda`: LA channel (or wiring name) of the bus's SDA; omit for the profile's i2c_sda.
- `temperature_c`

Output parameters:

- `reply` (object)

### `set_i2c_sensor` (~36 tokens)

Change what the emulated sensor reports (at least one value).

Input parameters:

- `pressure_pa`
- `temperature_c`

Output parameters:

- `reply` (object)

### `disable_i2c_sensor` (~24 tokens)

Disarm the emulated sensor (safe when none is active).

Output parameters:

- `stopped` (boolean)

### `i2c_sensor_status` (~29 tokens)

Emulated sensor state plus bus activity counters — did the DUT talk to it at all?

Output parameters:

- `reply` (object)

### `i2c_sensor_regs` (~34 tokens)

Read the emulated sensor's register image.

Input parameters:

- `length` (integer)
- `start` (integer)

Output parameters:

- `bytes` (array)
- `start` (integer)

### `i2c_sensor_capture` (~85 tokens)

Capture and decode the emulated sensor's I2C bus into a transaction trace (S/Sr/P, addr, data, ACK).

Input parameters:

- `address`: Also report whether the DUT addressed this device.
- `register`: With address: the bytes the DUT read from this register.
- `sample_rate_hz` (number)
- `samples` (integer)

Output parameters:

- `addressed`
- `addresses` (array)
- `register_value`
- `trace` (string)
- `transactions` (integer)
- `truncated` (boolean)

### `set_pull` (~87 tokens)

Engage or release the fixed bias resistors: LA1-LA6 pull UP (I2C lines), LA7/LA8 pull DOWN.

The resistors are 3V3-referenced, so the pod refuses to engage them with the LA bank at 1.8 V.

Input parameters:

- `enabled` (boolean, required)
- `las` (array, required): LA channels (1-8).

Output parameters:

- `channels` (array)

### `pull_status` (~24 tokens)

State, direction and value of the bias resistor on LA1-LA8.

Output parameters:

- `channels` (array)

### `la_pins` (~108 tokens)

What owns each of the 14 LA channels — none (free), gpio, uart_rx/uart_tx, swd_clk/swd_dio,
i2c_sda/i2c_scl or step — plus each channel's GPIO mode, commanded level and bias resistor.

Read this when a tool is refused with a pin conflict: it names the owner to stop. Live pin
levels come along when the gateware can read them. Captures observe every channel whatever
owns it.

Output parameters:

- `levels`: Live pin levels; null when the gateware cannot read them (no gpio_read capability).
- `pins` (array)

### `gpio_mode` (~182 tokens)

Claim LA channels as GPIO so the pod can drive or read them.

A channel stays GPIO — across disconnects — until gpio_release, and while it is GPIO nothing
else can use it: release it before uart_open, flash or enable_i2c_sensor on that channel.
A channel already owned by another function is refused with a PinConflictError naming the
owner, and an engaged bias resistor that would fight the mode with a PullConflictError.

Input parameters:

- `la` (array, required): LA channels as numbers 1-14 or wiring-profile names.
- `level`: Starting level of an output (default 0) or open-drain channel (default 1, released). Not allowed for input.
- `mode` (string): output = push-pull; open_drain = 0 pulls low and 1 releases; input = high-Z, level readable.

Output parameters:

- `pins` (array): The channels this call configured or released.

### `gpio_write` (~86 tokens)

Set the level of GPIO output / open-drain channels (all of them in one pod command).

The channels must already be in an output or open_drain mode (gpio_mode).

Input parameters:

- `la` (array, required): LA channels as numbers 1-14 or wiring-profile names.
- `level` (integer, required): 0 = low; 1 = high (open_drain: released).

Output parameters:

- `la` (array)
- `level` (integer)

### `gpio_read` (~65 tokens)

The live level (0/1) of LA channels — omit `la` for all 14.

Works whatever owns a channel; a GPIO input is the usual way to watch a DUT output.

Input parameters:

- `la`: LA channels as numbers 1-14 or wiring-profile names.

Output parameters:

- `levels` (array)

### `gpio_wait` (~152 tokens)

Wait until an LA channel reads `level` (reached=false when the timeout passes first).

Polled from this host, so it resolves to a few milliseconds plus the round trip — for precise
timing use a triggered capture (capture_la trigger_la) and la_timing instead.

Input parameters:

- `la` (required): LA channel 1-14, or a name from the wiring profile — a signal ('READY') or a role ('uart_rx', 'i2c_sda', 'swd_swclk'). Call `wiring` to see the names.
- `level` (integer): 0 = low; 1 = high (open_drain: released).
- `timeout` (number): Seconds to wait.

Output parameters:

- `la` (integer)
- `level` (integer)
- `reached` (boolean): False means the timeout passed without the channel reaching level.
- `waited` (number): Seconds spent waiting.

### `gpio_pulse` (~153 tokens)

Emit FPGA-timed pulses on an LA channel — a trigger for the DUT, or a step/dir motor train.

The channel must be free or a GPIO output at level 0 (it returns to its GPIO level afterwards).
The FPGA runs the train by itself, so this returns as soon as it starts.

Input parameters:

- `count` (integer): Number of pulses.
- `la` (required): LA channel 1-14, or a name from the wiring profile — a signal ('READY') or a role ('uart_rx', 'i2c_sda', 'swd_swclk'). Call `wiring` to see the names.
- `width` (number, required): Seconds the pulse is high (and low between pulses).

Output parameters:

- `count` (integer)
- `la` (integer)
- `width` (number): Seconds each pulse is high (and low between pulses).

### `gpio_release` (~80 tokens)

Stop using channels as GPIO — they go back to high-Z, watched by captures.

Omit `la` to release every GPIO channel (channels owned by other functions are left alone).
Do this before starting a UART session, flashing or emulating a sensor on those channels.

Input parameters:

- `la`: LA channels as numbers 1-14 or wiring-profile names.

Output parameters:

- `released` (array): Channels released; empty list = every GPIO channel.

### `analog_path` (~90 tokens)

Set every analog mux and relay for a named path in one step.

dac_3v3/dac_5v/dac_12v route the DAC to an output; adc_ext connects the ADC to the front SMA;
cal1/cal2 loop the 5 V/12 V DAC output back into the ADC; amp reads the current terminal;
off parks everything.

Input parameters:

- `path` (string, required)

Output parameters:

- `path` (string)

### `dac_output` (~65 tokens)

Route a DAC output (3v3, 5v, 12v = ±12 V, or off) and drive a calibrated DC voltage on it.

Input parameters:

- `path` (string, required)
- `volts`: Calibrated DC volts to drive; omit to only route.

Output parameters:

- `code`
- `path` (string)
- `voltage`: Voltage produced; null when only routing.

### `adc_read` (~59 tokens)

One calibrated voltage: ext = front SMA (true volts), cal1/cal2 = DAC loopbacks, amp = current terminal.

Refused while the input is still moving (e.g. a DAC output left running).

Input parameters:

- `source` (string)

Output parameters:

- `count` (integer)
- `source` (string)
- `span` (integer)
- `voltage` (number)

### `capture_adc` (~224 tokens)

Capture the ADC and summarise it: calibrated stats, dominant frequency and a min/max envelope.

Above 32768 samples the capture streams from PSRAM (multi-second captures work). With
trigger_la the capture waits for that edge or level, so t = 0 is the trigger moment. The
capture is kept for replay and save_capture_as_recording.

Input parameters:

- `points` (integer): Points in the returned min/max envelope.
- `sample_rate_hz`: Sample rate in Hz; omit for the device maximum.
- `samples` (integer)
- `source`: Route this ADC source first; omit to keep routing.
- `trigger_edge` (string): What starts the capture on trigger_la.
- `trigger_la`: Wait for an edge or level on this LA channel (a number, or a wiring-profile name) before sampling, so t = 0 is that moment. Omit for a free-running capture. Needs the capture_trigger capability.
- `trigger_timeout` (number): Seconds to wait for the trigger before the capture is abandoned with a TriggerTimeout.

Output parameters:

- `dominant_frequency_hz`
- `duration` (number)
- `envelope_max` (array)
- `envelope_min` (array)
- `envelope_step` (number): Seconds covered by each envelope point.
- `max` (number)
- `mean` (number)
- `min` (number)
- `peak_to_peak` (number)
- `rms` (number)
- `rms_ac` (number)
- `sample_rate_hz` (number)
- `samples` (integer)
- `source`
- `trigger`: The trigger that started the capture, e.g. 'LA9 rising'; null for a free-running capture. t = 0 is the trigger moment.

### `capture_la` (~208 tokens)

Capture all 14 LA channels and summarise each: levels, edge count, first edge, estimated frequency.

With trigger_la the capture starts on that edge or level instead of immediately, so a short
event can be caught at a high sample rate. The capture is kept for decode_la and la_timing.

Input parameters:

- `sample_rate_hz`: Sample rate in Hz; omit for the device maximum.
- `samples` (integer)
- `stop_dac_after`: Seconds into the capture to cut a running DAC output (see the DUT react).
- `trigger_edge` (string): What starts the capture on trigger_la.
- `trigger_la`: Wait for an edge or level on this LA channel (a number, or a wiring-profile name) before sampling, so t = 0 is that moment. Omit for a free-running capture. Needs the capture_trigger capability.
- `trigger_timeout` (number): Seconds to wait for the trigger before the capture is abandoned with a TriggerTimeout.

Output parameters:

- `channels` (array)
- `duration` (number)
- `sample_rate_hz` (number)
- `samples` (integer)
- `trigger`: The trigger that started the capture, e.g. 'LA9 rising'; null for a free-running capture. t = 0 is the trigger moment.

### `capture_correlated` (~214 tokens)

ADC and LA captured from one hardware trigger, so their timebases align. Both are kept.

With trigger_la both streams start on that edge or level.

Input parameters:

- `adc_sample_rate_hz`: Sample rate in Hz; omit for the device maximum.
- `adc_samples` (integer)
- `la_sample_rate_hz`: Sample rate in Hz; omit for the device maximum.
- `la_samples` (integer)
- `points` (integer): Points in the returned min/max envelope.
- `stop_dac_after`: Seconds into the capture to cut a running DAC.
- `trigger_edge` (string): What starts both streams on trigger_la.
- `trigger_la`: Wait for an edge or level on this LA channel (a number, or a wiring-profile name) before sampling, so t = 0 is that moment. Omit for a free-running capture. Needs the capture_trigger capability.
- `trigger_timeout` (number): Seconds to wait for the trigger before the capture is abandoned with a TriggerTimeout.

Output parameters:

- `adc`
- `la`

### `decode_la` (~174 tokens)

Decode I2C (sda, scl), UART (rx, baud) or SPI (sclk + mosi/miso/cs, mode) from LA data.

Sample at least ~10x the bit rate. Decoding the last capture again with other channels is free.

Input parameters:

- `baud`
- `capture` (string): last = decode the previous capture_la; new = capture first (needs samples/sample_rate_hz).
- `cs`
- `max_items` (integer)
- `miso`
- `mode` (integer)
- `mosi`
- `protocol` (string, required)
- `rx`
- `sample_rate_hz` (number)
- `samples` (integer)
- `scl`
- `sclk`
- `sda`

Output parameters:

- `count` (integer)
- `items` (array)
- `protocol` (string)
- `text`: UART only: the decoded bytes as text.
- `truncated` (boolean)

### `la_timing` (~164 tokens)

Timing of one channel of the last capture_la / capture_correlated: edge timestamps, pulse widths,
frequency and duty cycle; with to_la, the delay from its first edge to the next edge on to_la
(e.g. a trigger pin to a "result ready" pin). Resolution is one sample; no new capture is taken.

Input parameters:

- `after` (number): Ignore edges earlier than this many seconds into the capture.
- `edge` (string): Which transitions to list and to time the delay from.
- `la` (integer, required): LA channel (1-14) the signal is wired to.
- `max_edges` (integer)
- `to_edge` (string)
- `to_la`: Also measure the delay to the next to_edge on this channel.

Output parameters:

- `delay`: Seconds from the first `edge` on la (at or after `after`) to the next `to_edge` on to_la; null when to_la is not given or either edge is missing.
- `duty_cycle` (number)
- `edge` (string)
- `edge_times` (array): Edge timestamps in seconds from the capture start.
- `frequency_hz`: From the rising edges; null with fewer than two.
- `high_pulses`
- `la` (integer)
- `low_pulses`
- `resolution` (number): Timestamp resolution in seconds (one sample).
- `to_la`
- `truncated` (boolean): More edges exist than edge_times lists.

### `generate` (~174 tokens)

Drive a sine, square or sawtooth on a DAC output (built from 8-bit levels).

Input parameters:

- `amplitude` (number, required): Peak volts.
- `dac_path` (string)
- `duration`: Seconds; omit to run until dac_stop.
- `freq_hz` (number, required)
- `offset`: Centre volts; default mid-range of dac_path.
- `on_capture` (boolean): Start on the next capture's hardware t0 (phase-locked).
- `route` (boolean): Route dac_path first. false = keep the current analog path (e.g. after analog_path('cal1') for a DAC-to-ADC loopback); dac_path then only sets the volts scaling.
- `sample_rate_hz`: Sample rate in Hz; omit for the device maximum.
- `waveform` (string, required)

Output parameters:

- `cotrig` (boolean)
- `dac_path` (string)
- `freq_hz` (number)
- `waveform` (string)

### `dac_stop` (~20 tokens)

Stop any DAC output: generator, replay or control loop.

Output parameters:

- `stopped` (boolean)

### `replay` (~205 tokens)

Loop a waveform out of the DAC until dac_stop — agent-provided volts or the last ADC capture.

More than 2048 samples need the deep_replay gateware image, switched to automatically.

Input parameters:

- `dac_path` (string)
- `fault`
- `from_last_capture` (boolean): Replay the last capture_adc instead of volts.
- `mapping` (string): faithful = reproduce volts (clip); fit = auto-scale.
- `on_capture` (boolean)
- `route` (boolean): Route dac_path first (false = keep the current analog path).
- `sample_rate_hz`: Replay rate; defaults to the capture's own rate.
- `switch_image` (boolean): If the pod is on the other gateware image, switch it first (~3 s). The switch resets the FPGA, stopping any DAC output, UART session or I2C sensor emulation. false = fail instead.
- `volts`: Waveform in volts, one value per sample.

Output parameters:

- `cotrig` (boolean)
- `dac_path` (string)
- `deep` (boolean)
- `sample_rate_hz` (number)
- `samples` (integer)
- `switched_image`: The gateware image this call switched the pod to; null when no switch was needed.

### `list_waveforms` (~28 tokens)

The organisation's cloud waveform library (needs a cloud connection or BENCHPOD_API_KEY).

Output parameters:

- `waveforms` (array)

### `replay_waveform` (~222 tokens)

Loop a cloud-library waveform out of the DAC until dac_stop (needs server access).

A recording too long for a shallow replay switches the pod to the deep_replay gateware image
(unless target_samples asks for a downsampled replay).

Input parameters:

- `dac_path`: Output path; default the waveform's own (else 5v).
- `fault`
- `mapping` (string)
- `on_capture` (boolean)
- `sample_rate_hz`: Sample rate in Hz; omit for the device maximum.
- `switch_image` (boolean): If the pod is on the other gateware image, switch it first (~3 s). The switch resets the FPGA, stopping any DAC output, UART session or I2C sensor emulation. false = fail instead.
- `target_samples` (integer): Downsample a shallow replay to this many samples.
- `waveform_id` (string, required)
- `window_len` (integer): Samples to replay; 0 = to the end.
- `window_start` (integer): First recording sample to replay.

Output parameters:

- `cotrig` (boolean)
- `dac_path` (string)
- `deep` (boolean)
- `sample_rate_hz` (number)
- `samples` (integer)
- `switched_image`: The gateware image this call switched the pod to; null when no switch was needed.

### `save_capture_as_recording` (~51 tokens)

Save the last capture_adc to the cloud waveform library as a replayable recording.

Input parameters:

- `full_scale_v`: Volts the top code represents; default the peak.
- `name` (string, required)

Output parameters:

- `id` (string)
- `kind` (string)
- `name` (string)
- `sample_count` (integer)

### `control_loop` (~271 tokens)

Run a tabulated transfer function in the FPGA: each tick out = curve[input], damped and clamped.

Needs the loop gateware image, switched to automatically. Poll with loop_probe, move the input
with loop_input, stop with dac_stop.

Input parameters:

- `curve`: Transfer function: DAC output codes indexed by input (1-2048 points, spread over the input range).
- `input_code` (integer)
- `input_map`
- `k` (integer): Damping, Q15 (32767 = jump straight to the target).
- `points` (integer)
- `sharpness` (number): Knee sharpness of the panel preset.
- `source`: adc = closed loop; fixed = hold input_code (open loop); sweep = advance by step each tick.
- `step` (integer)
- `switch_image` (boolean): If the pod is on the other gateware image, switch it first (~3 s). The switch resets the FPGA, stopping any DAC output, UART session or I2C sensor emulation. false = fail instead.
- `tick_div` (integer)
- `vmax` (integer)
- `vmin` (integer)
- `voc_code`: Instead of curve: solar-panel I-V preset with this open-circuit code.

Output parameters:

- `armed` (boolean)
- `curve_points` (integer)
- `input_code` (integer)
- `k` (integer)
- `source`
- `step` (integer)
- `switched_image`: The gateware image this call switched the pod to; null when no switch was needed.
- `tick_div` (integer)
- `vmax` (integer)
- `vmin` (integer)

### `loop_input` (~45 tokens)

Re-target the running loop's input without re-arming (hold a point, meter, move on).

Input parameters:

- `input_code`
- `source`
- `step`

Output parameters:

- `input_code` (integer)
- `output_code` (integer)
- `source`
- `step` (integer)

### `loop_probe` (~29 tokens)

The running loop's live operating point; assert against loop_input, not i, in open-loop runs.

Output parameters:

- `i` (integer): Latest raw ADC reading.
- `input_code`
- `loop_input` (integer): The value the loop indexed the curve with.
- `source`
- `v` (integer): DAC code driven this tick.

### `fpga_image` (~87 tokens)

Reprogram the pod's FPGA with another stored gateware image (~2-3 s).

Rarely needed: control_loop, replay and replay_waveform switch automatically. The switch resets
the FPGA (any DAC output, UART session or I2C sensor emulation stops).

Input parameters:

- `image` (string, required): loop = control-loop image; deep_replay = deep DAC replay from PSRAM.

Output parameters:

- `features` (integer)
- `image` (string)
- `version` (integer)

### `can_open` (~72 tokens)

Bring up the pod's CAN interface (replacing any open CAN session).

Input parameters:

- `bitrate` (integer)
- `mode` (string): normal = on a bus with other nodes; internal/external = loopback self-test; listen = silent.
- `term` (boolean): Engage the 120 ohm termination.

Output parameters:

- `bitrate`
- `mode`
- `open` (boolean)
- `term`

### `can_write` (~53 tokens)

Queue one classic CAN frame.

Input parameters:

- `can_id` (integer, required)
- `data`: 0-8 data bytes.
- `ext` (boolean): 29-bit extended identifier.
- `rtr` (boolean)

Output parameters:

- `queued` (boolean)

### `can_read` (~68 tokens)

Read received CAN frames (id, data, timestamps in pod milliseconds).

Input parameters:

- `can_id`
- `max_frames` (integer)
- `timeout` (number): With can_id: wait up to this long for it. Without: collect frames for this long (0 = what is buffered).

Output parameters:

- `frames` (array)
- `matched`

### `can_respond` (~77 tokens)

Make the pod firmware answer a CAN id instantly (ECU simulation), or clear all rules.

Input parameters:

- `clear` (boolean): Remove all responder rules instead of adding one.
- `match_ext` (boolean)
- `match_id`
- `reply_data`
- `reply_ext` (boolean)
- `reply_id`

Output parameters:

- `cleared` (boolean)
- `rules`

### `can_status` (~22 tokens)

CAN link state: mode, bitrate, error counters, bus-off.

Output parameters:

- `reply` (object)

### `can_close` (~20 tokens)

Clear responder rules and stop CAN (safe when not open).

Output parameters:

- `bitrate`
- `mode`
- `open` (boolean)
- `term`

### `la_step` (~96 tokens)

Emit step pulses on an LA channel (step/dir motor drivers); with dir_la, set direction first.

The FPGA runs the train by itself; this returns as soon as it starts.

Input parameters:

- `delay` (number, required): Seconds between step pulses.
- `dir_la`
- `direction` (integer)
- `la` (integer, required): LA channel (1-14) the signal is wired to.
- `steps` (integer, required)

Output parameters:

- `delay` (number)
- `la` (integer)
- `status` (string)
- `steps` (integer)

### `command` (~49 tokens)

Escape hatch for firmware commands no tool covers. Prefer the dedicated tools.

Input parameters:

- `request` (object, required): A firmware JSON command with a "cmd" key, e.g. {"cmd": "usb_cc"}.

Output parameters:

- `reply`

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/embeddedci-com-embeddedci-mcp/embeddedci-mcp#diagnostics

## Score history

- 2026-09-23: 36
- 2026-09-22: 69
- 2026-09-21: 69

## Common questions

### What is the EmbeddedCI BenchPod MCP server?

EmbeddedCI BenchPod is an MCP server listed in the public MCP registry as io.github.embeddedci-com/embeddedci-mcp. Drive a BenchPod hardware-in-the-loop bench: power, flash, UART, I2C, CAN, analog and logic. This page covers its PyPI package (embeddedci-mcp).

### Is the EmbeddedCI BenchPod MCP server safe to use?

EmbeddedCI BenchPod scores 36 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 23 September 2026. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the EmbeddedCI BenchPod MCP server expose?

EmbeddedCI BenchPod exposes 61 tools: connect, disconnect, status, set_la_voltage, wiring, and 56 more. Their descriptions and schemas cost roughly 6,278 tokens of context every time the server is loaded.

### Is the EmbeddedCI BenchPod MCP server still maintained?

EmbeddedCI BenchPod is still listed as active in the MCP registry. We last reached this channel on 23 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- PyPI project: https://pypi.org/project/embeddedci-mcp/
- Socket report: https://socket.dev/pypi/package/embeddedci-mcp
- Repository: https://github.com/embeddedci-com/embeddedci-python
- Website: https://www.embeddedci.com/docs/benchpod-mcp
- Changelog RSS feed: https://verifymcp.io/servers/embeddedci-com-embeddedci-mcp/embeddedci-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/embeddedci-com-embeddedci-mcp/embeddedci-mcp.json
- HTML version of this page: https://verifymcp.io/servers/embeddedci-com-embeddedci-mcp/embeddedci-mcp
