Skip to main content
Glama

smolmux

Smolmux Gdb MCP on mcprush

A portable C11 device multiplexer. Holds a device connection open (serial UART, GDB stub) and multiplexes access to multiple clients over Unix sockets using newline-delimited JSON.

Single static binary, low latency, small footprint - built for daily serial and GDB bring-up on Linux.

New here? docs/START-HERE.md is a one-screen router - find your intent (run it, bring up a new board with an AI agent, understand the architecture, hack on the code) and it points you to the right doc.

One port, three clients

smolmux on an ESP32-S3: smolmux-monitor in the top pane; in the bottom pane a script sends a command and waits for the reboot, then esptool borrows the port through with-port

One broker holds an ESP32-S3's UART. The monitor (top) watches it live. In the bottom pane a script sends temp and gets the reply on the same wire, resets the board and waits for its prompt, then esptool reads the flash ID through with-port: the broker suspends, esptool runs, the broker resumes, and the monitor never loses the port.

Related MCP server: Board Terminal Skill

Example: probe an unknown board

One broker holds SWD; smolmux-gdb-mcp runs probe_unknown_board. On a SAM C21 Xplained Pro that path decoded Cortex-M0+ from CPUID, named the part via SAM DSU DID, rejected a false STM32 match, and wrote a starter *.gdb-profile.json. Tools, register values, and profile shape: docs/demo-samc21-probe-transcript.md.

Day-to-day serial (multi-client, U-Boot break-in, flasher handoff): docs/daily-driver.md.

Features

  • Serial UART, GDB MI, and serial-over-TCP (telnet + RFC2217) device links via vtable polymorphism

  • Multiple concurrent clients over Unix sockets with role-based access (observer/controller/takeover)

  • Expect engine - concurrent regex matching on the device byte stream with timeouts

  • Anomaly detection - pattern-based crash/error detection with cooldown and incident tracking

  • Output history - timestamped ring buffer for replay by late-joining clients

  • Structured logging - JSONL I/O log + human-readable text log with rotation (the I/O log records everything sent to and from the device, including anything typed at a login prompt — it is created 0600 under your private state directory, and smolmux refuses to write it through a symlink or to a file owned by another user)

  • Network sinks - TCP and WebSocket for remote access (loopback by default). The wire protocol is cleartext, and any client that completes the handshake gets full control of the device — console writes, pins, BREAK, SysRq, GDB. On loopback without --auth-token, the broker generates a token into a 0600 file in $XDG_RUNTIME_DIR (or /tmp), so other local users and processes cannot connect; your own smolmux-monitor / smolmux-mcp read it automatically, and smolmux-cli token prints it. For remote use, keep the bind on loopback and reach it over an SSH tunnel or WireGuard rather than exposing the port; smolmux refuses to serve a non-loopback TCP bind with no --auth-token. --insecure-no-auth turns both protections off.

  • MCP servers - standalone smolmux-mcp / smolmux-gdb-mcp attach to a running broker; optional in-process --mcp sink for single-process stdio

  • Boot tracking & autoresponder - ordered boot stages, stall events, standing expect->send rules

  • Autoboot interrupt - broker-side key flood (and optional DTR/RTS reset) for bootdelay=0 U-Boot

  • Device profiles / board manifests - JSON configs for prompts, anomalies, multi-wire boards

  • Auto-reconnect - exponential backoff recovery on USB-serial disconnect

  • Build-time feature selection - Kconfig-based; UART-only builds carry no GDB/TCP/WebSocket code

Quick start

Prerequisites: Linux, a C11 compiler, CMake 3.21+, Python 3 with kconfiglib (the configure step generates the feature header with it). libpcre2-8 is optional; without it regex matching falls back to POSIX.

# Debian/Ubuntu
sudo apt install build-essential cmake python3 python3-pip libpcre2-dev
pip install kconfiglib   # or in a venv; Debian's system Python may need
                         # --break-system-packages
cmake -B build && cmake --build build -j$(nproc)
./build/smolmux /dev/ttyUSB0

Connect a client:

# Needs a running broker (above). Auto-discovers the socket from the port name.
./build/smolmux-monitor /dev/ttyUSB0

Day-to-day workflows (profiles, logs, U-Boot break-in, multi-wire boards): docs/daily-driver.md. Intent router: docs/START-HERE.md.

Build

cmake -B build && cmake --build build -j$(nproc)   # Default (all features)
ctest --test-dir build                               # Run tests

Feature profiles

cp configs/defconfig.minimal .config && cmake -B build    # UART only, no sinks/watcher
cp configs/defconfig.uart .config && cmake -B build       # UART + MCP sink + watcher
cp configs/defconfig.embedded .config && cmake -B build   # Embedded target profile
cp configs/defconfig.full .config && cmake -B build       # Everything (GDB, TCP, WS, MCP, watcher)

Or toggle features directly:

cmake -B build -DSM_ENABLE_GDB=OFF -DSM_ENABLE_SINK_WS=OFF

Developer benchmarks (e.g. the output coalescer harness) are off by default:

cmake -B build -DSM_BUILD_BENCH=ON && cmake --build build --target bench_coalesce

Interactive configuration:

cmake --build build --target menuconfig

Static builds

cmake -B build -DSM_MUSL_STATIC=ON    # Fully static musl binary (zero runtime deps)
cmake -B build -DSM_STATIC=ON         # Static linking (except glibc)

Usage

smolmux <port> [options]

Options:
  -b, --baud <rate>           Baud rate (default: 115200)
  -s, --socket <path>         Unix socket path
  -l, --log-dir <dir>         I/O log directory
                              (default: $XDG_STATE_HOME/smolmux or
                               ~/.local/state/smolmux)
  -t, --text-log-dir <dir>    Text log directory
  -p, --profile <path>        Device profile JSON file
  --board <name>              Group this wire under a board (for discovery)
  --role <label>              This wire's role on the board (console, swd, ...)
  --gdb                       Use GDB MI link instead of UART
  --gdb-path <path>           Path to gdb binary (default: gdb)
  --gdb-target <spec>         GDB target (e.g., localhost:3333)
  --serial-tcp <host:port>    Connect to a serial-over-TCP device server
                              (ser2net, socat, terminal server; telnet +
                              RFC2217 baud/DTR/RTS/break control)
  --mcp                       Enable in-process MCP stdio sink (prefer standalone
                              smolmux-mcp against a daemon broker for daily use)
  --tcp-port <port>           Enable TCP sink (default: 5555)
  --tcp-bind <addr>           TCP bind address (default: 127.0.0.1)
  --auth-token <token>        Require token in hello from TCP clients
                              (prefer env SMOLMUX_AUTH_TOKEN - hidden from ps)
  --auth-token-file <path>    Read the token from a file
  --insecure-no-auth          Serve TCP/WS with no token. Without it, a
                              loopback listener gets a generated token
                              (0600 file; smolmux-cli token prints it) and
                              a non-loopback --tcp-bind is refused.
  --ws-port <port>            Enable WebSocket sink (default: 5556)
  --no-text-log               Disable text log
  --no-io-log                 Disable JSONL I/O log
  --no-reconnect              Don't auto-reconnect on disconnect
  --wait-device <seconds>     Wait for the device path before open
  --gdb-allow-shell           Permit GDB shell/python/eval (off by default)
  --list-ports                List available serial ports and exit
  --list-profiles             List available device profiles and exit
  --help-protocol             Show wire protocol documentation
  -v, --verbose               Enable debug logging
  -V, --version               Show version
  -h, --help                  Show this help

Wire protocol

Newline-delimited JSON over Unix sockets (also TCP/WS sinks). Binary data is base64-encoded. Message set covers session control, expect, history, anomaly, boot stages, autoboot flood, and autoresponder.

Full reference: ./build/smolmux --help-protocol (always matches this binary).

Client -> Broker: hello, send, send_expect, takeover, release, status, pin_control, set_baud, suspend, resume, history_request, incidents_request, configure_anomaly, interrupt_autoboot, configure_autoresponder, autoresponders_request

Broker -> Client: welcome, output, input_echo, expect_result, status_response, error, history_response, incidents_response, anomaly, autoboot_result, boot_stage, boot_stall, autoresponders_response, autoresponder_fired, suspended, resumed, link_down, link_up

Example session:

-> {"type":"hello","name":"my-tool","role":"controller","protocol_version":1}
<- {"type":"welcome","broker_version":"x.y.z","protocol_version":1,"port":"/dev/ttyUSB0","baud":115200,"your_role":"controller"}
-> {"type":"send","id":"1","data":"dW5hbWUgLWEK"}
<- {"type":"output","data":"TGludXggNC4xOS4w...","timestamp":1709654321.123}

Architecture

┌───────────┐  ┌───────────┐
│ uart link │  │  gdb link │   <- device-facing, compiled in/out via Kconfig
└─────┬─────┘  └─────┬─────┘
      │              │
      ▼              ▼
┌──────────────────────────────┐
│         smolmux core         │  <- epoll event loop, message bus,
│  history · logging · anomaly │    role enforcement, expect engine
└──┬───────┬───────┬───────┬───┘
   │       │       │       │
   ▼       ▼       ▼       ▼
┌──────┐┌─────┐┌─────┐┌──────┐
│ unix ││ tcp ││ ws  ││ mcp  │  <- sinks, also compiled in/out
│socket││sink ││sink ││ sink │
└──────┘└─────┘└─────┘└──────┘

See DESIGN.md for full architecture documentation.

Dependencies

Required: cJSON (vendored, single file)

Auto-detected: PCRE2 (libpcre2-8). Used as the regex engine when present, because it bounds backtracking internally; the build falls back to POSIX ERE automatically when it is absent, so it is never required. Force either way with -DSM_ENABLE_PCRE2=ON (error if missing) or -DSM_ENABLE_PCRE2=OFF.

Core has zero required external dependencies beyond POSIX + cJSON.

Companion tools

  • smolmux-cli - command-line client (send commands, read output; with-port <cmd> suspends the port, runs an external tool like a flasher, then always resumes)

  • smolmux-monitor - interactive terminal client with escape sequences (prefix key then a command; prefix defaults to Ctrl-], change with -e, e.g. -e esc or -e ^A)

  • smolmux-mcp - standalone MCP server: connects to a running broker and exposes serial tools (serial_send_command, serial_read, serial_boot_status, serial_add_autoresponder, ...). Alternative: broker --mcp sink embeds MCP in-process.

  • smolmux-gdb-mcp - standalone MCP server for GDB debugging: 21 tools over a broker holding a --gdb link - breakpoints, stepping, backtrace, name-labeled registers, memory, expression eval, fault-register decode (where the core has them), peripheral reads, gdb_interrupt, and unknown-board probing on ARM Cortex-M today (gdb_identify_target / gdb_generate_profile; other architectures are planned). Resources and prompts include smolmux-gdb://board-probing and probe_unknown_board. Built when SM_ENABLE_GDB is on; chip-ID validated on a SAM C21 Xplained Pro.

  • smolmux-watcher - daemon that monitors for anomalies and saves incident reports to disk

Free vs Pro

Everything in this repository is MIT-licensed - full source, wire protocol, generic device profiles, build system. Build it yourself and you have the complete product.

smolmux Pro is convenience, not a feature gate: prebuilt static binaries (x86_64 + aarch64, musl, zero runtime dependencies), the curated profile pack with per-profile notes, and 6 months of email support. One-time purchase ($79). MCP setup for zip paths is docs/MCP-SETUP-FULL.md (also in this public tree).

Buy smolmux Pro - $79 one-time - download includes current static binaries and the profile pack.

License

MIT. cJSON is vendored under its own MIT license (deps/cJSON/LICENSE).

Available Tools

9 tools
serial_boot_statusA
Read-only

Report cold-boot progress: which boot stages the device has reached, the furthest stage, and whether the boot has stalled. Requires the device profile to declare boot_stages; otherwise reports none. Use after a reset or power cycle; if stalled, check serial_get_incidents.

Inputs: no named parameters. Returns: state, total, stages, reached, +2 more

serial_generate_reportA
Read-only

Markdown summary of device profile, port status and incidents. Use to open a debugging session or hand a human a snapshot; for one fact, call the single-purpose tool.

Inputs: no named parameters.

serial_get_incidentsA
Read-only

List anomalies the broker detected in output (kernel panic, oops, hard fault, assert, watchdog, brownout, ESP resets, plus profile patterns) with the match and pre-context. Use when a boot stalls, a wait aborts, or output looks like a crash; pair with serial_output_history for the surrounding log.

Inputs: seconds. Returns: count, incidents

serial_list_portsA
Read-only

List serial ports on this host with by-id paths and USB VID/PID (works without a broker). Use to pick a port before starting a broker, or when output is garbage (wrong port). Bridge chips name the adapter, not the MCU.

Inputs: no named parameters. Returns: ports

serial_monitorA
Read-only

Listen for a fixed time (default 30 s, max 300 s) and return what the device printed plus anomalies in that window. Use when you do not know what to expect; when you do, serial_wait_for returns as soon as it matches.

Inputs: duration_seconds.

serial_output_historyA
Read-only

Read past output from the broker's shared history without consuming it. For anything you must not miss, page with since_seq (start at 0; pass the returned cursor back; check dropped and has_more). Without since_seq: text by seconds or last_bytes. serial_read is drain-only.

Inputs: seconds, max_bytes, since_seq, last_bytes. Returns: mode, text, chunks, cursor, +2 more

serial_port_statusA
Read-only

Check the link before sending, or when output looks wrong: port, baud, connected/suspended, by-id identity strength, pins, who holds write control, and connected clients. Boot progress: serial_boot_status. Crashes: serial_get_incidents.

Inputs: no named parameters. Returns: baud, port, role, board, +14 more

serial_readA
Read-only

Drain the output this MCP session buffered since the last read, without sending. Quick look after your own command; lossy across turns. Lossless capture: serial_output_history with since_seq. Waiting for known text: serial_wait_for.

Inputs: no named parameters.

serial_wait_forA
Read-only

Wait for a regex in serial output without sending. Use when you know the text to expect (a prompt, login:, a log line). Works for observers. Ends early on a critical anomaly ([ABORTED anomaly:...]). Open-ended listening: serial_monitor. Output that already arrived: serial_output_history.

Inputs: pattern (required), timeout_ms.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.3/5.0

    Scored across 9 tools

    Disambiguation4/5

    The four output-reading tools (serial_read, serial_output_history, serial_wait_for, serial_monitor) occupy adjacent territory, but their descriptions explicitly cross-reference each other and draw sharp boundaries (drain vs. lossless capture, known text vs. open-ended listen). The remaining tools target distinct concerns (ports, status, boot, incidents, report), so an agent can reliably pick apart.

    Naming Consistency4/5

    All nine tools share the serial_ prefix, giving a strong predictable namespace. Most are verb_noun (serial_read, serial_wait_for, serial_list_ports, serial_get_incidents), though a few are noun_status forms (serial_port_status, serial_boot_status) — a minor deviation, not a genuine inconsistency.

    Tool Count5/5

    Nine tools is well-scoped for a serial-debugging broker: each covers a distinct facet (port discovery, status, read/history, waiting/listening, boot, incidents, report). No tool appears redundant or padded.

    Completeness4/5

    Observational coverage is thorough — discovery, liveness, historical and live capture, waiting, anomaly detection, boot tracking, and reporting. The notable gap is that the surface is entirely read-only (no send/write tool), though the descriptions frame the server as an observer/broker, so this may be scoped intent rather than a true gap.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Stateful MCP server for driving debug probes (J-Link) to flash, debug, and inspect embedded targets. Enables AI agents to perform flash, memory, breakpoint, and ELF/SVD-aware operations conversationally.
      41
      49 PyPI
      10
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Agentic Hardware-in-the-Loop (Agentic HIL) lets coding agents develop firmware on real embedded boards: flash, reset, read UART and exchange CAN traffic, diagnose and fix. Local, policy-gated MCP tools support OpenOCD, pyOCD and STM32CubeProgrammer, with pytest for CI.
      44
      1,737 PyPI
      25
      Apache 2.0