BYD Vehicle Bridge MCP Server
by pavelpervi
README.md
# BYD Vehicle Bridge — MCP Server
[](https://www.python.org/)
[](https://modelcontextprotocol.io)
[](https://docker.com)
[](https://github.com/pavelpervi/byd-vehicle-bridge/actions/workflows/tests.yml)
[](https://github.com/pavelpervi/byd-vehicle-bridge/actions/workflows/docker-build.yml)
[](LICENSE)
A secure, read-only **MCP (Model Context Protocol) server** that connects to your **BYD electric vehicle** via the BYD cloud API. Designed for AI agents (like OpenClaw, Claude Code, or any MCP client) to query real-time vehicle data — battery SOC, range, tire pressures, door states, GPS, and more — while keeping your credentials safe on your own infrastructure.
Built with Python, pyBYD, and the MCP SDK.
---
## Architecture
```
┌─────────────────────── Host Machine ──────────────────────────┐
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Docker Network: byd-internal (172.28.0.0/16) │ │
│ │ │ │
│ │ ┌──────────────────────┐ MCP (SSE) ┌──────────┐ │ │
│ │ │ AI Agent │◄──────────────►│ BYD │ │ │
│ │ │ (OpenClaw / Claude) │ │ Bridge │ │ │
│ │ │ │ │ │ │ │
│ │ │ Tools: │ │ Tools: │ │ │
│ │ │ · get_battery() │ │ · poll │ │ │
│ │ │ · get_vehicle() │ │ · cache │ │ │
│ │ │ · get_all_data() │ │ · serve │ │ │
│ │ │ · get_health() │ │ │ │ │
│ │ └──────────────────────┘ └─────┬────┘ │ │
│ │ │ │ │
│ │ BYD Cloud API │ │
│ │ │ │ │
│ │ ┌────▼────┐ │ │
│ │ │ BYD │ │ │
│ │ │ Cloud │ │ │
│ │ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Credentials stored in .env — never leave this machine │
└───────────────────────────────────────────────────────────────┘
```
### Key Design Decisions
| Decision | Rationale |
|----------|-----------|
| **MCP over REST** | AI agents discover tools natively via the Model Context Protocol. No manual URL memorization, no raw HTTP parsing. |
| **SSE transport** | Server-Sent Events over HTTP — standard MCP transport, compatible with all MCP clients. |
| **Background polling** | The server polls the BYD API every 60s and caches results. Tools return instant cached data, never block on the network. |
| **Internal Docker network** | The bridge container exposes zero ports to the host. Only containers on the same Docker network can reach it. |
| **Read-only by design** | Remote commands (lock/unlock, AC control, windows) are intentionally excluded. This bridge reads, never writes. |
| **Dual mode** | `minimal` mode for privacy-sensitive users (no GPS, no door states). `full` mode for complete telemetry. |
| **ghcr.io registry** | Docker image published to GitHub Container Registry. Pull on any server — no rebuild needed. |
| **PR workflow** | All changes go through pull requests. Tests run automatically on every PR. |
---
## Features
- **🔋 Battery Monitoring** — State of charge (SOC %), estimated range, charging status
- **🚗 Driving Data** — Speed, power (kW), odometer, outside/cabin temperature
- **🔌 Charging Details** — Voltage, current, charge rate, time to full (full mode)
- **🛞 Tire Pressures** — All four wheels with units (full mode)
- **🚪 Door & Window States** — Open/closed status, lock state (full mode)
- **📍 GPS Location** — Latitude, longitude, heading (full mode)
- **🌡️ HVAC Status** — AC on/off, target temperature, fan speed (full mode)
- **🔒 Read-Only** — No remote commands. No write access. Ever.
- **🐳 Dockerized** — Single container, minimal footprint, non-root user
- **🔐 Credentials Safe** — BYD username/password never leave your VPS
- **🧪 28 Unit Tests** — Full test suite runs on every PR via GitHub Actions
- **⚙️ CI/CD** — Automated tests + Docker image build + push to ghcr.io
---
## MCP Tools
| Tool | Mode | Description |
|------|------|-------------|
| `get_battery()` | both | Battery SOC %, charging status, range, mileage, temps, speed, power |
| `get_vehicle()` | both | VIN, model, brand, plate, energy type |
| `get_all_data()` | both | Everything available (mode-dependent) |
| `get_health()` | both | Connection status, mode, poll interval, last poll time, errors |
### Tool Output Example
```json
// get_battery() response
{
"soc_percent": 73,
"charging_status": "disconnected",
"estimated_range_km": 280,
"mileage_km": 12450,
"outside_temp_c": 31.5,
"cabin_temp_c": 28.0,
"speed_kmh": 0,
"engine_power_kw": 0.0,
"fuel_percent": null,
"last_updated": "2026-07-31T08:30:00+00:00"
}
```
---
## Quick Start
### Prerequisites
- A BYD electric vehicle with an active BYD account
- A server with Docker and Docker Compose installed
- An MCP client (OpenClaw, Claude Code, etc.)
### 1. Pull the Image
```bash
docker pull ghcr.io/pavelpervi/byd-vehicle-bridge:latest
```
### 2. Configure
Create a directory and `.env` file:
```bash
mkdir -p /opt/byd-bridge && cd /opt/byd-bridge
cat > .env << 'EOF'
BYD_USERNAME=your@email.com
BYD_PASSWORD=your-password
BYD_COUNTRY=IL
BYD_MODE=minimal # or "full"
EOF
```
### 3. Deploy with Docker Compose
```bash
# docker-compose.yml
cat > docker-compose.yml << 'EOF'
services:
byd-bridge:
image: ghcr.io/pavelpervi/byd-vehicle-bridge:latest
container_name: byd-bridge
restart: unless-stopped
env_file: .env
networks:
- byd-internal
healthcheck:
test: ["CMD", "python3", "-c", "import socket; s=socket.create_connection(('localhost', 8000), timeout=5); s.close()"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
networks:
byd-internal:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
EOF
docker compose up -d
```
### 4. Connect Your MCP Client
**For OpenClaw** (running on the same host):
```bash
# Connect the OpenClaw container to the bridge network
docker network connect byd-internal <openclaw-container-name>
# Register the MCP server
openclaw mcp add byd-bridge --url http://byd-bridge:8000 --transport sse --timeout 30
```
**For Claude Code** or any stdio MCP client:
```json
{
"mcpServers": {
"byd-bridge": {
"command": "docker",
"args": ["exec", "-i", "byd-bridge", "python3", "-m", "byd_bridge"]
}
}
}
```
### 5. Verify
```bash
# Check the container is running
docker ps | grep byd-bridge
# View logs
docker logs byd-bridge
# Test the MCP tools
openclaw mcp call byd-bridge get_health
openclaw mcp call byd-bridge get_battery
```
Expected output from `get_health`:
```json
{
"status": "ok",
"mode": "minimal",
"vehicle_connected": true,
"last_successful_poll": "2026-07-31T08:30:00+00:00",
"poll_interval_s": 60,
"error": null
}
```
---
## Security
### What's Protected
| Concern | How It's Addressed |
|---------|-------------------|
| **Credentials** | BYD username/password stored in `.env` on your VPS only. Never sent to the AI agent. |
| **Network Exposure** | Zero ports published to the host. Only accessible on the internal Docker network. |
| **Write Access** | No remote commands implemented. The bridge reads data only. |
| **Privilege Escalation** | Container runs as non-root user, read-only filesystem, all Linux capabilities dropped. |
| **GPS Privacy** | GPS data only available in `full` mode. Default `minimal` mode excludes it. |
### Minimal vs Full Mode
| Data Point | `minimal` | `full` |
|-----------|-----------|--------|
| Battery SOC, range, charging | ✅ | ✅ |
| Speed, power, mileage | ✅ | ✅ |
| Outside/cabin temperature | ✅ | ✅ |
| Vehicle info (VIN, model) | ✅ | ✅ |
| Tire pressures | ❌ | ✅ |
| Door/window states | ❌ | ✅ |
| GPS location | ❌ | ✅ |
| Charging voltage/current | ❌ | ✅ |
| HVAC status | ❌ | ✅ |
---
## Configuration Reference
| Variable | Default | Description |
|----------|---------|-------------|
| `BYD_USERNAME` | — | BYD account email or phone (required) |
| `BYD_PASSWORD` | — | BYD account password (required) |
| `BYD_COUNTRY` | `IL` | ISO country code for API region |
| `BYD_MODE` | `minimal` | `minimal` or `full` data scope |
| `POLL_INTERVAL` | `60` | Seconds between BYD API polls |
| `BYD_PORT` | `8000` | MCP server port (internal) |
---
## Project Structure
```
byd-vehicle-bridge/
├── src/
│ └── byd_bridge/ ← Python package
│ ├── __init__.py
│ ├── __main__.py ← Entry: python -m byd_bridge
│ ├── config.py ← Settings from env vars (lazy singleton)
│ ├── state.py ← BridgeState + background poller
│ └── server.py ← MCP server with 4 tools
├── tests/
│ ├── __init__.py
│ ├── conftest.py ← Shared fixtures
│ ├── test_config.py ← 12 tests: validation, defaults, errors
│ ├── test_state.py ← 5 tests: init, data shapes, copy safety
│ └── test_server.py ← 11 tests: before/after poll, registration
├── .github/
│ └── workflows/
│ ├── tests.yml ← CI: runs tests on every PR
│ └── docker-build.yml ← CD: builds & pushes image to ghcr.io
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml ← Modern Python project config
├── requirements.txt
├── .env.example
├── .gitignore
├── README.md
└── LICENSE
```
---
## Running Tests
Tests are run automatically on every pull request via GitHub Actions, but you can also run them locally:
```bash
# Install dependencies
pip install -r requirements.txt
pip install -e .
# Run all 28 tests
python -m pytest tests/ -v
```
### Test Coverage
| Test File | Tests | What It Covers |
|-----------|-------|----------------|
| `test_config.py` | 12 | Mode validation, env parsing, defaults, missing credentials |
| `test_state.py` | 5 | BridgeState init, data shapes, copy safety |
| `test_server.py` | 11 | Tool registration, before/after poll states, async MCP calls |
---
## CI/CD Pipeline
| Event | Workflow | What Happens |
|-------|----------|-------------|
| Push to PR branch | `tests.yml` | Runs all 28 tests, lints with Ruff |
| PR merged to `main` | `tests.yml` + `docker-build.yml` | Tests pass, then Docker image is built and pushed to `ghcr.io/pavelpervi/byd-vehicle-bridge` |
| Tag pushed (`v*.*.*`) | `docker-build.yml` | Image tagged with semver (`v1.0.0`) |
**Retention:** Only the latest 5 versions are kept in ghcr.io. Old `sha-*` images are automatically cleaned up.
---
## How It Works
### Background Polling
When the server starts, a daemon thread launches an async event loop that:
1. Authenticates to the BYD cloud API using pyBYD
2. Fetches the vehicle list and real-time data
3. In `full` mode, also fetches GPS, charging details, and HVAC status
4. Caches everything in memory
5. Sleeps for `POLL_INTERVAL` seconds, then repeats
### MCP Tool Calls
When an AI agent calls a tool (e.g., `get_battery()`):
1. The MCP server receives the request
2. Looks up the cached data from the latest poll
3. Returns the data immediately — no network call to BYD
4. The agent receives structured, typed data
This means tool calls are instant (single-digit milliseconds) — the latency lives in the background poller, not in the agent's request.
### Why SSE Transport?
MCP supports three transports:
| Transport | Use Case |
|-----------|----------|
| **stdio** | Local subprocess — server runs as a child of the MCP client |
| **SSE** | Remote server — HTTP with Server-Sent Events (what we use) |
| **Streamable HTTP** | Newer HTTP transport — simpler than SSE |
SSE is the standard choice for a Docker-deployed MCP server. It requires no special client configuration beyond the URL.
---
## Tech Stack
- **[Python 3.12](https://www.python.org/)** — Core language
- **[pyBYD](https://github.com/jkaberg/pyBYD)** — Async Python client for the BYD vehicle API
- **[MCP SDK](https://github.com/modelcontextprotocol/python-sdk)** — Model Context Protocol SDK (v2.0.0+, `MCPServer` class)
- **[Docker](https://docker.com)** — Containerization
- **[GitHub Actions](https://github.com/features/actions)** — CI/CD: tests + image build
- **[GitHub Container Registry](https://ghcr.io)** — Docker image hosting
- **[python-dotenv](https://github.com/theskumar/python-dotenv)** — Environment variable management
- **[pytest](https://pytest.org)** — Test framework
- **[Ruff](https://astral.sh/ruff)** — Python linter
---
## Development
### Prerequisites
```bash
git clone https://github.com/pavelpervi/byd-vehicle-bridge.git
cd byd-vehicle-bridge
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -e .
```
### Run the Server Locally
```bash
# Set BYD credentials
export BYD_USERNAME=your@email.com
export BYD_PASSWORD=your-password
export BYD_COUNTRY=IL
export BYD_MODE=minimal
# Start the MCP server
python -m byd_bridge
```
The server starts on port 8000 (or `BYD_PORT` if set).
### Run Tests
```bash
# Set test env vars (required by config module)
export BYD_USERNAME=test@example.com
export BYD_PASSWORD=test-password
# Run all tests
python -m pytest tests/ -v
```
### Build the Docker Image Locally
```bash
docker build -t byd-bridge .
docker run --rm -p 8000:8000 --env-file .env byd-bridge
```
---
## Acknowledgements
- [pyBYD](https://github.com/jkaberg/pyBYD) — The excellent Python library that makes BYD API access possible
- [hass-byd-vehicle](https://github.com/jkaberg/hass-byd-vehicle) — Home Assistant integration that inspired this project
- [Model Context Protocol](https://modelcontextprotocol.io) — The protocol standardizing AI-tool communication
---
## License
MITThis server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues