MCP server for BLE scanning and GATT enumeration in IoT security testing
ble-mcp has well-structured tool definitions with complete input schemas and clear descriptions. All 7 tools are explicitly registered with Tool() objects in server.py, each with name, description, and inputSchema. Descriptions are detailed (100-200 chars) and include duration/risk metadata. However, parameter descriptions lack specificity on constraints (e.g., 'address' accepts 'MAC address or UUID' but no format/pattern guidance). Output schemas are not documented, LLMs cannot predict response structure. Error handling returns generic error dicts without recovery guidance. No tool annotations (readOnlyHint/destructiveHint) despite clear risk classifications in descriptions.
Connect to a BLE device by address and create an engagement folder. Returns a connection_id for subsequent calls. [allowed-write] [Duration: fast (~1-3 s for a clean connect; longer on flaky links). Lifecycle: persistent -- the BLE connection holds until disconnect is called or the peer drops.]
Disconnect a BLE device by connection ID. [allowed-write] [Duration: instant (~0.5 s).]
Walk GATT services and characteristics for a connected device. Saves results to logs/ble-gatt.json. [read-only] [Duration: slow (~5-20 s depending on service tree size). Foregroundable; background only if you need parallel work meanwhile.]
Monitor BLE advertisements for a fixed duration and return timestamped records. [read-only] [Duration: parameterized by duration_s (default 30 s -- slow). Background-dispatch recommended.]
Read a single GATT characteristic by UUID. Returns hex, text, and raw representations. [read-only] [Duration: fast (~0.5-2 s per read).]
Output schemas not documented. Tools return dicts with fields like 'devices', 'advertisements', 'services' but LLMs cannot predict structure or plan downstream calls. Baseline: 100% of A+ tools document return types.
Parameter constraints lack specificity. 'address' accepts 'MAC address or UUID (platform-dependent)' but no regex/format guidance. 'characteristic_uuid' has no format constraint. LLMs cannot validate inputs before calling.
Error handling returns generic {'error': str(exc)} without recovery guidance. Per pattern:recovery-guide, errors should tell LLMs what to do next. E.g., 'No connection with id X' should suggest 'Call connect() first with a valid device address.'
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 55 | <=2025-11-25 | v2 |
Scan for nearby BLE devices and return a list sorted by RSSI. [read-only] [Duration: parameterized by duration_s (default 10 s -- slow). Background-dispatch recommended if you need to do anything else during the scan window.]
Subscribe to GATT notifications for a characteristic for a fixed duration. Saves records to logs/ble-notifications.jsonl. [allowed-write] [Duration: parameterized by duration_s (default 30 s -- slow). Background-dispatch recommended; the main agent can do other work while notifications are collecting.]
Tool annotations missing. Descriptions embed [read-only] and [allowed-write] tags as text, but MCP spec supports readOnlyHint and destructiveHint in Tool.annotations. Machine-readable hints enable better agent planning.
Parameter 'engagement_path' in connect() is optional but its interaction with 'engagement_name' is undocumented. When both are provided, which takes precedence? Undocumented dependencies cause silent misuse.