Stop fumbling with GUI apps to debug Bluetooth Low Energy devices. blew gives you full BLE control from the macOS command line: scan the airwaves, drill into any device's GATT tree, read and write characteristics, stream live notifications, and even spin up a virtual peripheral that other devices can connect to. - One tool, zero ceremony. Scan, connect, inspect, read, write, subscribe -- each is a
Add this skill
npx mdskills install stass/blewComprehensive BLE CLI tool with excellent documentation, MCP integration, and rich feature set
1# blew -- BLE scanner and CLI tool for Mac OS X23Stop fumbling with GUI apps to debug Bluetooth Low Energy devices. `blew` gives you full BLE control from the macOS command line: scan the airwaves, drill into any device's GATT tree, read and write characteristics, stream live notifications, and even spin up a virtual peripheral that other devices can connect to.45<img src="img/scan.avif" alt="BLE scan" width="100%">67<img src="img/gatttree.avif" alt="GATT tree" width="49%"> <img src="img/uart.avif" alt="UART" width="49%">89### Why blew1011- **One tool, zero ceremony.** Scan, connect, inspect, read, write, subscribe -- each is a single command. Auto-connect means you never have to manually pair before doing real work.12- **Interactive when you want it.** Launch the REPL for an exploratory session with tab completion, persistent history, and background subscriptions that print while you keep typing.13- **Scriptable when you need it.** Chain commands with `exec`, pipe machine-readable `kv` output into `awk` or a log file, and use deterministic exit codes in CI or monitoring scripts.14- **Full GATT visibility.** Print the service/characteristic tree of any device in one shot. Read all values inline. Look up any Bluetooth SIG characteristic's field-level spec without even connecting.15- **Peripheral mode.** Turn your Mac into a virtual BLE device. Define a GATT server from a JSON config, or clone a real device's entire service tree and replay it.16- **Human-readable by default.** Standard Bluetooth SIG UUIDs are resolved to their names everywhere -- scan results, GATT trees, notifications -- sourced from the official Bluetooth SIG database.1718### Modes of operation1920- **Command mode** -- run a single command, then exit: `blew [global-options] <command> [command-options]`21- **Interactive REPL** -- run `blew` with no command for a readline-style shell with history and tab completion22- **Script mode** -- run a semicolon-separated sequence sharing one connection: `blew exec "connect -n Sensor; gatt tree; read -f uint8 2A19"`23- **MCP server mode** -- expose all BLE operations as MCP tools for AI agents: `blew mcp`2425> Requires macOS 13+ and Bluetooth permission.2627### Things you can do2829**Clone a real device and impersonate it.** Walk up to a heart rate monitor, clone its full GATT tree, and your Mac starts advertising as that device. Other apps can connect to the clone as if it were the real thing:3031```sh32% blew periph clone -n "Heart Rate Monitor" --save hr.json33```3435**Watch the BLE airwaves live.** See every device around you with signal-strength bars, updating in real time. Filter by name, service, or signal floor to zero in on what you need in a crowded venue:3637```sh38% blew scan -w -R -7039```4041**X-ray a device in one shot.** Connect, discover all services, read every readable characteristic, and print the whole thing as a tree, including names, properties, descriptors and live values:4243```sh44% blew -n "Thingy" gatt tree -dr45```4647**Look up any Bluetooth SIG characteristic without a device.** Instantly see the field-level structure of any standard characteristic, like byte layout, types, conditional fields:4849```sh50% blew gatt info 2A37 # Heart Rate Measurement spec51```5253**Stream sensor data straight to a log.** Subscribe to a characteristic, format values as key-value pairs, and pipe to a file or another tool. Runs headless, exits cleanly on timeout:5455```sh56% blew -o kv sub -n "Sensor" -f uint16le -d 3600 fff1 >> hourly.log57```5859**Spin up a virtual BLE device from a JSON file.** Define services, characteristics, properties, and initial values in a config file and start advertising in one command:6061```sh62% blew periph adv --config health-thermometer.json63```6465**Run a multi-step test sequence as a one-liner.** Connect, inspect, read, write, wait, read again or subscribe to notifications as a single command:6667```sh68% blew exec -k "connect -n Sensor; gatt tree; write -f uint8 fff2 01; sleep 2; read -f uint16le fff1"69```7071---7273## Installation7475### Homebrew7677```bash78brew install stass/tap/blew79```8081### Build from source8283```bash84git clone --recurse-submodules https://github.com/stass/blew.git85swift build -c release86cp .build/release/blew /usr/local/bin/blew87```8889`--recurse-submodules` is required — the Bluetooth SIG name and characteristic databases are included as git submodules under `Vendor/`. The Swift build generates all data files automatically from the submodule data.9091On first run, macOS will prompt for Bluetooth permission. Grant it in **System Settings → Privacy & Security → Bluetooth**.9293---9495## Quick start9697```sh98# Scan for nearby devices99% blew scan100101# Print the GATT tree of a device (connects automatically)102% blew -n "Thingy" gatt tree103104# Read a characteristic (battery level)105% blew -n "Thingy" read -f uint8 2A19106107# Subscribe to notifications for 10 seconds108% blew -n "Thingy" sub -d 10 fff1109110# Run a multi-step procedure in one invocation111% blew -n "Thingy" -x "gatt tree; read -f uint8 2A19"112113# Start the interactive REPL114% blew115```116117---118119## Global options120121These options apply to all commands and must be placed **before** the subcommand name:122123```sh124% blew [global-options] <command> [command-options]125```126127| Flag | Description |128|------|-------------|129| `-v, --verbose` | Increase log verbosity. Repeatable: `-vv` for debug-level detail. |130| `-o, --out <format>` | Output format for results: `text` (default, human-readable) or `kv` (key=value, one record per line — easier to parse). |131| `-t, --timeout <sec>` | Timeout in seconds for BLE operations. |132| `-h, --help` | Show help and exit. |133| `--version` | Show version and exit. |134135## Device targeting options136137Commands that connect to a device (`scan`, `connect`, `gatt`, `read`, `write`, `sub`, `periph clone`) accept these options to identify the target. Pass them after the subcommand name.138139Use `--id` to target an explicit device, or combine selectors to let blew find a match automatically.140141| Flag | Description |142|------|-------------|143| `-i, --id <device-id>` | Target a specific device by its identifier. |144| `-n, --name <substring>` | Filter by device name (substring match). |145| `-S, --service <uuid>` | Filter by advertised service UUID. Repeatable. |146| `-m, --manufacturer <id>` | Filter by manufacturer ID. |147| `-R, --rssi-min <dBm>` | Minimum RSSI threshold (e.g. `-R -70`). |148| `-p, --pick <strategy>` | How to pick when multiple devices match: `strongest` (default, highest RSSI), `first` (first seen), `only` (error if multiple match). |149150---151152## Commands153154### `scan` — Scan for BLE devices155156```157blew scan [-n <name>] [-S <uuid>] [-R <dBm>] [-i <id>] [-m <id>] [-p <strategy>] [-w]158```159160Scans for advertising BLE peripherals and prints a table of discovered devices. Timeout defaults to 5 seconds.161162**Flags:**163164| Flag | Description |165|------|-------------|166| `-n, --name` | Filter results by device name substring. |167| `-S, --service` | Filter by advertised service UUID. Repeatable. |168| `-R, --rssi-min` | Minimum RSSI threshold (e.g. `-R -70`). |169| `-i, --id` | Show only the device with this identifier. |170| `-m, --manufacturer` | Filter by manufacturer ID. |171| `-p, --pick` | Pick strategy: `strongest`, `first`, `only`. |172| `-w, --watch` | Continuously scan and show a live-updating device list. Runs until Ctrl-C or `-t` expires. Requires a TTY in text mode; use `-o kv` for piped output. |173174**Output columns (text):** ID, Name, RSSI, Signal (visual bar), Services175176Standard Bluetooth SIG service UUIDs in the Services column are shown with their human-readable name: e.g. `180F (Battery Service), 180A (Device Information)`.177178**Examples:**179```sh180% blew scan # Scan for 5 seconds181% blew -t 10 scan # Scan for 10 seconds182% blew scan -w # Continuously scan until Ctrl-C183% blew -t 30 scan -w # Watch for 30 seconds then stop184% blew scan -n "Heart" -w # Watch for Heart Rate devices185% blew scan -S 180D -R -65 # Heart Rate service, RSSI ≥ -65 dBm186% blew -o kv scan # Machine-readable output187% blew -o kv scan -w # Stream device updates (piping-friendly)188```189190---191192### `connect` — Connect to a device193194```195blew connect [-n <name>] [-S <uuid>] [-i <id>] [<device-id>]196```197198Explicitly connects to a BLE device and exits. Useful for testing connectivity or pre-warming a connection in `exec` scripts. The device can be specified by positional argument, `--id`, or device-targeting selectors (the tool will scan briefly to resolve them).199200For `gatt`, `read`, `write`, and `sub` you do not need to run `connect` first — those commands connect automatically using the same device-targeting options.201202The connection is automatically closed on exit.203204**Examples:**205```sh206% blew connect F3C2A1B0-... # Test connectivity to an explicit ID207% blew connect -n "Thingy" # Test connectivity by name208% blew connect -S 180F # Connect to device advertising Battery Service209```210211---212213### `gatt` — Inspect GATT structure214215`gatt` connects automatically if no connection is active. Specify the target device with `--id`, `--name`, or other device-targeting options.216217All UUID outputs include human-readable names for standard Bluetooth SIG UUIDs. Custom or vendor UUIDs are shown as-is.218219#### `gatt svcs` — List services220221```222blew gatt svcs [-n <name>] [-i <id>]223```224225Lists all discovered services. Output columns: UUID, Name, Primary.226227```228UUID Name Primary229------------------------------------230180F Battery Service yes231180A Device Information yes232FFF0 yes233```234235#### `gatt tree` — Show full GATT tree236237```238blew gatt tree [-n <name>] [-i <id>] [-d] [-r]239```240241Prints services and their characteristics with properties (read / write / notify / indicate). Standard UUIDs show their human-readable name alongside.242243```244Service 180F Battery Service245├── 2A19 Battery Level [read, notify]246│ └── 2902 Client Characteristic Configuration247└── FFF1 [read, write-without-response, notify]248249Service FFF0250└── FFF1 [read, write-without-response, notify]251```252253| Flag | Description |254|------|-------------|255| `-d, --descriptors` | Also show descriptors for each characteristic. |256| `-r, --read` | Read and display values for all readable characteristics inline. Well-known Bluetooth SIG characteristics are decoded to their natural type (e.g. battery level as an integer, manufacturer name as a string); others are shown as hex. |257258With `-r`:259```260Service 180F Battery Service261└── 2A19 Battery Level [read, notify] = 85262263Service 180A Device Information264├── 2A29 Manufacturer Name String [read] = Apple Inc.265└── 2A24 Model Number String [read] = MacBookPro18,3266```267268#### `gatt chars` — List characteristics for a service269270```271blew gatt chars [-n <name>] [-i <id>] [-r] <service-uuid>272```273274Output columns: UUID, Name, Properties.275276| Flag | Description |277|------|-------------|278| `-r, --read` | Read and display values for all readable characteristics. Adds a Value column to the table; non-readable characteristics get an empty cell. |279280#### `gatt desc` — List descriptors for a characteristic281282```283blew gatt desc [-n <name>] [-i <id>] <char-uuid>284```285286Output columns: UUID, Name.287288#### `gatt info` — Show Bluetooth SIG specification for a characteristic289290```291blew gatt info <char-uuid>292```293294Displays the Bluetooth SIG name, description, and field-level structure for any standard characteristic UUID. Does **not** require a connected device — it reads directly from the generated characteristic database.295296```297$ blew gatt info 2A37298Heart Rate Measurement (2A37)299300The Heart Rate Measurement characteristic is used to represent data related to a heart rate measurement.301302Structure:303 Flags boolean[8] 1 byte304 Heart Rate Measurement Value (8 bit resolution) uint8 1 byte [present if bit 0 of Flags is 0]305 Heart Rate Measurement Value (16 bit resolution) uint16 2 bytes [present if bit 0 of Flags is 1]306 Energy Expended uint16 2 bytes [present if bit 3 of Flags is 1]307 RR-interval opaque variable (array) [present if bit 4 of Flags is 1]308```309310Struct-typed fields (such as an embedded `Date Time`) are recursively inlined with dot-separated names:311312```313$ blew gatt info 2A0A314Day Date Time (2A0A)315316The Day Date Time characteristic is used to represent weekday, date, and time.317318Structure:319 Date Time.Year uint16 2 bytes320 Date Time.Month uint8 1 byte321 Date Time.Day uint8 1 byte322 Date Time.Hours uint8 1 byte323 Date Time.Minutes uint8 1 byte324 Date Time.Seconds uint8 1 byte325 Day of Week.Day of Week uint8 1 byte326```327328With `-o kv`, one record per field is emitted (for scripting).329330**Examples:**331```sh332% blew gatt tree -n "Thingy"333% blew gatt tree -n "Thingy" -d # Include descriptors334% blew gatt tree -n "Thingy" -r # Read values for readable characteristics335% blew gatt tree -n "Thingy" -dr # Descriptors + values336% blew gatt chars -n "Thingy" 180F337% blew gatt chars -n "Thingy" -r 180A # Read values for Device Information338% blew gatt desc -n "Thingy" 2A19339```340341---342343### `read` — Read a characteristic value344345```346blew read [-n <name>] [-i <id>] [-f <format>] <char-uuid>347```348349Reads the value of a characteristic and prints it in the requested format. Connects automatically if no connection is active.350351| Flag | Description |352|------|-------------|353| `-f, --format <fmt>` | Output format (see table below). Default: `hex`. |354355**Formats:**356357| Format | Description |358|--------|-------------|359| `hex` | Hexadecimal string (default) |360| `utf8` | UTF-8 string |361| `base64` | Base64-encoded string |362| `uint8` | Unsigned 8-bit integer |363| `uint16le` | Unsigned 16-bit integer, little-endian |364| `uint32le` | Unsigned 32-bit integer, little-endian |365| `float32le` | 32-bit float, little-endian |366| `raw` | Raw bytes |367368**Examples:**369```sh370% blew read -n "Thingy" -f uint8 2A19 # Battery level as integer371% blew read -n "Thingy" -f utf8 2A29 # Manufacturer name string372% blew read -n "Thingy" fff1 # Raw characteristic as hex373```374375---376377### `write` — Write to a characteristic378379```380blew write [-n <name>] [-i <id>] [-f <format>] [-r|-w] <char-uuid> <data>381```382383Writes data to a characteristic. Connects automatically if no connection is active. Write mode (with or without response) is auto-selected based on the characteristic's properties unless overridden.384385| Flag | Description |386|------|-------------|387| `-f, --format <fmt>` | Data format (same values as `read`). Default: `hex`. |388| `-r, --with-response` | Force write-with-response. |389| `-w, --without-response` | Force write-without-response. |390391**Examples:**392```sh393% blew write -n "Thingy" fff1 "deadbeef" # Write hex bytes394% blew write -n "Thingy" -f uint8 -r 2A06 1 # Write uint8 with response395% blew write -n "Thingy" -f utf8 fff2 "hello" # Write a UTF-8 string396```397398---399400### `sub` — Subscribe to notifications or indications401402```403blew sub [-n <name>] [-i <id>] [-f <format>] [-d <sec>] [-c <count>] [-b] <char-uuid>404```405406Subscribes to a characteristic and streams received values to stdout, one event per line. Connects automatically if no connection is active. Stops on `Ctrl-C`, or when a duration/count limit is reached.407408| Flag | Description |409|------|-------------|410| `-f, --format <fmt>` | Value format (same values as `read`). Default: `hex`. |411| `-d, --duration <sec>` | Stop after this many seconds. |412| `-c, --count <n>` | Stop after receiving this many notifications. |413| `-b, --bg` | Run in background (REPL and `exec` mode only). Returns the prompt immediately; notifications are printed as they arrive. Use `sub stop` to cancel. |414415With `--out kv`, each line includes `ts=`, `char=`, and `value=` fields.416417**Background mode** (`-b`) is available in the REPL and `exec` scripts. Multiple characteristics can be subscribed simultaneously. Background notifications are printed to stderr with ANSI cursor control so they appear cleanly over the prompt:418419```420blew> sub -b -f uint8 2A19421Subscribing to 2A19 (Battery Level) in background. Use 'sub stop 2A19' to stop.422blew> sub -b fff1423Subscribing to fff1 in background. Use 'sub stop fff1' to stop.424blew> sub status425Active background subscriptions:426 2A19 (Battery Level)427 fff1428blew> sub stop 2A19429Stopped subscription for 2A19 (Battery Level).430blew> sub stop431Stopped all background subscriptions.432```433434**Examples:**435```sh436% blew sub -n "Thingy" fff1 # Stream indefinitely (Ctrl-C to stop)437% blew sub -n "Thingy" -f uint16le -d 30 fff1 # 30-second capture as uint16438% blew -o kv sub -n "Thingy" -c 100 2A37 # Capture 100 events, kv output439% blew -o kv sub -n "Thingy" fff1 >> data.log # Append to a log file440```441442---443444## Interactive REPL445446Run `blew` with no arguments (or with global options but no subcommand) to start the interactive REPL:447448```sh449% blew450% blew -v # Start verbose REPL451```452453The REPL provides:454- **Line editing** — cursor movement, Ctrl-A/E, Ctrl-W, etc.455- **Persistent history** — saved to `~/.config/blew/history` across sessions456- **Tab completion** — commands, known UUIDs after GATT discovery, and format names457458### REPL commands459460| Command | Description |461|---------|-------------|462| `scan [options]` | Scan for devices |463| `connect [<id>]` | Connect to a device |464| `disconnect` | Disconnect |465| `status` | Show connection status |466| `gatt svcs\|tree\|chars\|desc\|info` | GATT inspection |467| `read [-f <fmt>] <uuid>` | Read a characteristic |468| `write [-f <fmt>] [-r\|-w] <uuid> <data>` | Write to a characteristic |469| `sub [-f <fmt>] [-d <s>] [-c <n>] <uuid>` | Subscribe to notifications (Ctrl-C to stop) |470| `sub [-b] [-f <fmt>] [-d <s>] [-c <n>] <uuid>` | Subscribe in background; prompt remains available |471| `sub stop [<uuid>]` | Stop one or all background subscriptions |472| `sub status` | List active background subscriptions |473| `periph adv [-n <n>] [-S <uuid>] [--config <f>]` | Start advertising as a virtual peripheral |474| `periph clone [--save <f>]` | Clone a real device's GATT |475| `periph stop` | Stop advertising |476| `periph set [-f <fmt>] <uuid> <val>` | Update a characteristic value |477| `periph notify [-f <fmt>] <uuid> <val>` | Push a notification to subscribers |478| `periph status` | Show peripheral advertising state |479| `help` | Show available commands |480| `quit` / `exit` | Exit the REPL |481482**Example session:**483```484blew> scan -t 3 -n Thingy485ID NAME RSSI Signal Services486-------------------------------------------------------------------------------------------487F3C2A1B0-1234-5678-ABCD-000000000001 Thingy -58 ████████ 180F (Battery Service), 180A (Device Information)488489blew> connect F3C2A1B0-1234-5678-ABCD-000000000001490491blew> gatt tree492Service 180F Battery Service493└── 2A19 Battery Level [read, notify]494495Service 180A Device Information496└── 2A29 Manufacturer Name String [read]497498blew> read -f uint8 2A1949987500501blew> sub -f uint8 -c 5 2A195028750386504865058550685507508blew> quit509```510511---512513## Script execution (`exec`)514515The `exec` subcommand runs a semicolon-separated sequence of commands in a single process, sharing one connection lifecycle. Commands are parsed identically to the REPL. The first command that requires a connection triggers an automatic connect; subsequent commands reuse it.516517```sh518% blew exec "connect -n Thingy; gatt tree; read -f uint8 2A19"519```520521**Flags:**522523| Flag | Description |524|------|-------------|525| `-k, --keep-going` | Continue after a command error; exit with the first non-zero code seen. |526| `--dry-run` | Print parsed steps without executing them. |527528**Additional commands available in exec and REPL:**529530| Command | Description |531|---------|-------------|532| `sleep <seconds>` | Pause for the given number of seconds. `0` means infinite (until Ctrl-C). |533534**Examples:**535```sh536# Connect by name, read a value537% blew exec "connect -n Thingy; read -f uint8 2A19"538539# Wait 2 seconds between operations540% blew exec "connect -n Thingy; sleep 2; read -f uint8 2A19"541542# Keep going after errors543% blew exec -k "read fff1; read fff9"544545# Preview what would run546% blew exec --dry-run "connect -n Thingy; gatt tree; read -f uint8 2A19"547```548549---550551## Output and logging552553### stdout vs stderr554555| Stream | Content |556|--------|---------|557| stdout | Command results — tables, values, notification lines |558| stderr | Operational messages (errors, verbose info, debug) |559560### Log format (stderr)561562Errors are always printed:563```564Error: timeout waiting for connection565```566567With `-v`, informational messages are also shown:568```569connecting to F3C2A1B0-...570connected to F3C2A1B0-...571```572573With `-vv`, debug-level messages are added:574```575[debug] discovered service 180F576[debug] discovered characteristic 2A19577```578579### Key-value output (`--out kv`)580581Pass `-o kv` to get machine-parseable output. Each record is one line of space-separated `key=value` pairs (values containing spaces are quoted). Easy to process with `awk`, `grep`, or any log parser:582583```584id=F3C2A1B0-... name=Thingy rssi=-58 services=180F,180A585char=2A19 name="Battery Level" value=57 fmt=uint8586ts=2026-02-21T12:34:56.789Z char=fff1 value=deadbeef587```588589The `name=` field is included when the UUID is a known Bluetooth SIG UUID. It is omitted for custom or vendor UUIDs.590591```sh592% blew -n "Thingy" -o kv sub -d 60 fff1 | awk -F'value=' '{print $2}'593```594595---596597## Exit codes598599| Code | Meaning |600|------|---------|601| `0` | Success |602| `2` | Not found / no matching device |603| `3` | Bluetooth unavailable or permission denied |604| `4` | Timeout |605| `5` | Operation failed (connect / GATT / read / write / subscribe) |606| `6` | Invalid arguments |607608---609610### `periph` — Peripheral (GATT server) mode611612`periph` turns the Mac into a virtual BLE peripheral that nearby devices can connect to, read/write, and subscribe to.613614> **CoreBluetooth peripheral limitations:** Only the local device name and service UUIDs can be advertised. Manufacturer data and other ADV payload fields are not settable from the peripheral API. ADV interval, TX power, and connection parameters are OS-controlled.615>616> **Advertised name vs hostname:** macOS always uses the computer hostname as the GAP (Generic Access Profile) device name. iOS scanners that have previously connected to the Mac will show the cached GAP name (hostname) in their device list, not the advertising name set via `-n`. The advertising name is still present in the raw advertisement data (`kCBAdvDataLocalName`) and is visible in tools like LightBlue under "Advertisement Data" after tapping the device.617>618> Most standard Bluetooth SIG service UUIDs work in short form (e.g. `180D`, `1809`, `181A`). However, services that macOS itself exposes as a peripheral are blocked in short form — `CBPeripheralManager` rejects `1800` (Generic Access), `1801` (Generic Attribute), `1805` (Current Time), `180A` (Device Information), `180F` (Battery), `1812` (HID), and `181E` (Bond Management) with "The specified UUID is not allowed for this operation." The full 128-bit Bluetooth Base UUID form (e.g. `0000180F-0000-1000-8000-00805F9B34FB`) bypasses the check but registers as a raw 128-bit UUID, so centrals will not recognise it as the standard service.619620#### `periph adv` — Advertise and host a GATT server621622```623blew periph adv [-n <name>] [-S <uuid>] [--config <file>]624```625626Starts advertising and runs until interrupted (Ctrl-C). Events (reads, writes, subscriptions) are logged to stdout.627628| Flag | Description |629|------|-------------|630| `-n, --name <name>` | Advertised device name. Defaults to `blew`. |631| `-S, --service <uuid>` | Service UUID to advertise, repeatable. |632| `-c, --config <file>` | JSON config file defining services and characteristics. |633634**Config file format:**635636```json637{638 "name": "My Device",639 "services": [640 {641 "uuid": "180F",642 "primary": true,643 "characteristics": [644 {645 "uuid": "2A19",646 "properties": ["read", "notify"],647 "value": "55",648 "format": "uint8"649 }650 ]651 }652 ]653}654```655656`properties` may contain: `read`, `write`, `writeWithoutResponse`, `notify`, `indicate`.657`format` accepts the same values as `--format` in `read`/`write`: `hex` (default), `utf8`, `uint8`, `uint16le`, `uint32le`, `float32le`, `base64`, `raw`.658659**Examples:**660661```sh662# Advertise a name + service UUID (scanner-visible, no GATT characteristics)663% blew periph adv -n "My Sensor" -S 180F664665# Multiple service UUIDs666% blew periph adv -n "My Sensor" -S 180F -S 180A667668# Full GATT server from a config file669% blew periph adv --config device.json670671# Config file with name override672% blew periph adv -n "Override Name" --config device.json673```674675Example config files are provided in the [`Examples/`](Examples/) directory:676677| File | Description |678|------|-------------|679| `health-thermometer.json` | Health Thermometer (`1809`) — indicate temperature measurement (2A1C), read temperature type (2A1D), read/write/notify measurement interval (2A21) |680| `environmental-sensing.json` | Environmental Sensing (`181A`) — read/notify temperature (2A6E), humidity (2A6F), and pressure (2A6D) |681| `blood-pressure.json` | Blood Pressure (`1810`) — indicate measurement (2A35), read feature flags (2A49) |682| `custom-sensor.json` | Fully custom vendor service with 128-bit UUIDs — read/notify value, read/write config register, write-only command endpoint |683684**Output (text mode):**685686```687Advertising "My Sensor" [180F (Battery Service)]688 Service 180F (Battery Service)689 +-- 2A19 (Battery Level) [read, notify]690691[12:34:56] central A1B2C3D4 connected692[12:34:56] read 2A19 (Battery Level) by A1B2C3D4693[12:34:57] subscribe 2A19 (Battery Level) by A1B2C3D4694[12:35:01] write 2A19 (Battery Level) by A1B2C3D4 <- 2a695^C696Stopped advertising.697```698699**Output (kv mode):**700701```702event=connected ts=12:34:56 central=A1B2C3D4-...703event=read ts=12:34:56 central=A1B2C3D4-... char=2A19704event=subscribe ts=12:34:57 central=A1B2C3D4-... char=2A19705event=write ts=12:35:01 central=A1B2C3D4-... char=2A19 value=2a706```707708#### `periph clone` — Clone a real device709710```711blew periph clone [-n <name>] [-i <id>] [--save <file>]712```713714Connects to a target device, discovers its full GATT tree, reads all readable characteristic values, disconnects, then starts advertising as a clone.715716| Flag | Description |717|------|-------------|718| `-n, --name` | Target device name filter. |719| `-i, --id` | Target device identifier. |720| `-o, --save <file>` | Save the cloned GATT structure to a JSON config file for later reuse. |721722**Clone scope:**723- Cloned: advertised name, service UUIDs, full GATT structure, initial characteristic values (for readable characteristics).724- Not cloned: manufacturer data, service data in ADV payload, ADV timing.725726**Examples:**727728```sh729% blew periph clone -n "Heart Rate Monitor"730% blew periph clone -i F3C2A1B0-... --save hr-monitor.json731```732733#### REPL-only periph commands734735In REPL and `exec` mode, `periph adv` and `periph clone` run in two phases. The startup phase (configure + start advertising) is synchronous and confirms the peripheral came up. Once advertising is confirmed, the event loop runs as a background task and the prompt is returned immediately. This makes `periph stop`, `periph set`, and `periph notify` usable in the same session:736737| Command | Description |738|---------|-------------|739| `periph stop` | Stop advertising and cancel the background event task. |740| `periph set [-f <fmt>] <char> <val>` | Update a characteristic's stored value. |741| `periph notify [-f <fmt>] <char> <val>` | Update value and push a notification to all subscribers. |742| `periph status` | Show advertising state, service/characteristic counts, subscriber count. |743744```745blew> periph adv --config device.json746Advertising "My Device" [180F]747Advertising in background. Use 'periph stop' to stop.748blew> periph notify -f uint8 2A19 42749blew> periph stop750Stopped advertising.751```752753In `exec` mode the same non-blocking behaviour applies, enabling scripts like:754755```sh756% blew exec "periph adv -n 'My Device' --config device.json; periph set 2A19 ff; periph notify 2A19 ff"757```758759---760761## MCP Server Mode762763`blew mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio, exposing all BLE operations as individual MCP tools. AI agents (Cursor, Claude Desktop, etc.) can discover and invoke these tools to interact with BLE devices programmatically.764765The server is stateful: BLE connections and peripheral state persist across tool calls, just like the REPL. Tool results are returned as structured JSON (`structuredContent`), giving agents typed access to scan results, GATT data, read values, and more.766767### Agent configuration768769**Cursor** (`.cursor/mcp.json`):770771```json772{773 "mcpServers": {774 "blew": {775 "command": "/path/to/blew",776 "args": ["mcp"]777 }778 }779}780```781782**Claude Desktop** (`claude_desktop_config.json`):783784```json785{786 "mcpServers": {787 "blew": {788 "command": "/path/to/blew",789 "args": ["mcp"]790 }791 }792}793```794795### Available tools796797| Tool | Description |798|------|-------------|799| `ble_scan` | Scan for nearby BLE devices |800| `ble_connect` | Connect to a device by ID, name, or filters |801| `ble_disconnect` | Disconnect from current device |802| `ble_status` | Show connection status |803| `ble_gatt_services` | List GATT services |804| `ble_gatt_tree` | Full GATT tree with optional values |805| `ble_gatt_chars` | List characteristics of a service |806| `ble_gatt_descriptors` | List descriptors of a characteristic |807| `ble_gatt_info` | Look up Bluetooth SIG characteristic spec (no device needed) |808| `ble_read` | Read a characteristic value |809| `ble_write` | Write to a characteristic |810| `ble_subscribe` | Collect notifications (returns batch) |811| `ble_periph_advertise` | Start advertising as a peripheral |812| `ble_periph_clone` | Clone a real device's GATT structure |813| `ble_periph_stop` | Stop advertising |814| `ble_periph_set` | Update a peripheral characteristic value |815| `ble_periph_notify` | Update value and notify subscribers |816| `ble_periph_status` | Show peripheral state |817818All tools that interact with a remote device accept optional targeting parameters (`name`, `device_id`, `service`, `manufacturer`, `rssi_min`, `pick`) and auto-connect if not already connected.819820### Example structured output821822A `ble_scan` call returns:823824```json825{826 "type": "devices",827 "devices": [828 {829 "id": "A1B2C3D4-...",830 "name": "Heart Rate Sensor",831 "rssi": -55,832 "serviceUUIDs": ["180D"],833 "serviceDisplayNames": ["Heart Rate"]834 }835 ]836}837```838839A `ble_read` call returns:840841```json842{843 "type": "readValue",844 "readValue": {845 "char": "2A19",846 "name": "Battery Level",847 "value": "85",848 "format": "uint8"849 }850}851```852853---854855## Recipes856857### Find a device in a crowded environment858```sh859% blew scan -n "Sensor" -S 180F -R -65 -t 10860```861862### Capture sensor data to a file863```sh864% blew -o kv sub -n "Sensor" -f uint16le -d 300 fff1 >> sensor.log865```866867### Quick GATT audit in one line868```sh869% blew gatt tree -n "Thingy" -d870```871872### Inspect device info characteristics with values873```sh874% blew gatt chars -n "Thingy" -r 180A875```876877### Read with scripting878```sh879% value=$(blew read -n "Thingy" -f uint8 2A19)880% echo "Battery: ${value}%"881```882883### Use `--pick only` to guard against accidental multi-match884```sh885% blew read -n "Thingy" -p only -f uint8 2A19886# Errors out if more than one "Thingy" is nearby887```
Full transparency — inspect the skill content before installing.