IP cameras (ONVIF/RTSP)

io.github.aidev-iprokszinPublished by GitHub account aidev-iprokszinv0.4.1Updated Nov 7, 2026

MCP server for local IP cameras (ONVIF/RTSP): snapshots, recordings, pan/tilt, presets

VerifiedSTDIODesktop onlyLocation & LifestyleMedia & Design

Overview

AI-generated overview

Lets an assistant view and control local ONVIF/RTSP IP cameras: snapshots, crops, short recordings, pan/tilt presets and change detection.

What you can do
  • List configured cameras and scenes, with capabilities and capture history
  • Take snapshots, crop regions at full resolution, and record short mp4 clips
  • Save named baselines and compare a view against them or the previous snapshot
  • Pan and tilt the camera and list, save, go to or delete presets
  • Capture and compare every camera in a scene, and browse a scene timeline by date
  • Add, rename, move, remove or delete cameras and reload the config
When to use it
Useful when you want an assistant to check what a local IP camera sees, compare a view against a saved reference, record a short clip, or move the camera, without a cloud service. Suited to home or office cameras on the same network as the machine running the server.
Requirements
Runs locally on Linux or WSL2 on the same network as the camera, with uv on PATH and ffmpeg installed. Needs an ONVIF-capable camera with ONVIF and RTSP enabled and its local camera account (username and password), not a cloud login. Config path is set by IPCAM_MCP_CONFIG, defaulting to a cameras.toml under the user config directory; secrets live in separate files.
Before you install
Camera credentials are stored in secrets files that should be readable only by you; the form route shows the password while typing. add_camera logs into whatever host it is given, so verify the IP before entering a password. Management tools can rename, remove or delete cameras and their media, and preset deletion cannot be undone; media files are never deleted automatically. The optional HTTP transport has no authentication, so keep it on localhost.

Installation

In SourceWeft

  1. Open IP cameras (ONVIF/RTSP) 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

ipcam-mcp

[PyPI]

An MCP server that lets Claude Code, or another MCP client, see through and control the IP cameras on your local network (ONVIF + RTSP): snapshots, video clips, pan/tilt and change detection, all handled locally.

Any ONVIF camera should work. See known cameras for what has been tested.

What you can ask

Once a camera is added, ask in plain words, for example:

  • "Take a snapshot of the front door."
  • "Save this view as door-closed." Later: "Does the front door still look like door-closed?"
  • "Did anything change at the front door since the last snapshot?"
  • "Pan the office camera a bit to the left."
  • "Record 10 seconds from the garage."
  • "Show me what the garage cameras captured yesterday."

Prerequisites

  • Linux or WSL2, on the same network as the camera
  • uv on your PATH (it installs Python 3.13+ and the dependencies for you)
  • ffmpeg: sudo apt install ffmpeg
  • An ONVIF-capable IP camera with a local camera account (username and password), with ONVIF and RTSP enabled
  • Claude Code or another MCP client that supports stdio servers

Install

Pick one. All of them run the ipcam-mcp package from PyPI and need ffmpeg and, except pipx, uv (see Prerequisites).

  • Claude Code plugin (recommended for Claude Code): /plugin install ipcam --marketplace aidev-iprokszin/ipcam-mcp

    On Claude Code older than 2.1.275, run /plugin marketplace add aidev-iprokszin/ipcam-mcp, then /plugin install ipcam@ipcam-mcp. The plugin runs the server from a lockfile (every dependency pinned with hashes). Update it with claude plugin update ipcam@ipcam-mcp, which also updates the server; a new server version reaches the plugin shortly after its PyPI release.

  • Claude Code, without the plugin: claude mcp add ipcam -s user -- uvx ipcam-mcp

  • Other MCP clients (Cursor, VS Code, Codex CLI, …): configure the stdio command uvx ipcam-mcp. See Other MCP clients.

  • A permanent ipcam-mcp command: uv tool install ipcam-mcp (or pipx install ipcam-mcp), then configure your client to run ipcam-mcp. Update with uv tool upgrade ipcam-mcp (or pipx upgrade ipcam-mcp).

Then add your first camera.

Add your first camera

  1. Camera account: create or find the camera's local ONVIF/RTSP account (username and password). This is not your cloud login. See known cameras for where your brand hides it.
  2. IP address: look it up in the camera's app or your router. Network discovery is not supported (WSL2 NAT blocks it).
  3. Ask Claude to add it, for example "add my camera at 192.0.2.10 as front-door". Claude calls add_camera, which asks for the username and password in a login form, so they never pass through the model. Before you type the password, check that the IP in the form is your camera's. Without a config file the server starts with no cameras; add_camera creates cameras.toml and the camera's secrets file. See Managing cameras for the other management tools and for clients that cannot show forms.

Or configure it by hand

  1. Secrets file (outside any repo), e.g. ~/.config/ipcam-mcp/secrets.env:
    CAMERA_IP=192.168.x.yCAMERA_USER=...CAMERA_PASSWORD=...
    Make it readable only by you: chmod 600 <file>. The server warns at startup if other users can read it.
  2. Camera config: copy cameras.example.toml to ~/.config/ipcam-mcp/cameras.toml (or point IPCAM_MCP_CONFIG at another path) and adjust the camera name / variable names. Only variable names go in this file, never the secrets themselves. Use type = "onvif".

After editing cameras.toml by hand while the server runs, ask Claude to reload the cameras (reload_cameras).

Camera settings

All optional, set in the camera's section of cameras.toml. A wrong type is reported when the config loads.

SettingTypeDefault
onvif_portinteger 1–6553580
rtsp_portinteger 1–65535the port in the camera's stream URI, else 554 (a value you set always wins)
invert_panbooleanfalse
ptzbooleanfalse
stream_highpath starting with /looked up over ONVIF
stream_lowpath starting with /looked up over ONVIF

ptz = false means the camera has no pan/tilt tools. Set stream_high/stream_low if the ONVIF lookup fails or picks the wrong stream. Set invert_pan = true if ptz_move left turns the view to the right. Some brands need other values; see known cameras.

Other MCP clients

Any client that supports stdio servers (Cursor, VS Code, Codex CLI, …) can run the same command: uvx ipcam-mcp, or uv --directory /absolute/path/to/ipcam-mcp run ipcam-mcp for a clone.

Claude Desktop on Windows: the server runs inside WSL2, so install uv and ffmpeg there (see Prerequisites). In Claude Desktop, open Settings → Developer → Edit Config, add the ipcam entry below to mcpServers and keep the rest of the file:

json
{  "mcpServers": {    "ipcam": {      "command": "wsl.exe",      "args": ["-e", "/home/<you>/.local/bin/uvx", "ipcam-mcp"]    }  }}

Replace <you> with your WSL user name (which uvx in WSL shows the path). With several WSL distributions, add "-d", "<distro>" before "-e". Then quit Claude Desktop from the tray icon (closing the window is not enough) and start it again. The server uses the same cameras.toml and media folder inside WSL as Claude Code there. Use Edit Config rather than a fixed path: the Microsoft Store version keeps the file under %LOCALAPPDATA%, not %APPDATA%\Claude. For a clone, use "args": ["-e", "/home/<you>/.local/bin/uv", "--directory", "/home/<you>/path/to/ipcam-mcp", "run", "ipcam-mcp"].

WSL2 networking

If the camera can't be reached from WSL (nc -zv <ip> 554 fails), enable mirrored networking in %UserProfile%\.wslconfig:

[wsl2]networkingMode=mirrored

then run wsl --shutdown from Windows.

Tools

ToolWhat it does
list_cameras(include_removed=False)Configured cameras with their uid, scene and capabilities (camera, ptz); include_removed=true also lists removed cameras whose files are still on disk
camera_snapshot(camera=None, quality="high", crop=None, region=None, from_path=None)Returns the picture (≤1280 px wide) and saves the full-size JPEG. crop=[x, y, w, h] (fractions 0–1) or region (top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right) returns a detail at full resolution. from_path reuses an earlier snapshot (for example one from scene_timeline) without contacting a camera; camera is then optional
camera_save_baseline(camera, name, quality="high")Takes a snapshot and keeps it as a named reference (e.g. door-closed)
camera_compare(camera, baseline=None, quality="high")Takes a snapshot and reports what changed against the camera's previous snapshot in its current scene, or against a named baseline saved in that scene
camera_record(camera, seconds=10, quality="high")Records 1–60 s to an mp4, returns its path
ptz_move(camera, direction, amount="small")Pan/tilt left/right/up/down by small/medium/large; waits for the move to finish and returns the final position if the camera reports one
ptz_list_presets(camera)Saved positions
ptz_goto_preset(camera, preset)Move to a preset by name or token
ptz_save_preset(camera, name, overwrite=False)Saves the current position as a preset; refuses an existing name unless overwrite=true
ptz_remove_preset(camera, preset)Deletes a preset by name or token (cannot be undone)
list_scenes()Every scene with its cameras, removed cameras that watched it, the first and last capture and counts
scene_snapshot(scene, quality="high")A snapshot from every camera in the scene, each labelled with the camera's name; a camera that fails reports its error without failing the others
scene_compare(scene, baseline=None, quality="high")camera_compare for every camera in the scene, labelled the same way
scene_timeline(scene, start=None, end=None, limit=50)The scene's snapshots, recordings and baselines, newest first, from every camera that watched it (removed ones too). start/end: YYYY-MM-DD (whole day) or an ISO datetime; limit 1–500

camera is a camera's name: its section name in cameras.toml. quality="high" uses the camera's main stream (1080p), "low" the sub stream (smaller, quicker). Media is saved to the camera's own folder, <media_dir>/<uid>/<camera>_<YYYYmmdd-HHMMSS>.jpg|.mp4 (media_dir defaults to ~/.local/share/ipcam-mcp/media).

Tool hints. Every tool carries MCP annotations so clients can tell what it does: list_cameras, list_scenes, scene_timeline and ptz_list_presets are read-only; camera_save_baseline, ptz_save_preset, ptz_remove_preset, remove_camera and delete_camera may replace or delete data; everything else only adds files or moves the camera. Snapshots and recordings are not marked read-only because they save files. These are hints for the client; the server's own safeguards (such as delete_camera's approval form) still apply.

Compare and crop. camera_compare lists the grid cells that changed (the same names as region) and a box around them. Pass a changed cell as region to camera_snapshot, ideally with from_path set to the snapshot you just looked at, to see it closer. Baselines live in <media_dir>/<uid>/baselines/ as a JPEG plus a small JSON file.

Managing cameras

Instead of editing cameras.toml by hand you can let Claude add, rename, remove and delete cameras. This is on by default. To turn it off, set allow_management = false in cameras.toml (see cameras.example.toml), then run /mcp reconnect ipcam; the flag is only read at startup. Claude Code still asks before each tool call unless you allowlist it; if you allowlist the camera tools, consider leaving remove_camera, delete_camera and ptz_remove_preset off the list.

add_camera logs into whatever host it is given. Before you type a password into the login form, check that the IP it shows is your camera's (from the camera's app or your router), not one the agent picked up elsewhere.

ToolWhat it does
add_camera(name, host, credentials_via="form", onvif_port=None, scene=None)Verifies the login over ONVIF (tries port 80, then 2020, unless you give onvif_port), detects pan/tilt, then adds the camera with a new uid. name and scene are lowercase letters, digits, -, _ (max 32); scene defaults to the name
rename_camera(old, new)Changes a camera's name; its uid, scene, baselines, snapshots and recordings stay with it (no files move), and its secrets file is renamed too if the tool created it
set_scene(camera, scene)Moves a camera to another scene (the place it watches). Earlier snapshots and baselines stay with the earlier scene. If removed cameras watched the scene, a form asks whether it replaces one of them (see Replacing a camera); with the current scene it asks that again if no predecessor is declared yet
remove_camera(name)Removes the camera from the config; keeps its snapshots, recordings and baselines (it becomes a removed camera)
delete_camera(name)Deletes the camera (config entry, its managed secrets file) and its folder with all snapshots, recordings and baselines, or the leftover folder of a removed camera with that name. You approve it in a form listing what will be deleted; with several matches you pick one or all. Without form support nothing is deleted and the result says what to delete by hand
reload_cameras()Always available. Re-reads cameras.toml after a manual edit and lists the cameras

Changes apply immediately, without restarting the server. If loading the new config fails, the previous cameras.toml is restored.

Credentials never pass through the model. Two routes:

  • Form (default): your client shows a login form. MCP forms have no masked field, so the password is visible while you type it, but it is not stored in the conversation. Some clients cannot show a form during a tool call (those on MCP protocol 2026-07-28, some phone (Remote Control) views); there add_camera uses the file route and says so in its result.
  • File (credentials_via="file"): add_camera creates <secrets_dir>/<name>.env (mode 0600, default secrets_dir is ~/.config/ipcam-mcp/secrets) with HOST=, empty USER= and PASSWORD=. Fill in USER and PASSWORD yourself, then ask Claude to call add_camera again with the same name and host, keeping credentials_via="file". Calling it with the default form would ask for the credentials again instead of reading the file.

Each camera added this way has its own <name>.env, and files at <secrets_dir>/<name>.env are treated as managed: renamed and deleted along with the camera. Keep hand-made files elsewhere; those, such as a shared secrets.env, are never renamed or deleted. Secrets files are read literally: ${VAR} in a value is not expanded.

How it works

Camera uids. Every camera has a permanent uid (8 characters, a-z0-9) in its cameras.toml section. The server fills it in when it is missing, so you never write one yourself; don't change or copy it. The uid names the camera's media folder, which also holds a camera.json with the camera's current name. Because of it, a renamed camera keeps its snapshots and baselines, and a new camera that reuses an old name starts with an empty history.

Scenes. A scene is the place a camera watches, such as front-door or garage. By default it is the camera's name; set scene = "..." in the camera's section (or use set_scene) when they differ, for example when several cameras watch one place. Renaming a camera keeps its scene. Moving a camera to another scene starts a new period in its camera.json: snapshots and baselines from before the move stay with the old scene, and camera_compare only compares within the current one, so it never reports a different place as a change.

Replacing a camera. When a camera enters a scene where a removed camera was last (with add_camera, or set_scene), you are asked in a form whether it replaces one of them, is an additional camera, or to cancel. A replacement is its successor: until it has a snapshot of its own there, camera_compare compares with the old camera's newest snapshot in the scene, and baselines are looked up along the predecessors (up to 10). The result then says which camera took the reference, since angle, lens or resolution may differ. Without form support the camera is added as an additional one; call set_scene with its current scene later, from a client that shows forms, to declare it a successor. A camera that moves away and later returns to the scene keeps its predecessor there. delete_camera mentions when a camera is another's predecessor.

Transports

ipcam-mcp uses stdio by default. ipcam-mcp --transport http [--host 127.0.0.1] [--port 8765] serves streamable HTTP, but without authentication, so only use it on a trusted machine and keep it bound to localhost. On 127.0.0.1, localhost or ::1 the server rejects requests addressed to other hosts, such as from web pages (DNS-rebinding protection). Any other --host is refused unless you also pass --allow-remote; then anyone who can reach the port controls your cameras.

Privacy

The project collects no data; everything stays on your machine except what your MCP client receives as tool results. See PRIVACY.md.

Security

See SECURITY.md for the threat model and how to report a vulnerability.

  • Credentials are read only from your secrets file, never from the repo, and are not put into environment variables.
  • They are kept in memory by the camera's config and adapter, and are excluded from repr.
  • The password is scrubbed (raw and URL-encoded) from every error message the server returns or logs.
  • RTSP URLs are masked (rtsp://***@host…), which also hides the username. The username is not scrubbed anywhere else.
  • Logs go to stderr; stdout carries only the MCP protocol.

Troubleshooting

  • Auth errors / 401 / ONVIF error on every PTZ call: use the camera's local account, not a cloud login. See known cameras.
  • Snapshots fail but ONVIF works: set stream_high/stream_low (see Camera settings).
  • timed out … (camera unreachable?): the camera is offline or not reachable from this machine. See WSL2 networking.
  • ffmpeg not installed: sudo apt install ffmpeg.
  • camera.json … unreadable, kept as camera.json.bad-… in the log: a camera's camera.json could not be read (for example after a bad hand edit), so a new one was written and the old one kept next to it. To get the camera's creation time and scene history back, fix the kept file and rename it to camera.json while the server is stopped (scene changes made since are then lost; redo them with set_scene). Timestamps without a UTC offset are read as local time.

Known limitations

  • While the camera is offline, failed ONVIF connection attempts aren't cleaned up, and you may see Unclosed client session warnings on stderr. They're harmless.
  • Filenames have one-second resolution; a second capture in the same second gets a -2, -3, … suffix.
  • Filenames carry local time without a UTC offset. In the hour that repeats when daylight saving time ends, a capture counts as taken in the first of the two hours, and after the machine's time zone changes, older captures shift by the difference. Both only matter for captures close to a scene change: they can be assigned to the neighbouring scene.
  • Media files are never deleted automatically. delete_camera deletes a camera's files after you approve it; otherwise clean up ~/.local/share/ipcam-mcp/media/ yourself.
  • HTTP transport has no authentication (see above).

Development

To work on the code, clone the repo:

git clone https://github.com/aidev-iprokszin/ipcam-mcp.git && cd ipcam-mcp && uv sync

The repo ships a project-scoped .mcp.json, so running claude anywhere inside the clone offers the ipcam server, run from your checkout. Approve it when prompted. If you rejected it by mistake, run claude mcp reset-project-choices and start claude again.

To use the checkout from every folder, also register it in your user scope (inside the repo the project entry takes precedence, so there are no duplicates):

claude mcp add ipcam -s user -- uv --directory /absolute/path/to/ipcam-mcp run ipcam-mcp

Tests and lint:

uv run pytest            # unit tests (no network; the preview test is skipped without ffmpeg)uv run pytest -m live    # against the real camera from your config (IPCAM_MCP_CAMERA, default livingroom)IPCAM_MCP_CAMERA=office uv run pytest -m live    # e.g. against another configured camerauv run ruff check . && uv run ruff format .

⚠️ The live tests physically move the camera (a small pan left and back) and save snapshots and a 3 s clip to the media folder. They also create and delete a preset and a baseline, both named cmcp_test.

Adding a camera type

  1. Add an adapter in src/ipcam_mcp/adapters/ taking (DeviceConfig, media_dir) and implementing the Camera protocol (and PTZ if it can pan/tilt) from src/ipcam_mcp/capabilities.py.
  2. Map its config type to the adapter in ADAPTERS in src/ipcam_mcp/registry.py.

A new camera feature (e.g. privacy mode) gets a Protocol in capabilities.py, registered in CAPABILITIES, plus its tools in src/ipcam_mcp/server.py.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md and the Code of Conduct.

This project was built with Claude Code.

License

MIT

Terms of use: TERMS.md.

Source: README.md at commit 51bb0e1

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

2
  1. v0.4.1LatestNov 7, 2026
  2. v0.4.0Nov 4, 2026

Score

50% 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

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • 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 listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts