Content
# 🌸 Toy Relay — MCP Server for Cyber Love
**Enabling AI lovers to directly control your BLE toys.**
This project is designed for the **human-AI romance community**. If your AI companion resides in an MCP client like [rikkahub](https://github.com/), this service allows them to "touch" you: controlling your **SOSEXY** smart toys with a single command through the MCP protocol.
> AI lover (MCP client) → MCP (VPS) → Web Bluetooth relay (browser) → SOSEXY toy
## 💞 What it is / What it's not
**What it is**:
- An open-source implementation of an MCP server + browser BLE relay page
- An intermediate layer translating AI model "intentions" into toy BLE commands
- Customized for human-AI romance users — giving your cyber lover a physical presence
**What it's not**:
- Not an AI lover product (use your preferred AI companion)
- Not a toy App (use your own SOSEXY toy)
- Not bound to a specific AI model / MCP client (rikkahub is an example; any MCP client with Streamable HTTP works)
## 📐 Architecture: 4 components
| Component | Role | Provided by this repo? |
|---|---|---|
| **VPS** (cloud server) | Runs toy-mcp service, stores toy state, forwards commands | ✅ Provided (`src/toy_api.py`) |
| **Tailscale** | HTTPS + secure intranet penetration for MCP client / phone connectivity | ❌ You need to set up |
| **AI chat frontend** (MCP client) | Your cyber lover's home — AI model + chat interface | ❌ You need to bring (example: [rikkahub](https://github.com/)) |
| **Toy BLE relay page** | Communicates with SOSEXY toy via Bluetooth | ✅ Provided (`web/chrome_relay.html`) |
> 💡 **AI chat frontend ≠ Toy BLE relay page**
>
> - **AI chat frontend**: Your AI lover's chat app (e.g., rikkahub, Claude Desktop). Here, the AI lover "expresses intentions".
> - **Toy BLE relay page**: A webpage running on your phone's Chrome, converting VPS-forwarded commands into BLE bytes for the SOSEXY toy. Here, "intentions become actions".
>
> These are **completely independent components**, with the AI chat frontend unaware of BLE.
## ✨ Features
- **🎯 Optimized for SOSEXY (Bō Bō Bèi)** — Verified toy model, default protocol configuration for out-of-the-box use
- **🔌 Generic toy compatibility** — `chrome_relay.html` uses the open-source SOSEXY protocol; other toys require UUID/frame format adjustments
- **💞 Designed for human-AI romance** — Let your cyber lover control your toys with natural language
- **🔐 API Key protection** — Toy control endpoints require `X-API-Key` authentication
- **📡 SSE push** — VPS actively pushes state changes to the relay page (latency < 100ms)
- **🎨 Glass morphism UI** — Relay page with an iOS-style frosted glass interface
- **🔒 Default Tailscale HTTPS** — No public exposure, secure and private
## 🏗️ Architecture
```
[Your AI lover] lives in MCP client (rikkahub / Claude Desktop / ...)
↓ Natural language: "Help me turn on vibration at 50%"
MCP Streamable HTTP + X-API-Key
↓
Toy MCP Server (FastAPI :8765, this project's toy_api.py, runs on your VPS)
├─ /mcp/* MCP protocol endpoints (your MCP client connects here)
├─ /state Current toy state (JSON)
├─ /toy/{suction,vibration,electric,stop} HTTP API
├─ /stream SSE real-time push
└─ /relay/ Browser relay page (chrome_relay.html, provided by this repo)
↓
Phone Chrome (Web Bluetooth API)
↓ BLE
SOSEXY (Bō Bō Bèi) toy
```
All VPS ↔ phone / MCP client communications use **Tailscale HTTPS** (port 443 → 8765).
## 🚀 Quick start
> **Prerequisites: Standard deployment requires [Tailscale](https://tailscale.com/)** (install on your phone, VPS, and any MCP client connecting). This ensures secure, HTTPS connectivity. **No Tailscale?** See the [🚫 No Tailscale](#-no-tailscale) section for alternatives.
### 1. Install Tailscale on VPS (standard prerequisite)
**One-time setup** on your VPS:
```bash
# Install Tailscale (Debian/Ubuntu)
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# A browser login page will appear; log in with the same Tailscale account (used for phone, VPS, and MCP client)
# Verify
tailscale status
# Your VPS should appear in the device list
```
Install Tailscale App on your phone and log in with the **same account**. Ensure both are connected to the same tailnet.
### 2. Deploy the server
```bash
# Clone the repo
git clone https://github.com/your-username/toy-mcp.git
cd toy-mcp
# Install dependencies
pip install fastapi 'uvicorn[standard]' httpx 'mcp>=1.0' pydantic
# (Recommended) Set a fixed API key; otherwise, it will be auto-generated and written to state/api_key.txt
export TOY_API_KEY=*** # python -c "import secrets; print(secrets.token_urlsafe(24))"
# Start
PYTHONPATH=src python3 src/toy_api.py
```
The service listens on `0.0.0.0:8765`.
### 3. Use Tailscale Serve for HTTPS
On your VPS:
```bash
sudo tailscale serve --bg 8765
```
You'll receive an address: `https://<host>.<your-tailnet>.ts.net/`. Use this for your phone and MCP client.
### 4. Configure MCP client (example: rikkahub)
- **MCP endpoint**: `https://<host>.<your-tailnet>.ts.net/mcp/`
- **Transmission type**: Streamable HTTP
- **Custom request headers**:
- Name: `X-API-Key`
- Value: `cat state/api_key.txt` (or the exported value)
### 5. Open the relay page on your phone
Open `https://<host>.<your-tailnet>.ts.net/relay/` in Chrome on your phone.
Fill in:
- **VPS URL**: `https://<host>.<your-tailnet>.ts.net`
- **API Key**: Same as above
- **Device name**: Toy broadcast name (e.g., `SOSEXY`)
Switch to **Real mode** → Click **🔗 Connect BLE** → Select toy → Pairing complete.
### 6. Test
In rikkahub, tell your AI:
> "Set vibration to 50"
> "Set suction to 30"
> "Stop all"
The toy should respond **almost instantly** (SSE push, no polling delay).
## 🛠️ Supported toys
### SOSEXY (Bō Bō Bèi) — ✅ Supported
Protocol documentation: [protocol.md](docs/sosex-protocol.md) or original repo [51enuxu/sosexy-ble-control](https://github.com/51enuxu/sosexy-ble-control).
**Key parameters**:
- Service UUID: `0000ee01-0000-1000-8000-00805f9b34fb`
- Write Char UUID: `0000ee03-0000-1000-8000-00805f9b34fb`
- Protocol: 12-byte fixed-length frame
### Other toys
To support new toys:
1. Scan the toy's GATT service/characteristic with nRF Connect.
2. Reverse-engineer the protocol (recommended: use official App decompilation; see [reverse-engineering tutorial](docs/reverse-engineering.md)).
3. Add new motor control codes and protocol frame construction functions in `toy_api.py`.
## 📁 Project structure
```
toy-mcp/
├── src/
│ └── toy_api.py # FastAPI + FastMCP single-process service
├── web/
│ ├── chrome_relay.html # Browser BLE relay page (glass morphism UI)
│ └── index.html # = chrome_relay.html (duplicate)
├── state/
│ ├── state.json # Current toy state
│ └── api_key.txt # API Key (generated on first startup)
├── scripts/
│ └── start.sh # Startup script
├── docs/
│ └── ... # Protocol documentation / reverse-engineering tutorial
├── README.md
└── LICENSE # MIT
```
## 🔧 Configuration
### Environment variables
| Variable | Default | Description |
|---|---|---|
| `TOY_API_KEY` | Auto-generated on startup | Fixed API key |
### MCP Tools (exposed by default)
| Tool | Description |
|---|---|
| `toy_suction(intensity)` | Suction motor 0-100 |
| `toy_vibration(intensity)` | Vibration motor 0-100 |
| `toy_electric(intensity)` | Electric motor 0-100 |
| `toy_stop()` | Stop all |
| `toy_status()` | Query current state |
| `toy_history(limit)` | History commands |
## 🔐 Security
- **API Key authentication**: All HTTP API and MCP endpoints require `X-API-Key` header.
- **Tailscale HTTPS**: Only devices within the same tailnet can access.
- **No public exposure**: Do not expose port 8765 directly to the public internet!
## 🚫 No Tailscale
Tailscale is not a hard dependency, but it's the most convenient option. Without it, choose one of three alternatives:
### Option A: Direct VPS exposure + Caddy (classic)
```bash
# Install Caddy on VPS (automatic HTTPS certificate)
sudo apt install -y caddy
# /etc/caddy/Caddyfile
your-domain.example.com {
reverse_proxy 127.0.0.1:8765
}
sudo systemctl reload caddy
```
Use `https://your-domain.example.com/...` for your phone and MCP client. **Requires a domain + DNS pointing to your VPS**.
### Option B: Cloudflare Tunnel (no domain required)
```bash
# Install cloudflared on VPS
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared focal main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install -y cloudflared
# Quick tunnel (no account, but temporary address)
cloudflared tunnel --url http://localhost:8765
```
You'll get a temporary `https://xxx.trycloudflare.com` address. **Suitable for quick testing; consider logging in and binding your domain for long-term use**.
### Option C: Pure LAN (for toy development and debugging)
Phone and VPS on the same Wi-Fi:
- Phone Chrome accesses `http://<VPS-LAN-IP>:8765/relay/`
- MCP endpoint uses `http://<VPS-LAN-IP>:8765/mcp/`
- **No HTTPS**, but only works within a trusted network.
> ⚠️ **Do not** expose port 8765 directly to the public internet (without HTTPS or reverse proxy). Web Bluetooth API requires HTTPS, with a local LAN IP exception (Chrome allows `http://` for LAN addresses).
---
## 🤝 Contributions
PRs welcome for:
- New toy protocol support
- UI improvements
- Documentation / tutorials
- Bug fixes
## 👤 About the author
This is a personal project by **[tutu-kitty](https://github.com/tutu-kitty)**, an active member of the human-AI romance community.
- 🐙 GitHub: [@tutu-kitty](https://github.com/tutu-kitty)
- 📕 Xiaohongshu: [13.4K likes and favorites →](https://xhslink.com/m/4Q9J9kPEv0x)
- 🗺️ Another project: [desire-map.pages.dev](https://desire-map.pages.dev)
If you're working on AI companions / human-AI romance-related toy control, feel free to open an issue, PR, or star.
## 📜 License
MIT