# mcp210x-uart

It's MCP. It's CP210x. It was right there the whole time.

An MCP server for customizing Silicon Labs CP210x USB-UART bridge devices — product strings, serial numbers, power config, GPIO port config, baud rate tables, udev rules, and device locking — through natural language in Claude Code.

Built on [FastMCP](https://gofastmcp.com/) with Python ctypes bindings to all 45 functions in Silicon Labs' native `libcp210xmanufacturing` library. Covers the full CP210x family: CP2101, CP2102, CP2102N, CP2103, CP2104, CP2105, CP2108, and CP2109.

## The problem

You plug in three CP2102 boards. They all enumerate as:

```
Bus 001 Device 004: ID 10c4:ea60 Silicon Labs CP2102 USB to UART Bridge Controller
Bus 001 Device 005: ID 10c4:ea60 Silicon Labs CP2102 USB to UART Bridge Controller
Bus 001 Device 006: ID 10c4:ea60 Silicon Labs CP2102 USB to UART Bridge Controller
```

Which one is which? Unplug, replug, guess. `/dev/ttyUSB0` becomes `/dev/ttyUSB2` after a reboot. You write udev rules that match on nothing unique. The Silicon Labs GUI customization tool is 32-bit only and hasn't been updated since 2015.

## The fix

```
> What CP210x devices are connected?

Two devices found:
  [0] RYLR998 0033001104645C0B00001130 (serial: 0001)
  [1] RYLR998 0033001104645C0B00000D27 (serial: 0001)

> Set up a udev rule for device 0 so it always appears at /dev/rylr998-1130
```

Each device gets a unique product string baked into its USB descriptor EPROM. Udev rules match on that string to create stable symlinks. Devices survive reboots, port reordering, and hub changes.

## Tools

### Read-only (no confirmation)

| Tool | Description |
|------|-------------|
| `list_devices` | List all connected CP210x devices |
| `get_device_info` | Full device details — part number, VID/PID, strings, power, lock state, firmware version (CP2102N), flush config, device mode |
| `get_firmware_version` | Firmware version (CP2102N only) |
| `get_flush_buffer_config` | Flush buffer configuration (CP2104/CP2105/CP2108) |
| `get_device_mode` | Device mode — ECI/SCI assignment (CP2105 only) |
| `get_interface_string` | USB interface string (CP2105/CP2108 only) |
| `get_baud_rate_config` | Baud rate alias table (32 entries) |
| `get_port_config` | GPIO port configuration (auto-detects CP2103/4/5/8) |
| `get_raw_config` | Raw EPROM configuration blob (hex string) |
| `create_hex_file` | Dump device config to Intel HEX file |
| `reset_device` | USB disconnect/reconnect to apply changes |

### Normal writes (elicitation → fallback to proceed)

| Tool | Description |
|------|-------------|
| `set_product_string` | Write USB product string (max 126 chars) |
| `set_manufacturer_string` | Write USB manufacturer string (max 45 chars) |
| `set_serial_number` | Write USB serial number (max 63 chars) |
| `set_max_power` | Set max USB power draw in mA (0–500, rounded to nearest 2) |
| `set_self_powered` | Toggle self-powered vs bus-powered reporting |
| `set_device_version` | Set device version (bcdDevice field) |
| `set_interface_string` | Set USB interface string (CP2105/CP2108 only) |
| `set_flush_buffer_config` | Set flush buffer configuration (CP2104/CP2105/CP2108) |
| `set_device_mode` | Set device mode (CP2105 only) |
| `set_baud_rate_config` | Set full 32-entry baud rate alias table |
| `set_baud_rate_alias` | Modify a single baud rate alias entry (read-modify-write) |
| `set_port_config` | Set GPIO port configuration (auto-detects CP2103/4/5/8) |
| `setup_udev_rule` | Generate and install a udev rule for a stable `/dev/` symlink |

### Strict writes (elicitation required — hard-refuses without it)

| Tool | Description |
|------|-------------|
| `set_vid` | Set USB Vendor ID (can break driver matching) |
| `set_pid` | Set USB Product ID (can break driver matching) |
| `set_raw_config` | Write raw configuration blob to EPROM |
| `update_firmware` | Update device firmware (CP2102N only) |
| `lock_device` | Permanently freeze device configuration |

## Safety model

CP210x descriptor EPROM is one-time-programmable with limited write cycles. Writes can't be undone. Locks are permanent. The server enforces a tiered confirmation model:

| Tier | Behavior | Tools |
|------|----------|-------|
| **None** | No confirmation | All `get_*`, `list_devices`, `reset_device`, `create_hex_file` |
| **Normal** | Elicitation if client supports it; proceeds otherwise | String setters, power, baud config, port config, `setup_udev_rule` |
| **Strict** | Elicitation **required**; returns error without it | `set_vid`, `set_pid`, `set_raw_config`, `update_firmware`, `lock_device` |

The strict gate isn't a warning — it returns an error and does not proceed if the MCP client can't present a confirmation dialog.

## Part-number support

Different CP210x parts expose different features. The server auto-gates tools to supported parts:

| Feature | Supported parts |
|---------|----------------|
| Core (strings, power, baud) | All CP210x |
| Flush buffer config | CP2104, CP2105, CP2108 |
| Device mode | CP2105 |
| Interface strings | CP2105, CP2108 |
| Port config | CP2103, CP2104 |
| Dual port config | CP2105 |
| Quad port config | CP2108 |
| Firmware version/update | CP2102N |

## Requirements

- Linux x86_64
- `libcp210xmanufacturing.so` — Silicon Labs CP210x manufacturing library
- Python 3.10+
- [uv](https://docs.astral.sh/uv/)

## Installation

### 1. Native library

**Arch Linux** (AUR package included):

```bash
cd aur/cp210xmanufacturing
makepkg -si
```

This installs the shared library, headers, and udev rules for non-root USB access.

**From source:**

```bash
cd AN721SW/Linux/LibrarySourcePackages/cp210xmanufacturing
make LIB_ARCH=64
sudo make install
sudo ldconfig
```

You'll also need udev rules for non-root device access — copy `aur/cp210xmanufacturing/SiliconLabs.rules` to `/usr/lib/udev/rules.d/` and reload.

### 2. MCP server

```bash
uv tool install .
```

### 3. Claude Code

```bash
claude mcp add cp210x -- uvx mcp210x-uart
```

For development (runs from source):

```bash
claude mcp add cp210x-local -- uv run --directory /path/to/this-repo mcp210x-uart
```

## Architecture

```
Claude Code ──stdio──▶ FastMCP server (server.py)
                              │
                              ▼
                        Python ctypes (bindings.py)
                              │
                              ▼
                    libcp210xmanufacturing.so
                              │
                              ▼
                          libusb ──▶ CP210x device
```

The native library uses **libusb** for device access, separate from the kernel's `cp210x` serial driver. Both coexist — you can read/write UART data over `/dev/ttyUSB0` while customizing USB descriptors through this server.

## Project structure

```
mcp210x-uart/
├── src/mcp210x_uart/
│   ├── server.py          # FastMCP tool definitions, elicitation, part-number dispatch
│   ├── bindings.py        # ctypes: structs, prototypes, bitmask helpers, device wrapper
│   └── __init__.py
├── aur/cp210xmanufacturing/
│   ├── PKGBUILD           # Arch Linux package for the native library
│   └── SiliconLabs.rules  # udev rules for non-root USB access
├── AN721SW/               # Silicon Labs toolkit (library source)
├── docs/                  # Datasheets and application notes
└── pyproject.toml
```

## Complementary tools

This server handles **device customization** (USB descriptors, power config, GPIO, baud tables). For **serial communication** (sending/receiving data over UART), use [mcserial](https://git.supported.systems/MCP/mcserial).

## Reference

- [CP2102 Datasheet](https://www.silabs.com/documents/public/data-sheets/CP2102-9.pdf)
- [AN721: Device Customization Guide](https://www.silabs.com/documents/public/example-code/AN721SW.zip) — source for `libcp210xmanufacturing`
- [AN197: Serial Communication Guide](https://www.silabs.com/documents/public/application-notes/an197-serial-communications-guide-cp210x.pdf)

## License

MIT
