blink-camera-mcp 0.1.0__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 Abhijat Saxena
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,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: blink-camera-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Amazon Blink cameras: see, aim (pan/tilt) and capture, including the undocumented pan/tilt accessory protocol.
5
+ Author: Abhijat Saxena
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/AbhijatSaxena/blink-camera-mcp
8
+ Project-URL: Issues, https://github.com/AbhijatSaxena/blink-camera-mcp/issues
9
+ Keywords: mcp,model-context-protocol,blink,amazon-blink,blink-camera,camera,pan-tilt,ptz,security-camera,home-automation
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Home Automation
18
+ Classifier: Topic :: Multimedia :: Video :: Capture
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: mcp<3,>=2.2.0
23
+ Requires-Dist: blinkpy>=0.25.0
24
+ Requires-Dist: aiohttp>=3.9
25
+ Dynamic: license-file
26
+
27
+ # blink-camera-mcp
28
+
29
+ [![ci](https://github.com/AbhijatSaxena/blink-camera-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/AbhijatSaxena/blink-camera-mcp/actions/workflows/ci.yml)
30
+
31
+ <!-- mcp-name: io.github.AbhijatSaxena/blink-camera-mcp -->
32
+
33
+ An [MCP](https://modelcontextprotocol.io) server for **Amazon Blink cameras**, including the
34
+ **pan/tilt mount** that Blink's own API gives you no way to move.
35
+
36
+ Give any MCP host — Claude Desktop, an IDE agent, your own client — the ability to see where
37
+ a camera is pointing, aim it, and look through it.
38
+
39
+ ```
40
+ camera_status where it points, travel limits, session state, stream URL
41
+ pan_tilt aim at an absolute angle, wait until the hardware confirms
42
+ pan_tilt_nudge turn relative to where it points now
43
+ pan_tilt_stop stop the motors
44
+ pan_tilt_home go to the saved home position
45
+ pan_tilt_set_home save the current angle as home
46
+ pan_tilt_overview 360° sweep
47
+ snapshot one still frame, returned as image content
48
+ ```
49
+
50
+ ## Why this exists
51
+
52
+ Blink cameras are cloud devices. There is no local API, no ONVIF, no UVC — video exists only
53
+ as a short-lived session brokered by Blink's cloud, capped at 300 seconds. Two consequences:
54
+
55
+ 1. Anything that wants to use the camera has to hold that session open and refresh it.
56
+ 2. The pan/tilt mount is not a device you can talk to. It has no endpoint of its own: it is
57
+ driven *inside* the camera's media session, and its position comes back on the same socket.
58
+ A client that reads only the video and skips every other message never sees any of that
59
+ traffic, which is why the mount has looked uncontrollable for years.
60
+
61
+ This server owns the session (or reuses one), frames it correctly, routes the accessory
62
+ messages, and exposes the whole thing as MCP tools with closed-loop semantics.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install blink-camera-mcp # or without installing: uvx blink-camera-mcp
68
+ ```
69
+
70
+ The distribution is `blink-camera-mcp`; the command it installs is `blink-mcp`.
71
+
72
+ You need Python 3.10+, a Blink account, and `ffmpeg` on PATH for `snapshot`
73
+ (set `BLINK_FFMPEG` if it lives somewhere unusual).
74
+
75
+ ## Configure
76
+
77
+ Credentials come from the environment, and only the first run needs them:
78
+
79
+ ```bash
80
+ export BLINK_USERNAME="you@example.com"
81
+ export BLINK_PASSWORD="..."
82
+ export BLINK_CAMERA_NAME="Front Door" # optional; defaults to the only camera
83
+ ```
84
+
85
+ The token is cached (token material only — a password is never written to disk), so later
86
+ runs need no credentials. Cache location defaults to `~/.blink-mcp/state.json`; override with
87
+ `BLINK_STATE_FILE`.
88
+
89
+ **If Blink asks for a 2FA code**, the server will not prompt you and will not take the code
90
+ from anywhere but a file:
91
+
92
+ ```bash
93
+ export BLINK_2FA_FILE=/path/to/code.txt
94
+ ```
95
+
96
+ Write the newest code into that file; it is read once and deleted. This keeps a live
97
+ credential out of chat logs, shell history and argument lists.
98
+
99
+ ## Add it to your MCP host
100
+
101
+ ```bash
102
+ blink-mcp --print-config
103
+ ```
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "blink": {
109
+ "command": "uvx",
110
+ "args": ["blink-camera-mcp", "--stream-port", "9000"]
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ Hosts that manage their own Python (Claude Desktop, VS Code, LM Studio, …) run `uvx` as above.
117
+ If you installed it into a virtualenv instead, set `command` to that interpreter and `args` to
118
+ `["-m", "blink_mcp", …]`.
119
+
120
+ ## Two ways to run it
121
+
122
+ **Standalone (default)** — the server owns the camera's live session and publishes the video
123
+ to a local TCP port (`tcp://127.0.0.1:<port>`, or a fixed one with `--stream-port 9000`). Point
124
+ OBS at it as a Media Source if you want the camera as a webcam as well.
125
+
126
+ **Against a bridge** — if something else already holds the session:
127
+
128
+ ```bash
129
+ blink-mcp --control-url http://127.0.0.1:9100 --stream-url tcp://127.0.0.1:9000
130
+ ```
131
+
132
+ This matters because **Blink allows one live session per camera**: a viewer and a controller
133
+ have to share it or take turns. Running two things that each insist on their own session means
134
+ one of them loses.
135
+
136
+ ## What "closed-loop" means here
137
+
138
+ The mount reports its angle *while it is moving*. So `pan_tilt` does not sleep and hope: it
139
+ sends the command and returns only when the hardware reports that its motors stopped at the
140
+ requested angle — typically well under a second.
141
+
142
+ ```
143
+ pan_tilt_nudge(+4) -> settled=True moved=True elapsed=0.67s -> pan=117 tilt=-75
144
+ pan_tilt_nudge(-4) -> settled=True moved=True -> pan=113 tilt=-75
145
+ ```
146
+
147
+ Two deliberate behaviours, because a confident wrong answer is worse than a failure:
148
+
149
+ * if the mount never confirms, the tool **fails** rather than returning the last angle it
150
+ happened to know (a stale position dressed up as success would have an agent announce a move
151
+ that never happened);
152
+ * a command to the angle the camera already holds is a no-op reported as `moved: false` —
153
+ success, not failure.
154
+
155
+ ## Privacy and safety
156
+
157
+ * `snapshot` decodes a frame of whatever the camera is pointed at. The tool description says
158
+ so, and tells the agent to use it only when the user asks. Nothing here captures anything on
159
+ its own.
160
+ * The server talks to Blink directly, and the token cache holds no password.
161
+ * Any bridge control plane is loopback-only by design: it moves a physical camera.
162
+
163
+ ## How it was built
164
+
165
+ The accessory channel is undocumented. It was recovered from the official Android app — dex
166
+ bytecode and the native library's symbol table — and then verified against hardware:
167
+
168
+ ```
169
+ frame [flag:1][id:4 big-endian][length:4 big-endian][payload]
170
+ send flag 0x14 INLINE_COMMAND, id = commandId
171
+ move=3 [0,0,0,0,<pan>,<tilt>,0] stop=4 home=5 set_home=6 overview=7
172
+ receive flag 0x15 ACCESSORY_MESSAGE, id = message id
173
+ POSITION=2 / HOME_POSITION=3 [<counter>,<pan>,<tilt>,<status>]
174
+ ROSIE_LIMITS=4 PAN_OVERVIEW_COMPLETE=5 lights/siren = 0/1/6/7
175
+ ```
176
+
177
+ `status` is `0x00` idle and `0x10` moving. Angles are single signed bytes.
178
+
179
+ The same protocol knowledge is being contributed upstream to
180
+ [blinkpy](https://github.com/fronzbot/blinkpy) so every Blink integration benefits, not just
181
+ this server. `blink_mcp/immi.py` is byte-identical to the module submitted there, and switches
182
+ to the upstream copy automatically once it ships.
183
+
184
+ Standing on the shoulders of [blinkpy](https://github.com/fronzbot/blinkpy), which implements
185
+ the Blink cloud API and the IMMI transport.
186
+
187
+ ## Limitations
188
+
189
+ * Verified on a **Blink Mini with the pan/tilt mount**. Other camera families use a different
190
+ live transport (WebRTC with JSON-RPC commands rather than this binary channel) and are not
191
+ supported yet.
192
+ * One live session per camera, as above.
193
+ * Blink caps a session at 300 s; the server rotates it around 270 s and consumers never notice.
194
+ * `snapshot` needs `ffmpeg`; it decodes one frame from the stream rather than opening a second
195
+ session.
196
+
197
+ ## Tests
198
+
199
+ ```bash
200
+ pytest # 55 offline tests: protocol bytes, closed-loop logic, tool layer,
201
+ # a real stdio handshake, and a stub control plane
202
+ ```
203
+
204
+ The suite is offline and needs no camera. For real hardware:
205
+
206
+ ```bash
207
+ python tests/live_smoke.py --state-file ~/.blink-mcp/state.json
208
+ ```
209
+
210
+ which logs in, moves the camera out and back, and captures a frame.
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,188 @@
1
+ # blink-camera-mcp
2
+
3
+ [![ci](https://github.com/AbhijatSaxena/blink-camera-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/AbhijatSaxena/blink-camera-mcp/actions/workflows/ci.yml)
4
+
5
+ <!-- mcp-name: io.github.AbhijatSaxena/blink-camera-mcp -->
6
+
7
+ An [MCP](https://modelcontextprotocol.io) server for **Amazon Blink cameras**, including the
8
+ **pan/tilt mount** that Blink's own API gives you no way to move.
9
+
10
+ Give any MCP host — Claude Desktop, an IDE agent, your own client — the ability to see where
11
+ a camera is pointing, aim it, and look through it.
12
+
13
+ ```
14
+ camera_status where it points, travel limits, session state, stream URL
15
+ pan_tilt aim at an absolute angle, wait until the hardware confirms
16
+ pan_tilt_nudge turn relative to where it points now
17
+ pan_tilt_stop stop the motors
18
+ pan_tilt_home go to the saved home position
19
+ pan_tilt_set_home save the current angle as home
20
+ pan_tilt_overview 360° sweep
21
+ snapshot one still frame, returned as image content
22
+ ```
23
+
24
+ ## Why this exists
25
+
26
+ Blink cameras are cloud devices. There is no local API, no ONVIF, no UVC — video exists only
27
+ as a short-lived session brokered by Blink's cloud, capped at 300 seconds. Two consequences:
28
+
29
+ 1. Anything that wants to use the camera has to hold that session open and refresh it.
30
+ 2. The pan/tilt mount is not a device you can talk to. It has no endpoint of its own: it is
31
+ driven *inside* the camera's media session, and its position comes back on the same socket.
32
+ A client that reads only the video and skips every other message never sees any of that
33
+ traffic, which is why the mount has looked uncontrollable for years.
34
+
35
+ This server owns the session (or reuses one), frames it correctly, routes the accessory
36
+ messages, and exposes the whole thing as MCP tools with closed-loop semantics.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install blink-camera-mcp # or without installing: uvx blink-camera-mcp
42
+ ```
43
+
44
+ The distribution is `blink-camera-mcp`; the command it installs is `blink-mcp`.
45
+
46
+ You need Python 3.10+, a Blink account, and `ffmpeg` on PATH for `snapshot`
47
+ (set `BLINK_FFMPEG` if it lives somewhere unusual).
48
+
49
+ ## Configure
50
+
51
+ Credentials come from the environment, and only the first run needs them:
52
+
53
+ ```bash
54
+ export BLINK_USERNAME="you@example.com"
55
+ export BLINK_PASSWORD="..."
56
+ export BLINK_CAMERA_NAME="Front Door" # optional; defaults to the only camera
57
+ ```
58
+
59
+ The token is cached (token material only — a password is never written to disk), so later
60
+ runs need no credentials. Cache location defaults to `~/.blink-mcp/state.json`; override with
61
+ `BLINK_STATE_FILE`.
62
+
63
+ **If Blink asks for a 2FA code**, the server will not prompt you and will not take the code
64
+ from anywhere but a file:
65
+
66
+ ```bash
67
+ export BLINK_2FA_FILE=/path/to/code.txt
68
+ ```
69
+
70
+ Write the newest code into that file; it is read once and deleted. This keeps a live
71
+ credential out of chat logs, shell history and argument lists.
72
+
73
+ ## Add it to your MCP host
74
+
75
+ ```bash
76
+ blink-mcp --print-config
77
+ ```
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "blink": {
83
+ "command": "uvx",
84
+ "args": ["blink-camera-mcp", "--stream-port", "9000"]
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ Hosts that manage their own Python (Claude Desktop, VS Code, LM Studio, …) run `uvx` as above.
91
+ If you installed it into a virtualenv instead, set `command` to that interpreter and `args` to
92
+ `["-m", "blink_mcp", …]`.
93
+
94
+ ## Two ways to run it
95
+
96
+ **Standalone (default)** — the server owns the camera's live session and publishes the video
97
+ to a local TCP port (`tcp://127.0.0.1:<port>`, or a fixed one with `--stream-port 9000`). Point
98
+ OBS at it as a Media Source if you want the camera as a webcam as well.
99
+
100
+ **Against a bridge** — if something else already holds the session:
101
+
102
+ ```bash
103
+ blink-mcp --control-url http://127.0.0.1:9100 --stream-url tcp://127.0.0.1:9000
104
+ ```
105
+
106
+ This matters because **Blink allows one live session per camera**: a viewer and a controller
107
+ have to share it or take turns. Running two things that each insist on their own session means
108
+ one of them loses.
109
+
110
+ ## What "closed-loop" means here
111
+
112
+ The mount reports its angle *while it is moving*. So `pan_tilt` does not sleep and hope: it
113
+ sends the command and returns only when the hardware reports that its motors stopped at the
114
+ requested angle — typically well under a second.
115
+
116
+ ```
117
+ pan_tilt_nudge(+4) -> settled=True moved=True elapsed=0.67s -> pan=117 tilt=-75
118
+ pan_tilt_nudge(-4) -> settled=True moved=True -> pan=113 tilt=-75
119
+ ```
120
+
121
+ Two deliberate behaviours, because a confident wrong answer is worse than a failure:
122
+
123
+ * if the mount never confirms, the tool **fails** rather than returning the last angle it
124
+ happened to know (a stale position dressed up as success would have an agent announce a move
125
+ that never happened);
126
+ * a command to the angle the camera already holds is a no-op reported as `moved: false` —
127
+ success, not failure.
128
+
129
+ ## Privacy and safety
130
+
131
+ * `snapshot` decodes a frame of whatever the camera is pointed at. The tool description says
132
+ so, and tells the agent to use it only when the user asks. Nothing here captures anything on
133
+ its own.
134
+ * The server talks to Blink directly, and the token cache holds no password.
135
+ * Any bridge control plane is loopback-only by design: it moves a physical camera.
136
+
137
+ ## How it was built
138
+
139
+ The accessory channel is undocumented. It was recovered from the official Android app — dex
140
+ bytecode and the native library's symbol table — and then verified against hardware:
141
+
142
+ ```
143
+ frame [flag:1][id:4 big-endian][length:4 big-endian][payload]
144
+ send flag 0x14 INLINE_COMMAND, id = commandId
145
+ move=3 [0,0,0,0,<pan>,<tilt>,0] stop=4 home=5 set_home=6 overview=7
146
+ receive flag 0x15 ACCESSORY_MESSAGE, id = message id
147
+ POSITION=2 / HOME_POSITION=3 [<counter>,<pan>,<tilt>,<status>]
148
+ ROSIE_LIMITS=4 PAN_OVERVIEW_COMPLETE=5 lights/siren = 0/1/6/7
149
+ ```
150
+
151
+ `status` is `0x00` idle and `0x10` moving. Angles are single signed bytes.
152
+
153
+ The same protocol knowledge is being contributed upstream to
154
+ [blinkpy](https://github.com/fronzbot/blinkpy) so every Blink integration benefits, not just
155
+ this server. `blink_mcp/immi.py` is byte-identical to the module submitted there, and switches
156
+ to the upstream copy automatically once it ships.
157
+
158
+ Standing on the shoulders of [blinkpy](https://github.com/fronzbot/blinkpy), which implements
159
+ the Blink cloud API and the IMMI transport.
160
+
161
+ ## Limitations
162
+
163
+ * Verified on a **Blink Mini with the pan/tilt mount**. Other camera families use a different
164
+ live transport (WebRTC with JSON-RPC commands rather than this binary channel) and are not
165
+ supported yet.
166
+ * One live session per camera, as above.
167
+ * Blink caps a session at 300 s; the server rotates it around 270 s and consumers never notice.
168
+ * `snapshot` needs `ffmpeg`; it decodes one frame from the stream rather than opening a second
169
+ session.
170
+
171
+ ## Tests
172
+
173
+ ```bash
174
+ pytest # 55 offline tests: protocol bytes, closed-loop logic, tool layer,
175
+ # a real stdio handshake, and a stub control plane
176
+ ```
177
+
178
+ The suite is offline and needs no camera. For real hardware:
179
+
180
+ ```bash
181
+ python tests/live_smoke.py --state-file ~/.blink-mcp/state.json
182
+ ```
183
+
184
+ which logs in, moves the camera out and back, and captures a frame.
185
+
186
+ ## License
187
+
188
+ MIT
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: blink-camera-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Amazon Blink cameras: see, aim (pan/tilt) and capture, including the undocumented pan/tilt accessory protocol.
5
+ Author: Abhijat Saxena
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/AbhijatSaxena/blink-camera-mcp
8
+ Project-URL: Issues, https://github.com/AbhijatSaxena/blink-camera-mcp/issues
9
+ Keywords: mcp,model-context-protocol,blink,amazon-blink,blink-camera,camera,pan-tilt,ptz,security-camera,home-automation
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Home Automation
18
+ Classifier: Topic :: Multimedia :: Video :: Capture
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: mcp<3,>=2.2.0
23
+ Requires-Dist: blinkpy>=0.25.0
24
+ Requires-Dist: aiohttp>=3.9
25
+ Dynamic: license-file
26
+
27
+ # blink-camera-mcp
28
+
29
+ [![ci](https://github.com/AbhijatSaxena/blink-camera-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/AbhijatSaxena/blink-camera-mcp/actions/workflows/ci.yml)
30
+
31
+ <!-- mcp-name: io.github.AbhijatSaxena/blink-camera-mcp -->
32
+
33
+ An [MCP](https://modelcontextprotocol.io) server for **Amazon Blink cameras**, including the
34
+ **pan/tilt mount** that Blink's own API gives you no way to move.
35
+
36
+ Give any MCP host — Claude Desktop, an IDE agent, your own client — the ability to see where
37
+ a camera is pointing, aim it, and look through it.
38
+
39
+ ```
40
+ camera_status where it points, travel limits, session state, stream URL
41
+ pan_tilt aim at an absolute angle, wait until the hardware confirms
42
+ pan_tilt_nudge turn relative to where it points now
43
+ pan_tilt_stop stop the motors
44
+ pan_tilt_home go to the saved home position
45
+ pan_tilt_set_home save the current angle as home
46
+ pan_tilt_overview 360° sweep
47
+ snapshot one still frame, returned as image content
48
+ ```
49
+
50
+ ## Why this exists
51
+
52
+ Blink cameras are cloud devices. There is no local API, no ONVIF, no UVC — video exists only
53
+ as a short-lived session brokered by Blink's cloud, capped at 300 seconds. Two consequences:
54
+
55
+ 1. Anything that wants to use the camera has to hold that session open and refresh it.
56
+ 2. The pan/tilt mount is not a device you can talk to. It has no endpoint of its own: it is
57
+ driven *inside* the camera's media session, and its position comes back on the same socket.
58
+ A client that reads only the video and skips every other message never sees any of that
59
+ traffic, which is why the mount has looked uncontrollable for years.
60
+
61
+ This server owns the session (or reuses one), frames it correctly, routes the accessory
62
+ messages, and exposes the whole thing as MCP tools with closed-loop semantics.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install blink-camera-mcp # or without installing: uvx blink-camera-mcp
68
+ ```
69
+
70
+ The distribution is `blink-camera-mcp`; the command it installs is `blink-mcp`.
71
+
72
+ You need Python 3.10+, a Blink account, and `ffmpeg` on PATH for `snapshot`
73
+ (set `BLINK_FFMPEG` if it lives somewhere unusual).
74
+
75
+ ## Configure
76
+
77
+ Credentials come from the environment, and only the first run needs them:
78
+
79
+ ```bash
80
+ export BLINK_USERNAME="you@example.com"
81
+ export BLINK_PASSWORD="..."
82
+ export BLINK_CAMERA_NAME="Front Door" # optional; defaults to the only camera
83
+ ```
84
+
85
+ The token is cached (token material only — a password is never written to disk), so later
86
+ runs need no credentials. Cache location defaults to `~/.blink-mcp/state.json`; override with
87
+ `BLINK_STATE_FILE`.
88
+
89
+ **If Blink asks for a 2FA code**, the server will not prompt you and will not take the code
90
+ from anywhere but a file:
91
+
92
+ ```bash
93
+ export BLINK_2FA_FILE=/path/to/code.txt
94
+ ```
95
+
96
+ Write the newest code into that file; it is read once and deleted. This keeps a live
97
+ credential out of chat logs, shell history and argument lists.
98
+
99
+ ## Add it to your MCP host
100
+
101
+ ```bash
102
+ blink-mcp --print-config
103
+ ```
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "blink": {
109
+ "command": "uvx",
110
+ "args": ["blink-camera-mcp", "--stream-port", "9000"]
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ Hosts that manage their own Python (Claude Desktop, VS Code, LM Studio, …) run `uvx` as above.
117
+ If you installed it into a virtualenv instead, set `command` to that interpreter and `args` to
118
+ `["-m", "blink_mcp", …]`.
119
+
120
+ ## Two ways to run it
121
+
122
+ **Standalone (default)** — the server owns the camera's live session and publishes the video
123
+ to a local TCP port (`tcp://127.0.0.1:<port>`, or a fixed one with `--stream-port 9000`). Point
124
+ OBS at it as a Media Source if you want the camera as a webcam as well.
125
+
126
+ **Against a bridge** — if something else already holds the session:
127
+
128
+ ```bash
129
+ blink-mcp --control-url http://127.0.0.1:9100 --stream-url tcp://127.0.0.1:9000
130
+ ```
131
+
132
+ This matters because **Blink allows one live session per camera**: a viewer and a controller
133
+ have to share it or take turns. Running two things that each insist on their own session means
134
+ one of them loses.
135
+
136
+ ## What "closed-loop" means here
137
+
138
+ The mount reports its angle *while it is moving*. So `pan_tilt` does not sleep and hope: it
139
+ sends the command and returns only when the hardware reports that its motors stopped at the
140
+ requested angle — typically well under a second.
141
+
142
+ ```
143
+ pan_tilt_nudge(+4) -> settled=True moved=True elapsed=0.67s -> pan=117 tilt=-75
144
+ pan_tilt_nudge(-4) -> settled=True moved=True -> pan=113 tilt=-75
145
+ ```
146
+
147
+ Two deliberate behaviours, because a confident wrong answer is worse than a failure:
148
+
149
+ * if the mount never confirms, the tool **fails** rather than returning the last angle it
150
+ happened to know (a stale position dressed up as success would have an agent announce a move
151
+ that never happened);
152
+ * a command to the angle the camera already holds is a no-op reported as `moved: false` —
153
+ success, not failure.
154
+
155
+ ## Privacy and safety
156
+
157
+ * `snapshot` decodes a frame of whatever the camera is pointed at. The tool description says
158
+ so, and tells the agent to use it only when the user asks. Nothing here captures anything on
159
+ its own.
160
+ * The server talks to Blink directly, and the token cache holds no password.
161
+ * Any bridge control plane is loopback-only by design: it moves a physical camera.
162
+
163
+ ## How it was built
164
+
165
+ The accessory channel is undocumented. It was recovered from the official Android app — dex
166
+ bytecode and the native library's symbol table — and then verified against hardware:
167
+
168
+ ```
169
+ frame [flag:1][id:4 big-endian][length:4 big-endian][payload]
170
+ send flag 0x14 INLINE_COMMAND, id = commandId
171
+ move=3 [0,0,0,0,<pan>,<tilt>,0] stop=4 home=5 set_home=6 overview=7
172
+ receive flag 0x15 ACCESSORY_MESSAGE, id = message id
173
+ POSITION=2 / HOME_POSITION=3 [<counter>,<pan>,<tilt>,<status>]
174
+ ROSIE_LIMITS=4 PAN_OVERVIEW_COMPLETE=5 lights/siren = 0/1/6/7
175
+ ```
176
+
177
+ `status` is `0x00` idle and `0x10` moving. Angles are single signed bytes.
178
+
179
+ The same protocol knowledge is being contributed upstream to
180
+ [blinkpy](https://github.com/fronzbot/blinkpy) so every Blink integration benefits, not just
181
+ this server. `blink_mcp/immi.py` is byte-identical to the module submitted there, and switches
182
+ to the upstream copy automatically once it ships.
183
+
184
+ Standing on the shoulders of [blinkpy](https://github.com/fronzbot/blinkpy), which implements
185
+ the Blink cloud API and the IMMI transport.
186
+
187
+ ## Limitations
188
+
189
+ * Verified on a **Blink Mini with the pan/tilt mount**. Other camera families use a different
190
+ live transport (WebRTC with JSON-RPC commands rather than this binary channel) and are not
191
+ supported yet.
192
+ * One live session per camera, as above.
193
+ * Blink caps a session at 300 s; the server rotates it around 270 s and consumers never notice.
194
+ * `snapshot` needs `ffmpeg`; it decodes one frame from the stream rather than opening a second
195
+ session.
196
+
197
+ ## Tests
198
+
199
+ ```bash
200
+ pytest # 55 offline tests: protocol bytes, closed-loop logic, tool layer,
201
+ # a real stdio handshake, and a stub control plane
202
+ ```
203
+
204
+ The suite is offline and needs no camera. For real hardware:
205
+
206
+ ```bash
207
+ python tests/live_smoke.py --state-file ~/.blink-mcp/state.json
208
+ ```
209
+
210
+ which logs in, moves the camera out and back, and captures a frame.
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ blink_camera_mcp.egg-info/PKG-INFO
5
+ blink_camera_mcp.egg-info/SOURCES.txt
6
+ blink_camera_mcp.egg-info/dependency_links.txt
7
+ blink_camera_mcp.egg-info/entry_points.txt
8
+ blink_camera_mcp.egg-info/requires.txt
9
+ blink_camera_mcp.egg-info/top_level.txt
10
+ blink_mcp/__init__.py
11
+ blink_mcp/__main__.py
12
+ blink_mcp/backend.py
13
+ blink_mcp/control.py
14
+ blink_mcp/immi.py
15
+ blink_mcp/server.py
16
+ blink_mcp/session.py
17
+ blink_mcp/stream.py
18
+ tests/test_control.py
19
+ tests/test_immi.py
20
+ tests/test_server.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ blink-mcp = blink_mcp.__main__:main
@@ -0,0 +1,3 @@
1
+ mcp<3,>=2.2.0
2
+ blinkpy>=0.25.0
3
+ aiohttp>=3.9
@@ -0,0 +1,13 @@
1
+ """MCP server for Amazon Blink cameras.
2
+
3
+ Exposes a Blink camera and its pan/tilt mount to any Model Context Protocol host: see
4
+ where the camera points, aim it, and capture a frame. Works either standalone (it owns
5
+ the camera's live session) or against a running bridge, which is useful when the same
6
+ camera is already streaming video somewhere else.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ __version__ = "0.1.0"
12
+
13
+ __all__ = ["__version__"]