# Tools Reference

All tools return structured JSON:
`{ "ok": true, ... }` on success,
`{ "ok": false, "error": { "code": "...", "message": "..." } }` on failure.

---

## BLE Core

### ble.scan_start

Start a background BLE scan. Returns a `scan_id` immediately. The scan runs for up to `timeout_s` seconds (auto-stops), or you can stop it early with `ble.scan_stop`.

```json
{ "timeout_s": 10, "name_filter": "Arduino", "service_uuid": "180a" }
```

Returns `{ "ok": true, "scan_id": "a1b2c3" }`.

### ble.scan_get_results

Non-blocking: return the devices discovered so far by a running (or finished) scan.

```json
{ "scan_id": "a1b2c3" }
```

Returns:

```json
{
  "ok": true,
  "active": true,
  "devices": [{
    "name": "Arduino",
    "address": "AA:BB:CC:DD:EE:FF",
    "rssi": -55,
    "tx_power": -12,
    "service_uuids": ["0000180a-0000-1000-8000-00805f9b34fb"],
    "manufacturer_data": { "76": "0215..." },
    "service_data": { "0000180a-...": "0a1b" }
  }]
}
```

Fields `tx_power`, `service_uuids`, `manufacturer_data`, and `service_data` are included when advertised by the device. `manufacturer_data` keys are company IDs; values are hex-encoded. `service_data` keys are UUIDs; values are hex-encoded.

### ble.scan_stop

Stop a running scan early and return the final device list. Safe to call on an already-finished scan.

```json
{ "scan_id": "a1b2c3" }
```

Returns `{ "ok": true, "devices": [...], "active": false }`.

### ble.connect

Connect to a device by address.

```json
{ "address": "AA:BB:CC:DD:EE:FF", "timeout_s": 10, "pair": true }
```

Set `pair` to `true` to bond during connection. Pairing works on Linux (BlueZ) and Windows (WinRT). On macOS, the OS pairs automatically when you access a secured characteristic, so this flag is not needed.

Returns `{ "ok": true, "connection_id": "abc123", "address": "..." }`.

### ble.disconnect

```json
{ "connection_id": "abc123" }
```

### ble.connection_status

Check whether a connection is still alive. If the device disconnected unexpectedly, the server detects it automatically and returns a clear status instead of letting subsequent calls fail with timeouts.

```json
{ "connection_id": "abc123" }
```

Returns `{ "ok": true, "connected": true, "address": "AA:BB:CC:DD:EE:FF" }` or `{ "ok": true, "connected": false, "address": "...", "disconnect_ts": 1700000000.0 }`.

### ble.discover

List services and characteristics (cached per connection).

```json
{ "connection_id": "abc123" }
```

Returns `{ "ok": true, "services": [{ "uuid": "...", "characteristics": [{ "uuid": "...", "handle": 3, "properties": ["read", "notify"], "descriptors": [{ "uuid": "...", "handle": 5 }] }] }] }`.

### ble.mtu

Return the negotiated MTU for a connection. The effective max write payload per packet is `mtu - 3` bytes (ATT header overhead).

```json
{ "connection_id": "abc123" }
```

Returns `{ "ok": true, "mtu": 517, "max_write_payload": 514 }`.

### ble.read

Read a characteristic value.

```json
{ "connection_id": "abc123", "char_uuid": "2a00" }
```

Returns `{ "ok": true, "value_b64": "...", "value_hex": "...", "value_len": 4 }`.

### ble.write

Write to a characteristic (requires `BLE_MCP_ALLOW_WRITES=true`).

```json
{ "connection_id": "abc123", "char_uuid": "2a00", "value_hex": "0102", "with_response": true }
```

### ble.read_descriptor

Read a GATT descriptor by handle. Handles are returned by `ble.discover`.

```json
{ "connection_id": "abc123", "handle": 5 }
```

Returns `{ "ok": true, "value_b64": "...", "value_hex": "...", "value_len": 2 }`.

### ble.write_descriptor

Write to a GATT descriptor by handle (requires `BLE_MCP_ALLOW_WRITES=true`). Rarely needed directly — bleak handles CCCD for notify/indicate automatically.

```json
{ "connection_id": "abc123", "handle": 5, "value_hex": "0100" }
```

### ble.subscribe

Subscribe to notifications on a characteristic.

```json
{ "connection_id": "abc123", "char_uuid": "2a37" }
```

Returns `{ "ok": true, "subscription_id": "sub456" }`.

### ble.unsubscribe

```json
{ "connection_id": "abc123", "subscription_id": "sub456" }
```

### ble.wait_notification

Block until the next single notification arrives, or timeout. For bursty/bulk flows prefer `ble.drain_notifications`.

```json
{ "connection_id": "abc123", "subscription_id": "sub456", "timeout_s": 10 }
```

Returns `{ "ok": true, "notification": { "value_b64": "...", "value_hex": "...", "ts": 1700000000.0 } }` or `{ "ok": true, "notification": null }` on timeout.

### ble.poll_notifications

Non-blocking: return up to `max_items` buffered notifications immediately (or an empty list).

```json
{ "connection_id": "abc123", "subscription_id": "sub456", "max_items": 50 }
```

Returns `{ "ok": true, "notifications": [...], "dropped": 0 }`.

### ble.drain_notifications

Batch-collect: waits up to `timeout_s` for the first notification, then keeps collecting until `idle_timeout_s` passes with no new data, `max_items` is reached, or `timeout_s` expires. Ideal for bursty flows like downloading a log file or dataset over BLE notifications.

```json
{ "connection_id": "abc123", "subscription_id": "sub456", "timeout_s": 2, "idle_timeout_s": 0.25, "max_items": 200 }
```

Returns `{ "ok": true, "notifications": [...], "dropped": 0 }`.

---

## Introspection

### ble.connections.list

List all tracked connections with their status, address, name, timestamps, and subscription count. Useful for recovering connection IDs after context loss.

```json
{}
```

Returns:

```json
{
  "ok": true,
  "connections": [{
    "connection_id": "abc123",
    "address": "AA:BB:CC:DD:EE:FF",
    "name": "Arduino",
    "connected": true,
    "created_ts": 1700000000.0,
    "last_seen_ts": 1700000050.0,
    "subscription_count": 1,
    "spec": { "spec_id": "a1b2c3", "name": "MyDevice Protocol" }
  }],
  "count": 1
}
```

Disconnected entries include `disconnect_ts`. The `spec` field is `null` if no spec is attached.

### ble.subscriptions.list

List all active subscriptions with their status, queue depth, and dropped count. Optionally filter by `connection_id`.

```json
{ "connection_id": "abc123" }
```

Returns:

```json
{
  "ok": true,
  "subscriptions": [{
    "subscription_id": "sub456",
    "connection_id": "abc123",
    "char_uuid": "00002a37-0000-1000-8000-00805f9b34fb",
    "active": true,
    "queue_depth": 3,
    "dropped": 0,
    "created_ts": 1700000010.0
  }],
  "count": 1
}
```

### ble.scans.list

List all tracked scans with their status, filters, timestamps, and device count. Does not dump full device lists — use `ble.scan_get_results` for that.

```json
{}
```

Returns:

```json
{
  "ok": true,
  "scans": [{
    "scan_id": "a1b2c3",
    "active": true,
    "started_ts": 1700000000.0,
    "timeout_s": 10.0,
    "num_devices_seen": 5,
    "filters": { "name_filter": "Arduino", "service_uuid": null }
  }],
  "count": 1
}
```

The `filters` field is `null` when no filters were applied.

---

## Protocol Specs

Tools for managing BLE device protocol specs. Specs are markdown files with YAML front-matter stored in `.ble_mcp/specs/`.

### ble.spec.template

Return a markdown template for a new BLE protocol spec.

```json
{ "device_name": "MyDevice" }
```

Returns `{ "ok": true, "template": "---\nkind: ble-protocol\n...", "suggested_path": ".ble_mcp/specs/mydevice.md" }`.

### ble.spec.register

Register a spec file in the index. Validates YAML front-matter (requires `kind: ble-protocol` and `name`). The path must be inside the project directory (where `.ble_mcp/` lives).

```json
{ "path": ".ble_mcp/specs/mydevice.md" }
```

Returns `{ "ok": true, "spec_id": "a1b2c3d4e5f67890", "name": "MyDevice Protocol", ... }`.

### ble.spec.list

List all registered specs with their metadata.

```json
{}
```

Returns `{ "ok": true, "specs": [...], "count": 2 }`.

### ble.spec.attach

Attach a registered spec to a connection session (in-memory only). The spec will be available via `ble.spec.get` for the duration of the connection.

```json
{ "connection_id": "abc123", "spec_id": "a1b2c3d4e5f67890" }
```

### ble.spec.get

Get the attached spec for a connection (returns `null` if none attached).

```json
{ "connection_id": "abc123" }
```

### ble.spec.read

Read full spec content, file path, and metadata by spec_id.

```json
{ "spec_id": "a1b2c3d4e5f67890" }
```

### ble.spec.search

Full-text search over a spec's content. Returns matching snippets with line numbers and context.

```json
{ "spec_id": "a1b2c3d4e5f67890", "query": "sensor read", "k": 10 }
```

---

## Tracing

Tools for inspecting the JSONL trace log. Tracing is enabled by default and records every tool call.

### ble.trace.status

Return tracing config and event count.

```json
{}
```

Returns `{ "ok": true, "enabled": true, "event_count": 42, "file_path": ".ble_mcp/traces/trace.jsonl", "payloads_logged": false, "max_payload_bytes": 16384 }`.

### ble.trace.tail

Return last N trace events (default 50).

```json
{ "n": 20 }
```

Returns `{ "ok": true, "events": [{ "ts": "...", "event": "tool_call_start", "tool": "ble.read", ... }, ...] }`.

---

## Plugins

Tools for managing user plugins. Plugins live in `.ble_mcp/plugins/` and can add device-specific tools without modifying the core server. Requires `BLE_MCP_PLUGINS` env var to be set.

### ble.plugin.template

Return a Python plugin template. Optionally pre-fill with a device name.

```json
{ "device_name": "SensorTag" }
```

Returns `{ "ok": true, "template": "\"\"\"Plugin for SensorTag...", "suggested_path": ".ble_mcp/plugins/sensortag.py" }`.

### ble.plugin.list

List loaded plugins with their tool names and metadata.

```json
{}
```

Returns:

```json
{
  "ok": true,
  "plugins": [{
    "name": "sensortag",
    "path": "/path/to/.ble_mcp/plugins/sensortag.py",
    "tools": ["sensortag.read_temp"],
    "meta": {
      "description": "SensorTag plugin",
      "service_uuids": ["f000aa00-0451-4000-b000-000000000000"],
      "device_name_contains": "SensorTag"
    }
  }],
  "count": 1,
  "plugins_dir": "/path/to/.ble_mcp/plugins",
  "enabled": true,
  "policy": "*"
}
```

The `meta` field is plugin-defined (optional). Common keys: `description`, `service_uuids`, `device_name_contains`.

### ble.plugin.reload

Hot-reload a plugin by name. Re-imports the module and refreshes tools.

```json
{ "name": "sensortag" }
```

Returns `{ "ok": true, "name": "sensortag", "tools": ["sensortag.read_temp"], "notified": true }`.

### ble.plugin.load

Load a new plugin from a file or directory path. The path must be inside `.ble_mcp/plugins/`.

```json
{ "path": ".ble_mcp/plugins/sensortag.py" }
```

Returns `{ "ok": true, "name": "sensortag", "tools": ["sensortag.read_temp"], "notified": true, "hint": "Plugin loaded on the server. The client may need a restart to call the new tools." }`.

---

## MCP log notifications

The server sends MCP `notifications/message` log events to proactively alert the client. These are not tool calls — they are asynchronous messages sent outside the request/response cycle.

| Event | Level | When |
|---|---|---|
| Device disconnect | `warning` | A connected BLE device disconnects unexpectedly |
| Notification available | `info` | First GATT notification arrives on a subscription since the last `ble.drain_notifications`, `ble.poll_notifications`, or `ble.wait_notification` |

These are best-effort and fire-and-forget. The server's internal state (connection status, notification queue) is always the source of truth. Not all MCP clients surface log notifications — Claude Code currently ignores them. The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) shows them in real time and is useful for verifying the behavior.
