ipcam-mcp 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aidev-iprokszin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,342 @@
1
+ Metadata-Version: 2.4
2
+ Name: ipcam-mcp
3
+ Version: 0.3.2
4
+ Summary: MCP server for local IP cameras (ONVIF/RTSP): snapshots, recordings, pan/tilt, presets
5
+ Keywords: mcp,mcp-server,ip-camera,onvif,rtsp,ptz,claude
6
+ Author: aidev-iprokszin
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Multimedia :: Video :: Capture
16
+ Requires-Dist: mcp>=2.3.0
17
+ Requires-Dist: onvif-zeep-async>=4.3.0
18
+ Requires-Dist: pillow>=13.0.0
19
+ Requires-Dist: python-dotenv>=1.2.4
20
+ Requires-Dist: tomlkit>=0.15.2
21
+ Requires-Python: >=3.13
22
+ Project-URL: Homepage, https://github.com/aidev-iprokszin/ipcam-mcp
23
+ Project-URL: Issues, https://github.com/aidev-iprokszin/ipcam-mcp/issues
24
+ Project-URL: Changelog, https://github.com/aidev-iprokszin/ipcam-mcp/releases
25
+ Description-Content-Type: text/markdown
26
+
27
+ # ipcam-mcp
28
+
29
+ <!-- mcp-name: io.github.aidev-iprokszin/ipcam-mcp -->
30
+
31
+ An [MCP](https://modelcontextprotocol.io) server that lets Claude Code see through and control
32
+ the IP cameras on your local network (ONVIF + RTSP): snapshots, video clips, pan/tilt and
33
+ change detection, all handled locally.
34
+
35
+ Any ONVIF camera should work. See [known cameras](https://github.com/aidev-iprokszin/ipcam-mcp/tree/main/docs/cameras) for what has been tested.
36
+
37
+ ## Tools
38
+
39
+ | Tool | What it does |
40
+ |---|---|
41
+ | `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 |
42
+ | `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 |
43
+ | `camera_save_baseline(camera, name, quality="high")` | Takes a snapshot and keeps it as a named reference (e.g. `door-closed`) |
44
+ | `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 |
45
+ | `camera_record(camera, seconds=10, quality="high")` | Records 1–60 s to an mp4, returns its path |
46
+ | `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 |
47
+ | `ptz_list_presets(camera)` | Saved positions |
48
+ | `ptz_goto_preset(camera, preset)` | Move to a preset by name or token |
49
+ | `ptz_save_preset(camera, name, overwrite=False)` | Saves the current position as a preset; refuses an existing name unless `overwrite=true` |
50
+ | `ptz_remove_preset(camera, preset)` | Deletes a preset by name or token (cannot be undone) |
51
+ | `list_scenes()` | Every scene with its cameras, removed cameras that watched it, the first and last capture and counts |
52
+ | `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 |
53
+ | `scene_compare(scene, baseline=None, quality="high")` | `camera_compare` for every camera in the scene, labelled the same way |
54
+ | `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 |
55
+
56
+ `camera` is a camera's name: its section name in `cameras.toml`. `quality="high"` uses the
57
+ camera's main stream (1080p), `"low"` the sub stream (smaller, quicker). Media is saved to the
58
+ camera's own folder, `<media_dir>/<uid>/<camera>_<YYYYmmdd-HHMMSS>.jpg|.mp4` (`media_dir`
59
+ defaults to `~/.local/share/ipcam-mcp/media`).
60
+
61
+ **Compare and crop.** `camera_compare` lists the grid cells that changed (the same names as
62
+ `region`) and a box around them. Pass a changed cell as `region` to `camera_snapshot`, ideally
63
+ with `from_path` set to the snapshot you just looked at, to see it closer. Baselines live in
64
+ `<media_dir>/<uid>/baselines/` as a JPEG plus a small JSON file.
65
+
66
+ **Camera uids.** Every camera has a permanent `uid` (8 characters, `a-z0-9`) in its
67
+ `cameras.toml` section. The server fills it in when it is missing, so you never write one
68
+ yourself; don't change or copy it. The uid names the camera's media folder, which also holds a
69
+ `camera.json` with the camera's current name. Because of it, a renamed camera keeps its
70
+ snapshots and baselines, and a new camera that reuses an old name starts with an empty history.
71
+ When you upgrade from 0.2, the first start moves existing snapshots, recordings and baselines
72
+ from the old flat layout into these folders; files of cameras that are no longer configured get
73
+ a folder of their own and are kept.
74
+
75
+ **Scenes.** A scene is the place a camera watches, such as `front-door` or `garage`. By default
76
+ it is the camera's name; set `scene = "..."` in the camera's section (or use `set_scene`) when
77
+ they differ, for example when several cameras watch one place. Renaming a camera keeps its
78
+ scene. Moving a camera to another scene starts a new period in its `camera.json`: snapshots and
79
+ baselines from before the move stay with the old scene, and `camera_compare` only compares within
80
+ the current one, so it never reports a different place as a change.
81
+
82
+ **Replacing a camera.** When a camera enters a scene where a removed camera was last (with
83
+ `add_camera`, or `set_scene`), you are asked in a form whether it replaces one of them, is an
84
+ additional camera, or to cancel. A replacement is its *successor*: until it has a snapshot of
85
+ its own there, `camera_compare` compares with the old camera's newest snapshot in the scene, and
86
+ baselines are looked up along the predecessors (up to 10). The result then says which camera
87
+ took the reference, since angle, lens or resolution may differ. Without form support the camera
88
+ is added as an additional one; call `set_scene` with its current scene later, from a client that
89
+ shows forms, to declare it a successor. A camera that moves away and later returns to the
90
+ scene keeps its predecessor there. `delete_camera` mentions when a camera is another's
91
+ predecessor.
92
+
93
+ ## Prerequisites
94
+
95
+ - Linux or WSL2, on the same network as the camera
96
+ - [uv](https://docs.astral.sh/uv/) on your `PATH` (it installs Python 3.13+ and the dependencies
97
+ for you)
98
+ - ffmpeg: `sudo apt install ffmpeg`
99
+ - An ONVIF-capable IP camera with a local camera account (username and password), with ONVIF
100
+ and RTSP enabled
101
+ - [Claude Code](https://claude.com/claude-code) or another MCP client that supports stdio
102
+ servers
103
+
104
+ ## Setup
105
+
106
+ 1. **Install**: nothing to do if you run it with `uvx ipcam-mcp` (see step 5); uv fetches it
107
+ from [PyPI](https://pypi.org/project/ipcam-mcp/). To work on the code, or to use the
108
+ project-scoped `.mcp.json`, clone it instead:
109
+ ```
110
+ git clone https://github.com/aidev-iprokszin/ipcam-mcp.git && cd ipcam-mcp && uv sync
111
+ ```
112
+ 2. **Camera account**: create or find the camera's local ONVIF/RTSP account (username and
113
+ password). This is **not** your cloud login. See [known cameras](https://github.com/aidev-iprokszin/ipcam-mcp/tree/main/docs/cameras) for where
114
+ your brand hides it.
115
+ 3. **Secrets file** (outside the repo), e.g. `~/.config/ipcam-mcp/secrets.env`:
116
+ ```
117
+ CAMERA_IP=192.168.x.y
118
+ CAMERA_USER=...
119
+ CAMERA_PASSWORD=...
120
+ ```
121
+ Make it readable only by you: `chmod 600 <file>`. The server warns at startup if other
122
+ users can read it.
123
+ 4. **Camera config**: copy [`cameras.example.toml`](https://github.com/aidev-iprokszin/ipcam-mcp/blob/main/cameras.example.toml) to `~/.config/ipcam-mcp/cameras.toml` (or
124
+ point `IPCAM_MCP_CONFIG` at another path) and adjust the camera name / variable names. Only
125
+ variable *names* go in this file, never the secrets themselves. Use `type = "onvif"`.
126
+
127
+ Steps 3 and 4 are optional if you let Claude add cameras: without a config file the server
128
+ starts with no cameras, and `add_camera` (see [Managing cameras](#managing-cameras))
129
+ creates `cameras.toml` and the camera's secrets file for your first camera.
130
+ 5. **Use it in Claude Code**: without a clone, register the PyPI package in your user scope:
131
+ ```
132
+ claude mcp add ipcam -s user -- uvx ipcam-mcp
133
+ ```
134
+ In a clone, the repo ships a project-scoped `.mcp.json`, so running `claude`
135
+ anywhere inside this repo offers the `ipcam` server. Approve it when prompted. If you
136
+ rejected it by mistake, run `claude mcp reset-project-choices` and start `claude` again. Then
137
+ ask Claude e.g. "list my cameras" or "take a snapshot of livingroom".
138
+
139
+ To use it from **every** folder, also register it in your user scope (inside the repo the
140
+ project entry takes precedence, so there are no duplicates):
141
+ ```
142
+ claude mcp add ipcam -s user -- uv --directory /absolute/path/to/ipcam-mcp run ipcam-mcp
143
+ ```
144
+
145
+ ### Camera settings
146
+
147
+ All optional, set in the camera's section of `cameras.toml`. A wrong type is reported when the
148
+ config loads.
149
+
150
+ | Setting | Type | Default |
151
+ |---|---|---|
152
+ | `onvif_port` | integer 1–65535 | 80 |
153
+ | `rtsp_port` | integer 1–65535 | the port in the camera's stream URI, else 554 (a value you set always wins) |
154
+ | `invert_pan` | boolean | false |
155
+ | `ptz` | boolean | false |
156
+ | `stream_high` | path starting with `/` | looked up over ONVIF |
157
+ | `stream_low` | path starting with `/` | looked up over ONVIF |
158
+
159
+ `ptz = false` means the camera has no pan/tilt tools. Set `stream_high`/`stream_low` if the
160
+ ONVIF lookup fails or picks the wrong stream. Set `invert_pan = true` if `ptz_move left` turns
161
+ the view to the right. Some brands need other values; see [known cameras](https://github.com/aidev-iprokszin/ipcam-mcp/tree/main/docs/cameras).
162
+
163
+ ### Other MCP clients
164
+
165
+ Any client that supports stdio servers (Cursor, VS Code, Codex CLI, …) can run the same
166
+ command: `uvx ipcam-mcp`, or `uv --directory /absolute/path/to/ipcam-mcp run ipcam-mcp` for a
167
+ clone.
168
+
169
+ **Claude Desktop on Windows with the repo in WSL** (untested): add to
170
+ `%APPDATA%\Claude\claude_desktop_config.json` and restart the app:
171
+ ```json
172
+ {
173
+ "mcpServers": {
174
+ "ipcam": {
175
+ "command": "wsl.exe",
176
+ "args": ["-e", "/home/<you>/.local/bin/uv", "--directory",
177
+ "/home/<you>/path/to/ipcam-mcp", "run", "ipcam-mcp"]
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ ### WSL2 networking
184
+
185
+ If the camera can't be reached from WSL (`nc -zv <ip> 554` fails), enable mirrored networking
186
+ in `%UserProfile%\.wslconfig`:
187
+ ```
188
+ [wsl2]
189
+ networkingMode=mirrored
190
+ ```
191
+ then run `wsl --shutdown` from Windows.
192
+
193
+ ## Managing cameras
194
+
195
+ Instead of editing `cameras.toml` by hand you can let Claude add, rename, remove and delete cameras.
196
+ This is on by default. To turn it off, set `allow_management = false` in `cameras.toml` (see
197
+ `cameras.example.toml`), then run `/mcp reconnect ipcam`; the flag is only read at startup.
198
+ Claude Code still asks before each tool call unless you allowlist it; if you allowlist the
199
+ camera tools, consider leaving `remove_camera`, `delete_camera` and `ptz_remove_preset` off the list.
200
+
201
+ `add_camera` logs into whatever host it is given. Before you type a password into the login form,
202
+ check that the IP it shows is your camera's (from the camera's app or your router), not one the
203
+ agent picked up elsewhere.
204
+
205
+ | Tool | What it does |
206
+ |---|---|
207
+ | `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 |
208
+ | `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 |
209
+ | `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 |
210
+ | `remove_camera(name)` | Removes the camera from the config; keeps its snapshots, recordings and baselines (it becomes a *removed camera*) |
211
+ | `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 |
212
+ | `reload_cameras()` | Always available. Re-reads `cameras.toml` after a manual edit and lists the cameras |
213
+
214
+ Changes apply immediately, without restarting the server. If loading the new config fails, the
215
+ previous `cameras.toml` is restored.
216
+
217
+ **IP address.** Network discovery is not supported (WSL2 NAT blocks it), so tell Claude the
218
+ camera's IP from the camera's app or your router.
219
+
220
+ **Credentials never pass through the model.** Two routes:
221
+
222
+ - **Form** (default): your client shows a login form. MCP forms have no masked field, so the
223
+ password is visible while you type it, but it is not stored in the conversation. Clients on
224
+ MCP protocol 2026-07-28 cannot show the form mid-call, and neither can some phone (Remote
225
+ Control) views; there `add_camera` always uses the secrets-file route and says so in its
226
+ result.
227
+ - **File** (`credentials_via="file"`): `add_camera` creates `<secrets_dir>/<name>.env` (mode
228
+ `0600`, default `secrets_dir` is `~/.config/ipcam-mcp/secrets`) with `HOST=`, empty `USER=` and
229
+ `PASSWORD=`. Fill in USER and PASSWORD yourself, then ask Claude to call `add_camera` again
230
+ with the same name and host, **keeping `credentials_via="file"`**. Calling it with the default
231
+ `form` would ask for the credentials again instead of reading the file.
232
+
233
+ Each camera added this way has its own `<name>.env`, and files at `<secrets_dir>/<name>.env` are
234
+ treated as managed: renamed and deleted along with the camera. Keep hand-made files elsewhere;
235
+ those, such as a shared `secrets.env`, are never renamed or deleted. Secrets files are read literally: `${VAR}` in a
236
+ value is not expanded.
237
+
238
+ ## Transports
239
+
240
+ `ipcam-mcp` uses stdio by default. `ipcam-mcp --transport http [--host 127.0.0.1] [--port 8765]`
241
+ serves streamable HTTP, but **without authentication**, so only use it on a trusted machine
242
+ and keep it bound to localhost. On `127.0.0.1`, `localhost` or `::1` the server rejects
243
+ requests addressed to other hosts, such as from web pages (DNS-rebinding protection). Any other
244
+ `--host` is refused unless you also pass `--allow-remote`; then anyone who can reach the port
245
+ controls your cameras.
246
+
247
+ ## Security
248
+
249
+ See [SECURITY.md](https://github.com/aidev-iprokszin/ipcam-mcp/blob/main/SECURITY.md) for the threat model and how to report a vulnerability.
250
+
251
+ - Credentials are read only from your secrets file, never from the repo, and are not put into
252
+ environment variables.
253
+ - They are kept in memory by the camera's config and adapter, and are excluded from `repr`.
254
+ - The password is scrubbed (raw and URL-encoded) from every error message the server returns
255
+ or logs.
256
+ - RTSP URLs are masked (`rtsp://***@host…`), which also hides the username. The username is
257
+ not scrubbed anywhere else.
258
+ - Logs go to stderr; stdout carries only the MCP protocol.
259
+
260
+ ## Troubleshooting
261
+
262
+ - **Auth errors / `401` / ONVIF error on every PTZ call**: use the camera's local account, not a
263
+ cloud login. See [known cameras](https://github.com/aidev-iprokszin/ipcam-mcp/tree/main/docs/cameras).
264
+ - **Snapshots fail but ONVIF works**: set `stream_high`/`stream_low` (see
265
+ [Camera settings](#camera-settings)).
266
+ - **`timed out … (camera unreachable?)`**: the camera is offline or not reachable from this
267
+ machine. See [WSL2 networking](#wsl2-networking).
268
+ - **`ffmpeg not installed`**: `sudo apt install ffmpeg`.
269
+ - **`camera.json … unreadable, kept as camera.json.bad-…`** in the log: a camera's
270
+ `camera.json` could not be read (for example after a bad hand edit), so a new one was written
271
+ and the old one kept next to it. To get the camera's creation time and scene history back,
272
+ fix the kept file and rename it to `camera.json` while the server is stopped (scene changes
273
+ made since are then lost; redo them with `set_scene`). Timestamps without a UTC offset are
274
+ read as local time.
275
+
276
+ ## Known limitations
277
+
278
+ - While the camera is offline, failed ONVIF connection attempts aren't cleaned up, and you may
279
+ see `Unclosed client session` warnings on stderr. They're harmless.
280
+ - Filenames have one-second resolution; a second capture in the same second gets a `-2`, `-3`, …
281
+ suffix.
282
+ - Filenames carry local time without a UTC offset. In the hour that repeats when daylight
283
+ saving time ends, a capture counts as taken in the first of the two hours, and after the
284
+ machine's time zone changes, older captures shift by the difference. Both only matter for
285
+ captures close to a scene change: they can be assigned to the neighbouring scene.
286
+ - Media files are never deleted. Clean up `~/.local/share/ipcam-mcp/media/` yourself.
287
+ - HTTP transport has no authentication (see above).
288
+
289
+ ## Upgrading from 0.2
290
+
291
+ - On the first start, every camera in `cameras.toml` gets a permanent `uid = "..."` line, and
292
+ existing snapshots, recordings and baselines move from the flat layout into per-camera folders,
293
+ `<media_dir>/<uid>/`. Files of cameras that are no longer configured are kept in folders of
294
+ their own (*removed cameras*: `list_cameras(include_removed=true)`, `delete_camera`).
295
+ Only files named like a camera's capture (`<name>_YYYYmmdd-HHMMSS.jpg|mp4`, where `<name>`
296
+ is a configured camera or a valid camera name: `a-z`, `0-9`, `-`, `_`) move; other files, such
297
+ as `IMG_…` photos, stay. A file whose target already exists is left in place with
298
+ a warning. Once the move has finished, an empty `.flat-layout-migrated` file in `media_dir`
299
+ keeps it from running again; delete it to repeat the migration. `cameras.toml` must be
300
+ writable while cameras still need a uid: a read-only file is not changed, and the server
301
+ stops with an error. (The server never replaces a read-only `cameras.toml`; management tools
302
+ then fail with "it is read-only".)
303
+ - Every camera is in a scene, by default its name, so existing snapshots and baselines stay in
304
+ scenes named after their cameras. `camera_compare` now compares only within the camera's
305
+ current scene.
306
+ - `add_camera` and `remove_camera` take `name` instead of `id`.
307
+ - New tools: `delete_camera`, `set_scene`, `list_scenes`, `scene_snapshot`, `scene_compare`,
308
+ `scene_timeline`. `camera_snapshot` accepts `from_path` without `camera`.
309
+
310
+ ## Development
311
+
312
+ ```
313
+ uv run pytest # unit tests (no network; the preview test is skipped without ffmpeg)
314
+ uv run pytest -m live # against the real camera from your config (IPCAM_MCP_CAMERA, default livingroom)
315
+ IPCAM_MCP_CAMERA=office uv run pytest -m live # e.g. against another configured camera
316
+ uv run ruff check . && uv run ruff format .
317
+ ```
318
+
319
+ > ⚠️ The live tests **physically move the camera** (a small pan left and back) and save
320
+ > snapshots and a 3 s clip to the media folder. They also create and delete a preset and a
321
+ > baseline, both named `cmcp_test`.
322
+
323
+ ### Adding a camera type
324
+
325
+ 1. Add an adapter in `src/ipcam_mcp/adapters/` taking `(DeviceConfig, media_dir)` and
326
+ implementing the `Camera` protocol (and `PTZ` if it can pan/tilt) from
327
+ `src/ipcam_mcp/capabilities.py`.
328
+ 2. Map its config `type` to the adapter in `ADAPTERS` in `src/ipcam_mcp/registry.py`.
329
+
330
+ A new camera feature (e.g. privacy mode) gets a `Protocol` in `capabilities.py`, registered in
331
+ `CAPABILITIES`, plus its tools in `src/ipcam_mcp/server.py`.
332
+
333
+ ## Contributing
334
+
335
+ Issues and pull requests are welcome. See [CONTRIBUTING.md](https://github.com/aidev-iprokszin/ipcam-mcp/blob/main/CONTRIBUTING.md) and the
336
+ [Code of Conduct](https://github.com/aidev-iprokszin/ipcam-mcp/blob/main/CODE_OF_CONDUCT.md).
337
+
338
+ This project was built with [Claude Code](https://claude.com/claude-code).
339
+
340
+ ## License
341
+
342
+ [MIT](https://github.com/aidev-iprokszin/ipcam-mcp/blob/main/LICENSE)