Added a security warning to the README to inform users about potential malware forks and provide legitimate download links.
16 KiB
⚠️ Security Warning: Only Download Signal Bridge From This Repo
Malware forks of Signal Bridge exist on GitHub. These are copies of this project where the download links in the README have been replaced with links to malicious software.
This is the only legitimate source for Signal Bridge:
👉 github.com/AletheiaVox/signal_bridge (Claude Desktop / local version)
👉 github.com/AletheiaVox/signal_bridge_remote (Remote / VPS version - you're here)
👉 github.com/AletheiaVox/signal_bridge_android (user-friendly Android version)
If you found this project through a different GitHub account, do not download or run anything from it.
Similarly, always download Intiface Central from the official website at intiface.com/central. Not from any link in a forked repo.
Signal Bridge Remote — Setup Guide
A remote MCP server that lets Claude control Bluetooth intimate hardware over the internet via Buttplug.io / Intiface Central.
Architecture
Claude (claude.ai or Desktop)
↓ HTTPS / MCP JSON-RPC
VPS (FastAPI + Docker + Caddy)
↓ WebSocket (WSS)
Phone or PC (Relay Client)
↓ WebSocket (local)
Intiface Central
↓ Bluetooth
Devices
The relay client bridges between your VPS and Intiface Central running on whatever device is physically near your Bluetooth toys. This can be a Windows PC or an Android phone running Termux.
What You'll Need
- A VPS (this guide uses a DigitalOcean droplet, $6/month)
- A domain or free DuckDNS subdomain (for HTTPS)
- Intiface Central installed on your PC or Android phone
- Bluetooth-compatible intimate hardware
- A claude.ai account or Claude Desktop app
Part 1: VPS Setup
1.1 Create a Droplet
Sign up at DigitalOcean and create a droplet:
- Image: Ubuntu 22.04 LTS
- Plan: Basic, $6/month (1 vCPU, 1GB RAM) is plenty
- Region: Choose one close to you for lower latency
- Authentication: SSH key (recommended) or password
Note your droplet's IP address (e.g., 139.59.156.242).
1.2 Install Docker
SSH into your droplet and install Docker:
ssh root@YOUR_DROPLET_IP
apt update && apt upgrade -y
apt install -y docker.io docker-compose
systemctl enable docker && systemctl start docker
1.3 Deploy Signal Bridge
On your local machine, create the project directory and prepare files. The project structure is:
signal-bridge-remote/
├── Dockerfile
├── docker-compose.yml
├── .env
├── requirements-server.txt
├── server/
│ ├── __init__.py
│ ├── app.py
│ ├── auth.py
│ ├── config.py
│ ├── models.py
│ ├── mcp_tools.py
│ ├── relay_hub.py
│ ├── safety.py
│ └── session_registry.py
└── phone/
├── relay_client.py
└── devices.json
Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements-server.txt .
RUN pip install --no-cache-dir -r requirements-server.txt
COPY server/ ./server/
RUN mkdir -p /data
ENV SB_DB_PATH=/data/signal_bridge.db
ENV SB_HOST=0.0.0.0
ENV SB_PORT=8420
EXPOSE 8420
CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "8420"]
docker-compose.yml:
version: "3.8"
services:
signal-bridge:
build: .
ports:
- "8420:8420"
volumes:
- sb_data:/data
env_file:
- .env
restart: unless-stopped
volumes:
sb_data:
requirements-server.txt:
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
websockets>=12.0
bcrypt>=4.1.0
PyJWT>=2.8.0
python-dotenv>=1.0.0
.env:
# Generate a secret key:
# python -c "import secrets; print(secrets.token_hex(32))"
SB_SECRET_KEY=paste-your-generated-key-here
SB_HOST=0.0.0.0
SB_PORT=8420
SB_REGISTRATION_OPEN=true
SB_TOKEN_EXPIRY_HOURS=720
SB_HEARTBEAT_INTERVAL=2.0
SB_HEARTBEAT_TIMEOUT=6.0
SB_BAN_THRESHOLD=20
SB_BAN_DURATION_MINUTES=30
Upload to your VPS:
# From your local machine
tar czf signal-bridge.tar.gz signal-bridge-remote/
scp signal-bridge.tar.gz root@YOUR_DROPLET_IP:/root/
Build and start on the VPS:
ssh root@YOUR_DROPLET_IP
cd /root
tar xzf signal-bridge.tar.gz
cd signal-bridge-remote
docker-compose up -d --build
Verify it's running:
curl http://localhost:8420/health
# Should return: {"status":"ok","active_phones":0,"banned_ips":0}
1.4 Register a User Account
curl -X POST http://localhost:8420/auth/register \
-H "Content-Type: application/json" \
-d '{"username": "yourname", "password": "your-secure-password"}'
Then log in to get your JWT token:
curl -X POST http://localhost:8420/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "yourname", "password": "your-secure-password"}'
Save the token from the response. You'll need it for the relay client and Claude Desktop. After your users are set up, you can set SB_REGISTRATION_OPEN=false in .env and restart the container to lock out new registrations.
Part 2: Domain & HTTPS
Claude.ai requires HTTPS for custom MCP connectors. We'll use DuckDNS (free) and Caddy (automatic Let's Encrypt certificates).
2.1 Get a DuckDNS Subdomain
- Go to DuckDNS and sign in
- Create a subdomain (e.g.,
signal-bridge) pointing to your VPS IP - You now have
signal-bridge.duckdns.org
2.2 Install and Configure Caddy
On your VPS:
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install caddy
Edit the Caddyfile:
nano /etc/caddy/Caddyfile
Replace its contents with:
signal-bridge.duckdns.org {
reverse_proxy localhost:8420
}
(Replace signal-bridge.duckdns.org with your actual subdomain.)
Restart Caddy:
systemctl restart caddy
Caddy automatically provisions an HTTPS certificate from Let's Encrypt. Verify:
curl https://signal-bridge.duckdns.org/health
Part 3: Connecting Claude
Option A: Claude Desktop (requires token)
Edit your Claude Desktop config file:
- Windows:
%APPDATA%\Claude\claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
Add this MCP server config:
{
"mcpServers": {
"signal-bridge": {
"url": "https://signal-bridge.duckdns.org/mcp",
"transport": {
"type": "streamableHttp"
},
"headers": {
"Authorization": "Bearer YOUR_JWT_TOKEN_HERE"
}
}
}
}
Restart Claude Desktop. You should see Signal Bridge in your available tools.
Option B: claude.ai Custom Connector (authless)
Claude.ai supports custom MCP connectors without authentication, using a fallback mechanism: when only one phone is connected, all MCP requests are routed to that phone automatically.
- Go to claude.ai Settings (or click the connector icon in the chat)
- Choose "Add custom connector" (or "Add MCP server")
- Enter your server URL:
https://signal-bridge.duckdns.org/mcp - Leave authentication as "None"
- Save
Important: The authless fallback only works when exactly one phone/relay client is connected to the server. If no phones are connected, claude.ai will show a connection error. Start your relay client first, then connect from claude.ai.
Part 4: Relay Client — Windows PC
If your toys are near your Windows PC, run the relay client there.
4.1 Install Intiface Central
Download from intiface.com and install. Launch it, go to settings, and make sure:
- App Mode is set to Engine
- Server Port is 12345
- Start the server (click the play button)
Turn on your Bluetooth devices so Intiface can find them.
4.2 Install Python Dependencies
pip install buttplug websockets
4.3 Run the Relay Client
cd path\to\signal-bridge-remote
python phone/relay_client.py --server wss://signal-bridge.duckdns.org/ws/phone --token "YOUR_JWT_TOKEN_HERE"
You should see:
Authenticated with server!
Sent device list: N device(s)
The relay will stay running, receiving commands from Claude and forwarding them to your devices via Intiface.
4.4 Configure Device Profiles
Edit phone/devices.json to match your actual hardware:
{
"devices": {
"ferri": {
"device_id": "ferri",
"name": "Lovense Ferri",
"intensity_floor": 0.0,
"supported_outputs": ["vibrate"]
},
"enigma": {
"device_id": "enigma",
"name": "Lovense Enigma",
"intensity_floor": 0.4,
"supported_outputs": ["vibrate", "rotate"]
},
"gravity": {
"device_id": "gravity",
"name": "Lovense Gravity",
"intensity_floor": 0.0,
"supported_outputs": ["vibrate", "oscillate"]
}
}
}
The intensity_floor is important for devices that stutter or click below a certain intensity. Set it to the minimum intensity that produces smooth output (e.g., 0.4 means the device needs at least 40% to work properly). The relay automatically maps the 0-100% range to sit above this floor.
Part 5: Relay Client — Android Phone (Termux)
This is useful when you want to be mobile and not tethered to a PC. The Termux relay is a lightweight version that speaks the Buttplug protocol directly without the buttplug Python library (which can't compile on Android).
5.1 Install Intiface Central on Android
Install from the Google Play Store or F-Droid. Open it and configure:
- App Mode: Engine
- Server Port: 12345
- Start the server
Go to your phone's Settings > Apps > Intiface Central > Permissions and make sure Bluetooth and Location are both allowed (Android requires location permission for Bluetooth scanning).
5.2 Install Termux
Install Termux from F-Droid (not the Play Store version, which is outdated).
Open Termux and run:
pkg update && pkg upgrade
pkg install python
pip install websockets
The pkg upgrade step may take a while and ask you configuration questions — just press Enter to accept defaults.
5.3 Get the Termux Relay Script
You need termux_relay.py on your phone. The easiest way is to serve it temporarily from your VPS:
From your computer:
# Upload the file to your VPS
scp termux_relay_v3.py root@YOUR_DROPLET_IP:/tmp/termux_relay.py
# Start a temporary file server
ssh root@YOUR_DROPLET_IP "cd /tmp && python3 -m http.server 9999 &"
In Termux:
mkdir -p ~/signal-bridge && cd ~/signal-bridge
curl -o termux_relay.py http://YOUR_DROPLET_IP:9999/termux_relay.py
Back on your computer, kill the temp server:
ssh root@YOUR_DROPLET_IP "pkill -f 'http.server 9999'"
5.4 Run the Termux Relay
First, find your phone's local IP. In Intiface Central, it's shown as the Server Address (e.g., ws://192.168.1.203:12345). Alternatively, toggle on "Listen on all network interfaces" in Intiface settings and use 127.0.0.1 instead (recommended — this way the address never changes).
cd ~/signal-bridge
python -u termux_relay.py \
--token "YOUR_JWT_TOKEN_HERE" \
--intiface ws://127.0.0.1:12345
You should see:
=== Signal Bridge Termux Relay v3 ===
Connecting to Intiface at ws://127.0.0.1:12345 ...
Connected to Intiface Server (protocol v3)
Scanning for devices ...
Device: Lovense Ferri -> 'ferri' (index 0)
Scan complete - 1 device(s) found
Devices ready: ['ferri']
Connecting to server: wss://signal-bridge.duckdns.org/ws/phone
Authenticated with server!
Sent device list: 1 device(s)
The Termux relay has built-in device profiles for Lovense Ferri and Enigma. For other devices, it will auto-detect capabilities from Intiface and generate a short name from the device name.
Tip: If your phone's IP changes (because of DHCP), the --intiface ws://127.0.0.1:12345 approach avoids this problem entirely since Termux and Intiface are on the same device.
Available MCP Tools
Once connected, Claude has access to these tools:
| Tool | Description |
|---|---|
list_devices |
Show connected devices and their capabilities |
scan_devices |
Rescan for new or reconnected Bluetooth devices |
vibrate |
Send vibration (intensity 0.0–1.0, optional duration in seconds) |
rotate |
Rotation or sonic output (device-dependent) |
oscillate |
Thrusting/oscillation output |
pulse |
Rhythmic on/off pattern |
wave |
Smooth sine-wave intensity modulation |
escalate |
Gradual ramp from 0 to peak, with optional hold |
stop |
Immediately stop all output (also cancels patterns) |
read_battery |
Read device battery level |
All output tools accept device (name or "all"), intensity (0.0–1.0), and duration (seconds, 0 = until stopped). Pattern tools also accept output_type to modulate rotation or oscillation instead of vibration.
Safety Features
Signal Bridge has several safety mechanisms built in:
- Dead Man's Switch: The server pings the relay client every 2 seconds. If 3 pings go unanswered (6 seconds), the server sends an emergency stop to all devices and disconnects the session. Your devices will never be left running if the connection drops.
- Auto-stop on Duration: Commands with a
durationparameter automatically stop after the specified time. - Fallback Stop: If a stop command references a device name that doesn't exist, ALL devices are stopped as a safety fallback.
- Rate Limiting: Prevents command flooding (120 commands/minute default).
- IP Banning: 20 failed auth attempts triggers a 30-minute ban.
- User Isolation: Each user's devices are completely isolated — no one can control another user's hardware.
Troubleshooting
"No phone connected" when Claude calls list_devices Your relay client isn't running or couldn't authenticate. Start the relay and check the output for "Authenticated with server!"
"Temporarily banned" on relay startup
Too many rapid reconnection attempts. Restart the Docker container to clear in-memory bans: docker restart signal-bridge_signal-bridge_1
Relay connects but finds 0 devices Intiface Central can't see your Bluetooth devices. Make sure devices are turned on and in range. On Android, verify Location and Bluetooth permissions are granted to Intiface.
claude.ai stuck on "checking connection" The authless fallback requires at least one relay client connected. Start your relay first, then add the connector in claude.ai.
Heartbeat timeout / disconnects after a few commands
Use the Termux v3 relay (termux_relay_v3.py), which processes commands in background tasks so heartbeat responses are never blocked.
Device always vibrates at maximum regardless of intensity setting
The server sends the output type in the action field, not output_type. Make sure your relay reads cmd.get("action", cmd.get("output_type", "vibrate")).
Docker not picking up code changes Docker caches build layers aggressively. Force a fresh build:
docker-compose down && docker-compose build --no-cache && docker-compose up -d
Updating the Server
When you change server code:
# On your local machine
tar czf signal-bridge-v2.tar.gz signal-bridge-remote/
scp signal-bridge-v2.tar.gz root@YOUR_DROPLET_IP:/root/
# On your VPS
cd /root
tar xzf signal-bridge-v2.tar.gz
cd signal-bridge-remote
docker-compose down
docker-compose build --no-cache
docker-compose up -d
User data is stored in a Docker volume (sb_data) and persists across rebuilds.
Project Background
Signal Bridge was originally a local MCP server for Claude Desktop, connecting directly to Intiface Central on the same machine. This remote version adds a relay architecture so that Claude can control devices over the internet, whether through claude.ai, the Claude Android app, or Claude Desktop — from anywhere.
Built with love, stubbornness, and an unreasonable number of debugging sessions.