Metadata-Version: 2.4
Name: wisent-wire-mcp
Version: 0.15.2
Summary: MCP server for the Wisent Wire hardware testing platform — drive power, GPIO, UART/RS485/CAN, firmware flashing, and GDB debug from an AI agent
Author-email: Wisent Wire <info@wisent-wire.com>
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: wisent-wire-sdk==0.15.2
Requires-Dist: mcp<2,>=1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: responses>=0.23; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# wisent-wire-mcp

MCP (Model Context Protocol) server for the [Wisent Wire](https://wisent-wire.com) hardware testing platform. Lets AI assistants interact with WisentWire devices — power control, firmware flashing & flasher catalog, UART / RS485 / CAN buses, GPIO control & telemetry, GDB debug sessions, device registry, reservations, and protocol frame definitions.

## Quick setup

One command, with [`uv`](https://docs.astral.sh/uv/) installed:

```bash
uvx wisent-wire-mcp setup
```

It asks for an API key (create one under **Settings → API keys**), checks it
against the backend, and adds the server to Claude Code with `claude mcp add`.
Then run `/mcp` or restart Claude Code.

| Option | Meaning |
|--------|---------|
| `--url https://int.wisent-wire.com` | Target another backend. Omit it for production. |
| `--client codex` | Add it to another client instead of Claude Code — see below. |
| `--scope local` | Claude Code only: just the current folder. The default, `user`, covers every folder. |
| `--print` | Add it nowhere; print the config block for any other client. |

Running it again replaces the entry, which is how you change the key. If an
entry named `wisent-wire` also exists in another scope — a project `.mcp.json`,
say — Claude Code uses the narrower one; `claude mcp get wisent-wire` shows
which is active.

Already installed with `pipx`? Run `wisent-wire-mcp setup` instead; the entry
then points at that installation rather than at `uvx`.

### Other MCP clients

`--client` hands the server to another client through that client's own CLI, so
setup never edits a client's config file itself:

| `--client` | Client | How | Tested |
|------------|--------|-----|--------|
| `claude` (default) | Claude Code | `claude mcp add` | yes |
| `vscode` | VS Code | `code --add-mcp`, into the user profile | yes |
| `codex` | Codex CLI | `codex mcp add` | not yet |
| `gemini` | Gemini CLI | `gemini mcp add --scope user` | not yet |
| `cursor` | Cursor | opens Cursor's install link; confirm it in Cursor | not yet |

"Not yet" means the command follows that client's published CLI syntax but has
not been run against the client itself. If one fails, `--print` below still works.

For anything else — Claude Desktop, Windsurf — `--print` checks the key just the
same, then writes the `mcpServers` block instead of adding it anywhere:

```bash
uvx wisent-wire-mcp setup --print
```

Paste the output into your client's MCP config and restart it. The block holds
your API key in plain text, so keep it out of anything you share. Only the JSON
goes to stdout, so `setup --print > wisent-wire.json` saves just the block. The
Claude Desktop config locations are listed under [Configuration](#configuration).

## Installation

Requires Python **3.10+**. Install with [`pipx`](https://pipx.pypa.io) —
it puts the MCP server in an isolated environment and adds the
`wisent-wire-mcp` binary to your `PATH`.

### 1. Install `pipx` (one-time)

```bash
# macOS
brew install pipx
pipx ensurepath

# Linux / WSL
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

Open a new terminal so the updated `PATH` is picked up.

### 2. Install the MCP server

```bash
pipx install wisent-wire-mcp
```

Verify:

```bash
which wisent-wire-mcp         # ~/.local/bin/wisent-wire-mcp (or similar)
```

### 3. Upgrade / uninstall

```bash
pipx upgrade   wisent-wire-mcp
pipx uninstall wisent-wire-mcp
```

## Configuration

### `.mcp.json` location

Claude Code reads MCP servers from `.mcp.json`. Two scopes:

| Scope | Path | When to use |
|-------|------|-------------|
| **Project** | `<repo-root>/.mcp.json` | Shared with the project (checked into VCS or per-repo) |
| **User** | `~/.claude.json` under the `mcpServers` key | Available in every Claude Code session for this user |

Claude Desktop uses a separate file:

| Platform | Path |
|----------|------|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |

### Example `.mcp.json`

```json
{
  "mcpServers": {
    "wisent-wire": {
      "command": "wisent-wire-mcp",
      "env": {
        "WISENT_WIRE_API_KEY": "wwk_..."
      }
    }
  }
}
```

After editing `.mcp.json`, restart Claude Code (or reload the window)
for the server to be picked up.

### Environment variables

| Variable | Required | Description |
|----------|----------|-------------|
| `WISENT_WIRE_API_KEY` | Yes* | API key (`wwk_…`). **Recommended** — create one under **Settings → API keys**. |
| `WISENT_WIRE_EMAIL` | Yes* | Account email. |
| `WISENT_WIRE_PASSWORD` | Yes* | Account password. |
| `WISENT_WIRE_URL` | No | API base URL. Omit it to target production. |

\* Provide either `API_KEY` or `EMAIL` + `PASSWORD`. The API key is tried first.

`WISENTWIRE_API_KEY` (no underscore) still works as a **deprecated** alias and
warns on use — it is the one legacy spelling with released history. The same
names configure the `wisent-wire-sdk` package, so one exported environment does
both. An exported-but-empty value counts as unset.

The server reads none of these itself — it hands the whole job to the SDK, which
is what keeps the two from disagreeing about a name. See the SDK README for the
canonical list.

**Choosing a backend.** `WISENT_WIRE_URL` is the address, and omitting it targets
production (`https://app.wisent-wire.com`). To point at integration, or at a
backend running on your own machine:

```json
"env": {
  "WISENT_WIRE_URL": "https://int.wisent-wire.com",
  "WISENT_WIRE_API_KEY": "wwk_..."
}
```

There is no named-environment form. `WISENT_WIRE_ENV` used to exist and was what
this page recommended; setting it now **stops the server with an error naming
the replacement**, rather than being ignored and quietly starting against
production. If you configured this server during a release candidate, that is
the line to change.

For a local backend, use its address — `"WISENT_WIRE_URL": "http://localhost:7998"`.
Mint a key for it once with the header the local gateway trusts:

```bash
curl -sS -X POST http://localhost:7998/api/me/api-keys \
  -H 'X-Forwarded-User-Email: e2e@wisentwire.test' \
  -H 'Content-Type: application/json' -d '{"name":"local"}'
```

### Available tools

Tools are grouped below.

#### System & Device

| Tool | Description |
|------|-------------|
| `get_backend_version` | Get the backend version |
| `list_wisentwires` | List all WisentWire devices |
| `get_ww_status` | Get current device status |
| `check_ww_available` | Check if a device is available |
| `rename_wisentwire` | Change a device display name — that field only, nothing else |

#### Device Registry

| Tool | Description |
|------|-------------|
| `list_registered_devices` | List registered devices, optionally filtered by label |
| `list_connected_devices` | Scan a WisentWire and list physically connected devices |
| `register_device` | Register a device against a fingerprint |

#### Power & Telemetry

| Tool | Description |
|------|-------------|
| `list_power_supplies` | Supplies on a wire, with the tag the other power tools take |
| `power_on` / `power_off` | Control power supply |
| `update_power_supply` | Change voltage / current limit while powered |
| `get_telemetry` | Recent voltage/current readings |
| `set_power_supply_telemetry` | Enable or disable power telemetry streaming |

#### GPIO

| Tool | Description |
|------|-------------|
| `get_gpio_shadow` | Current GPIO pin states |
| `get_gpio_names` | Pin name mapping for a device |
| `set_gpio_output` | Drive an output pin high or low |
| `set_gpio_input_pull_policy` | Set an input pin's pull policy |
| `set_gpio_telemetry` | Enable or disable GPIO telemetry streaming |
| `get_gpio_telemetry` | Recent GPIO telemetry readings |

#### Firmware & Flashing

| Tool | Description |
|------|-------------|
| `list_firmware` | List firmware binaries with their tag ids |
| `list_firmware_tags` | List the org firmware tags with the ids `set_firmware_tags` takes |
| `create_firmware_tag` | Create an org-wide firmware tag |
| `set_firmware_tags` | Replace **all** tags on a binary — not additive |
| `get_firmware_download_url` | Short-lived pre-signed download link for a binary |
| `upload_firmware` | Upload a firmware binary |
| `delete_firmware` | Delete a firmware binary |
| `flash_firmware` | Flash firmware to a device (msp430/openocd/st-link/j-link). `device_id` picks which probe, when more than one is attached |
| `flash_firmware_file` | One-shot: upload a local firmware file, flash it, then delete it (by default). Also takes `device_id` |
| `list_launch_commands` | Valid target/programmer/interface/toolchain combinations. Needs at least one filter — unfiltered the catalog is too large for the backend to return |
| `list_programmers` | Probe types (ST-Link v3, J-Link, ...) with the ids `register_device` needs |
| `list_targets` | Find flashable chips by name fragment and get the `target_id` `list_launch_commands` needs |
| `get_flash_status` | Check a flash job's status by job id |
| `get_flasher_logs` | Get flasher debug logs |

#### Debug

| Tool | Description |
|------|-------------|
| `get_debug_state` | Get the current GDB debug session state |
| `get_debug_job_status` | Get a debug job's status by id |
| `get_debug_logs` | Buffered device-side debug log; the `last_n` most recent entries (default 50) |
| `stop_debug` | Stop the active debug session |

#### UART & Buses

| Tool | Description |
|------|-------------|
| `send_uart_command` | Send a text command over UART |
| `send_uart_hex` | Send raw hex bytes over UART |
| `send_rs485_hex` | Send raw hex bytes over RS485 |
| `send_can_frame` | Send a CAN frame |
| `get_uart_log` | Get TX/RX message history |
| `get_bus_log` | Get TX/RX message history for a given bus (uart/rs485/can) |

#### Frame Definitions

| Tool | Description |
|------|-------------|
| `list_frame_definitions` | List all frame definitions |
| `get_frame_definition` | Get a frame definition with fields |
| `create_frame_definition` | Create a new frame definition |
| `update_frame_definition` | Update a frame definition |
| `delete_frame_definition` | Delete a frame definition |

#### Commands

| Tool | Description |
|------|-------------|
| `list_commands` | List commands for a frame definition |
| `get_command` | Get one command with the field values bound to it |
| `create_command` | Create a command |
| `update_command` | Update a command |
| `delete_command` | Delete a command |

#### Reservations

| Tool | Description |
|------|-------------|
| `list_reservations` | List reservations |
| `get_reservation` | Get a single reservation |
| `create_reservation` | Create a reservation |
| `update_reservation_end` | Change the end time using the last seen end time for conflict detection |
| `end_reservation` | End an in-progress reservation now |
| `delete_reservation` | Delete a reservation |


## Development

```bash
pip install -e ".[dev]"
pytest tests/ -v
ruff check src/ tests/
```

## License

MIT
