sphero-rvr-mcp
# Sphero RVR MCP Server
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that enables AI assistants to control a [Sphero RVR](https://sphero.com/collections/rvr) robot. Run this on a Raspberry Pi connected to your RVR, and use any MCP-compatible client to drive, control LEDs, read sensors, and more.
## Features
### Core Capabilities
- **Full RVR Control**: Movement, LEDs, sensors, battery monitoring, IR communication
- **Distance-Based Movement**: Drive forward/backward by meters, pivot by degrees
- **Safety System**: Configurable speed limits, auto-stop timeout, emergency stop
- **Sensor Streaming**: Background streaming with cached data access
- **Natural Language Control**: Let AI drive your robot with conversational commands
- **Client Agnostic**: Works with any MCP-compatible client
### Low-Latency Architecture (v0.2.1+)
- **Direct Serial Protocol**: Bypasses SDK for sub-millisecond command latency
- **No Sphero SDK Required**: Works on Python 3.10 - 3.13+ (SDK was limited to 3.10)
- **Minimal Dependencies**: Just 4 packages (fastmcp, pydantic, structlog, pyserial)
- **Distance Control**: `drive_forward(0.5)` moves exactly 0.5 meters
- **Angle Control**: `pivot(90)` rotates exactly 90 degrees
- **Command Queue**: Priority-based async queue eliminates race conditions
- **Atomic State Management**: Thread-safe state with validated transitions
- **Structured Logging**: JSON or console format for easy debugging
## Compatible MCP Clients
- [Claude Code](https://claude.com/claude-code) (CLI)
- [Claude Desktop](https://claude.ai/download)
- [Cursor](https://cursor.sh/)
- [Zed](https://zed.dev/)
- [Continue.dev](https://continue.dev/)
- Any custom client using the [MCP SDK](https://modelcontextprotocol.io/sdks)
## Requirements
- Raspberry Pi 3 or newer (connected to Sphero RVR via serial)
- Python 3.10+ (tested on 3.10, 3.12, 3.13 - no SDK limitations)
- Sphero RVR with serial connection to Pi
## Installation
### Install from PyPI (recommended)
```bash
pip install sphero-rvr-mcp
```
### Install from source
```bash
git clone https://github.com/jsperson/sphero_rvr_mcp.git
cd sphero_rvr_mcp
pip install -e .
```
### Verify Installation
Run the pre-flight check to verify everything is set up correctly:
```bash
sphero-rvr-mcp --check
```
This will verify:
- Python version (requires 3.10+)
- FastMCP is installed
- Serial port exists and is accessible
- Current configuration settings
### Configure Your MCP Client
The server runs via stdio. Configure your MCP client with:
- **Command**: `python -m sphero_rvr_mcp`
#### Claude Code
```bash
claude mcp add sphero-rvr -c "python -m sphero_rvr_mcp"
```
Or edit `~/.claude.json`:
```json
{
"mcpServers": {
"sphero-rvr": {
"type": "stdio",
"command": "python",
"args": ["-m", "sphero_rvr_mcp"]
}
}
}
```
#### Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"sphero-rvr": {
"command": "python",
"args": ["-m", "sphero_rvr_mcp"]
}
}
}
```
#### Other Clients
Refer to your client's MCP configuration documentation. The server uses stdio transport.
## Configuration
Environment variables (optional):
| Variable | Default | Description |
|----------|---------|-------------|
| **Connection** | | |
| `RVR_SERIAL_PORT` | `/dev/ttyAMA0` | Serial port for RVR |
| `RVR_BAUD_RATE` | `115200` | Serial baud rate |
| **Safety** | | |
| `RVR_MAX_SPEED_PERCENT` | `50.0` | Default speed limit (0-100) |
| `RVR_COMMAND_TIMEOUT` | `5.0` | Auto-stop timeout (seconds, 0=disabled) |
| **Performance** | | |
| `RVR_COMMAND_QUEUE_SIZE` | `100` | Max queued commands |
| **Observability** | | |
| `RVR_LOG_LEVEL` | `INFO` | Log level (DEBUG, INFO, WARNING, ERROR) |
| `RVR_LOG_FORMAT` | `json` | Log format (json, console) |
## Usage
### Example Commands
Once your MCP client is connected to the server:
```
You: Connect to the RVR
You: Drive forward 6 inches
You: Pivot 90 degrees to the right
You: Drive forward 1 meter
You: Set all LEDs to blue
You: What's the battery level?
You: Which direction is the rover facing?
# Returns heading in degrees and cardinal direction (N, NE, E, SE, S, SW, W, NW)
You: Turn left 45 degrees and drive backward 0.5 meters
You: Emergency stop!
```
## Available Tools
### Connection (3 tools)
| Tool | Description |
|------|-------------|
| `connect` | Connect to RVR and wake it up |
| `disconnect` | Safely disconnect |
| `get_connection_status` | Get connection state, uptime, firmware |
### Movement (11 tools)
| Tool | Description |
|------|-------------|
| `drive_forward` | Drive forward by distance in meters |
| `drive_backward` | Drive backward by distance in meters |
| `pivot` | Turn in place by degrees (positive=right, negative=left) |
| `drive_with_heading` | Drive at speed toward heading (0-359) |
| `drive_tank` | Tank drive with left/right velocities (m/s) |
| `drive_rc` | RC-style with linear + yaw velocity |
| `stop` | Normal stop |
| `emergency_stop` | Immediate stop, blocks movement |
| `clear_emergency_stop` | Allow movement after e-stop |
| `reset_yaw` | Set current heading as 0 |
| `reset_locator` | Set current position as origin |
### LEDs (3 tools)
| Tool | Description |
|------|-------------|
| `set_all_leds` | Set all LEDs to RGB color |
| `set_led` | Set specific LED group to RGB |
| `turn_leds_off` | Turn off all LEDs |
LED groups: `headlight_left`, `headlight_right`, `brakelight_left`, `brakelight_right`, `status_indication_left`, `status_indication_right`, `battery_door_front`, `battery_door_rear`, `power_button_front`, `power_button_rear`, `undercarriage_white`, `all`
### Sensors (12 tools)
| Tool | Description |
|------|-------------|
| `start_sensor_streaming` | Start background sensor streaming |
| `stop_sensor_streaming` | Stop all streaming |
| `get_sensor_data` | Get cached sensor readings |
| `get_ambient_light` | Query light sensor directly |
| `enable_color_detection` | Enable/disable color sensor LED |
| `get_color_detection` | Query color sensor (auto LED) |
| `get_magnetometer` | Get compass heading and cardinal direction |
| `calibrate_magnetometer` | Calibrate compass (rotate 360°) |
| `get_temperature` | Get motor temperatures (°C) |
| `get_encoder_counts` | Get wheel encoder tick counts |
| `get_motor_thermal_protection_status` | Motor thermal state (ok/warning/critical) |
| `get_ir_readings` | Get IR sensor readings (bot-to-bot, 255=no signal) |
Streamable sensors: `accelerometer`, `gyroscope`, `imu`, `locator`, `velocity`, `speed`, `quaternion`, `color_detection`, `ambient_light`, `core_time`
### Battery & System (11 tools)
| Tool | Description |
|------|-------------|
| `get_battery_status` | Battery percentage |
| `get_battery_voltage` | Battery voltage in volts |
| `get_battery_voltage_state` | Battery state (ok/low/critical) |
| `get_battery_thresholds` | Voltage thresholds (critical/low/hysteresis) |
| `get_safety_status` | Current safety settings |
| `get_firmware_version` | Firmware versions (Nordic + MCU) |
| `get_processor_name` | Processor identifiers (Nordic/ST) |
| `get_mac_address` | Bluetooth MAC address |
| `get_board_revision` | PCB board revisions |
| `get_sku` | Product SKU |
| `get_core_uptime` | Uptime in milliseconds |
### Safety & Motor Protection (5 tools)
| Tool | Description |
|------|-------------|
| `set_speed_limit` | Set max speed (0-100%) |
| `set_command_timeout` | Set auto-stop timeout |
| `get_motor_fault_state` | Check for motor faults |
| `enable_motor_stall_notify` | Enable/disable stall detection |
| `enable_motor_fault_notify` | Enable/disable fault detection |
### IR Communication (7 tools)
| Tool | Description |
|------|-------------|
| `send_ir_message` | Send IR code (0-7) |
| `start_ir_broadcasting` | Start robot-to-robot IR broadcasting |
| `stop_ir_broadcasting` | Stop IR broadcasting |
| `start_ir_following` | Follow an IR-broadcasting robot |
| `stop_ir_following` | Stop following |
| `start_ir_evading` | Evade an IR-broadcasting robot |
| `stop_ir_evading` | Stop evading |
## Architecture
### Component Overview
```
MCP Tool Handlers (FastMCP)
|
v
Direct Serial Protocol (low-latency)
|
+-- Packet Builder (commands.py)
+-- Serial Connection (direct_serial.py)
|
v
Sphero RVR (via /dev/ttyAMA0)
```
### Key Design Patterns
**Direct Serial Protocol**
- Bypasses the Sphero SDK for minimal latency
- Constructs raw SOP/EOP packets directly
- Sub-millisecond command transmission
- Supports all core RVR commands
**Distance-Based Movement**
- `drive_forward(distance)` - Uses RVR's internal position controller
- `drive_backward(distance)` - Accurate reverse movement
- `pivot(degrees)` - Precise rotation using heading control
**Command Queue**
- All hardware commands go through async priority queue
- Priority levels: EMERGENCY (0) -> HIGH (1) -> NORMAL (2) -> LOW (3)
- Per-command timeout enforcement
- Eliminates race conditions in concurrent access
**Atomic State Management**
- Thread-safe with explicit locks
- Validated state transitions
- All changes logged
### Observability
**Structured Logging**
```json
{
"event": "command_submitted",
"command_type": "drive_forward",
"distance": 0.5,
"timestamp": "2026-01-15T01:23:45.678Z"
}
```
## Safety Features
### Speed Limiting
All movement commands are limited to a configurable percentage of max speed (default 50%). This prevents accidental high-speed collisions.
```
You: Set the speed limit to 25%
You: Now drive forward at full speed
# RVR will only go at 25% of max speed
```
### Command Timeout
If no movement command is received within the timeout period (default 5 seconds), the RVR automatically stops. This prevents runaway situations if connection is lost.
### Emergency Stop
Immediately stops all movement and blocks further motion until explicitly cleared. Has highest priority in the command queue.
```
You: Emergency stop!
# RVR stops immediately
# All movement commands will fail until:
You: Clear the emergency stop
```
## Troubleshooting
### Connection Issues
**"Failed to connect to RVR"**
- Check serial connection: `ls -l /dev/ttyAMA0`
- Ensure RVR is powered on and charged
- Verify baud rate (default 115200)
**Connection times out**
- RVR might be off or in deep sleep
- Try power cycling the RVR
- Check serial cable connection
### Performance Issues
**Slow response**
- Raspberry Pi 3 may be slower than Pi 4/5
- Reduce sensor streaming frequency
- Close unnecessary applications
### Sensor Issues
**Color detection returns all zeros**
- Ensure RVR is on a non-dark surface
- Try increasing stabilization time
- Check that belly LED is working
### RVR Behavior Issues
**RVR not responding to commands**
- Check if emergency stop is active: `get_safety_status`
- Check speed limit isn't set to 0%
- Try disconnecting and reconnecting
**RVR stops on its own**
- Check command timeout setting (default 5 seconds)
- Disable auto-stop: `set_command_timeout(0)`
- Verify battery level isn't critical
## Project Structure
```
sphero_rvr_mcp/
├── pyproject.toml # Package configuration
├── README.md # This file
├── LICENSE # MIT License
└── src/sphero_rvr_mcp/
├── __init__.py
├── __main__.py # Entry point
├── config.py # Configuration
├── api.py # Direct API (non-MCP)
├── server.py # MCP server & tool handlers
│
├── protocol/ # Direct serial protocol
│ ├── __init__.py
│ ├── commands.py # Packet builders
│ ├── direct_serial.py # Serial connection
│ └── packet.py # SOP/EOP framing
│
├── core/ # Core infrastructure
│ ├── command_queue.py # Priority command queue
│ ├── state_manager.py # Atomic state management
│ └── exceptions.py # Exception hierarchy
│
├── hardware/ # Hardware abstraction
│ ├── connection_manager.py # Connection lifecycle
│ ├── sensor_stream_manager.py # Sensor streaming
│ └── safety_monitor.py # Safety system
│
├── services/ # Application services
│ ├── movement_service.py
│ ├── led_service.py
│ └── ir_service.py
│
└── observability/ # Logging
└── logging.py # Structured logging
```
## Development
### Running Tests
```bash
# Unit tests
pytest tests/unit/
# Integration tests (requires RVR)
pytest tests/integration/
# All tests
pytest
```
### Viewing Logs
```bash
# Console format (human-readable)
RVR_LOG_FORMAT=console python -m sphero_rvr_mcp
# JSON format (machine-readable)
RVR_LOG_FORMAT=json python -m sphero_rvr_mcp
```
### Contributing
Contributions welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request
## Changelog
### v0.2.3 (2026-01-29)
- **22 new MCP tools** for full SDK sensor coverage:
- Temperature & thermal protection monitoring
- System info (firmware, MAC, SKU, uptime, board revision)
- Extended battery info (voltage, state, thresholds)
- Motion sensors (encoders, magnetometer with compass heading)
- Motor protection (fault state, stall/fault notifications)
- IR follow/evade behaviors
- **Fixed 4 sensor commands** with incorrect protocol parameters
- **Magnetometer heading** - returns `heading` (degrees) and `cardinal` direction
### v0.2.1 (2026-01-15)
- **Direct serial protocol** for low-latency control (bypasses SDK)
- Added `drive_forward` and `drive_backward` with distance in meters
- Added `pivot` command for precise angle rotation
- Removed unused dependencies (prometheus-client, tenacity, nest-asyncio)
- Added pyserial dependency
- Simplified architecture (removed circuit breaker, event bus, metrics)
- No longer requires Sphero SDK installation
### v0.2.0 (2026-01-14)
- Complete architectural rewrite for production reliability
- Added command queue with priority levels
- Added circuit breaker for connection resilience
- Added event bus for sensor distribution
- Added atomic state management
- Added comprehensive observability (logging + metrics)
- Fixed SDK response key parsing (color sensor now works!)
### v0.1.1 (2024-12-XX)
- Added connection timeouts
- Updated documentation
### v0.1.0 (2024-12-XX)
- Initial release
## License
MIT
## Credits
- [FastMCP](https://github.com/jlowin/fastmcp)
- [Model Context Protocol](https://modelcontextprotocol.io/)
- [Sphero RVR](https://sphero.com/collections/rvr)
TDQS
Scored across 55 tools
Most tools target distinct actions or sensors, but there is some overlap: multiple battery getters (status, voltage, voltage_state, thresholds) and get_sensor_data could be seen as a catch-all that duplicates specific getters. The test tools and connect_simple/connect add minor confusion, but descriptions generally clarify boundaries.
The majority of tools use a consistent verb_noun pattern (get_*, set_*, start_*, stop_*, drive_*). However, there are deviations like test_immediate_return, test_slow_return, connect_simple, emergency_stop, clear_emergency_stop, and turn_leds_off which break the pattern slightly.
With 55 tools, the server is well beyond the typical 3-15 range and even exceeds the 25+ threshold. The surface is bloated with redundant test utilities and multiple specialized getters that could be consolidated, making it overwhelming for agents.
The toolset covers core robot operations: connection, driving, LEDs, sensors, IR, battery, safety, and system info. Notable gaps include a direct position getter (only reset_locator and encoder counts) and no gyroscope/accelerometer readings, but the domain is largely covered.