Anyone else running Claude Code with an MCP server?

whoami ™

Pulling my weight
Aug 4, 2019
245
240
South Florida
Posting this in the AI subforum on purpose, since the audience here is more likely to actually have opinions on agent tooling than the general Blue Iris room.

Curious how many folks here are using Claude Code (or any LLM agent) with MCP servers. I've been building one over the last few weeks and it's changing how I troubleshoot BI completely. Wanted to see if there's interest in the project going public, and try to gauge if anyone might want to contribute. The reason I'm asking before pushing it out is that standing up a real OSS project (CI on GitHub Actions, a release pipeline, contributor docs, a test matrix across BI versions, issue triage) is a meaningful chunk of work on top of the code itself. If it's just me using it, none of that overhead is worth doing. If there are a few others who'd actually use it, and ideally a couple who'd contribute, it pays for itself.

What it is

bi-mcp is a stdio MCP server that wraps Blue Iris's HTTP/JSON API, the undocumented camconfig cmd, and a parser for BI's .reg camera exports (for the stuff the API doesn't expose: trigger zone polygons, per-class AI thresholds, alert action chains, ONVIF event handlers, etc.).

Built and tested against BI 5.9.9.71 (x64) on Windows 10. Currently 21 tools, 15 read-only by default, plus 6 mutating tools that only register when an env flag is set. It wraps 18 of the 23 documented JSON cmds. The remaining 5 are either niche or destructive enough that I haven't decided whether to expose them.

What you can do with it

A real session from today: I asked it to "find issues on my install." Over about 15 minutes of back-and-forth, here's what came out of it.

It noticed my PTZ camera had "AI: not responding" warnings in the log, pulled the CPAI-side config, cross-referenced to a fix I'd applied earlier that day, did the UTC to EDT timezone math to confirm the warnings had stopped after the fix, and flagged a verification window to re-check in the morning.

The bigger one was my busiest spotter. Road-facing cam with about 5900 alerts in the queue, and three PTZ-handoff action rows that hadn't fired in two days. It pulled the BI log filtered for "ONVIF Event:" on that camera and showed me the IVS tripwires WERE firing on the camera side, roughly 160 events in 48 hours, but BI wasn't translating any of them into PTZ preset calls. Then it walked the alert lifecycle and found every ONVIF event was landing as a "Retriggered: ONVIF,Motion_A" on top of an in-flight motion alert, never as a fresh trigger. From there it identified the "Run on retrigger: New zones/sources (exclusive)" dropdown as the culprit. The "exclusive" qualifier requires the source set to replace, not add, and since Motion_A stayed present from the original trigger, the rows never re-evaluated. One dropdown change, three months of broken PTZ handoffs explained.

It also surfaced cross-camera action-set inconsistencies via a .reg walker that buckets rows by type, description, and filter shape, then flags fields where one camera diverges from the cohort majority. Two were known intentional and already in its memory of my install. One was a real misconfig.

For mutations (PTZ presets, profile switches, manual record toggles, clip exports, alert memo updates) every write is verified after the fact. Read before, write, read after, report whether the change actually landed. It won't claim success on writes that BI silently dropped.

It's not infallible. Twice in that same session it misread a config field and I had to correct it. But the workflow of "explain what you see, here's what I think, push back if I'm wrong" turns into a real investigative pair-debugging loop, not a black box.

The other nice part is the agent builds up a memory of your install over time by writing markdown files. My camera roster, which spotters call which PTZ presets, which ONVIF source tokens have to be hand-typed because the BI UI leaves them blank, all of it gets remembered between sessions, so it doesn't re-ask the same questions every time.

What's left before I push it to GitHub

A couple of useful JSON cmds I haven't wrapped yet (camset for preset image management and grid layout, and the alert-counter reset path). The parser exception taxonomy could be tighter, so .reg parse failures report distinct error kinds instead of flattening to one. Install docs need to actually exist; the README currently assumes you already know what MCP is. And I haven't picked a publishing pipeline. Right now it's just a local Python package; I haven't decided between PyPI, a tagged GitHub release, or "clone and pip install -e ."

The question

Anyone else here doing this? If so, what are you using and what's working or not working for you? And if there's interest in this one going open source, I'd love contributors. There's plenty of room to wrap more of the API, add tests against a wider range of BI versions, and build out the agent-facing operating manual.
 
Update: I went ahead and pushed it. Repo is at https://github.com/whoamiTM/bi-mcp

Important caveat up front: this is built and tested against Blue Iris 5.9.9.71 only. v6 is out but I'm not planning to update my own install until the end of the year, so I won't be testing against it until then. Some of the tools call undocumented BI endpoints whose response shapes may have changed in 6.x. There's a note in the README saying the same thing. If anyone on v6 wants to try it and report back what works or breaks, that'd be useful, but go in expecting rough edges.

Since the original post, the tool count grew. Currently 23 tools total. 17 read-only by default, plus 6 mutating ones that only register when you set BI_MCP_ALLOW_MUTATIONS=1. That covers nearly all of the documented JSON cmds plus a few that aren't documented (camconfig's set-half, the .reg parser for trigger zones and AI thresholds and alert actions, and a cross-camera audit tool that surfaces action-set outliers between cameras).

The README is now written for someone who's never set up an MCP server before. It walks through prereqs, install, creating a dedicated low-privilege BI user, and getting it registered with Claude Code or Claude Desktop.

On the publishing pipeline question I flagged in the original post: for now it installs straight from the GitHub source via "pipx install git+https://github.com/whoamiTM/bi-mcp". PyPI release is coming next, which will make the install one command without the git+ prefix.

Open to contributors. The wishlist includes wrapping a few more JSON cmds I haven't gotten to yet (camset for preset image management, the alert-counter reset path), tightening the .reg parser exception taxonomy, and broadening the BI version test matrix once I'm on v6 myself. AGENTS.md in the repo is the canonical operating manual for an LLM working through the server. Worth reading even if you're just curious about how the tools fit together.

Issues and PRs welcome.
 
Last edited:
Quick update for anyone watching this thread: bi-mcp 0.2.0 is now on PyPI. You can install it with pipx install bi-mcp (or uv tool install bi-mcp) instead of the git+https://... form from the first post. Same code, same tools, just easier to grab.

Nothing has changed about how it runs or what it can do. The 0.2.0 number is mostly a marker for "this is the first version that exists outside GitHub." If you already installed from the git source you don't need to do anything; the next time you want to update, you can switch over to the PyPI name or keep using pipx install --force git+https://github.com/whoamiTM/bi-mcp, your call.

Project page: https://pypi.org/project/bi-mcp/
Source as before: https://github.com/whoamiTM/bi-mcp
 
  • Like
Reactions: mat200
I wanted to give Claude spatial awareness of my property so it could reason about which cameras should fire together when something moves through the yard. This works for anyone running the bi-mcp server because Claude can pull each camera's config, motion sensitivity, AI zones and alert history through the MCP tools. The map turns abstract camera names into a coordinate system Claude can reason about.

The approach uses a grid drawn over a Google Maps satellite of the property. For each camera you paint blue dots in the cells it can see and a yellow dot in the cell where the camera is mounted. A small python script reads the dots and gives Claude a list of cells per camera. Claude then builds an overlap index showing which cells multiple cameras share. Those shared cells are where handoff predictions come from. If two cameras cover the same patch of yard then anything moving through that patch should trigger both within seconds.

Here is the test I ran. At 14:45:54 today SecCam_3 fired with memo car 85 percent and person 71 percent on zone 1. Five seconds later at 14:45:59 SecCam_1 fired with car 85 percent on zone 1. At the same instant the PTZ swung to preset 3 and SecCam_11AI registered person 75 percent. Then at 14:46:03 SecCam_1 fired a second time as the car continued through its coverage. SecCam_4 stayed silent the whole time and so did the doorbell on SecCam_2.

Before I looked at the actual log I asked Claude to predict the chain from the spatial map alone. It correctly called SC1 firing within 3 to 8 seconds (actual was 5), called the PTZ callout to preset 3, predicted SC4 would stay silent because the direction was northbound and SC4 watches south, and predicted SC2 would stay silent because no one approached the front door. Every prediction matched. The only thing it did not predict was the second SC1 alert four seconds after the first which is just the car continuing through the same coverage rather than a separate event.

The plan below is what we wrote so anyone can build the same map for their own install. Copy paste it into Claude Code and it will walk you through. There is also a txt attachment for easier download. You will need to tell Claude your camera names and file paths but the steps are the same.

Code:
# Camera Spatial Map — Reusable Playbook

A method for giving a Claude Code agent shared spatial vocabulary about
your security camera installation, so it can reason about alert chains,
overlap, and missed events instead of just camera names.

**Output:** a memory file mapping each camera (and PTZ preset) to a list
of grid cells covering the property, plus an overlap index and handoff
chain table. The agent reads this on every alert-chain investigation.

**Time required:** 1–2 hours for ~8 cameras. Most of it is painting dots.

**Cost:** zero — no drawing skill, no geometry, no measurements.

---

## Background — why this works

The agent cannot see your property the way you do. Saying
"SecCam_3 looks across the street" gives the agent nothing it can use
when investigating an alert. The agent needs a **labeling system** that
both of you can refer to identically.

A grid overlay on a satellite image is that labeling system. Cells are
discrete tokens (G3, H17, etc.) — exactly what an LLM is good at
reasoning over. The grid does not have to be precise. Two cameras
claiming the same cell means "coverage overlap." Small boundary errors
don't matter because the system is for diagnostic *prediction*, not
geometric measurement.

The agent can then answer:
- "Camera A fired. Which neighbors should also have fired?"
- "Why didn't camera B catch this when camera A did?"
- "What part of the property has no coverage?"

---

## Prerequisites

- A Claude Code session in your project directory
- An overhead satellite image of your property (Google Maps satellite,
  zoomed so the whole monitored area + some buffer is visible)
- Some way to view each camera's current frame (NVR web UI, mobile app,
  whatever you use). You'll snapshot or describe what each camera sees.
- An image editor that lets you paint dots on a PNG. **MS Paint
  works fine** (used for the reference build). Also: Paint.NET, GIMP,
  Preview (macOS), Krita, Photos. Anything with a colored brush tool
  is enough — you do not need layers, transparency, or precision.
- Python 3 with Pillow (`pip install pillow`). Used by two small
  scripts the agent will write.

Optional but helpful:
- An MCP server (or equivalent) that lets the agent read your NVR's
  config so it can auto-populate motion sensitivity, AI zones,
  breaktime, etc., per camera. Without this, the agent can still build
  the spatial map; it just can't include the live-state fields.

---

## Step 1 — Capture and crop the satellite

You do this once.

1. Open Google Maps in satellite view. Center on your property.
2. Zoom so the whole monitored area plus ~50 ft of surrounding street
   is visible.
3. Crop to a square (any size; ~1500–2000 px is plenty). Square cells
   are easier to reason about than rectangular ones.
4. Save the image to your project directory. Example name:
   `overhead-raw.png`.

Tell the agent the file is ready.

---

## Step 2 — Have the agent draw the grid

Give the agent this prompt (adjust paths):

> Write a Python script using Pillow that takes `overhead-raw.png` and
> overlays a 36×36 grid with edge labels only. Columns are A–Z then
> AA–AJ (left-to-right). Rows are 0–35 (top-to-bottom). Labels go on
> all four edges (top + bottom for columns, left + right for rows) so
> I can read coordinates from any side. Yellow lines, ~16pt bold yellow
> labels on a black background pill. Save as `overhead-grid.png`.

The agent will produce the script and run it. Open the output and check
it's readable — labels at the edges, no labels inside cells, grid lines
clearly visible.

**If the image is too dense or unreadable, ask for one of:**
- Bigger cells (e.g. 24×24 instead of 36×36)
- Larger labels
- Different line color

The grid resolution doesn't matter for accuracy — pick whatever you can
read clearly.

---

## Step 3 — Have the agent make a template copy per camera

> Make a copy of `overhead-grid.png` for each enabled outdoor camera I
> want on the map. Name them `overhead-grid_cam_<NAME>.png` (one per
> camera). Skip indoor cameras and disabled ones.

The agent will `cp` the file once per camera.

---

## Step 4 — Paint dots for each camera

You do this part. For each camera:

1. Open its template PNG in any image editor.
2. Look at the camera's live view in your NVR.
3. Identify the structures visible in frame (driveway, sidewalk, the
   neighbor's house, the street, a fence, etc.). Find those same
   structures on the overhead grid.
4. With a paint/brush tool set to a **saturated bright blue** (not
   navy, not sky-blue — think pure blue like #0000FF), drop one dot in
   the **center of every grid cell** the camera can see.
5. **Drop one yellow dot in the cell where the camera is physically
   mounted.** The mount cell often overlaps with a covered cell (a
   front-yard cam mounted on the front of the house can usually see
   the cell it sits in) — that's fine, paint *both* a yellow dot and
   a blue dot in that cell. The yellow marks the mount; the blue marks
   "this cell is covered." Use a saturated yellow like #FFFF00 — not
   the muted yellow of the grid lines themselves.
6. Save the file in place (overwrite the template).
7. Tell the agent that camera is ready.

**Tips for painting:**
- Don't worry about cells the camera *partially* sees. Dot it or not —
  the system tolerates small errors. A common rule: if more than ~half
  the cell is in frame, dot it.
- Don't try to be "geometric." Camera FOVs are not perfect cones; just
  dot every cell where you'd say "yes, I can see that part of the
  property in this feed."
- For PTZ presets, paint each preset on its own template — they're
  separate from the PTZ camera's general coverage. PTZ presets do not
  get a yellow mount dot — the PTZ camera's mount is a property of the
  camera, not the preset.
- Saturated blue + saturated yellow are important for the detector.
  Pale or muted colors won't match. If in doubt, use the brightest
  versions your editor has.
- **In MS Paint specifically:** pick the brush tool (not the pencil),
  use the default blue and default yellow from the color palette
  (both saturated enough for the detector), and click once per cell —
  no need to drag. Brush size ~10–15 px works well at the 50px cell
  scale. The yellow mount dot can be the same brush size as the blue
  dots; the detector tells them apart by color, not size.

---

## Step 5 — Have the agent extract cells from the painted PNGs

Give the agent this prompt:

> Write a Python script `extract_camera_cells.py` that takes a painted
> grid PNG as input, finds saturated blue pixels AND saturated yellow
> pixels, snaps each cluster to its grid cell (36×36, columns A–Z then
> AA–AJ, rows 0–35), and prints two outputs:
>
> 1. The sorted list of **covered cells** (cells with blue dots) — the
>    camera's FOV in grid coordinates
> 2. The **mount cell** (the single cell with a yellow dot) — where
>    the camera is physically located
>
> Also write a verification PNG that outlines every detected covered
> cell in red and the mount cell in green, so I can confirm the
> detection is correct.
>
> **Color thresholds:**
> - Blue (coverage): `b >= 180 and b > r + 80 and b > g + 60 and
>   r < 100 and g < 140`
> - Yellow (mount): `r >= 200 and g >= 200 and b < 100 and
>   abs(r - g) < 40`
>
> **Density thresholds (critical):**
> - Blue: minimum 30 pixels per cell (tunable via `--min-blue-pixels`)
> - Yellow: minimum 80 pixels per cell (tunable via
>   `--min-yellow-pixels`). The grid lines themselves are yellow but
>   thin (~1px), so a 1-cell border contributes ~200 yellow pixels
>   spread along the edges. A deliberate yellow dot adds a dense
>   cluster in the cell center. Set the threshold to require pixels
>   clustered in the **center 60% of the cell** to reject the border
>   contribution — or subtract the expected per-cell border pixel
>   count before applying the threshold. The agent should test both
>   approaches and pick whichever gives clean detection.
> - Tight thresholds avoid false positives (blue cars, water, shadows,
>   yellow grid lines, sunlit pavement).

When a camera's PNG is ready, the agent runs:

```
python3 extract_camera_cells.py overhead-grid_cam_<NAME>.png \
    --out-png /tmp/<NAME>_verify.png \
    --out-json /tmp/<NAME>_cells.json
```

The agent reads the verify PNG to confirm:
- Every red box contains a real blue dot (no false-positive coverage)
- Exactly one green box at the mount cell (no false-positive mount,
  no missed mount)

If the detector is missing dots or grabbing non-dot pixels, tune the
relevant `--min-*-pixels` threshold and re-run.

---

## Step 6 — Confirm mount cells (or correct them)

The extractor in Step 5 already pulled each camera's mount cell from
the yellow dot. Have the agent read back the list to you so you can
catch any extraction errors. For cameras the extractor missed (e.g.
indoor cameras with no overhead mount, or mount painted at the wrong
spot), tell the agent the correct cell or "skip — interior" and the
agent will update its working notes.

---

## Step 7 — Supply daylight snapshots (optional but recommended)

For each camera, capture one daylight snapshot of its current view.
Save them in the project directory with names that identify the camera
(e.g. `cam_<NAME>_daylight.jpg`).

The agent will read each snapshot and extract **in-frame landmarks** —
the stable structures visible in the feed (your house, neighbor houses,
fences, the mailbox post, etc.). These descriptions help the agent
match cell-coverage claims to what cameras actually look at.

**Tell the agent explicitly:**

> Snapshots show transient objects (cars in driveway, trash bins out
> for pickup) as well as stable structures (house, fences, driveways).
> Only anchor your camera descriptions to stable structures. Don't
> bake transient objects into the camera's identity.

For PTZ presets, your NVR may store preset preview JPGs that the agent
can read directly — saves a manual snapshot pass. (In Blue Iris, these
live in the install directory's `presets/` folder per camera.)

---

## Step 8 — Have the agent pull live camera config

If your NVR has an MCP server or API the agent can query, ask:

> For each camera, pull its current motion sensitivity, AI zone
> bitmask, contrast, breaktime (motion debounce window), and any
> profile-sync state. Use these to populate per-camera config in the
> spatial map.

This makes the map a snapshot of real current state, not assumptions.
Without an MCP server, you can hand-dictate these values from the NVR
UI or skip this step.

---

## Step 9 — Have the agent write the spatial-map memory file

Give the agent this prompt (adjust the memory location):

> Assemble a spatial map memory file at the project's auto-memory
> location. Structure:
>
> 1. **Coordinate system** — describe the grid, total cells, mount
>    cluster summary
> 2. **Per-camera section** — for each camera: mount cell, cell list,
>    in-frame landmarks from the snapshot, motion config from live
>    pull, any special notes (hardware quirks, intentional outliers)
> 3. **PTZ preset table** — preset number, name, which spotter (if
>    any) it mirrors, cell list, in-frame landmarks
> 4. **Cell → cameras inverted index** — for cells where multiple
>    cameras have coverage, list the overlapping cameras. This is
>    what makes handoff prediction work.
> 5. **Expected handoff chains** — common transit directions across
>    the property (street N→S, driveway approach, side-yard cut-
>    through) with the camera sequence and expected timing gaps
> 6. **Missed-event diagnostic rules** — when a neighbor *should*
>    have caught an event but didn't, what to check in order
> 7. **Transient-vs-stable note** — which fields in the snapshots
>    are reliable for identification vs. which are noise
> 8. **What the map does NOT do** — explicit out-of-scope statement
>
> Add a one-line entry to the memory index file pointing to this
> file.

The agent writes the file from the cell lists, snapshots, and config
data already collected.

---

## Step 10 — Correct the agent's draft

Read the file the agent wrote. Look for:

- **Wrong mount cells** — if the agent guessed any (it shouldn't have
  if you did Step 6), correct them
- **Wrong in-frame landmarks** — the agent's image reading is decent
  but not perfect. It may swap left/right, miss a landmark, or invent
  one
- **Wrong handoff chains** — the agent will infer chains from the cell
  overlap data; if your property has a path the cell overlap doesn't
  capture, add it
- **Made-up geometry narration** — the agent has a habit of saying
  things like "aimed northwest" based on cell-pattern shape. If you
  didn't tell it the aim direction, that's speculation; ask it to
  remove geometry claims it can't back up.

Have the agent edit the file in place with your corrections.

---

## Step 11 — Validate against a real alert

This is the proof the map works. Give the agent this prompt:

> Pull the most recent person alert chain from the NVR (any cluster
> of alerts where multiple cameras fired within ~30 seconds of each
> other). Using only the spatial map you just wrote, predict:
>
> 1. Which cameras *should* have fired given the apparent direction
>    of motion
> 2. Which cameras *should* have stayed silent
> 3. The expected timing gap between the first and second alert
> 4. Which PTZ preset (if any) should have been called
>
> Then compare to what actually happened in the NVR logs. Score the
> map's predictions: which were correct, which were wrong, and what
> would have to change in the map to fix the wrong ones.

If the map's predictions match reality, you're done. If they don't,
the misses tell you what to refine — usually missing overlap entries
or wrong handoff timing.

Record the validation result in the memory file so future agents see
the map is trustworthy.

---

## What this method does NOT cover

- **Night/IR coverage.** Camera FOVs change at night (IR range is
  shorter than daylight optical range). A v2 pass would re-snapshot
  after dark and add a `night_visible` cell list per camera.
- **AI zone-to-cell mapping.** Most AI-enabled NVRs divide each
  camera's frame into 8 fixed zones (A–H or 1–8). Mapping each AI
  zone to specific grid cells would require per-camera UI inspection
  and is a separate exercise.
- **Mask polygons.** If you've drawn motion-detection masks on each
  camera, the cells the map claims are covered may be partially
  excluded by the mask. A stretch goal is to decode the mask polygons
  and render them as cell overlays.
- **Indoor cameras.** No overhead position; exclude them from the
  spatial map.

---

## Failure modes and tuning

**The detector grabs blue cars / blue siding / shaded pavement.**
Tighten the blue threshold or raise `--min-pixels`. If a specific
camera's image has problematic non-dot blues, take it into your editor
and crop those areas before extraction.

**The detector misses dots.** Your paint color isn't saturated enough,
or the dots are too small. Use a brighter blue, paint larger dots, or
lower `--min-pixels`.

**The agent's narrative descriptions are wrong.** Image reading is
its weakest skill. Stick to cell lists + landmarks; reject any
"aimed at X" or "facing northeast" sentences the agent invents.

**The grid is too coarse / too fine.** Coarse grids miss small camera
FOVs (the agent says "this camera covers nothing" because all its
dots fit in one cell). Fine grids produce huge cell lists and labels
that overflow the image. Pick a size where your most narrow-FOV
camera still covers 3+ cells.

**Two cameras share a mount cell.** Fine. Cell coordinates are about
*location*, not aim direction. Two cameras on the same fixture
pointing different ways will have different cell lists and the same
mount.

---

## Adapting this to your NVR

This was developed against Blue Iris + Dahua PTZ + Home Assistant.
The method generalizes to any NVR. The only pieces that change:

- **Live config pull (Step 8)** — replace `bi_get_camera_config`
  with whatever your NVR's API or MCP exposes. If no API, dictate
  from the UI.
- **Preset preview JPGs (Step 7)** — Blue Iris stores these in the
  install folder. Other NVRs may store them elsewhere or not at all.
  Live PTZ-preset snapshots via mutation are an alternative.
- **Alert validation (Step 11)** — replace the alert-listing tool
  with your NVR's equivalent. The validation logic itself is generic.

The grid, painting, extraction, and memory-file structure work the
same regardless of NVR brand.

---

## What you end up with

A memory file the agent loads automatically on every alert
investigation. Instead of:

> SC3 fired with zone bit 1 at 14:45:54.

The agent now thinks:

> SC3 fired at 14:45:54. SC3 covers cells L12–Q15 + K17–K21. Zone
> 1 is the AI's north zone. Looking at the overlap index, SC1
> shares cells P14, Q14, O13–Q13 with SC3. SC4 shares K17–Q17.
> If motion is northbound, expect SC1 to fire within 3–8s. If
> southbound, SC4. Let me check the next alert in the chain to
> confirm direction… SC1 fired at 14:45:59 (5s gap, exact match
> for northbound). The chain validates as a northbound transit.

That's the difference.
 
Last edited:
  • Like
Reactions: mat200