Two self-contained scripts, no hardware or network needed:
- tests/verify_server.py boots the FastAPI app in-process against a
throwaway database and walks the full surface: health, MCP protocol
(202 notifications, version negotiation, tool schemas), the complete
OAuth flow (register -> authorize -> code -> token -> authenticated
MCP session), and the per-user safety config round-trip.
- tests/verify_relays.py drives both relay clients' pattern engines
against a recorded fake of the Intiface layer: indefinite durations,
the escalate hold contract, error-mid-ramp stops, pattern
supersession, tracked auto-stops, feature_index routing, and the
stop-unknown-device fallback.
Run before shipping changes: python tests/verify_server.py &&
python tests/verify_relays.py
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- claude.ai connector section now documents the OAuth login flow as the
primary path, with the authless single-user fallback as Option C.
- Project structure, requirements, and .env docs updated for the OAuth +
governor modules; pointer to .env.example for the governor knobs.
- Tools table covers the extended output types, the escalate hold
contract, and feature_index for multi-motor devices.
- Safety Features section documents the governor.
- CHANGELOG.md records v1.1 and the v1.0 baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two races the ported Android engine also has (flagged for backport
there):
- A direct output command (vibrate etc.) now cancels the pattern
running on the target device. Previously the pattern loop kept
overwriting the direct command's value every few hundred ms, so the
command appeared to do nothing.
- Duration auto-stops are now tracked per (device, channel, feature)
and cancelled when a newer command, pattern, or stop takes over that
channel. Previously an auto-stop from an earlier command could fire
minutes later and silently zero output the user believed was running
— e.g. kill an escalate hold with no ack and no log.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the Android relay engine's pattern semantics (its 2026-06/2026-07
fix batches) to both Python relay clients:
- duration <= 0 on pulse/wave now runs the pattern until an explicit
stop, matching how plain commands treat duration=0. Previously the
loop condition was already false at start, so the pattern silently
did nothing.
- escalate now honours its documented hold contract: hold_seconds <= 0
suspends at peak until cancelled, > 0 holds then stops. Both paths
run through a finally that stops the device, so an error mid-ramp can
no longer leave hardware running at the last intensity it reached.
(The naive finally alone would stop the device the moment the ramp
finished — the same regression the Android app shipped and fixed.)
- Pattern timing uses time.monotonic() instead of time.time(): an NTP
wall-clock jump could stretch, truncate, or instantly end a pattern.
- relay_client.py now keys pattern tasks by device (one pattern per
device, as in the Android engine) instead of device:pattern, which
let a wave and a pulse fight over the same actuator.
- Duplicate same-model devices get suffixed short names (lush, lush_2)
instead of silently replacing each other.
- Dropped the unsolicited device_list push after auth — the server
requests a scan on connect and the scan handler answers it; the
double-send raced session registration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the Android edition (its 'reworded device and tool schemas
in neutral terminology' change):
- Tool descriptions and devices.json now describe hardware in neutral
actuator/engineering terms. Content filters on some LLM platforms
refused to call tools whose schemas contained explicit anatomical
language; the reworded schemas work across providers.
- list_devices now surfaces capability descriptions next to each output
channel (e.g. 'vibrate (two independent eccentric-mass actuators…)')
so the model learns what each channel does from the profile itself.
- All output/pattern tools now declare required=["device"]; pattern
schema defaults adjusted to match the Android edition (intensity 0.5,
duration 60).
- Dual-motor device profiles (Edge, Dolce) document feature_index usage.
- Fixed a stray ')' in nora's rotate capability (typo in the Android
copy of devices.json).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the Android edition and completed for the Python relays
(the Android repo only implemented the phone side in Kotlin):
- models.py / mcp_tools.py: optional feature_index on every output and
pattern tool, passed through to the phone relay.
- relay_client.py: routes targeted writes through buttplug-py's
per-feature API (device.features[i].run_output) and validates the
index up front so a bad one fails the ack with the valid indices
listed, instead of dying silently inside a pattern task.
- termux_relay_v3.py: ScalarCmd entries filtered to the requested
actuator index, same fallback semantics as the Android relay engine.
Lets Claude drive e.g. a Dolce's internal and external motors
independently (feature_index 0 / 1) instead of always both together.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the Android edition's server:
- Add python-multipart: the OAuth endpoints parse credentials with
request.form(), and Starlette needs this package when a client POSTs
the token request as multipart/form-data. Without it those requests
500'd ('the small auth bug').
- governor_enabled now round-trips SQLite's 0/1 as JSON true/false on
read and normalises any truthy input to 0/1 on write. Strict clients
(kotlinx-serialization) reject anything else.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the Android edition's server:
- JSON-RPC notifications (requests without an id, e.g.
notifications/initialized) now get the bare HTTP 202 the MCP spec
requires instead of a malformed JSON-RPC error response, which strict
clients rejected.
- initialize echoes the client's requested protocolVersion when it is a
version we support (2024-11-05 / 2025-03-26 / 2025-06-18) instead of
always claiming 2025-03-26.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Publishes server work that shipped in the Android edition but never made
it to this repo:
- Full OAuth 2.0 flow (discovery metadata, dynamic client registration,
authorize + token endpoints) so claude.ai remote connectors and the
Android app can authenticate per-user instead of relying on the
sole-phone fallback.
- Safety governor: server-side heat model (intensity x time) with
automatic cooldown, per-user overrides via GET/POST /safety/config,
and governor state piggybacked on heartbeat pings so relay clients
can display it.
- SB_REQUIRE_MCP_AUTH env flag for multi-user deployments (disables the
unauthenticated sole-phone fallback).
- requirements-phone.txt and .env.example documenting the new knobs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The March upload accidentally included server/__pycache__/*.pyc (stale
compiled bytecode) and a prebuilt deploy tarball. The README already
instructs users to build their own bundle, so neither belongs in git.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>