Skip to content

Document a recommended setup - #3

Merged
impuls42 merged 2 commits into
mainfrom
claude/mcp-stale-frames-fix-5nujk9
Jul 30, 2026
Merged

impuls42 merged 2 commits into
mainfrom
claude/mcp-stale-frames-fix-5nujk9

Conversation

@impuls42

@impuls42 impuls42 commented Jul 30, 2026 •

Copy link
Copy Markdown
Owner

Adds a "Recommended setup" section to the README, between the quick starts and
"How frames stay fresh".

The quick starts show the shortest thing that works. Nothing said what to run if
you want it to keep working, so the non-obvious choices — the ones the live
hardware testing turned up — were only recorded in the frame-freshness deep dive
or in comments in docker-compose.yml, where nobody setting the thing up for the
first time will look.

Documentation only; no code, no behaviour change.

What it covers

A compose file to copy, then the reasoning for each choice that isn't obvious:

  • A Linux host with a real V4L2 device — not a preference. Docker Desktop on
    macOS and Windows cannot see a webcam and fails outright on the device mapping.
  • CAM2IP_LAZY=true — the counter-intuitive one, since it makes cold grabs
    ~450ms slower. It buys the device being released when nobody is subscribed:
    cam2ip cannot serve a stale frame at all, an unplug/replug self-heals in under
    a second instead of needing a container restart, and the indicator light goes
    off when nobody is looking.
  • A /dev/v4l/by-id/ device path — devices: resolves once at
    container-create, so after a replug the container points at a minor number the
    kernel has moved on from. The by-id symlink follows the device, so a plain
    restart finds it. The /dev bind-mount alternative is mentioned with its cost
    (every device on the machine) rather than recommended.
  • Streamable HTTP when unattended, stdio when a desktop client launches it —
    warm stream (~10ms vs ~700ms grabs), and the restart policy and healthcheck
    only mean something in HTTP mode.
  • Publishing on 127.0.0.1 from the host side, while leaving the in-container
    bind at its 0.0.0.0 default
    — the two are separate questions and only the
    first is the security boundary. Docker forwards a published port to the
    container's bridge address, not its loopback, so binding the server to
    127.0.0.1 inside the container makes it unreachable from the host — quietly,
    because the healthcheck runs inside the container against loopback, passes,
    and leaves docker ps reporting healthy. The section also notes the
    consequence: a server not bound to loopback gets no automatic Host allowlist
    from mcp, so MCP_ALLOWED_HOSTS is the knob once the publish widens past
    loopback — and it becomes required outright the moment MCP_ALLOWED_ORIGINS
    is set, since origins alone leaves an empty host allowlist that rejects
    everything.
  • Ports through .env so both sides move together, and a pinned sha-
    tag
    rather than latest.

Then two subsections:

  • Leave the frame settings alone — the defaults drop 7 frames per reconnect
    against a queue that holds 4. Lists the only three reasons to touch them, and
    notes uvcvideo was measured granting exactly the buffer count requested at
    every value from 1 to 32, so raising MCP_WARMUP_FRAMES buys nothing there.
  • Monitoring — watch camera_status → state, not the container
    healthcheck. The healthcheck deliberately never touches the camera, because
    probing a frame would dequeue one and wake the device every interval, defeating
    CAM2IP_LAZY. An unplugged camera therefore leaves the container healthy and
    both of camera_status's connection booleans true; state is what reads
    connected_but_no_frames.

Verification

  • CI green on the first revision: test, image-smoke and build-and-push all
    passed. The follow-up revision touches only README.md.
  • The recommended YAML snippet was extracted and run through
    docker compose config, which confirms it resolves to host_ip: 127.0.0.1 on
    both publishes and to an environment: block that sets no bind address
  • docker compose config on the repo's own docker-compose.yml still parses
  • The #on-macos cross-reference resolves, the /dev bind-mount notes referred
    to do exist in docker-compose.yml, and every env var named
    (CAM2IP_LAZY, MCP_WARMUP_S, MCP_WARMUP_FRAMES, MCP_STREAM_IDLE_S,
    MCP_ALLOWED_HOSTS, MCP_ALLOWED_ORIGINS) is one the code actually reads

claude added 2 commits July 30, 2026 17:05
The quick starts show the shortest thing that works, which is not the same as
what to run if you want it to keep working. Three of the choices that matter are
counter-intuitive enough that a new user would get them wrong, and all three came
out of hardware testing rather than taste:

* CAM2IP_LAZY=true, despite making cold grabs ~450ms slower. It stops cam2ip
  being able to serve a stale frame at all, self-heals an unplug in under a
  second where holding the device open needs a container restart, and turns the
  indicator light off when nothing is watching.

* A /dev/v4l/by-id/ device path rather than /dev/video0. A devices: mapping is
  resolved once at container-create, and a replugged camera may come back on a
  different minor -- so the default needs a compose edit where a by-id path needs
  only a restart.

* Watching camera_status.state rather than the container healthcheck. The
  healthcheck deliberately never touches the camera, so an unplugged camera
  leaves the container healthy and both connection booleans true. Only the state
  field says connected_but_no_frames.

Also states the three -- and only three -- reasons to touch the frame settings,
since the defaults were measured with three frames of margin on every platform
tested and are otherwise best left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The compose snippet set MCP_HTTP_HOST=127.0.0.1, which binds the server to
loopback inside the container. Docker forwards a published port to the
container's bridge address, not its loopback, so nothing on the host could
reach the MCP endpoint.

It fails quietly: the healthcheck runs inside the container and connects to
loopback, which is exactly where the server is listening, so it passes and
`docker ps` keeps reporting healthy. That is the same healthy-but-unreachable
state the section's own "Ports" paragraph warns about, reached by a different
route.

Drop the variable so the image's 0.0.0.0 default applies, and rewrite the
paragraph around what the actual boundary is: the 127.0.0.1: prefix on the
host side of the ports mapping. Spell out the consequence too -- a server not
bound to loopback gets no automatic Host allowlist from mcp, so
MCP_ALLOWED_HOSTS is the knob once the publish widens past loopback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@impuls42
impuls42 marked this pull request as ready for review July 30, 2026 17:19
@impuls42
impuls42 merged commit 1caa741 into main Jul 30, 2026
3 checks passed
@impuls42
impuls42 deleted the claude/mcp-stale-frames-fix-5nujk9 branch July 30, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants