# OBSBOT Tiny 2 Camera Control (npm · obsbot-mcp)

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

- Trust score: 77/100 (medium)
- Change this week: +54
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- npm · `obsbot-mcp`: 77/100 (this document), [markdown](https://verifymcp.io/servers/lxman-obsbot-mcp/obsbot-mcp.md), [page](https://verifymcp.io/servers/lxman-obsbot-mcp/obsbot-mcp)

## Channel facts

- Registry: `npm`
- Package: `obsbot-mcp`
- Version: `0.6.3`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Supply Chain Security**: 87/100
  - No malware found by supply-chain analysis.
  - Only part of the dependency tree could be resolved (95 of 99), so this covers what we could see, not the whole tree.
  - No install/post-install scripts declared.
  - Only part of the dependency tree could be resolved (95 of 99), so this covers what we could see, not the whole tree.
- **Provenance & Transparency**: 97/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to lxman/obsbot-mcp).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 1 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 80/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 3666 tokens (~107/item across 34 items; 34 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 23/100
  - Stability observed for 7 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 67/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 0% of tool parameters carry a description.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add lxman-obsbot-mcp -- npx -y obsbot-mcp
```

### Codex

```bash
codex mcp add lxman-obsbot-mcp -- npx -y obsbot-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "lxman-obsbot-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "obsbot-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add lxman-obsbot-mcp --command npx --arg -y --arg obsbot-mcp
```

### Hermes

```yaml
mcp_servers:
  lxman-obsbot-mcp:
    command: "npx"
    args: ["-y", "obsbot-mcp"]
```

### Other

```json
{
  "mcpServers": {
    "lxman-obsbot-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "obsbot-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-03 (score 77, +57)

- [security improvement] Install scripts: unverified → pass
- [security improvement] Provenance: unverified → pass
- [security improvement] Known CVEs: unverified → partial
- [security] The attested source repository moved: lxman/obsbot-mcp
- [functional regression] Security disclosure: unverified → fail
- [functional improvement] Schema quality: unverified → excellent
- [functional improvement] Tool coverage: unverified → 100
- [functional improvement] License: unverified → pass
- [functional improvement] Dependency health: unverified → partial
- [functional improvement] Maintenance: unverified → pass
- [functional improvement] MCP protocol: unverified → pass
- [functional improvement] Stability: unverified → 0.23
- [functional] Licence: MIT

### 2026-08-02 (score 20, +3)

- [security improvement] Malware scan: unverified → pass
- [functional regression] Security disclosure: fail → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional] First check of Schema quality: unverified

### 2026-08-01 (score 17, 0)

- [functional] Package version: 0.6.2 → 0.6.3

### 2026-07-31 (score 17, −6)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-27 (score 23)

First indexed and scored.

## MCP tools (34)

### `obsbot_devices` (~130 tokens)

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.

### `obsbot_wake` (~65 tokens)

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.

Input parameters:

- `camera` (string)

### `obsbot_sleep` (~80 tokens)

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.

Input parameters:

- `camera` (string)

### `obsbot_gimbal_move` (~98 tokens)

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 [-150,150], pitch to [-90,90]. Absolute positioning (1:1 degrees), verified on hardware.

Input parameters:

- `camera` (string)
- `pitch` (number, required)
- `roll` (number)
- `yaw` (number, required)

### `obsbot_gimbal_recenter` (~75 tokens)

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.

Input parameters:

- `camera` (string)

### `obsbot_zoom_uvc` (~164 tokens)

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.

Input parameters:

- `camera` (string)
- `ratio` (number, required)

### `obsbot_ai_track` (~145 tokens)

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).

Input parameters:

- `camera` (string)
- `enabled` (boolean, required)
- `mode` (string)

### `obsbot_ai_track_speed` (~57 tokens)

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

Input parameters:

- `camera` (string)
- `speed` (string, required)

### `obsbot_zoom_vendor` (~103 tokens)

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.

Input parameters:

- `camera` (string)
- `ratio` (number, required)
- `speed` (number)

### `obsbot_focus_face` (~32 tokens)

Enable or disable face-priority autofocus.

Input parameters:

- `camera` (string)
- `enabled` (boolean, required)

### `obsbot_status` (~289 tokens)

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).

Input parameters:

- `camera` (string)

### `obsbot_image_fov` (~52 tokens)

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

Input parameters:

- `camera` (string)
- `fov` (string, required)

### `obsbot_image_hdr` (~33 tokens)

Toggle HDR/WDR imaging on or off.

Input parameters:

- `camera` (string)
- `enabled` (boolean, required)

### `obsbot_focus_auto` (~21 tokens)

Enable continuous autofocus.

Input parameters:

- `camera` (string)

### `obsbot_focus_manual` (~43 tokens)

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

Input parameters:

- `camera` (string)
- `position`

### `obsbot_gimbal_position` (~112 tokens)

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).

Input parameters:

- `camera` (string)

### `obsbot_aim_at_pixel` (~399 tokens)

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.

Input parameters:

- `camera` (string)
- `frameHeight` (required)
- `frameWidth` (required)
- `source` (string)
- `x` (required)
- `y` (required)

### `obsbot_zoom_to_fit` (~449 tokens)

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.

Input parameters:

- `camera` (string)
- `frameHeight` (required)
- `frameWidth` (required)
- `height` (required)
- `margin`
- `source` (string)
- `width` (required)
- `x` (required)
- `y` (required)

### `obsbot_preset_list` (~74 tokens)

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).

Input parameters:

- `camera` (string)

### `obsbot_preset_save` (~92 tokens)

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.

Input parameters:

- `camera` (string)
- `slot` (required)

### `obsbot_preset_recall` (~78 tokens)

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.

Input parameters:

- `camera` (string)
- `slot` (required)

### `obsbot_preset_update` (~79 tokens)

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.

Input parameters:

- `camera` (string)
- `slot` (required)

### `obsbot_preset_rename` (~73 tokens)

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.

Input parameters:

- `camera` (string)
- `name` (string, required)
- `slot` (required)

### `obsbot_preset_delete` (~50 tokens)

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

Input parameters:

- `camera` (string)
- `slot` (required)

### `obsbot_image_wb_auto` (~24 tokens)

Enable auto white balance.

Input parameters:

- `camera` (string)

### `obsbot_image_wb_manual` (~45 tokens)

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

Input parameters:

- `camera` (string)
- `temperature` (number)

### `obsbot_image_adjust` (~131 tokens)

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.

Input parameters:

- `camera` (string)
- `control` (string, required)
- `level` (required)

### `obsbot_image_exposure_auto` (~104 tokens)

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.

Input parameters:

- `camera` (string)
- `priority` (string)

### `obsbot_image_exposure_manual` (~124 tokens)

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.

Input parameters:

- `camera` (string)
- `level`

### `obsbot_capture_snapshot` (~156 tokens)

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.

Input parameters:

- `camera` (string)
- `quality`
- `resolution`
- `settleMs`
- `source` (string)

### `obsbot_capture_record` (~145 tokens)

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.

Input parameters:

- `audio` (boolean)
- `durationSec`
- `outputPath` (string)
- `source` (string)

### `obsbot_capture_preview` (~73 tokens)

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.

Input parameters:

- `source` (string)

### `obsbot_capture_stop` (~42 tokens)

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

Input parameters:

- `sessionId` (string, required)

### `obsbot_capture_list` (~29 tokens)

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

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/lxman-obsbot-mcp/obsbot-mcp#diagnostics

## Score history

- 2026-08-03: 77
- 2026-08-02: 20
- 2026-08-01: 17
- 2026-07-31: 17
- 2026-07-30: 23
- 2026-07-28: 23
- 2026-07-27: 23

## Links

- npm package: https://www.npmjs.com/package/obsbot-mcp
- Socket report: https://socket.dev/npm/package/obsbot-mcp
- Repository: https://github.com/lxman/obsbot-mcp
- Changelog RSS feed: https://verifymcp.io/servers/lxman-obsbot-mcp/obsbot-mcp/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/lxman-obsbot-mcp/obsbot-mcp/changelog.json
- HTML version of this page: https://verifymcp.io/servers/lxman-obsbot-mcp/obsbot-mcp
