---
title: Common Tools
description: Core serial port tools available in all modes
sidebar:
  order: 1
---

import { Aside } from '@astrojs/starlight/components';

These 20 tools work in both RS-232 and RS-485 modes. They cover port discovery, connection management, reading and writing data, configuration, flow control, diagnostics, and logging.

<Aside type="note">
Examples use tool-call notation: `tool_name(param=value)`. These are MCP tool calls made by the assistant, not code you write directly. Boolean values use JSON convention (`true`/`false`).
</Aside>

---

## Port Discovery

### list_serial_ports

List available serial ports on the system. Call this first to discover what ports are available before opening one.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `usb_only` | `bool` | `True` | Only show USB serial devices. Set `False` to include legacy/phantom ttyS ports. |
| `grep` | `str \| None` | `None` | Regex pattern to filter ports. Matches against device path, description, hardware ID, manufacturer, product, and serial number. |

**Returns:** List of port objects with `device`, `description`, `hwid`, `manufacturer`, `product`, `serial_number`, and `is_open` fields. Sorted with USB ports (ttyUSB, ttyACM) first.

```
# List all USB serial devices
list_serial_ports()

# Include legacy ttyS ports
list_serial_ports(usb_only=false)

# Find FTDI adapters
list_serial_ports(grep="FTDI")

# Match specific USB VID:PID
list_serial_ports(grep="VID:PID=0403:6001")
```

---

## Connection Management

### open_serial_port

Open a serial port connection with optional auto-baud detection. If `baudrate` is not specified, the server automatically detects the baud rate by analyzing incoming data patterns.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path (`/dev/ttyUSB0`, `COM3`) or URL scheme (`socket://`, `rfc2217://`, `loop://`, `spy://`, `cp2110://`). See [URL Schemes](/reference/url-handlers/) for details. |
| `baudrate` | `int \| None` | `None` | Baud rate. If `None`, auto-detect is attempted (recommended for unknown devices). Falls back to `MCSERIAL_DEFAULT_BAUDRATE` if detection fails. |
| `bytesize` | `Literal[5, 6, 7, 8]` | `8` | Number of data bits. |
| `parity` | `Literal["N", "E", "O", "M", "S"]` | `"N"` | Parity checking: None, Even, Odd, Mark, or Space. |
| `stopbits` | `Literal[1, 1.5, 2]` | `1` | Number of stop bits. |
| `timeout` | `float` | `1.0` | Read timeout in seconds. Default comes from `MCSERIAL_DEFAULT_TIMEOUT`. |
| `write_timeout` | `float \| None` | `None` | Write timeout in seconds. `None` means blocking writes. |
| `inter_byte_timeout` | `float \| None` | `None` | Timeout between bytes during read. `None` disables inter-byte timeout. |
| `xonxoff` | `bool` | `False` | Enable software flow control (XON/XOFF). |
| `rtscts` | `bool` | `False` | Enable hardware RTS/CTS flow control. |
| `dsrdtr` | `bool` | `False` | Enable hardware DSR/DTR flow control. |
| `exclusive` | `bool` | `False` | Request exclusive access (lock port from other processes). |
| `autobaud_probe` | `str \| None` | `None` | String to send during auto-detection (e.g., `"UUUUU"` for sync). |
| `autobaud_timeout` | `float` | `0.3` | Timeout per rate during auto-detection in seconds. |

**Returns:** Connection status dict with `success`, `port`, `mode`, `baudrate`, `bytesize`, `parity`, `stopbits`, flow control settings, and `resource_uri`. When auto-baud is used, includes an `autobaud` object with detection details.

<Aside type="tip">
When `baudrate` is `None` and the port is a local device (not a URL scheme), auto-baud detection runs automatically. For best results with devices that only respond to input, pass an `autobaud_probe` string like `"UUUUU"` or `"AT\r\n"`.
</Aside>

```
# Auto-detect baud rate
open_serial_port(port="/dev/ttyUSB0")

# Explicit baud rate with 8N1 (most common settings)
open_serial_port(port="/dev/ttyUSB0", baudrate=115200)

# RS-232 with hardware flow control
open_serial_port(port="/dev/ttyUSB0", baudrate=9600, rtscts=true)

# Exclusive access prevents other processes from using the port
open_serial_port(port="/dev/ttyUSB0", baudrate=9600, exclusive=true)

# Auto-detect with sync probe
open_serial_port(port="/dev/ttyUSB0", autobaud_probe="UUUUU")

# Open a network serial port
open_serial_port(port="socket://192.168.1.100:4001", baudrate=115200)

# Loopback for testing (no hardware needed)
open_serial_port(port="loop://", baudrate=9600)
```

<Aside type="note">
URL-opened ports skip auto-baud detection and exclusive access. The `inter_byte_timeout` and `exclusive` parameters are not passed to URL-based connections.
</Aside>

### close_serial_port

Close an open serial port connection and release it for other processes.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to close. |

**Returns:** Status dict with `success`, `port`, and `message` fields.

```
close_serial_port(port="/dev/ttyUSB0")
```

### set_port_mode

Switch a serial port between RS-232 and RS-485 modes. Mode determines which mode-specific tools are available.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to configure. |
| `mode` | `Literal["rs232", "rs485"]` | required | Target mode. |

**Returns:** Status dict with `success`, `previous_mode`, `current_mode`, and `mode_tools` (list of tools available in the new mode).

```
# Switch to RS-485 mode for Modbus communication
set_port_mode(port="/dev/ttyUSB0", mode="rs485")

# Switch back to RS-232
set_port_mode(port="/dev/ttyUSB0", mode="rs232")
```

<Aside type="caution">
Ports open in RS-232 mode by default. You must call `set_port_mode` before using RS-485 specific tools like `rs485_transact` or `rs485_scan_addresses`.
</Aside>

### get_connection_status

Get status of all open serial connections. Takes no parameters.

**Returns:** Dict with `connections` (map of port to status details) and `count`. Each connection includes `is_open`, `mode`, `baudrate`, `bytesize`, `parity`, `stopbits`, `timeout`, `write_timeout`, `inter_byte_timeout`, flow control flags, buffer counts (`in_waiting`, `out_waiting`), modem line states (`cts`, `dsr`, `ri`, `cd`, `rts`, `dtr`), and `resource_uri`.

```
get_connection_status()
```

---

## Writing Data

### write_serial

Write string data to an open serial port. Data is encoded using the specified encoding before sending.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to write to. |
| `data` | `str` | required | String data to send. |
| `encoding` | `str` | `"utf-8"` | Character encoding for the data. |

**Returns:** Dict with `success`, `bytes_written`, and `port`.

```
# Send an AT command
write_serial(port="/dev/ttyUSB0", data="AT\r\n")

# Send with specific encoding
write_serial(port="/dev/ttyUSB0", data="Hello", encoding="ascii")
```

### write_serial_bytes

Write raw bytes to an open serial port. Use this when you need to send binary data or specific byte sequences.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to write to. |
| `data` | `list[int]` | required | List of byte values (0-255). Values outside this range return an error. |

**Returns:** Dict with `success`, `bytes_written`, and `port`.

```
# Send AT\r\n as raw bytes
write_serial_bytes(port="/dev/ttyUSB0", data=[65, 84, 13, 10])

# Send a Modbus frame
write_serial_bytes(port="/dev/ttyUSB0", data=[0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A])
```

<Aside type="caution">
Byte values are validated before conversion. Any value outside 0-255 returns an error listing the invalid values, rather than silently truncating.
</Aside>

---

## Reading Data

### read_serial

Read data from an open serial port. Returns both decoded text and raw hex representation.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to read from. |
| `size` | `int \| None` | `None` | Maximum bytes to read. `None` reads all available data. |
| `encoding` | `str` | `"utf-8"` | Character encoding for decoding. |
| `timeout` | `float \| None` | `None` | Override the port's read timeout for this call. `None` uses the port default. |

**Returns:** Dict with `success`, `data` (decoded string), `bytes_read`, `raw_hex`, and `port`. Decoding errors are replaced rather than raising exceptions.

```
# Read all available data
read_serial(port="/dev/ttyUSB0")

# Read exactly 10 bytes
read_serial(port="/dev/ttyUSB0", size=10)

# Read with a longer timeout
read_serial(port="/dev/ttyUSB0", timeout=5.0)
```

### read_serial_line

Read a single line (until newline character) from an open serial port.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to read from. |
| `encoding` | `str` | `"utf-8"` | Character encoding for decoding. |

**Returns:** Dict with `success`, `line` (decoded string with trailing CR/LF stripped), `bytes_read`, and `port`.

```
read_serial_line(port="/dev/ttyUSB0")
```

### read_serial_lines

Read multiple lines from an open serial port. Reads up to `max_lines`, stopping early if no more data is available (readline returns empty due to timeout).

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to read from. |
| `max_lines` | `int` | `10` | Maximum number of lines to read. Must be 1-1000. |
| `encoding` | `str` | `"utf-8"` | Character encoding for decoding. |

**Returns:** Dict with `success`, `lines` (list of decoded strings), `count`, `bytes_read`, and `port`.

```
# Read up to 10 lines (default)
read_serial_lines(port="/dev/ttyUSB0")

# Read up to 50 lines of buffered output
read_serial_lines(port="/dev/ttyUSB0", max_lines=50)
```

### read_until

Read data until a specific terminator sequence is received. More flexible than `read_serial_line` because it supports any terminator string, not just newline.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to read from. |
| `terminator` | `str` | `"\n"` | Byte sequence to stop at. |
| `size` | `int \| None` | `None` | Maximum bytes to read. `None` means no limit (uses timeout). |
| `encoding` | `str` | `"utf-8"` | Character encoding for the terminator and result. |

**Returns:** Dict with `success`, `data` (decoded string), `bytes_read`, `raw_hex`, `port`, and `terminator_found` (bool indicating whether the terminator was actually found or the read timed out).

```
# Read until "OK\r\n"
read_until(port="/dev/ttyUSB0", terminator="OK\r\n")

# Read until a prompt character
read_until(port="/dev/ttyUSB0", terminator=">")

# Read until terminator with a size limit
read_until(port="/dev/ttyUSB0", terminator="\r\n", size=256)
```

---

## Configuration

### configure_serial

Modify settings on an already-open serial port. Only the parameters you provide are changed; everything else stays the same.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to configure. |
| `baudrate` | `int \| None` | `None` | New baud rate. `None` = no change. |
| `timeout` | `float \| None` | `None` | New read timeout in seconds. `None` = no change. |
| `write_timeout` | `float \| None` | `None` | New write timeout in seconds. `None` = no change. |
| `inter_byte_timeout` | `float \| None` | `None` | Timeout between bytes. `None` = no change. |
| `xonxoff` | `bool \| None` | `None` | Software flow control XON/XOFF. `None` = no change. |
| `rtscts` | `bool \| None` | `None` | Hardware RTS/CTS flow control. `None` = no change. |
| `dsrdtr` | `bool \| None` | `None` | Hardware DSR/DTR flow control. `None` = no change. |
| `rts` | `bool \| None` | `None` | Set RTS line state. `None` = no change. |
| `dtr` | `bool \| None` | `None` | Set DTR line state. `None` = no change. |

**Returns:** Dict with `success`, `port`, and all current settings (`baudrate`, `timeout`, `write_timeout`, `inter_byte_timeout`, `xonxoff`, `rtscts`, `dsrdtr`, `rts`, `dtr`).

```
# Change baud rate on the fly
configure_serial(port="/dev/ttyUSB0", baudrate=115200)

# Increase read timeout
configure_serial(port="/dev/ttyUSB0", timeout=5.0)

# Enable hardware flow control
configure_serial(port="/dev/ttyUSB0", rtscts=true)

# Set DTR low (useful for reset sequences)
configure_serial(port="/dev/ttyUSB0", dtr=false)
```

### flush_serial

Clear serial port input and/or output buffers. Discards any pending data.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to flush. |
| `input_buffer` | `bool` | `True` | Clear the input/receive buffer. |
| `output_buffer` | `bool` | `True` | Clear the output/transmit buffer. |

**Returns:** Dict with `success`, `port`, `flushed_input`, and `flushed_output`.

```
# Flush both buffers (default)
flush_serial(port="/dev/ttyUSB0")

# Flush only the input buffer
flush_serial(port="/dev/ttyUSB0", input_buffer=true, output_buffer=false)
```

---

## Flow Control

### set_flow_control

Manually gate input/output flow control signals on a serial port. Flow control must already be enabled via `configure_serial` (set `xonxoff=True` or `rtscts=True`) for these signals to have effect.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port. |
| `input_flow` | `bool \| None` | `None` | `True` sends XON (allow remote to send), `False` sends XOFF (tell remote to stop). `None` = no change. |
| `output_flow` | `bool \| None` | `None` | `True` resumes outgoing data, `False` pauses it. `None` = no change. |

**Returns:** Dict with `success`, `port`, `changes` (what was set), and `flow_control_enabled` (current `xonxoff` and `rtscts` state).

<Aside type="caution">
This function is not portable across all platforms. Behavior depends on the OS and driver support.
</Aside>

```
# Pause incoming data (send XOFF)
set_flow_control(port="/dev/ttyUSB0", input_flow=false)

# Resume incoming data (send XON)
set_flow_control(port="/dev/ttyUSB0", input_flow=true)
```

### cancel_read

Cancel any pending read operation on a serial port. Interrupts a blocking read by calling `cancel_read()` on the underlying pyserial connection.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port. |

**Returns:** Dict with `success`, `port`, and `cancelled`.

<Aside type="note">
Only effective on platforms that support it (POSIX with select-based reads).
</Aside>

```
cancel_read(port="/dev/ttyUSB0")
```

### cancel_write

Cancel any pending write operation on a serial port. Interrupts a blocking write by calling `cancel_write()` on the underlying pyserial connection.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port. |

**Returns:** Dict with `success`, `port`, and `cancelled`.

<Aside type="note">
Only effective on platforms that support it (POSIX with select-based writes).
</Aside>

```
cancel_write(port="/dev/ttyUSB0")
```

---

## Diagnostics

### set_low_latency_mode

Enable or disable low-latency mode on a serial port. Reduces latency by changing how the kernel buffers data. Linux only.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port. |
| `enabled` | `bool` | `True` | Enable or disable low latency mode. |

**Returns:** Dict with `success`, `port`, and `low_latency`.

<Aside type="note">
This is a Linux-specific feature. It may not be available on all hardware or kernel versions. Returns an error on unsupported systems rather than silently failing.
</Aside>

```
# Enable low latency for time-critical communication
set_low_latency_mode(port="/dev/ttyUSB0", enabled=true)
```

### detect_baud_rate

Auto-detect baud rate using multiple heuristics: 0x55 sync pattern analysis, byte distribution analysis, ASCII readability scoring, and framing indicators.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path. Opened temporarily if not already open. If open, the baud rate is changed during testing and restored afterward. |
| `probe` | `str \| None` | `None` | String to send to trigger a response. Use `"U"` or `"UUUUU"` for sync-based detection on echo-enabled devices. |
| `timeout_per_rate` | `float` | `0.5` | Seconds to wait for data at each baud rate. |
| `baudrates` | `list[int] \| None` | `None` | Custom list of rates to try. Default tries common rates: 115200, 9600, 57600, 38400, 19200, 230400, 460800, 921600, 4800, 2400, 1200, 300, 500000, 576000, 1000000, 1500000. |
| `use_sync_pattern` | `bool` | `True` | Analyze 0x55 sync patterns for improved detection accuracy. |

**Returns:** Dict with `success`, `port`, `detected_baudrate` (or `None` if confidence is below 50), `confidence` (0-100 score), `results` (top 5 candidates with detailed scoring), and `rates_tested`.

<Aside type="tip">
Detection works best when the remote device is actively sending data. For devices that only respond to input, use the `probe` parameter to send a string that triggers a response at each candidate baud rate.
</Aside>

```
# Detect baud rate on an active device
detect_baud_rate(port="/dev/ttyUSB0")

# Use a sync probe for echo devices
detect_baud_rate(port="/dev/ttyUSB0", probe="UUUUU")

# Test only common embedded rates
detect_baud_rate(port="/dev/ttyUSB0", baudrates=[9600, 115200, 57600])

# Longer wait time for slow devices
detect_baud_rate(port="/dev/ttyUSB0", timeout_per_rate=1.0)
```

---

## Logging

### configure_logging

Configure server-wide logging behavior. Controls the minimum log level and buffer size for the in-memory log accessible via `serial://log`.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `level` | `str` | `"INFO"` | Minimum log level: `DEBUG`, `INFO`, `WARNING`, or `ERROR`. |
| `max_entries` | `int` | `1000` | Maximum entries in the server log buffer (10-100000). |
| `clear` | `bool` | `False` | Clear existing log entries before applying new settings. |

**Returns:** Dict with `success`, `level`, `previous_level`, `max_entries`, `current_entries`, `cleared`, and `resource_uri`.

Log levels filter what gets captured:
- **DEBUG**: All events including TX/RX byte counts
- **INFO**: Port open/close, configuration changes (default)
- **WARNING**: Potential issues like sanitized filenames
- **ERROR**: Failed operations only

```
# Set to DEBUG for detailed operation logging
configure_logging(level="DEBUG")

# Reduce buffer size to save memory
configure_logging(max_entries=500)

# Clear logs and set to ERROR only
configure_logging(level="ERROR", clear=true)
```

<Aside type="tip">
Set level to `DEBUG` when troubleshooting communication issues. This captures TX/RX byte counts for all read/write operations without the overhead of full traffic logging.
</Aside>

### enable_traffic_log

Enable or disable per-port traffic capture. When enabled, all TX/RX data on the port is captured to an in-memory buffer accessible via `serial://{port}/log`.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to configure. |
| `enabled` | `bool` | `True` | Enable or disable traffic logging. |
| `max_entries` | `int` | `500` | Maximum traffic entries to keep (10-10000). |
| `clear` | `bool` | `False` | Clear existing traffic log entries. |

**Returns:** Dict with `success`, `port`, `traffic_log_enabled`, `max_entries`, `current_entries`, and `resource_uri`.

```
# Enable traffic logging on a port
enable_traffic_log(port="/dev/ttyUSB0")

# Enable with larger buffer for long sessions
enable_traffic_log(port="/dev/ttyUSB0", max_entries=2000)

# Clear and restart traffic capture
enable_traffic_log(port="/dev/ttyUSB0", clear=true)

# Disable traffic logging
enable_traffic_log(port="/dev/ttyUSB0", enabled=false)
```

<Aside type="note">
Traffic logging is automatically enabled when opening a port with the `spy://` URL scheme. The buffer is set to 1000 entries for spy connections.
</Aside>

<Aside type="caution">
Traffic capture stores raw bytes in memory. For high-throughput connections, use a reasonable `max_entries` limit to avoid excessive memory usage.
</Aside>
