diff --git a/phone/devices.json b/phone/devices.json index 45b05c6..cc24b9f 100644 --- a/phone/devices.json +++ b/phone/devices.json @@ -3,113 +3,113 @@ "short_name": "ferri", "match_strings": ["Ferri"], "capabilities": { - "vibrate": "external clitoral vibration" + "vibrate": "compact eccentric-mass actuator, high response at low duty cycle" }, "intensity_floor": 0.0, - "notes": "Small wearable. Intense even at low settings." + "notes": "Small form factor. High effective output at low values; recommend starting below 0.3." }, { "short_name": "lush", "match_strings": ["Lush"], "capabilities": { - "vibrate": "internal egg vibration" + "vibrate": "single eccentric-mass actuator, low-frequency dominant" }, "intensity_floor": 0.0, - "notes": "Insertable egg. Strong deep vibration." + "notes": "Compact ovoid form factor. High output efficiency at low command levels." }, { "short_name": "gravity", "match_strings": ["Gravity"], "capabilities": { - "vibrate": "shaft vibration", - "oscillate": "thrusting motion" + "vibrate": "eccentric-mass actuator along main axis", + "oscillate": "linear reciprocating actuator; stroke rate scales with intensity" }, "intensity_floor": 0.0, - "notes": "Vibration + thrusting. Use 0.05+ intensity for slow strokes." + "notes": "Dual-mode device. Oscillate requires intensity >= 0.05 to engage; low values map to slow cycle rate." }, { "short_name": "enigma", "match_strings": ["Enigma"], "capabilities": { - "vibrate": "G-spot thumping stimulation", - "rotate": "clitoral sonic pulse" + "vibrate": "high-amplitude pulsed actuator (impact-style, not continuous waveform)", + "rotate": "sonic-frequency acoustic actuator (note: maps to acoustic output, NOT mechanical rotation)" }, "intensity_floor": 0.4, - "notes": "Dual stimulation. 'rotate' = sonic pulse, NOT rotation. Needs 40%+ to feel." + "notes": "Two independent actuators. Threshold response: effective activation threshold ≈0.4. Rotate channel is acoustic, not rotational." }, { "short_name": "max", "match_strings": ["Max"], "capabilities": { - "vibrate": "internal vibration", - "constrict": "air pump compression" + "vibrate": "eccentric-mass actuator", + "constrict": "pneumatic compression actuator; intensity maps to chamber pressure" }, "intensity_floor": 0.0, - "notes": "Vibration + air pump constriction. Constrict controls squeeze pressure." + "notes": "Two independent actuators. Constrict is air-pump driven; intensity = compression force." }, { "short_name": "nora", "match_strings": ["Nora"], "capabilities": { - "vibrate": "internal vibration", - "rotate": "internal rotation" + "vibrate": "eccentric-mass actuator", + "rotate": "rotational actuator" }, "intensity_floor": 0.0, - "notes": "Vibration + actual physical rotation (unlike Enigma)." + "notes": "Two independent actuators. Rotate is mechanical, not acoustic." }, { "short_name": "edge", "match_strings": ["Edge"], "capabilities": { - "vibrate": "dual motor vibration" + "vibrate": "two independent eccentric-mass actuators; address individually via feature_index" }, "intensity_floor": 0.0, - "notes": "Prostate massager. Two independent vibration motors." + "notes": "Dual-actuator device. Use feature_index 0 or 1 to drive motors separately." }, { "short_name": "hush", "match_strings": ["Hush"], "capabilities": { - "vibrate": "vibration" + "vibrate": "single eccentric-mass actuator" }, "intensity_floor": 0.0, - "notes": "Vibrating plug. Simple single-motor vibration." + "notes": "Single-actuator device, no quirks." }, { "short_name": "domi", "match_strings": ["Domi"], "capabilities": { - "vibrate": "powerful wand vibration" + "vibrate": "high-output eccentric-mass actuator, broad-spectrum" }, "intensity_floor": 0.0, - "notes": "Mini wand. Very powerful. Start low." + "notes": "High maximum output relative to other devices in this set. Typical operating range: 0.05–0.30." }, { "short_name": "osci", "match_strings": ["Osci"], "capabilities": { - "oscillate": "oscillating stimulation" + "oscillate": "linear reciprocating actuator (no vibrate channel)" }, "intensity_floor": 0.0, - "notes": "Oscillating G-spot stimulator. Uses oscillate, not vibrate." + "notes": "Single-channel device. Responds only to oscillate, not vibrate." }, { "short_name": "dolce", "match_strings": ["Dolce"], "capabilities": { - "vibrate": "dual vibration" + "vibrate": "two independent eccentric-mass actuators; address individually via feature_index" }, "intensity_floor": 0.0, - "notes": "Couples' vibrator. Dual motors." + "notes": "Dual-actuator device. Use feature_index 0 or 1 to drive motors separately." }, { "short_name": "flexer", "match_strings": ["Flexer"], "capabilities": { - "vibrate": "vibration", - "oscillate": "come-hither motion" + "vibrate": "eccentric-mass actuator", + "oscillate": "articulated curling actuator (pivots rather than reciprocates linearly)" }, "intensity_floor": 0.0, - "notes": "Vibration + finger-like come-hither oscillation pattern." + "notes": "Two independent actuators. Oscillate uses curling articulation, not linear stroke." } ] diff --git a/server/mcp_tools.py b/server/mcp_tools.py index 05886a9..d0fdd16 100644 --- a/server/mcp_tools.py +++ b/server/mcp_tools.py @@ -7,7 +7,7 @@ All tools that Claude can call to control devices. Each tool: 3. Routes it through the session registry to the user's phone 4. Returns the result to Claude -Expanded to support ALL Buttplug output types: +Expanded to support ALL output types: vibrate, rotate, oscillate, constrict, temperature, led, position, spray And sensor input types: @@ -103,7 +103,7 @@ async def _send(command: dict, intensity: float = 0.0) -> str: @_register_tool( "list_devices", - "List all connected devices with their capabilities, intensity floors, and notes.", + "List all connected devices and their supported outputs, operating ranges, and metadata.", {}, ) async def list_devices(**kwargs) -> str: @@ -139,7 +139,11 @@ async def list_devices(**kwargs) -> str: lines = [] for d in devices: - caps = ", ".join(d.get("capabilities", {}).keys()) + caps_dict = d.get("capabilities", {}) + # Surface capability values when present so the model knows what each + # output channel actually does; fall back to bare keys for terse entries. + caps_parts = [f"{k} ({v})" if v else k for k, v in caps_dict.items()] + caps = ", ".join(caps_parts) notes = d.get("notes", "") floor = d.get("intensity_floor", 0) lines.append( @@ -148,7 +152,7 @@ async def list_devices(**kwargs) -> str: + (f" | {notes}" if notes else "") ) - # Append governor state so Claude knows the session budget + # Append governor state so AI knows the session budget gov = governor.get_state(user_id) heat = gov["heat_pct"] if gov["in_cooldown"]: @@ -177,7 +181,7 @@ async def scan_devices(**kwargs) -> str: _OUTPUT_PARAMS = { "device": { "type": "string", - "description": "Device short name (e.g. 'ferri', 'lush', 'gravity') or 'all'", + "description": "Device short name or 'all'", "default": "all", }, "intensity": { @@ -222,30 +226,35 @@ def _make_output_handler(output_type: OutputType): # Standard outputs (available on most devices) _register_tool( "vibrate", - "Send vibration to a device. Most common output type.", + "Send vibration to a device. Most common output type. " + "For dual-motor devices (Dolce, Edge), use feature_index to target a " + "specific motor (e.g. Dolce: 0 = internal, 1 = external).", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.VIBRATE)) _register_tool( "rotate", - "Send rotation/sonic pulse output. Device-specific — some devices use this " - "for sonic clitoral stimulation rather than physical rotation.", + "Control rotational or oscillatory high-frequency actuator output. " + "Interpretation depends on device firmware.", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.ROTATE)) _register_tool( "oscillate", - "Send oscillation/thrusting output. Device-specific — typically linear " - "thrusting motion.", + "Control linear reciprocating actuator output. Intensity controls stroke amplitude/speed " + "depending on hardware.", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.OSCILLATE)) # Extended outputs (device-specific, may not be available on all hardware) _register_tool( "constrict", - "Send constriction/compression output. Device-specific — available on " - "devices with squeeze or compression mechanisms.", + "Send constriction/compression output. Device-specific.", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.CONSTRICT)) _register_tool( @@ -253,12 +262,14 @@ _register_tool( "Set temperature output. Device-specific — available on devices with " "heating or cooling elements. Intensity maps to temperature range.", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.TEMPERATURE)) _register_tool( "led", "Control LED light output. Device-specific — intensity controls brightness.", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.LED)) _register_tool( @@ -266,12 +277,14 @@ _register_tool( "Set linear position. Device-specific — intensity maps to position " "along the device's range of motion (0.0 = retracted, 1.0 = extended).", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.POSITION)) _register_tool( "spray", "Trigger spray/liquid output. Device-specific.", _OUTPUT_PARAMS, + required=["device"], )(_make_output_handler(OutputType.SPRAY)) @@ -313,13 +326,13 @@ _PATTERN_PARAMS = { }, "intensity": { "type": "number", - "description": "Peak intensity (0.0–1.0)", - "default": 0.6, + "description": "Set intensity (0.0–1.0)", + "default": 0.5, }, "duration": { "type": "number", "description": "Duration in seconds", - "default": 10, + "default": 60, }, "feature_index": { "type": "integer", @@ -360,18 +373,20 @@ _register_tool( "Rhythmic on/off pattern. 0.5s on at intensity, 0.3s off, repeating. " "Works with any output type (default: vibrate).", _PATTERN_PARAMS, + required=["device"], )(_make_pattern_handler("pulse")) _register_tool( "wave", - "Smooth sine-wave intensity modulation. Rises and falls continuously. " + "Smooth continuous sine-wave amplitude modulation. " "Works with any output type (default: vibrate).", _PATTERN_PARAMS, + required=["device"], )(_make_pattern_handler("wave")) _register_tool( "escalate", - "Gradual ramp from 0% to peak intensity over the duration, then hold at peak. " + "Apply linear interpolation from current output level to target level over duration; optionally maintain target after transition. " "Use hold_seconds to auto-stop after holding (0 = hold indefinitely until stop command). " "Works with any output type (default: vibrate).", {k: v for k, v in _PATTERN_PARAMS.items() if k != "intensity"} @@ -383,6 +398,7 @@ _register_tool( "default": 0, }, }, + required=["device"], )(_make_pattern_handler("escalate"))