OBSBOT Tiny 2 Camera Control

io.github.lxmanPublished by GitHub account lxmanv0.9.6Updated Nov 6, 2026

Control an OBSBOT Tiny 2 camera: pan/tilt/zoom, AI tracking, focus, exposure, snapshots, recording

VerifiedSTDIODesktop onlyDeveloper ToolsMedia & Design

Overview

AI-generated overview

Controls an OBSBOT Tiny 2 camera from an assistant: pan/tilt/zoom, AI tracking, focus, exposure, snapshots and recording.

What you can do
  • List attached cameras and wake, sleep or read their live status
  • Move the gimbal to absolute angles, recenter it, read its position, and save, recall, rename or delete three presets
  • Set zoom, field of view, HDR, focus, white balance, exposure and image adjustments, and toggle AI tracking with framing modes
  • Capture a still frame, aim the camera at a pixel or frame a region from that frame, and start, list or stop preview and MP4 recording sessions
  • Discover, wake, sleep and read status or configuration of OBSBOT Tail 2 network cameras
When to use it
Useful when a physical OBSBOT Tiny 2 (or Tail 2) is attached and you want an assistant to reposition the camera, follow a subject, tune the image, or take snapshots and recordings during a call, stream or demo.
Requirements
Runs locally over stdio as the npm package obsbot-mcp (Node.js), on Windows, macOS 14+ or Linux; desktop only. A physically connected OBSBOT Tiny 2 is required, and Tail 2 tools need those cameras on the same network. Recording and preview need ffmpeg/ffplay installed. Optional OBSBOT_LOG_FILE sets a debug log path; a --debug flag adds raw diagnostics tools.
Before you install
Tools physically move the camera, and waking or sleeping stows or un-stows the gimbal; AI tracking overrides manual moves. Recording writes MP4 files to disk (default ~/Videos/OBSBOT) and preview opens a window. Preset slots are create-once, so saving over an occupied slot is rejected. On macOS the first snapshot triggers a camera permission prompt for the MCP client. Only the Tiny 2 is supported; Intel Macs are untested.

Installation

In SourceWeft

  1. Open OBSBOT Tiny 2 Camera Control in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

obsbot-mcp

A cross-platform Model Context Protocol server that controls an OBSBOT Tiny 2 camera over its standard UVC/USB interface — pan/tilt/roll the gimbal, zoom, AI subject tracking, focus/exposure/white-balance/image controls, HDR and field-of-view, plus snapshot, preview, and recording — without any vendor SDK. It also controls the OBSBOT Tail 2 network camera over its HTTP/WS API (see OBSBOT Tail 2 below).

Install

bash
npm install obsbot-mcp

MCP client configuration

Add a stdio server entry pointing at the installed binary (or directly at dist/index.js):

json
{  "mcpServers": {    "obsbot": {      "command": "obsbot-mcp"    }  }}

If you're running from a local checkout instead of an npm install, point command/args at node and the built entry point instead:

json
{  "mcpServers": {    "obsbot": {      "command": "node",      "args": ["path/to/obsbot-mcp/dist/index.js"]    }  }}

Debug / diagnostics tools

By default the server advertises only the normal control surface. Pass --debug to additionally expose the diagnostics surface — the obsbot_debug_probe tool (raw XU byte get/set/query) and the raw 60-byte status block on obsbot_status:

json
{  "mcpServers": {    "obsbot": {      "command": "node",      "args": ["path/to/obsbot-mcp/dist/index.js", "--debug"]    }  }}

With the installed binary, use "command": "obsbot-mcp" and "args": ["--debug"].

Tools

35 tools on Windows and macOS, 34 on Linux (obsbot_gimbal_move_speed is unavailable there — see limitations). --debug adds obsbot_debug_probe for one more. Every Tail 2 camera on the network adds the 48 obsbot_tail2_* tools in OBSBOT Tail 2 below (all platforms — they are pure TypeScript). All names below are current as of v0.4.0 — every tool was renamed in this release and there is no backward-compatible alias; see CHANGELOG.md for the full old→new mapping if you're updating a caller.

The camera selector

Every camera-addressing tool accepts an optional camera parameter: the target camera's serial number. Omit it with a single camera attached and nothing changes — this matches the server's pre-v0.4.0, single-camera behaviour exactly. With more than one camera attached, a call that omits camera fails with an error naming every attached serial, so you always know what to pass next.

Exempt (no camera parameter, ever): obsbot_devices (enumerates the whole fleet), obsbot_capture_stop / obsbot_capture_list (address a sessionId, not a device), and obsbot_debug_probe (operates on the current diagnostics transport). Two more tools honor it only partially — see Capture below.

Multi-camera support is new in v0.4.0. It's exercised by the unit test suite against fakes; running two physical Tiny 2s at once has not yet been hardware-verified (see Known limitations).

obsbot_devices is the way to discover the serials you pass as camera: it reports each attached camera's serial (where obtainable — reading it requires briefly opening the camera), name, and status (available | bound | busy). A camera another process already holds comes back busy with no serial, since it can't be opened to read one.

Device & power

ToolParametersDescription
obsbot_devices—List attached OBSBOT cameras with each one's serial (where obtainable), name, and status (available/bound/busy). A busy camera is held by another process.
obsbot_wakecamera?Wake the camera/gimbal (sends "run"). Moves the camera: un-stows the gimbal back to level (pitch ~0). Most control commands also wake it implicitly.
obsbot_sleepcamera?Sleep the camera/gimbal (sends "sleep"). Moves the camera: stows the gimbal face-down at roughly pitch 84, so obsbot_gimbal_position reads ~84 rather than the pose you left.
obsbot_statuscamera?Read the live status block: { awake, hdr, faceAe, aiMode, trackSpeed, fovMode, zoomPercent, focusMode, focusPosition } (faceAe = auto-exposure metering for a detected face; fovMode = wide|medium|narrow|custom|unknown, where custom means a continuous zoom overrode the discrete modes; zoomPercent = zoom position, 0-100; focusMode = auto|manual|unknown). focusPosition appears only in manual mode, on the same 0-100 scale obsbot_focus_manual takes — under autofocus the camera echoes the last written value rather than exposing the motor, so a number there would read as a live focus distance while being stale. Focus is a standard UVC control, not part of the status block, so it costs one extra read; a device that can't answer reports focusMode: "unknown" rather than failing the whole call. Under --debug, also returns the raw 60-byte block as hex.

Gimbal (PTZ)

ToolParametersDescription
obsbot_gimbal_moveyaw, pitch, roll (degrees, roll defaults 0), camera?Move the gimbal to an absolute angle. Positive yaw pans to the camera's left, positive pitch tilts down. Yaw clamped to [-150, 150], pitch to [-90, 90]. Absolute 1:1 degrees, hardware-verified.
obsbot_gimbal_move_speedyaw, pitch, roll (deg/s, clamped to ±150, roll defaults 0), autoStopMs (default 800), camera?Drive the gimbal at a speed, then auto-stop after autoStopMs so it can't run away. Same yaw/pitch sign convention as gimbal_move. Returns the speeds actually used. Past its limit the firmware ignores the command outright rather than saturating — 180 deg/s and above move the gimbal exactly 0° — so requests are clamped into the hardware-verified band. Not available on Linux — see limitations.
obsbot_gimbal_recentercamera?Recenter the gimbal — drives it to yaw 0 / pitch 0. Returns as soon as the command is sent, so poll obsbot_gimbal_position if you need to know it arrived.
obsbot_gimbal_positioncamera?Read the gimbal's current absolute { yaw, pitch } in degrees via standard UVC Pan/Tilt. Valid during a move as well as after one. On Linux this is the last-commanded value, not a live in-flight reading — see limitations.
obsbot_aim_at_pixelx, y, frameWidth, frameHeight, source (default "device"), camera?Point the camera at a pixel from a frame you just captured. Reads the camera's magnification from its own reported state — a discrete FOV mode or a continuous zoom alike — so it needs no FOV or zoom argument and works at any zoom. Refuses while AI tracking is active, when the FOV mode can't be decoded, or when a corrupt zoom reading would resolve to an implausible magnification, and refuses if the camera had to be woken (waking moves the gimbal, invalidating the frame you measured) or if the zoom is still ramping (the frame was captured at a magnification the camera has already left, so wait for the zoom and take a fresh snapshot). source declares which feed the frame came from (default device) — it cannot be inferred from the pixels. A virtual/ndi frame is accepted, not refused, but only aims correctly if that feed is an unmodified pass-through of the camera; a compositor's rescale or letterbox is invisible in the picture and silently wrong here, so a non-device declaration returns a note stating that assumption. It reads the live pose to compute the aim, so on Linux it is affected the same way obsbot_gimbal_position is — see limitations. Returns clamped:true if the target was outside the gimbal's range, in which case the camera still moves — to the nearest reachable pose. Refuses (ok:false) instead of moving when the pixel lies past vertical from the current pose, since the only rotation that reaches it would swing the camera toward the opposite side of the room; tilt toward the pixel first, then re-aim.
obsbot_zoom_to_fitx, y, width, height, frameWidth, frameHeight, margin (default 0.1), source (default "device"), camera?Frame a region of a frame you just captured: centre the gimbal on it and zoom so the region fills the frame. Same refusal conditions as obsbot_aim_at_pixel (AI tracking, undecodable FOV/zoom, a woken camera, a zoom still ramping, an over-the-top target, a non-16:9 frame), and the same source declaration, plus a refusal if the region isn't within the frame (edges included) or has non-positive size. margin backs the zoom off by that fraction so the region isn't framed edge-to-edge; the tighter of the region's two axes sets the zoom, so the whole region stays visible rather than being cropped on one side. Moves the gimbal before zooming — zoom is centre-preserving but not target-preserving, so zooming first can push the region out of frame. Zoom ramps rather than jumping, so the tool polls for up to 3s and returns settled:false (not an error) if the zoom hadn't arrived in time — check it before trusting a follow-up snapshot.
Aiming at what you can see

obsbot_capture_snapshot returns the frame as an image plus its width/height, so a model can locate something in the picture and then point the camera at it:

  1. obsbot_capture_snapshot — look at the frame
  2. obsbot_aim_at_pixel — pass the target's pixel and that frame's dimensions
  3. obsbot_capture_snapshot again — confirm it landed, and repeat if needed

Pass the frameWidth/frameHeight from the same snapshot the pixel came from. Mixing a pixel from one frame with dimensions from another aims at the wrong place, and nothing can detect it.

The snapshot must be source: "device". obsbot_capture_snapshot can also read from source: "virtual" or "ndi", which come from OBSBOT Center's own output rather than the camera's raw stream. Those are framed and cropped by OBSBOT Center, not by this camera's optics, so the measured field-of-view constants this tool relies on don't describe them — aiming from a virtual or NDI frame lands in the wrong place with no way to detect it. Only use a device-source snapshot's pixel and dimensions here.

The tool reads the camera's magnification itself — a discrete FOV mode or a continuous zoom alike (m = 3*ratio-2, measured on hardware to better than 0.05%) — so there is no FOV or zoom argument to get wrong, and it works at any zoom. It refuses rather than guessing when AI tracking is on (tracking drives the gimbal and would fight the aim), when the FOV mode can't be decoded, when a corrupt zoom reading would resolve to an implausible magnification, or when the camera had to be woken from sleep (waking moves the gimbal, so the frame you measured no longer matches where the camera is pointing — take a fresh snapshot and retry).

Framing what you can see

obsbot_zoom_to_fit extends the same idea from a point to a region: instead of just centring on a pixel, it also zooms so that region fills the frame.

  1. obsbot_capture_snapshot — look at the frame
  2. Pick a bounding box around whatever should fill the frame (a face, a whiteboard, ...)
  3. obsbot_zoom_to_fit — pass the box (x, y, width, height) and that frame's dimensions
  4. obsbot_capture_snapshot again — confirm the framing, and repeat if needed

It shares obsbot_aim_at_pixel's refusals (AI tracking, undecodable FOV/zoom, a woken camera, a zoom still ramping, a non-16:9 frame), and adds one of its own: the region must lie within the frame — edges included, so a region that already IS the full frame is valid — with a positive width and height, or the call refuses rather than guess what a negative width or an off-frame box was supposed to mean.

margin (default 0.1, i.e. 10%) backs the requested zoom off by that fraction so the region isn't framed exactly edge-to-edge — some breathing room around it survives small aim/zoom error. The region's two axes rarely need the same zoom to fill the frame; the tool always picks the smaller of the two required magnifications, because zooming to the larger one would fill one axis by cropping the other. The result is clamped to the camera's [1x, 4x] magnification range (reported via clamped) — a region demanding more zoom than the camera has still gets the closest fit available, rather than being refused outright.

The gimbal moves before the zoom is commanded. Zoom re-centres what's already in frame but does not keep a specific pixel under the crosshair as it changes — zooming first can push the region's centre out of frame entirely, which would make the subsequent move aim at a pixel that no longer means what it did when the caller measured it.

Zoom is not instantaneous: on this hardware it ramps toward the commanded value rather than jumping to it, so a status read taken immediately after commanding it can catch it mid-transit (observed: commanding ratio 1.5 read back partway there before settling). obsbot_zoom_to_fit polls for up to 3 seconds waiting for the zoom to arrive and returns settled:false — not an error — if it didn't. A frame captured while the zoom is still moving is at an unknown magnification, so check settled before trusting a follow-up snapshot; a false just means the camera was moving slower than expected, not that anything failed.

Gimbal presets

Three on-device preset slots (1–3). Slots are create-once: obsbot_preset_save requires an empty slot (delete first to reuse one); every other preset tool requires the slot to already be occupied. Each tool re-reads the slot list after writing and returns a structured { ok:false } failure if the device didn't land the change.

ToolParametersDescription
obsbot_preset_listcamera?Read the three preset slots: occupied/empty, name, and pose in degrees.
obsbot_preset_saveslot (1|2|3), camera?Save the gimbal's current live pose into an empty slot.
obsbot_preset_recallslot (1|2|3), camera?Recall an occupied slot, driving the gimbal to its saved pose.
obsbot_preset_updateslot (1|2|3), camera?Overwrite an occupied slot with the gimbal's current live pose.
obsbot_preset_renameslot (1|2|3), name, camera?Rename an occupied slot (names over 40 bytes are truncated).
obsbot_preset_deleteslot (1|2|3), camera?Delete an occupied slot, freeing it for obsbot_preset_save.

Zoom

Two tools, not one — they ride different transports (standard UVC vs. the vendor command frame) and produce different physical zoom at the same commanded ratio, so merging them would silently change what ratio means. Pick by which behaviour you need.

ToolParametersDescription
obsbot_zoom_uvcratio (1.0–2.0), camera?Standard UVC zoom: set an absolute zoom ratio, clamped to [1.0, 2.0]. Snaps to the requested target exactly. Waits for the zoom to arrive and returns settled — the ramp is not instant (a full 1.0→2.0 sweep takes about 2.4s), and obsbot_aim_at_pixel / obsbot_zoom_to_fit refuse while it is in flight, so returning early would only move the failure downstream. settled:false means it hadn't arrived within the timeout; the command was still sent.
obsbot_zoom_vendorratio (1.0–2.0), speed (default 0), camera?Vendor zoom path with adjustable speed: zoom to a ratio at a chosen speed (0 = device default, 1–10 slow→fast, 255 = maximum). Its ratio scale differs from obsbot_zoom_uvc's and may not land exactly on the requested target — see Known limitations.

AI tracking

ToolParametersDescription
obsbot_ai_trackenabled (bool), mode (default "normal"), camera?Enable/disable AI tracking and choose the mode: a human framing (normal | upper-body | close-up | headless | lower-body) or a scene mode (group | whiteboard | desk | hand). Polls status and returns { verified, matched } (matched:false = no subject tracked yet).
obsbot_ai_track_speedspeed: "standard" | "sport", camera?Set the tracking-speed preset (Center's Standard/Sport): standard (slower follow) or sport (snappier).
obsbot_focus_faceenabled (bool), camera?Enable or disable face-priority autofocus.

Image & lens

Focus, white balance, and exposure each split into a dedicated _auto and _manual tool in v0.4.0 (previously one tool with a mode parameter) — auto and manual take different parameters, so splitting them lets each schema say exactly what it needs.

ToolParametersDescription
obsbot_image_fovfov: "wide" | "medium" | "narrow", camera?Set the field of view: wide (86°), medium (78°), narrow (65°).
obsbot_image_hdrenabled (bool), camera?Toggle HDR/WDR imaging on or off.
obsbot_focus_autocamera?Enable continuous autofocus.
obsbot_focus_manualposition (0–100, default 50), camera?Set the focus motor to position (near→far).
obsbot_image_exposure_autopriority ("global" | "face", optional), camera?Enable auto-exposure; optional priority selects global vs face metering.
obsbot_image_exposure_manuallevel (0–100, default 50), camera?Set exposure level (0 darkest → 100 brightest).
obsbot_image_wb_autocamera?Enable auto white balance.
obsbot_image_wb_manualtemperature (Kelvin, default 5000), camera?Set a colour temperature (clamped to device range).
obsbot_image_adjustcontrol, level (0–100), camera?Adjust brightness | contrast | hue | saturation | sharpness | gain | backlight-compensation; level maps onto the device range. gain and backlight-compensation are not implemented on the Tiny 2 (it reports them as zero-length controls) and are refused with an error; the other five work.

Capture

obsbot_capture_record and obsbot_capture_preview do not take camera. They select a device by source (device/virtual/ndi) through ffmpeg/ffplay, not by serial — there is no serial-to-ffmpeg-device mapping yet. obsbot_capture_snapshot honors camera only for source:"device"; for source:"virtual"/"ndi" the pixel source is still resolved by device name, independent of camera.

ToolParametersDescription
obsbot_capture_snapshotresolution (256–1920, default 640), quality (1–100, default 80), settleMs (0–15000, default 600), source (default "device"), camera? (source:"device" only)Grab one still frame and return it as an image (for framing/lighting/exposure checks). resolution is the longest edge in pixels — larger costs proportionally more tokens. source: device | virtual | ndi. Also returns sourceFormat — the format the capture graph negotiated, e.g. MJPG [email protected] — because frame rate selects the field of view on this camera (see limitations); Windows only, absent means unknown. A source that connects lazily (NDI) may need a larger settleMs.
obsbot_capture_recorddurationSec (optional), audio (default true), outputPath (optional), source (default "device")Start recording to MP4. Open-ended recordings auto-stop after 60 min; audio uses the OBSBOT mic; defaults to ~/Videos/OBSBOT on every platform, including macOS, where that is not the usual ~/Movies. Returns a sessionId. **Needs ffmpeg.**¹ No camera.
obsbot_capture_previewsource (default "device")Open a live preview window. Returns a sessionId. **Needs ffplay.**¹ No camera. device is pinned to 1080p60 mjpeg for smooth motion — which costs field of view (see limitations). virtual/ndi negotiate instead, since neither offers mjpeg and pinning it there prevents the device opening at all.
obsbot_capture_stopsessionIdStop a recording or preview session (recordings are finalized gracefully). No camera.
obsbot_capture_list—List active recording/preview sessions. No camera.

Diagnostics (--debug only)

ToolParametersDescription
obsbot_debug_probemode: "get" | "set" | "query", plus selector, length, hex, opcode, payloadHexRE/diagnostics only — raw XU byte get/set and framed table queries. Advertised only under --debug. No camera.

¹ record/preview shell out to ffmpeg/ffplay (install: winget install Gyan.FFmpeg on Windows, brew install ffmpeg on macOS, apt install ffmpeg on Linux). snapshot does not need ffmpeg — it grabs the frame through the native helper.

OBSBOT Tail 2 (network cameras)

The Tail 2 is a different animal: a network PTZ camera (NDI/RTSP/SRT/RTMP output, ethernet + WiFi, and a USB-C port that is either MTP for footage offload or a UVC webcam). The tools below use its network control plane — an HTTP REST API plus a WebSocket status push, documented in TAIL2-PROTOCOL.md from on-device reverse engineering. That makes the whole module pure TypeScript with zero platform-specific code: no native helper, no helper build, identical behavior on Windows/Linux/macOS.

In UVC mode the camera also answers the standard UVC controls over USB (pan, tilt, zoom, pan/tilt speed, focus, exposure, white balance), and its vendor Extension Unit gives tracking on/off, human tracking mode, tracking speed and Auto Zoom. This server has no Tail 2 USB transport yet, so none of that is exposed as a tool; presets, portrait rotation and roll trim are network only. See TAIL2-PROTOCOL.md §11 for what was measured.

Getting started:

  1. Put the Tail 2 on your network (it defaults to DHCP on ethernet).
  2. Call obsbot_tail2_scan once — it listens ~5 s for the camera's own mDNS announcements (it multicasts its MAC, name and IPs every few seconds, the same channel OBSBOT Center discovers by) and falls back to an HTTP subnet sweep only on multicast-filtered networks. Or set OBSBOT_TAIL2_HOSTS (comma-separated addresses) in the server's environment, or just pass any Tail 2 tool a camera parameter holding the camera's IP.

The camera selector for these tools is the camera's MAC (its stable identity), or its device_name, or its host address — case-insensitive. Omitted with one Tail 2 registered it resolves to that one.

ToolParametersDescription
obsbot_tail2_devices—List registered Tail 2 cameras (mac, name, host(s)). Registration is not liveness.
obsbot_tail2_scan—Discover Tail 2 cameras: ~5 s mDNS listen (the camera's own announcements), HTTP subnet sweep fallback. Registers what it found.
obsbot_tail2_wakecamera?Wake a sleeping Tail 2 (POST /camera/sdk/poweron). Moves the camera: un-stows the gimbal back to its active pose and resumes the media/streaming daemon. Polls power_on: true (~4–6 s). Hardware-verified 2026-11-03.
obsbot_tail2_sleepcamera?Put a Tail 2 to sleep (POST /camera/sdk/poweroff). Despite the path name, the camera's own web UI uses this for sleep/hibernate rather than a hard power cut: moves the camera (stows the gimbal face-down at pitch ~84–89°), suspends the media/streaming daemon, and keeps the HTTP/WS server online so obsbot_tail2_wake can resume it over the network. Polls power_on: false (~3–6 s). Hardware-verified 2026-11-03.
obsbot_tail2_statuscamera?One WebSocket status push: the full live block — power, rec, portrait, AI mode + tracking settings, zoom ratio, roll bias, focus modes, NDI/RTSP/SRT/RTMP flags, SD card, presets with poses, and per-subsystem health. No live yaw/pitch — Tail 2 gimbal moves are open-loop.
obsbot_tail2_infocamera?Identity + static config in one call: device_info, range (zoom 1.0–12.0, focus 1–100, WB 2000–10000 K), networkconfig (NDI/stream settings).
obsbot_tail2_zoomratio (1.0–12.0), speed (1–10, default 5), camera?Absolute zoom on the camera's own ratio scale. speed is required by the firmware. Returns settled (readback-verified) — see below. Silently pins at 5.0 (the optical ceiling) unless hybrid zoom is unlocked — see obsbot_tail2_hybrid_zoom.
obsbot_tail2_hybrid_zoomenabled, camera?Unlock (or re-lock) the 5–12x digital zoom region — the switch OBSBOT Center owns, which does not exist in the REST API (zoom above 5.0 is acknowledged but pinned until this runs). Speaks Center's private UDP-9999 protocol directly, both checksums decoded; verified by readback from the live status snapshot (zoom_infos.digital_enable), works with Center closed. Hardware-verified both directions 2026-09-30.
obsbot_tail2_recentercamera?Gimbal recenter (yaw/pitch/roll 0). Open-loop: returns on ack. Can drop AI tracking to none; re-enable with obsbot_tail2_ai_track.
obsbot_tail2_gimbal_positioncamera?Live gimbal pose in degrees (yaw/pitch/roll) + zoom ratio, via save-scratch-preset → read → delete (~2 s). Needs one empty preset slot (probe slots are self-cleaning).
obsbot_tail2_gimbal_speedyaw? pitch? roll? (±150 speed), durationMs? (100–5000, default 500), camera?Jog the gimbal (the joystick primitive) with automatic stop. Measured: command 20 ≈ 7.9°/s; positive yaw command DECREASES recorded yaw. Disable AI tracking first.
obsbot_tail2_gimbal_moveyaw? pitch? (±178°), camera?Absolute move via closed loop (jog → pose read → correct, ≤5 rounds, ±1.5° tolerance). Slow by design (~1 s per small move, more for large). Refuses while AI tracking is active. Hardware-verified 2026-09-30: 4° move converged in one iteration.
obsbot_tail2_gimbal_invertenable?, camera?Read (bare call) or set (verified) control-direction inversion.
obsbot_tail2_recordenable?, camera?Read/control the recording switch. Needs storage — check obsbot_tail2_status's sdcard field.
obsbot_tail2_capture_photocamera?Trigger a still photo (to storage).
obsbot_tail2_focusmode? (afc/afs/mf), position? (0–100), camera?Read or set focus. Position only in mf (mode-gated; the tool refuses otherwise).
obsbot_tail2_exposuremode?, face?, evbias?, iso?, shutter?, camera?Read (mode-appropriate) or set exposure: auto (face-priority AE + EV bias) or manual (ISO 100–6400, shutter 1/N). Note: the firmware rejects JSON-integer evbias — the client always sends 0.0-style floats.
obsbot_tail2_image_adjustcontrol?, value? (0–100), styleMode?, camera?Read or set image style (brightness/contrast/hue/saturation/sharpness). Control writes are gated to styleMode: manual — the tool applies a given styleMode first and refuses otherwise.
obsbot_tail2_hdrenabled?, camera?Read/set HDR.
obsbot_tail2_wbmode?, temperature? (2000–10000 K), camera?Read/set white balance (6 modes + Kelvin in manual).
obsbot_tail2_streamoutput? (ndi/rtsp/srt/off), camera?Read/set the ONE active network output. The programmatic way to arm SRT for obsbot_tail2_snapshot — no Center needed.
obsbot_tail2_only_meenabled?, camera?Read/set OnlyMe human-tracking (track one person, not everyone).
obsbot_tail2_audiovolume?, mute?, camera?Read/set audio input.
obsbot_tail2_zoom_typetype? (normal/shot/halfBody/fullBody/P7/P9/P16/P24), camera?Read/set the auto-zoom framing pattern (Center's tracking slider). Writes gated: requires human tracking armed.
obsbot_tail2_preset_speedspeed? (1–5), camera?Read/set the preset switching speed.
obsbot_tail2_usb_modemode? (mtp/uvc), camera?Read/set the USB-C function mode.
obsbot_tail2_antiflickermode? (off/50hz/60hz), camera?Read/set anti-flicker.
obsbot_tail2_af_trackmode? (global/face/front), camera?Read/set the autofocus track mode.
obsbot_tail2_iso_rangemin?, max? (100–6400), camera?Read/set the auto-ISO range (double slider; omitted bounds keep current).
obsbot_tail2_gesturelockedTarget?, recording?, zoom?, zoomFactor?, camera?Read (bare) or set the gesture-control switches and zoom factor.
obsbot_tail2_stream_configencoder?, resolution?, bitrate?, camera?Read (bare, incl. RTSP URLs) or set the stream encoder config. Resolution writes gated on output off.
obsbot_tail2_export_logpath, camera?Download the full diagnostic bundle (the Export Log archive: ust.json, status.json, factory records, kernel logs) to path. ~10–20 s.
obsbot_tail2_live_statuscamera?Fresh runtime snapshot distilled: live gimbal pose (euler+joint), zoom internals incl. hybridDigitalEnable, runtime exposure truth, temps, boot stage, accessories.
obsbot_tail2_zone_trackingenabled, camera?Toggle zone tracking (UDP 9999 key 03). No readback exists — effect shows in tracking behavior.
obsbot_tail2_auto_zoom_speedspeed (1–10), camera?Set the AI auto-zoom framing speed (UDP key 0x17; distinct from manual zoom speed). No readback.
obsbot_tail2_track_customenabled?, pan?, tilt? (1–10), panAuto?, tiltAuto?, camera?Read (bare) or configure custom tracking speed: per-axis speeds, Auto buttons (readback-verified via tracking_settings). Axis locks shown read-only — no known write path.
obsbot_tail2_track_targetx, y (0.01–0.99), camera?Tap-to-track: engage AI tracking on the subject at a normalized frame coordinate (snapshot pixel ÷ frame size). Undocumented endpoint, hardware-measured.
obsbot_tail2_focus_pointx?, y? (0.01–0.99), camera?Tap-to-focus: move the focus window to a normalized frame coordinate and start a point focus (afc/afs only). Bare call reads the window.
obsbot_tail2_portraitenable, camera?Motorized 90° barrel rotation for portrait framing — no Tiny 2 equivalent. Verified on orientation feedback; retry if settled:false (writes can be silently dropped while the motor is in motion — measured).
obsbot_tail2_roll_biasangle (±90), camera?Roll trim in degrees (horizon correction / deliberate tilt).
obsbot_tail2_ai_trackenabled, mode (default "humanTrackingSingleMode"), camera?Enable/disable AI tracking. Modes are the camera's own enum: human (single/group), animal (normal/close-up), object tracking (objectTracking/objectTrackingNormal/objectTrackingCloseUp). While active, tracking owns the gimbal.
obsbot_tail2_track_speedspeed: "superLazy" | "lazy" | "slow" | "fast" | "crazy" | "customized", camera?Tracking follow speed — the Tail 2's own six-speed enum (NOT the Tiny 2's standard/sport pair).
obsbot_tail2_preset_listcamera?The three preset slots: occupied/empty, decoded name, pose in degrees + zoom ratio.
obsbot_tail2_preset_saveslot (1–3), name? (default P1/P2/P3), camera?Save the CURRENT live pose into a slot — aim first, then save. Overwrites an occupied slot (unlike the Tiny 2's create-once), and there is no pose-by-value write in this API at all.
obsbot_tail2_preset_recallslot, camera?Drive gimbal + zoom to a saved pose. Refuses an empty slot. Arrival verified via the zoom readback (gimbal axes are open-loop — the Tail 2 reports no live pose). Disable AI tracking first or it fights the move.
obsbot_tail2_preset_deleteslot, camera?Free a slot.
obsbot_tail2_preset_renameslot, name, camera?Rename a slot (base64 handled transparently).
obsbot_tail2_snapshotresolution (256–1920, default 640), quality (1–100, default 80), camera?Grab one still frame and return it as an image. Pulls from the camera's SRT output (ffmpeg SRT caller, port 5000) — requires SRT listener mode enabled in OBSBOT Center first (it disables NDI while active and is cleared by a camera reboot; Center only allows changing streaming settings while the output is off, so a failure usually means SRT is off — re-enable and retry). Hardware-verified 2026-09-26.

Every write is verified by readback (settled in the result): the Tail 2 acknowledges commands it may drop while an actuator is mid-motion — {"code":200} means acknowledged, not applied (measured 2026-09-26; see TAIL2-PROTOCOL.md §8). settled:false means the ladder ran out before the readback agreed; the command was still sent — retry it.

Known Tail 2 gaps (details in TAIL2-PROTOCOL.md §9): image-control writes via REST are unprobed, and snapshot depends on the camera's SRT output being enabled from OBSBOT Center (exclusive with NDI, cleared by reboots — the tool explains this when SRT is off; on this firmware the RTSP output never serves despite its enable flag). The camera's control API is also unauthenticated — anyone on your LAN can drive it, including poweroff.

Supported platforms

  • Windows x64 — supported today. The native helper is built from source in native/windows/ (CMake + MSVC); the published npm package ships a prebuilt binary so end users need no toolchain.

  • Linux x64 — supported from v0.2. The native helper is in native/linux/ (CMake + GCC); it uses V4L2 for standard UVC controls (zoom, focus, exposure, pan/tilt, white balance, image controls) and UVCIOC_CTRL_QUERY for vendor Extension Unit commands (gimbal speed/AI tracking, wake/sleep, HDR, FOV). Snapshots capture a MJPEG or YUYV frame via V4L2 mmap streaming and encode to JPEG using libjpeg. The linux-x64 prebuilt binary ships with the published npm package. Build dependencies: build-essential cmake libjpeg-dev libv4l-dev.

    Gimbal position reads (obsbot_gimbal_position) reflect the last-commanded value, not live in-flight position — see "Linux gimbal position feedback" below for why, and what would fix it.

  • macOS 14+ (Apple Silicon and Intel) — supported. The native helper is in native/macos/ (Objective-C + IOKit/AVFoundation). It uses IOKit USB control transfers for both standard UVC controls and vendor Extension Unit commands, and AVFoundation for enumeration and snapshots. Both darwin-arm64 and darwin-x64 prebuilt binaries ship with the published npm package (darwin-x64 also covers Apple Silicon running Node under Rosetta, where process.arch reports x64). macOS 14 is the floor because the helper uses AVCaptureDeviceTypeExternal; the build pins -mmacosx-version-min so the binary does not inherit the build machine's OS as its minimum.

    Note on macOS specifically: UVCAssistant (a DriverKit system extension) owns the camera's UVC interfaces exclusively, so USBInterfaceOpen — and even USBInterfaceOpenSeize — fail with kIOReturnExclusiveAccess. The helper therefore opens the USB device, which is not locked, and issues UVC control requests on its default control endpoint. This coexists with UVCAssistant: the camera keeps working as a normal webcam while under control, so no driver-replacement step is needed.

Building the native helper (Linux)

bash
cd native/linuxmkdir build && cd buildcmake ..make -j$(nproc)make install  # copies to native/prebuilt/linux-x64/

Linux gimbal position feedback is not live

obsbot_gimbal_position on Linux reports the last position obsbot_gimbal_move/ obsbot_gimbal_recenter commanded — not a live, in-flight reading. Hardware testing (2026-07-21) confirmed the OBSBOT Tiny 2's CT_PANTILT_ABSOLUTE control genuinely tracks live position — a raw USB read of that same control, bypassing the kernel, showed a real slew progressing in real time. The reason plain V4L2 (VIDIOC_G_CTRL) never sees that is that uvcvideo caches the control's value and serves the cache instead of re-querying the device (confirmed via VIDIOC_QUERY_EXT_CTRL, which reports no V4L2_CTRL_FLAG_VOLATILE). The driver invalidates that cache when the device sends a UVC Control Change interrupt — which this camera's firmware never does, and never advertises support for.

Getting a genuinely live reading through V4L2 requires briefly detaching the kernel driver from the camera's control interface and reading the control directly over raw USB — but detaching that interface (even briefly, even without writing anything) breaks any concurrent video capture on this device: streaming and control share one kernel-managed USB function, so pulling the driver off one takes both down together. That makes a libusb-based workaround incompatible with anything actually using the camera as a webcam at the same time, which ruled it out as a shipped default.

A kernel patch has been submitted upstream (media: uvcvideo: query pan/tilt position from the device on every read, July 2026 — awaiting review, not merged). It marks CT_PANTILT_ABSOLUTE volatile so the driver queries the device on every read; verified on this hardware to track a live slew through plain VIDIOC_G_CTRL, concurrently with streaming. If it is accepted, obsbot_gimbal_position becomes live on Linux with no code changes needed here. Until it ships in a kernel near you:

  • obsbot_gimbal_move and obsbot_gimbal_recenter work normally — hardware-verified, repeatedly, via direct V4L2 VIDIOC_S_CTRL writes. Their target values are known and clamped before being sent, so they can't exceed the gimbal's mechanical range regardless of the missing feedback.
  • obsbot_gimbal_move_speed is not available on Linux (hidden from the tool list entirely, not just refused at runtime). A speed×duration burst has no target position to clamp — without a live reading to confirm where the gimbal actually is, there's no way to bound it against the mechanical limits before it gets there. It remains available on Windows/macOS.

Building the native helper (macOS)

bash
make -C native/macos    # -> native/prebuilt/darwin-arm64/obsbot-helper

Requires the Xcode command line tools. CMake works too (cmake -S native/macos -B native/macos/build && cmake --build native/macos/build), which is what CI uses.

Known limitations

What has actually been exercised against hardware, and what hasn't:

PlatformStatus
win32-x64Hardware-verified — mid-session disconnect recovery (ERROR_DEV_NOT_EXIST, measured not guessed), camera arrival/removal push events, proactive re-bind across a same-port replug with no tool call, white balance, and the ProcAmp control ranges, on a real Tiny 2. A different-port replug recovers on the next tool call rather than proactively — see below
linux-x64Hardware-verified — gimbal absolute moves and recenter via V4L2 (20 consecutive moves with a live preview running), and the arc-second scaling fix confirmed by physical swing. Gimbal position is not live and obsbot_gimbal_move_speed is unavailable — see below
darwin-arm64Hardware-verified — control, gimbal movement and per-axis position readback, zoom, snapshot, USB vid/pid candidacy, serial readback and serial-keyed binding, single-owner IPC coordination, helper-death recovery, and unaided recovery from an unplug/replug, on a real Tiny 2
darwin-x64Build-verified only — compiles with the right architecture and deployment target, never executed
  • The Intel (darwin-x64) helper has never been run. No Intel Mac was available to test it. It cross-compiles cleanly and is packaged, but nothing has confirmed it talks to a camera. It also covers Apple Silicon running Node under Rosetta, where process.arch reports x64 — likewise untested. Reports from Intel users are welcome.

  • macOS 14 or newer is required, and macOS runtime is verified on 26.5 only. The helper uses AVCaptureDeviceTypeExternal (macOS 14+), so the build pins -mmacosx-version-min=14.0. The binary will load on 14 through 25, but behavior there is untested — in particular the UVC control path relies on UVCAssistant holding the camera's UVC interfaces while leaving the USB device itself openable. That is how current macOS behaves; older releases are unconfirmed.

  • The first snapshot on macOS raises a camera permission prompt. The helper is a plain CLI tool with no bundle identifier, so macOS attributes camera access to whichever app spawned it — your MCP client — and that app is named in the prompt and holds the grant. Approve once; the grant survives helper updates, since it is keyed to the client rather than to the helper's signature.

  • AI tracking overrides manual gimbal moves. When AI tracking is active (the Tiny 2's default on wake), a commanded pan/tilt executes and is then pulled back to the tracked subject — obsbot_gimbal_position shows the yaw/pitch move out and decay back to rest. This is the camera's behaviour, not a bug: turn tracking off for unopposed manual control.

  • The camera may not enumerate through a USB hub or dock. A Tiny 2 connected through a USB-C dock was invisible to ioreg and system_profiler entirely — not just to this server. If obsbot_devices comes back empty, try a direct connection before assuming a software fault.

  • Only the OBSBOT Tiny 2 is supported. On Windows and macOS candidacy is gated on the Remo USB vendor ID plus a known-model product ID (0x3564/0xFEF8), so no other model is detected at all — and a name-matching software source, such as the "OBSBOT Virtual Camera" that OBSBOT Center registers, is rejected because it reports no vid/pid. Linux still matches by name, because its helper does not report vid/pid yet, so a different OBSBOT may be found there — but the vendor command set is Tiny 2 specific either way. (On macOS the virtual camera cannot appear at all: the helper enumerates USB devices through the IORegistry, which a software camera never enters.)

  • The vendor reply mailbox is unreliable for several seconds after a replug. On 2026-07-21 a Tiny 2 was seen returning only the host's own echoed request frame from the vendor reply mailbox (XU selector 2) — magic byte 0xaa cleared to 0x00, every other byte identical — for a continuous 3.2 s. readSerial() threw, bind() found no serial, and every tool needing a bound camera failed with "no OBSBOT camera found" while the device was plainly healthy: correct vid/pid, opened fine, XU node 2, live status block on selector 6.

    That was unexplained for a while. It is now reproducible: immediately after a USB re-enumeration. Polling readSerial every 50 ms across a replug failed 22 times in 80 attempts spread over the first 14 s, against 0 in 120 in steady state; the first read after arrival showed exactly the echoed-request signature above, and later failures showed the reply slot populated but with its magic byte still zeroed. Ruled out as causes: stale per-process device state (the same long-lived helper read a brand-new uniqueID cleanly at t+49 ms), re-opening the device (0/40 either way), and the per-transport sequence counter restarting at 1 (0/80).

    Consequence for callers: a bind attempted in the first seconds after a replug can fail even though the camera is fine. Retrying works. The arrival-driven re-bind now retries on a bounded ladder for this reason, and a readSerial failure reports what the mailbox actually held (echoed request / unparseable / a reply to another request) rather than only "no valid reply". Ruled out earlier and still ruled out: reply latency (polled 3.2 s), the wrong extension unit (the VideoControl interface exposes exactly one, bUnitID 2), the wrong wLength (every XU selector is 60 bytes by GET_LEN), the reply arriving on another selector (1–19 swept), camera sleep state, and contention from OBSBOT Center.

  • Recovery after a replug is proactive, but not in every case, and it differs by platform. The server subscribes to OS camera arrival/removal events, so in the common case a replugged camera re-binds itself with no tool call — obsbot_devices reports it bound again on its own. Every cell below is hardware-measured:

    scenariomacOSWindowsLinux
    same-port replugproactiveproactivenext tool call
    different-port replugproactivenext tool callnext tool call

    Where it says "next tool call", nothing is stranded — the call that follows detects the stale binding, prunes it and re-binds. It costs one failed call, which is exactly how every platform behaved before these events existed. The Windows difference comes from its arrival filter requiring a path it has already enumerated, which is also what stops the Tiny 2's audio interface from being reported as a second camera; macOS has no equivalent problem because it re-binds by serial and ignores the path. Linux emits no bus events at all yet.

    Note the interaction with the mailbox entry above: a re-bind attempted immediately after a replug can still lose the first attempt, so the server retries on a short bounded ladder.

  • Two-camera operation is not yet hardware-verified. The camera selector and the per-camera device registry are covered by the unit test suite against fake transports; running two physical Tiny 2s attached at once has not been confirmed on real hardware (a second unit wasn't available). Single-camera use is unaffected either way.

  • Linux gimbal position feedback is not live, and obsbot_gimbal_move_speed is unavailable there as a result. See "Linux gimbal position feedback is not live" above — a kernel patch fixing this at the source has been submitted upstream (July 2026, awaiting review). obsbot_gimbal_move and obsbot_gimbal_recenter are unaffected; both are hardware-verified to work normally. obsbot_aim_at_pixel is affected — it depends on a live pose reading to compute the aim, the same way obsbot_gimbal_position does.

  • obsbot_zoom_vendor's ratio scale doesn't match obsbot_zoom_uvc's at the same ratio. A hardware snapshot comparison at ratio: 2.0 showed the vendor path framed tighter than the UVC path. Whether the vendor-side ratio encoding is off by a scale factor, or the two zoom controls simply have different physical ranges, isn't determined yet — one comparison isn't enough to tell. Tracked separately; use obsbot_zoom_uvc if you need the ratio to land exactly.

No proprietary SDK

This project speaks the camera's USB protocol directly through the OS's standard UVC driver stack and does not use, link, bundle, or ship any vendor SDK. See PROTOCOL.md for the protocol reference (frame format, checksum, command table).

How it works

The camera exposes two independent control surfaces, both reachable through the OS's standard UVC (USB Video Class) driver stack — this project never talks to the USB device directly, so the OS keeps mediating access and the camera remains usable as a normal webcam at the same time commands are sent:

  • Standard UVC controls — zoom (CT_ZOOM_ABSOLUTE), focus and exposure (IAMCameraControl), gimbal position readback (UVC Pan/Tilt), and the image controls plus white balance (IAMVideoProcAmp) — are the camera's built-in UVC properties, driven via DirectShow on Windows.
  • Vendor commands — gimbal moves, recenter, wake/sleep, AI tracking, HDR, and field of view — are sent through the camera's UVC Extension Unit, driven via IKsControl::KsProperty against the XU's topology node on Windows.

Both are issued through a small native helper process (obsbot-helper.exe on Windows, obsbot-helper on Linux) that the Node server spawns and talks to over a line-delimited JSON-RPC protocol on stdin/stdout. The helper is the only platform-specific piece; the codec (frame encoding, CRC-16/USB checksum, command table), transport abstraction, device manager, and MCP tool definitions are all pure TypeScript/JavaScript and shared across platforms.

Verifying against real hardware

scripts/e2e.mjs drives the built stack (dist/) against a physically connected camera: it wakes the device, zooms in, pans the gimbal, recenters, zooms back out, and puts the camera to sleep, with a short pause and console log before each step so a human can watch it happen. This moves the physical gimbal — only run it under supervision:

bash
npm run buildnode scripts/e2e.mjs

Testing changes through the live MCP tools

Two traps make it easy to test the wrong thing and believe the result. Both cost real time on 2026-07-25.

Rebuild, then start one server from the new build. The MCP server runs from dist/, so a source change is invisible until npm run build. This project coordinates concurrent clients by electing a single owner process (see IPC-DESIGN.md), and the owner executes every tool call, whichever session made it. The owner is the newest build alive: when a server starts from a newer build than the owner's, the owner finishes the call in flight, releases the camera, and hands the endpoint over. Reloading the server in one MCP client is therefore enough. Other sessions stay open and their calls are served by the new build.

Three things to know:

  • The tool list of other sessions does not refresh. A session that started on an older build keeps the list it had; its calls run the new code. Reload that session's server to get new tools.
  • A server that predates the handover cannot hand over (0.7.0 and earlier). A newer server will not run calls on it, and says so: an older instance owns the camera endpoint and cannot hand it over. Stop the old process once; after that, rebuilds take over on their own.
  • Use npm run build and npm run build:helper. They stamp the build with its identity (dist/build-info.json). A bare tsc, or a helper copied into place by hand, is not stamped and does not count as a newer build.

To see which build will run your next call, read the last ipc role= line in the server's log. role=owner means this process; role=client owner-pid=… owner-build=… names the one that will. Claude Code keeps the log under ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-obsbot/ on macOS.

bash
# Linux / macOS — every server process, with when it startedps -o pid,lstart,command -p $(pgrep -f "obsbot.*dist/index.js" | paste -sd, -)
powershell
# WindowsGet-CimInstance Win32_Process -Filter "Name='node.exe'" |  Where-Object { $_.CommandLine -like "*Obsbot*" } |  Select-Object ProcessId, CreationDate

Set OBSBOT_IPC_NAME to give a server a rendezvous endpoint of its own (1–64 characters from A-Z a-z 0-9 . _ -). Test harnesses do this so they leave a live session alone. Two owners on one machine can contend for the camera, so it is not for everyday use.

Frame rate selects the field of view, so the preview shows less than snapshots. At 1920×1080 this camera has two different windows onto the sensor and the frame rate picks between them — not the codec. Measured at one pose and one zoom: MJPEG@30 vs YUYV@30 came out at scale 1.00001 over 2382 inliers, i.e. the same field to within a fifth of a pixel, while MJPEG@60 is a 1.214× crop of both. obsbot_capture_preview pins 60fps for smooth motion, so it shows ~21% less of the room than obsbot_capture_snapshot, which negotiates 1080p30. Framing by eye in the preview and then aiming at a pixel from a snapshot will not agree. The geometry constants describe the 30fps field. Any measurement you make must state pixel format and frame rate; neither a resolution nor a codec alone identifies the field.

A preview holds the camera stream, so snapshots fail while one is open. obsbot_capture_preview and obsbot_capture_snapshot both need the device stream, and on Windows the second one gets Camera is in use by another application. This matters for the aim loop above, which is snapshot → aim → snapshot: stop the preview around each snapshot, or work without one. Gimbal control is unaffected — it uses control transfers, not the stream — so obsbot_aim_at_pixel itself works fine with a preview running. The error text suggests source: "virtual" or "ndi" as a workaround. That is safe for looking, and safe for aiming only if the feed is an unmodified pass-through of the camera — declare it with the source parameter on obsbot_aim_at_pixel / obsbot_zoom_to_fit and read the note it returns. A compositor that rescales or letterboxes the frame silently invalidates the geometry.

Source: README.md at commit 6870d97

Tools

82

obsbot_devices

List attached OBSBOT-compatible cameras. Each entry is { serial?, locationId?, name, status, reason? }: serial is the value to pass as `camera` to any camera-addressing tool (present where obtainable — reading it requires briefly opening the camera); status is available (free to bind), bound (already opened by this process), or busy (could not be opened and identified, so it can't be targeted here — usually another process holds it, but it also covers a camera that opened yet would not answer). On a busy entry, `reason` carries the underlying error and distinguishes those cases.

Takes no parameters.

obsbot_wake

Wake the camera/gimbal (sends "run"). This MOVES the camera: waking un-stows the gimbal and brings it back to level (pitch ~0). Most control commands also wake the camera implicitly as a side effect.

Parameterscamera

obsbot_sleep

Sleep the camera/gimbal (sends "sleep"). This MOVES the camera: sleeping STOWS the gimbal, tilting it face-down to roughly pitch 84°, so obsbot_gimbal_position will read ~84 rather than the pose you left it in. obsbot_wake un-stows it.

Parameterscamera

obsbot_gimbal_move

Move the gimbal to an absolute yaw/pitch angle (degrees); positive yaw pans to the camera's left, positive pitch tilts down. Yaw is clamped to [-130,130], pitch to [-90,90]. Absolute positioning (1:1 degrees), verified on hardware.

Parametersyawrequiredrollpitchrequiredcamera

obsbot_gimbal_recenter

Recenter the gimbal — drives it back to yaw 0 / pitch 0 (level and facing forward). Returns as soon as the command is sent: the gimbal may still be moving, so poll obsbot_gimbal_position if you need to know it has arrived.

Parameterscamera

obsbot_zoom_uvc

Standard UVC zoom: set an absolute zoom ratio, clamped to [1.0, 2.0]. Snaps to the requested target exactly (unlike obsbot_zoom_vendor, whose ratio scale differs and may not land exactly where asked). Waits for the zoom to actually arrive and returns { settled }: the ramp is not instant (a full 1.0->2.0 sweep takes about 2.4s), and obsbot_aim_at_pixel and obsbot_zoom_to_fit both refuse while it is in flight, so this returning early would just move the failure downstream. settled:false means the zoom had not arrived within the timeout — the command was still sent.

Parametersratiorequiredcamera

obsbot_ai_track

Enable or disable AI tracking and choose the mode. When enabled the camera follows the subject; disabling stops tracking. `mode` is either a human framing (normal | upper-body | close-up | headless | lower-body) or a standalone scene mode (group | whiteboard | desk | hand); scene modes imply enabled:true. After writing, the tool polls the status block until the mode settles and returns { verified, matched } — the aiMode the device actually landed on (matched:false means no subject was being tracked, so the mode could not take effect yet).

Parametersmodecameraenabledrequired

obsbot_ai_track_speed

Set the AI tracking-speed preset (OBSBOT Center's Standard/Sport). speed: standard (slower follow) | sport (snappier follow).

Parametersspeedrequiredcamera

obsbot_zoom_vendor

Vendor zoom path with adjustable speed: zoom to a ratio at a chosen speed. This tool's ratio scale differs from obsbot_zoom_uvc's and may not land exactly on the requested target. ratio is clamped to [1.0,2.0]; speed 0=device default, 1-10 slow→fast, 255=maximum.

Parametersratiorequiredspeedcamera

obsbot_focus_face

Enable or disable face-priority autofocus.

Parameterscameraenabledrequired

obsbot_status

Read the camera's live status block. Returns { awake, hdr, faceAe, aiMode, trackSpeed, fovMode, zoomPercent, focusMode, focusPosition }: faceAe is whether auto-exposure is metering for a detected face; aiMode is the current AI framing (no-tracking|normal|upper-body|close-up|headless|lower-body|desk|whiteboard|hand|group|unknown); trackSpeed is standard|sport|unknown; fovMode is the field-of-view mode (wide|medium|narrow|custom|unknown), where custom means a continuous zoom overrode the discrete modes; zoomPercent is the zoom position, 0-100; focusMode is auto|manual|unknown. focusPosition is present ONLY in manual mode, on the same 0-100 scale obsbot_focus_manual takes: under autofocus this camera does not expose the motor, it echoes the last written value, so reporting it would look like a live focus distance while being stale. Focus is a standard UVC control rather than a field of the status block, so it costs an extra read; a device that cannot answer it reports focusMode unknown rather than failing the whole read. Under --debug the result also carries `raw`: the full 60-byte status block as hex (for reverse-engineering undecoded offsets).

Parameterscamera

obsbot_image_fov

Set the field of view. fov: wide (86°) | medium (78°) | narrow (65°).

Parametersfovrequiredcamera

obsbot_image_hdr

Toggle HDR/WDR imaging on or off.

Parameterscameraenabledrequired

obsbot_focus_auto

Enable continuous autofocus.

Parameterscamera

obsbot_focus_manual

Set the focus motor to position (0-100, near→far), mapped onto the device range.

Parameterscameraposition

obsbot_gimbal_position

Read the gimbal's current absolute yaw/pitch in degrees (positive yaw = camera's left, positive pitch = down) via the standard UVC Pan/Tilt controls. This is a live hardware readout accurate to ±1°, reported rounded to 2 decimal places (finer digits would be noise, not precision): it is valid during a move as well as after one, and reflects motion the host did not command (speed moves, recenter, tracking).

Parameterscamera

obsbot_aim_at_pixel

Point the camera at a specific pixel in a frame you just captured. Give the pixel's x/y and the frameWidth/frameHeight from THE SAME obsbot_capture_snapshot result — mixing a pixel from one frame with dimensions from another aims at the wrong place and cannot be detected. `source` DECLARES which feed the frame came from (default device); it cannot be inferred from the pixels. A virtual or ndi frame is accepted, but only aims correctly if that feed is an unmodified pass-through of the camera — a compositor's rescale or letterbox is invisible in the picture and silently wrong here — so a non-device declaration comes back with that assumption stated. Takes no field-of-view or zoom argument: it reads the camera's magnification from its reported state, a discrete FOV mode or a continuous zoom alike, so it works at any zoom. Refuses when AI tracking is active (tracking moves the gimbal itself and would fight the aim), when the FOV mode can't be decoded, or when a corrupt zoom reading would resolve to an implausible magnification, so it never aims on an assumption it cannot check. If the camera was asleep, waking it moves the gimbal and invalidates the frame you measured, so the call refuses instead of aiming on stale geometry — take a fresh snapshot and retry. Returns clamped:true if the target was outside the gimbal's range; the camera still moves, to the nearest reachable pose. Refuses (ok:false) instead of moving when the pixel lies past vertical from the current pose — reachable only by an "over the top" rotation that would swing the camera toward the opposite side of the room, not toward the target; tilt toward the pixel first, then re-aim.

ParametersxrequiredyrequiredcamerasourceframeWidthrequiredframeHeightrequired

obsbot_zoom_to_fit

Frame a region of a frame you just captured: centre the gimbal on it and zoom so the region fills the frame. Give x/y/width/height of the region plus the frameWidth/frameHeight from THE SAME obsbot_capture_snapshot result — mixing a region from one frame with dimensions from another frames the wrong place and cannot be detected. Must come from a snapshot, and takes the same `source` declaration as obsbot_aim_at_pixel. `margin` (default 0.1) backs the zoom off by that fraction so the region isn't framed edge-to-edge; the tighter of the region's two axes decides the zoom, so the WHOLE region stays visible rather than being cropped on one side. Moves the gimbal BEFORE zooming, since zooming first can push the region's centre out of frame. Refuses on the same conditions as obsbot_aim_at_pixel: AI tracking active, the camera was asleep (waking it moves the gimbal and invalidates the frame), the FOV mode can't be decoded, a corrupt zoom reading, or the region's centre lying past vertical from the current pose, or a frame that isn't 16:9 (obsbot_capture_snapshot always returns 16:9; a non-16:9 pair looks transposed). Also refuses a region that isn't within the frame (edges included), or has non-positive width/height. The requested zoom is clamped to the camera's [1x, 4x] magnification range and reported via `clamped`; a partial fit still moves and zooms to the limit. Zoom ramps rather than jumping, so the tool polls the status block for up to 3s waiting for it to arrive and returns `settled:false` (not an error) if it didn't — a frame captured mid-ramp is at an unknown magnification, so check `settled` before trusting a follow-up snapshot.

ParametersxrequiredyrequiredwidthrequiredcameraheightrequiredmarginsourceframeWidthrequiredframeHeightrequired

obsbot_preset_list

Read the three gimbal preset slots (occupied/empty, name, pose in degrees). Reads flat XU selectors 12 (list) and 13 (entry cursor), NOT the vendor V3 framed-reply path (which is non-functional for preset data on this device).

Parameterscamera

obsbot_preset_save

Save the gimbal's current live pose (yaw/pitch, via the standard UVC Pan/Tilt controls) into preset slot 1|2|3. Slots are create-once on this device — there is no overwrite, so an occupied slot is rejected (delete it first). Verifies by re-reading the slot list after writing.

Parametersslotrequiredcamera

obsbot_preset_recall

Recall preset slot 1|2|3, driving the gimbal to that slot's saved pose. The slot must be occupied. The gimbal may still be moving when this returns — verification only confirms the slot is still occupied, not that the pose has arrived.

Parametersslotrequiredcamera

obsbot_preset_update

Overwrite preset slot 1|2|3 with the gimbal's current live pose (yaw/pitch via the standard UVC Pan/Tilt controls). The slot must already be occupied (save first to create it). Verifies by re-reading the slot list after writing.

Parametersslotrequiredcamera

obsbot_preset_rename

Rename preset slot 1|2|3. The slot must already be occupied. Names longer than 40 bytes are truncated to fit the wire frame. Verifies by re-reading the slot list after writing.

Parametersnamerequiredslotrequiredcamera

obsbot_preset_delete

Delete preset slot 1|2|3. The slot must be occupied. Verifies by re-reading the slot list after writing.

Parametersslotrequiredcamera

obsbot_image_wb_auto

Enable auto white balance.

Parameterscamera

obsbot_image_wb_manual

Set white balance to a colour temperature in Kelvin (clamped to the device's supported range).

Parameterscameratemperature

obsbot_image_adjust

Adjust a standard image control: control is brightness | contrast | hue | saturation | sharpness | gain | backlight-compensation; level 0-100 is mapped onto the device's supported range for that control. Standard UVC (IAMVideoProcAmp), no auto. NOTE: `gain` and `backlight-compensation` are NOT implemented on the Tiny 2 — it reports them as zero-length controls — so they are refused with an error rather than silently doing nothing. The other five work.

Parameterslevelrequiredcameracontrolrequired

obsbot_image_exposure_auto

Enable auto-exposure. Optional priority 'global' | 'face' selects the metering region (face-priority meters for a detected face). Uses the proprietary V3 frame protocol (CAM_SET_EXPOSURE_TINY2, which carries mode and value in one command) because the standard UVC/IAMCameraControl V4L2 path is a stub on the Tiny 2.

Parameterscamerapriority

obsbot_image_exposure_manual

Set exposure level 0-100 (0 darkest → 100 brightest), mapped onto the device's exposure range. Also returns `raw`: the device-native exposure value the level mapped to, for diagnostics — `level` is the number to reason with. Uses the proprietary V3 frame protocol (CAM_SET_EXPOSURE_TINY2, which carries mode and value in one command) because the standard UVC/IAMCameraControl V4L2 path is a stub on the Tiny 2.

Parameterslevelcamera

obsbot_capture_snapshot

Grab one still frame from the camera and return it as an image (for you to see and for framing/lighting/exposure checks). resolution is the longest edge in pixels, 256-1920, default 640 — larger images cost proportionally more tokens, so ask for more only when you need the detail. NOTE: before calling, ensure the camera is focused (call obsbot_focus_auto for autofocus) unless otherwise directed. source: device (default) | virtual | ndi. If the camera is in use by another app, returns a message instead of an image.

ParameterscamerasourcequalitysettleMsresolution

obsbot_capture_record

Start recording the camera to an MP4 (for the user). durationSec optional (open-ended recordings auto-stop after 60 min); audio defaults to on (the OBSBOT mic); outputPath optional (defaults to ~/Videos/OBSBOT on every platform, including macOS, where that is NOT the usual ~/Movies). NOTE: before calling, ensure the camera is focused (call obsbot_focus_auto for autofocus) unless otherwise directed. source: device|virtual|ndi. Returns a sessionId for obsbot_capture_stop.

ParametersaudiosourceoutputPathdurationSec

obsbot_capture_preview

Open a live preview window of the camera (for the user to watch). NOTE: before calling, ensure the camera is focused (call obsbot_focus_auto for autofocus) unless otherwise directed. source: device|virtual|ndi. Returns a sessionId for obsbot_capture_stop.

Parameterssource

obsbot_capture_stop

Stop a recording or preview session by its sessionId. Recordings are stopped gracefully so the MP4 finalizes correctly.

ParameterssessionIdrequired

obsbot_capture_list

List active recording/preview sessions (id, kind, source, output path, start time).

Takes no parameters.

obsbot_tail2_devices

List registered OBSBOT Tail 2 cameras (network cameras; MAC-keyed). Each entry: mac (the identity — pass it as `camera` to any obsbot_tail2_* tool), name, and the host(s) it has answered on. Registration is not liveness: an offline camera still appears here; use obsbot_tail2_status to check one is reachable.

Takes no parameters.

obsbot_tail2_scan

Discover OBSBOT Tail 2 cameras on the network and register them. Listens for the camera's own mDNS announcements (~5s — it multicasts its MAC, name and IPs every few seconds, the same channel OBSBOT Center discovers by) and falls back to an HTTP subnet sweep only when nothing was heard (multicast-filtered networks; ~20s on a full /24). Returns the cameras found; they stay registered afterwards. Run this once to onboard a Tail 2 whose IP you don't know.

Takes no parameters.

obsbot_tail2_wake

Wake a sleeping Tail 2 (POST /camera/sdk/poweron). This MOVES the camera: waking un-stows the gimbal back to its active pose and resumes the media/streaming daemon. Polls the WebSocket status push (riding out brief socket restarts while the daemon wakes) until power_on reads true (~4–6s). Hardware-verified 2026-11-03.

Parameterscamera

obsbot_tail2_sleep

Put a Tail 2 to sleep (POST /camera/sdk/poweroff). Despite the endpoint name, the camera's own web UI uses this for sleep/hibernate rather than a hard power cut: this MOVES the camera (stowing the gimbal face-down at pitch ~84–89°), suspends the media/streaming daemon (NDI/SRT/RTSP and /camera/test/status stop while asleep), and keeps the HTTP/WS server online so obsbot_tail2_wake can resume it over the network. Polls the status push until power_on reads false (~3–6s). Hardware-verified 2026-11-03.

Parameterscamera

obsbot_tail2_status

Read a Tail 2's full live status block (one WebSocket push): power, recording, portrait rotation, AI mode + tracking settings, zoom ratio, roll bias, focus modes, NDI/RTSP/SRT/RTMP enablement, SD card, preset list with poses (degrees + zoom ratio), and per-subsystem health (gimbal/AI/battery/lens/ToF). NOTE: no live yaw/pitch — the Tail 2 reports pose only via saved presets, so gimbal moves are open-loop (same limitation as the Tiny 2 on Linux).

Parameterscamera

obsbot_tail2_info

Read a Tail 2's identity and static configuration in one call: device_info (name, MAC, wired/wireless IPs), range (zoom 1.0–12.0, focus 1–100, exposure, white balance 2000–10000K, image adjust ranges), and networkconfig (NDI/stream encoder settings). Read-only.

Parameterscamera

obsbot_tail2_zoom

Set absolute zoom ratio on a Tail 2 (1.0–12.0 — twelve x, six times the Tiny 2's range; scale is the camera's own ratio, not a magnification multiple). speed 1–10 is REQUIRED by the firmware and defaults to 5. Zoom ramps mechanically; the tool polls the readback and returns settled:false if it hadn't arrived within ~5s — the command was still sent. Hardware-verified 2026-09-26.

Parametersratiorequiredspeedcamera

obsbot_tail2_hybrid_zoom

Unlock (or re-lock) the Tail 2's hybrid digital zoom — the control OBSBOT Center owns. Out of the box the camera's advertised 12x range is walled at its 5x OPTICAL ceiling across the entire REST API: PUT ptz/zoom above 5.0 is acknowledged but silently pins at 5.0. Center unlocks the 5-12x digital region through its private UDP 9999 protocol; this tool speaks that protocol directly (both checksums decoded — the synthesizer reproduces Center's captured frames byte-for-byte; TAIL2-PROTOCOL.md §10). Verified by readback from the live status snapshot (zoom_infos.digital_enable); returns settled:false if the readback hadn't caught up — behavioral double-check: obsbot_tail2_zoom ratio 6.0 settles above 5.0 only when enabled. Works with OBSBOT Center closed. Hardware-verified both directions 2026-09-30, including full synthesis with a fresh sequence number.

Parameterscameraenabledrequired

obsbot_tail2_zoom_type

Read or set the auto-zoom framing pattern (Center's Console slider under Single/Group tracking: off/3/5/7/9/…/24). Off=normal, 7/9/16/24=P7/P9/P16/P24 (subject-size framing percentages), 3/5≈halfBody/fullBody (3 disabled in group mode). Arming AI tracking sets this to `shot` — which is why the status block's zoom_type reads shot while tracking. Writes are GATED (MEASURED 2026-10-01): the camera 500s unless human tracking is armed — obsbot_tail2_ai_track enabled:true first. Readback-verified write.

Parameterstypecamera

obsbot_tail2_preset_speed

Read or set the preset switching speed (1-5, Center's Console page). How fast recalls drive the gimbal. Readback-verified write.

Parametersspeedcamera

obsbot_tail2_usb_mode

Read or set the USB-C function mode: mtp or uvc. Control rides the network either way, so switching is safe remotely. `mtp` is the measured wire value; `uvc` is the presumed counterpart (GET-measured 2026-10-01). Readback-verified write.

Parametersmodecamera

obsbot_tail2_antiflicker

Read or set the anti-flicker mode: off, 50hz or 60hz (60hz measured on this unit). Readback-verified write.

Parametersmodecamera

obsbot_tail2_af_track

Read or set the autofocus track mode: global, face or front (Center's radios; `face` measured on the wire, the other two are the presumed strings). Readback-verified write.

Parametersmodecamera

obsbot_tail2_iso_range

Read or set the auto-ISO range (the double-ended slider: both bounds, 100-6400 in doubling stops). Pass min and/or max; both default to the current value when omitted. Readback-verified write.

Parametersmaxmincamera

obsbot_tail2_gesture

Read (bare) or set the gesture-control switches and the gesture zoom factor: lockedTarget, recording, zoom (booleans) and zoomFactor (the multiplier one zoom gesture applies, e.g. 2.0). All measured on the wire 2026-10-01. Writes are readback-verified; each field is optional.

ParameterszoomcamerarecordingzoomFactorlockedTarget

obsbot_tail2_stream_config

Read (bare) or set the stream encoder configuration shared by the network outputs: encoder (h264/h265), resolution (camera format like 1920X1080P30), bitrate (Mbps). The bare read also includes the RTSP URLs for both network interfaces. GATED (MEASURED 2026-10-01): resolution writes 500 while an output is live — set obsbot_tail2_stream output:"off" first (encoder and bitrate writes are ungated). Readback-verified writes.

Parameterscamerabitrateencoderresolution

obsbot_tail2_export_log

Download the camera's full diagnostic bundle (GET /camera/test/log — the same archive OBSBOT Center's Export Log button produces) and save it to `path`. ~12 MB tar.gz containing the settings tree (ust.json), the runtime status snapshot, factory records, kernel logs. Takes ~10-20 s (server-side archive generation).

Parameterspathrequiredcamera

obsbot_tail2_live_status

Read a FRESH runtime snapshot (GET /camera/test/status) distilled to what the REST tree hides: live gimbal pose (euler + joint angles — the WS push has no pose), zoom internals including digital_enable (the hybrid-zoom state, the readback obsbot_tail2_hybrid_zoom verifies against), runtime exposure truth (shutter/ISO/aperture as actually running), battery/lens temperatures, boot stage and uptime, and accessory state (360° base, BT remote, tally). Regenerated server-side per request.

Parameterscamera

obsbot_tail2_zone_tracking

Toggle zone tracking (Center's Console page switch) on or off. Rides the camera's private UDP 9999 channel (generic key-value command, key 03 — decoded from a labeled capture 2026-10-01, TAIL2-PROTOCOL.md §10b). No ack and no REST-readable state — the effect shows in the tracking behavior. Synthesized frame, works with Center closed.

Parameterscameraenabledrequired

obsbot_tail2_auto_zoom_speed

Set the AUTO zoom speed 1-10 — how fast AI-tracking auto-zoom framing moves (Center's Console slider; distinct from the MANUAL zoom speed on obsbot_tail2_zoom's speed param). Rides UDP 9999 key 0x17. No REST-readable state; no ack — the frame is synthesized and works with Center closed.

Parametersspeedrequiredcamera

obsbot_tail2_track_custom

Configure CUSTOM tracking speed (Center's `customized` track speed): enable it, set the per-axis Pan/Tilt speeds 1-10, and toggle the per-axis Auto buttons. Bare call reads current state from the status push (tracking_settings, locks included read-only). Writes ride UDP 9999 keys 04/07/0a/06/09 (pan/tilt travel as float32 slider/10 on the wire; keys 06/09 are the Auto buttons — hardware-verified against horizontal_auto/vertical_auto). The AXIS LOCKS have no known write path (not UDP, REST guesses 404) — shown read-only. Verified by tracking_settings readback.

ParameterspantiltcameraenabledpanAutotiltAuto

obsbot_tail2_recenter

Recenter a Tail 2's gimbal (yaw 0 / pitch 0 / roll 0). Returns as soon as the command is acknowledged — the Tail 2 exposes no live pose to verify arrival against (open-loop, like the Tiny 2 on Linux), and note that commanding a reset can drop AI tracking to none (observed once 2026-09-26; re-enable with obsbot_tail2_ai_track if that matters). Hardware-verified 2026-09-26.

Parameterscamera

obsbot_tail2_gimbal_position

Read the Tail 2's live gimbal pose in degrees (yaw/pitch/roll) plus the zoom ratio. The API has no direct pose read — this works by saving the current pose into an empty preset slot, reading it back, and deleting the slot (takes ~2s). Requires at least one empty preset slot: overwriting a preset is irreversible (no pose-by-value write exists to restore it). Leftover 'pose-probe' slots from crashed reads are deleted, not trusted.

Parameterscamera

obsbot_tail2_gimbal_speed

Jog the Tail 2's gimbal: per-axis SPEED (sign = direction, magnitude = speed, ±150 of the firmware's ±178 range) with an automatic stop after durationMs (default 500, max 5000). This is the joystick primitive (POST ptz/gimbalcontrol) — there is no absolute move endpoint, so the camera moves continuously until stopped; this tool always stops it. Measured: command 20 ≈ 7.9°/s, and a POSITIVE yaw command DECREASES the recorded yaw (obsbot_tail2_gimbal_position's convention). Disable AI tracking first or it will fight the move.

ParametersyawrollpitchcameradurationMs

obsbot_tail2_gimbal_move

Move the Tail 2's gimbal to an absolute yaw/pitch in degrees — the Tail 2's pose-record convention (obsbot_tail2_gimbal_position reports the same axes). There is no move-to-angle endpoint in the API, so this is a closed loop over the speed primitive: jog, read the pose (each read ~2s), correct, up to 5 rounds, tolerance ±1.5°. Slow by design — expect ~5-15s. REFUSES while AI tracking is active (tracking drives the gimbal itself and would fight the loop); obsbot_tail2_ai_track enabled:false first.

Parametersyawpitchcamera

obsbot_tail2_gimbal_invert

Read or set the Tail 2's control-direction inversion (gimbalinvert). Call with no arguments to read; pass enable to write (verified by readback).

Parameterscameraenable

obsbot_tail2_record

Read (bare call) or control (enable) the Tail 2's recording switch over REST. Recording needs storage — with no SD card the state still reads ("off") but starting will not succeed; check obsbot_tail2_status's sdcard field first.

Parameterscameraenable

obsbot_tail2_capture_photo

Trigger a still-photo capture on the Tail 2 (POST capture/trigger). Needs storage (SD card — check obsbot_tail2_status's sdcard field). The ack means the camera accepted the trigger; the photo lands on the card (see obsbot_tail2_status / album).

Parameterscamera

obsbot_tail2_track_target

Tap-to-track: give a normalized frame coordinate (x/y 0.01-0.99, top-left ~0,0) and the camera engages AI tracking on the subject there — measured to arm humanTrackingSingleMode on its own from mode none. Divide a snapshot pixel by frameWidth/frameHeight and pass it straight in. The mode that engages depends on what is at the point, so read it back (obsbot_tail2_status ai_mode) when it matters; objectTracking cannot be entered this way (it demands a bounding box the API grammar for which is not yet decoded).

Parametersxrequiredyrequiredcamera

obsbot_tail2_focus_point

Tap-to-focus: move the focus window to a normalized frame coordinate (x/y 0.01-0.99, top-left ~0,0) AND start a point focus there. Effective in afc/afs focus modes only — in mf the motor position is the tool (obsbot_tail2_focus). Bare call reads the current window. Divide a snapshot pixel by frameWidth/frameHeight and pass it straight in.

Parametersxycamera

obsbot_tail2_focus

Read (bare call) or set the Tail 2's focus: mode afc|afs|mf, and position (0-100, the focus motor) which is only valid in mf — the camera errors on it in afc/afs and this tool refuses rather than trigger that. Bare call reports the mode, and the position when in mf.

Parametersmodecameraposition

obsbot_tail2_exposure

Read (bare call) or set the Tail 2's exposure. mode manual|auto; in AUTO: face (face-priority AE, global|face) and evbias (-3.0..3.0, ~0.3 steps); in MANUAL: iso (100-6400) and shutter ("1/N", 1/6400..1/30). Manual values only exist in manual mode and face/evbias only in auto — a bare call reports whichever set the current mode exposes. Note: the camera's manual ISO granularity is finer than the doc claims (a live-AE value like 894 reads back).

Parametersisofacemodecameraevbiasshutter

obsbot_tail2_image_adjust

Read (bare call) or set the Tail 2's image style: brightness/contrast/hue/saturation/sharpness, each an absolute 0-100, plus styleMode standard|outdoor|pastel|manual. Individual control writes are MODE-GATED (measured): they apply only in styleMode manual — HTTP 500 "style mode is not manual" otherwise — so this tool applies a given styleMode FIRST and refuses a control write in a preset mode. A styleMode switch preserves the current values. Bare call returns the full style bundle.

ParametersvaluecameracontrolstyleMode

obsbot_tail2_hdr

Read (bare call) or set (enabled) the Tail 2's HDR.

Parameterscameraenabled

obsbot_tail2_wb

Read (bare call) or set the Tail 2's white balance: mode auto|daylight|fluorescent|tungsten|cloudy|manual, plus temperature in Kelvin (2000-10000) which applies in manual mode.

Parametersmodecameratemperature

obsbot_tail2_stream

Read (bare call) or set the Tail 2's active network output: ndi|rtsp|srt|off — exactly ONE is active at a time (setting srt displaces ndi, and a camera reboot resets to off). This is the programmatic way to arm SRT for obsbot_tail2_snapshot without touching OBSBOT Center.

Parameterscameraoutput

obsbot_tail2_only_me

Read (bare call) or set (enabled) the Tail 2's OnlyMe human-tracking switch — track only the nearest/locked person rather than reframing for everyone.

Parameterscameraenabled

obsbot_tail2_audio

Read (bare call) or set the Tail 2's audio input: volume 0-100 and mute. Changes apply to the encoded/recorded audio stream.

Parametersmutecameravolume

obsbot_tail2_portrait

Rotate a Tail 2's barrel 90° for portrait framing (enable) or back to landscape (disable) — a motorized physical rotation no Tiny 2 has. Takes ~1.5s; the tool polls the status push for the new orientation and returns settled:false if the motor hadn't finished within ~5s. MEASURED 2026-09-26: while the motor is in motion the camera can acknowledge a write and silently drop it — if settled stays false, retry the command. Hardware-verified both directions.

Parameterscameraenablerequired

obsbot_tail2_roll_bias

Set a Tail 2's roll trim angle in degrees (horizon correction / deliberate tilt), clamped to ±90. Unlike the Tiny 2 (whose roll slot is inert), this moves the image. Verified write+readback on hardware 2026-09-26.

Parametersanglerequiredcamera

obsbot_tail2_ai_track

Enable or disable a Tail 2's AI tracking. enabled:false stops tracking (mode `none`). enabled:true starts the chosen mode — default humanTrackingSingleMode (single-person framing, the camera's power-on default); also group human framing, animal tracking, and object tracking (the mode strings are the camera's own enum). NOTE: while tracking is active it owns the gimbal — manual moves fight it, and a recenter can drop tracking to none. Mode strings verified against firmware 7.2.13.1.

Parametersmodecameraenabledrequired

obsbot_tail2_track_speed

Set a Tail 2's tracking follow speed: superLazy | lazy | slow | fast | crazy | customized — the camera's own six-speed enum (NOT the Tiny 2's standard/sport pair). Verified by readback on hardware 2026-09-26.

Parametersspeedrequiredcamera

obsbot_tail2_preset_list

List a Tail 2's three preset slots: occupied/empty, decoded name, and pose in degrees + zoom ratio (pitch/yaw/roll/ratio). Slot numbers are 1–3 (the camera's own ids are 0-based; mapped for consistency with the Tiny 2 preset tools).

Parameterscamera

obsbot_tail2_preset_save

Save a Tail 2's CURRENT live pose into a slot (1–3): aim the camera first (via tracking, a recall, or physically), then save. name defaults to P1/P2/P3. NOTE: unlike the Tiny 2, save OVERWRITES an occupied slot (no create-once), and there is no explicit-pose write in this API at all — a slot always captures the camera's CURRENT pose (measured 2026-09-26; TAIL2-PROTOCOL.md §3) — deleting first is unnecessary. Hardware-verified 2026-09-26.

Parametersnameslotrequiredcamera

obsbot_tail2_preset_recall

Recall a Tail 2 preset slot (1–3): drives the gimbal and zoom to the saved pose. Refuses an empty slot. Arrival is verified through the only live observable — the zoom ratio from the status push — so gimbal axes are open-loop (the Tail 2 reports no live pose). Disable AI tracking first or it will fight the move. Hardware-verified 2026-09-26 (zoom 1.5 → saved 3.0 observed).

Parametersslotrequiredcamera

obsbot_tail2_preset_delete

Delete a Tail 2 preset slot (1–3), freeing it. Verified by the slot disappearing from the list.

Parametersslotrequiredcamera

obsbot_tail2_preset_rename

Rename a Tail 2 preset slot (1–3). Names travel base64 on the wire; the tool encodes/decodes transparently (up to 40 chars).

Parametersnamerequiredslotrequiredcamera

obsbot_tail2_snapshot

Grab one still frame from a Tail 2 and return it as an image (for framing/lighting checks — and for measuring pixels). Source: the camera's SRT output via ffmpeg SRT caller (port 5000) — requires SRT listener mode in OBSBOT Center; exclusive with NDI, cleared by reboots, and Center only allows changing streaming settings while the output is off. Serves immediately once on. If SRT is off, returns a report of what it found with the available options and their costs (SRT toggle, NDI Webcam bridge caveats, NDI Tools install) so you can pick the cheapest path instead of dead-ending. resolution is the longest edge (256–1920, default 640); quality 1–100 (default 80). Needs ffmpeg. Frames are 16:9 1080p-sourced.

Parameterscameraqualityresolution

Version history

7
  1. v0.9.6LatestNov 5, 2026
  2. v0.9.5Nov 3, 2026
  3. v0.9.3Nov 1, 2026
  4. v0.9.2Oct 1, 2026
  5. v0.9.0Oct 1, 2026
  6. v0.8.0Sep 30, 2026
  7. v0.7.0Sep 16, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts