#
tokens: 4961/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── .python-version
├── images
│   ├── magnet-claude.png
│   ├── magnet-webcam.jpg
│   ├── orange-claude.png
│   └── orange-webcam.jpg
├── pyproject.toml
├── README.md
├── uv.lock
└── videocapture_mcp.py
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.10
2 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Video Still Capture MCP
  2 | 
  3 | **A Model Context Protocol server for accessing and controlling webcams via OpenCV**
  4 | 
  5 | ## Overview
  6 | 
  7 | Video Still Capture MCP is a Python implementation of the Model Context Protocol (MCP) that provides AI assistants with the ability to access and control webcams and video sources through OpenCV. This server exposes a set of tools that allow language models to capture images, manipulate camera settings, and manage video connections. There is no video capture.
  8 | 
  9 | ## Examples
 10 | 
 11 | Here are some examples of the Video Still Capture  MCP server in action:
 12 | 
 13 | ### Orange Example
 14 | Left: Claude's view of the image | Right: Actual webcam capture
 15 | :-------------------------:|:-------------------------:
 16 | ![Claude's view of orange](images/orange-claude.png) | ![Webcam capture of orange](images/orange-webcam.jpg)
 17 | 
 18 | ### Magnet Example
 19 | Left: Claude's view of the image | Right: Actual webcam capture
 20 | :-------------------------:|:-------------------------:
 21 | ![Claude's view of magnet](images/magnet-claude.png) | ![Webcam capture of magnet](images/magnet-webcam.jpg)
 22 | 
 23 | ## Installation
 24 | 
 25 | ### Prerequisites
 26 | 
 27 | - Python 3.10+
 28 | - [OpenCV](https://opencv.org/) (`opencv-python`)
 29 | - [MCP Python SDK](https://modelcontextprotocol.io/docs/)
 30 | - [UV](https://astral.sh/uv/) (optional)
 31 | 
 32 | ### Installation from source
 33 | 
 34 | ```bash
 35 | git clone https://github.com/13rac1/videocapture-mcp.git
 36 | cd videocapture-mcp
 37 | pip install -e .
 38 | ```
 39 | 
 40 | Run the MCP server:
 41 | 
 42 | ```bash
 43 | mcp dev videocapture_mcp.py
 44 | ```
 45 | 
 46 | ## Integrating with Claude for Desktop
 47 | 
 48 | ### macOS/Linux
 49 | 
 50 | Edit your Claude Desktop configuration:
 51 | 
 52 | ```bash
 53 | # Mac
 54 | nano ~/Library/Application\ Support/Claude/claude_desktop_config.json
 55 | # Linux
 56 | nano ~/.config/Claude/claude_desktop_config.json 
 57 | ```
 58 | 
 59 | Add this MCP server configuration:
 60 | 
 61 | ```json
 62 | {
 63 |   "mcpServers": {
 64 |     "VideoCapture ": {
 65 |       "command": "uv",
 66 |       "args": [
 67 |         "run",
 68 |         "--with",
 69 |         "mcp[cli]",
 70 |         "--with",
 71 |         "numpy",
 72 |         "--with",
 73 |         "opencv-python",
 74 |         "mcp",
 75 |         "run",
 76 |         "/ABSOLUTE_PATH/videocapture_mcp.py"
 77 |       ]
 78 |     }
 79 |   }
 80 | }
 81 | ```
 82 | 
 83 | Ensure you replace `/ABSOLUTE_PATH/videocapture-mcp` with the project's absolute path.
 84 | 
 85 | ### Windows
 86 | 
 87 | Edit your Claude Desktop configuration:
 88 | 
 89 | ```powershell
 90 | nano $env:AppData\Claude\claude_desktop_config.json
 91 | ```
 92 | 
 93 | Add this MCP server configuration:
 94 | 
 95 | ```json
 96 | {
 97 |   "mcpServers": {
 98 |     "VideoCapture": {
 99 |       "command": "uv",
100 |       "args": [
101 |         "run",
102 |         "--with",
103 |         "mcp[cli]",
104 |         "--with",
105 |         "numpy",
106 |         "--with",
107 |         "opencv-python",
108 |         "mcp",
109 |         "run",
110 |         "C:\ABSOLUTE_PATH\videocapture-mcp\videocapture_mcp.py"
111 |       ]
112 |     }
113 |   }
114 | }
115 | ```
116 | 
117 | Ensure you replace `C:\ABSOLUTE_PATH\videocapture-mcp` with the project's absolute path.
118 | 
119 | ### Using the Installation Command
120 | 
121 | Alternatively, you can use the `mcp` CLI to install the server:
122 | 
123 | ```bash
124 | mcp install videocapture_mcp.py
125 | ```
126 | 
127 | This will automatically configure Claude Desktop to use your videocapture MCP server.
128 | 
129 | Once integrated, Claude will be able to access your webcam or video source when requested. Simply ask Claude to take a photo or perform any webcam-related task.
130 | 
131 | ## Features
132 | 
133 | - **Quick Image Capture**: Capture a single image from a webcam without managing connections
134 | - **Connection Management**: Open, manage, and close camera connections
135 | - **Video Properties**: Read and adjust camera settings like brightness, contrast, and resolution
136 | - **Image Processing**: Basic image transformations like horizontal flipping
137 | 
138 | ## Tools Reference
139 | 
140 | ### `quick_capture`
141 | 
142 | Quickly open a camera, capture a single frame, and close it.
143 | 
144 | ```python
145 | quick_capture(device_index: int = 0, flip: bool = False) -> Image
146 | ```
147 | 
148 | - **device_index**: Camera index (0 is usually the default webcam)
149 | - **flip**: Whether to horizontally flip the image
150 | - **Returns**: The captured frame as an Image object
151 | 
152 | ### `open_camera`
153 | 
154 | Open a connection to a camera device.
155 | 
156 | ```python
157 | open_camera(device_index: int = 0, name: Optional[str] = None) -> str
158 | ```
159 | 
160 | - **device_index**: Camera index (0 is usually the default webcam)
161 | - **name**: Optional name to identify this camera connection
162 | - **Returns**: Connection ID for the opened camera
163 | 
164 | ### `capture_frame`
165 | 
166 | Capture a single frame from the specified video source.
167 | 
168 | ```python
169 | capture_frame(connection_id: str, flip: bool = False) -> Image
170 | ```
171 | 
172 | - **connection_id**: ID of the previously opened video connection
173 | - **flip**: Whether to horizontally flip the image
174 | - **Returns**: The captured frame as an Image object
175 | 
176 | ### `get_video_properties`
177 | 
178 | Get properties of the video source.
179 | 
180 | ```python
181 | get_video_properties(connection_id: str) -> dict
182 | ```
183 | 
184 | - **connection_id**: ID of the previously opened video connection
185 | - **Returns**: Dictionary of video properties (width, height, fps, etc.)
186 | 
187 | ### `set_video_property`
188 | 
189 | Set a property of the video source.
190 | 
191 | ```python
192 | set_video_property(connection_id: str, property_name: str, value: float) -> bool
193 | ```
194 | 
195 | - **connection_id**: ID of the previously opened video connection
196 | - **property_name**: Name of the property to set (width, height, brightness, etc.)
197 | - **value**: Value to set
198 | - **Returns**: True if successful, False otherwise
199 | 
200 | ### `close_connection`
201 | 
202 | Close a video connection and release resources.
203 | 
204 | ```python
205 | close_connection(connection_id: str) -> bool
206 | ```
207 | 
208 | - **connection_id**: ID of the connection to close
209 | - **Returns**: True if successful
210 | 
211 | ### `list_active_connections`
212 | 
213 | List all active video connections.
214 | 
215 | ```python
216 | list_active_connections() -> list
217 | ```
218 | 
219 | - **Returns**: List of active connection IDs
220 | 
221 | ## Example Usage
222 | 
223 | Here's how an AI assistant might use the Webcam MCP server:
224 | 
225 | 1. **Take a quick photo**:
226 |    ```
227 |    I'll take a photo using your webcam.
228 |    ```
229 |    (The AI would call `quick_capture()` behind the scenes)
230 | 
231 | 2. **Open a persistent connection**:
232 |    ```
233 |    I'll open a connection to your webcam so we can take multiple photos.
234 |    ```
235 |    (The AI would call `open_camera()` and store the connection ID)
236 | 
237 | 3. **Adjust camera settings**:
238 |    ```
239 |    Let me increase the brightness of the webcam feed.
240 |    ```
241 |    (The AI would call `set_video_property()` with the appropriate parameters)
242 | 
243 | ## Advanced Usage
244 | 
245 | ### Resource Management
246 | 
247 | The server automatically manages camera resources, ensuring all connections are properly released when the server shuts down. For long-running applications, it's good practice to explicitly close connections when they're no longer needed.
248 | 
249 | ### Multiple Cameras
250 | 
251 | If your system has multiple cameras, you can specify the device index when opening a connection:
252 | 
253 | ```python
254 | # Open the second webcam (index 1)
255 | connection_id = open_camera(device_index=1)
256 | ```
257 | 
258 | ## Troubleshooting
259 | 
260 | - **Camera Not Found**: Ensure your webcam is properly connected and not in use by another application
261 | - **Permission Issues**: Some systems require explicit permission to access the camera
262 | - **OpenCV Installation**: If you encounter issues with OpenCV, refer to the [official installation guide](https://docs.opencv.org/master/d5/de5/tutorial_py_setup_in_windows.html)
263 | 
264 | ## License
265 | 
266 | This project is licensed under the MIT License - see the LICENSE file for details.
267 | 
268 | ## Contributing
269 | 
270 | Contributions are welcome! Please feel free to submit a Pull Request.
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "videocapture-mcp"
 3 | version = "0.1.0"
 4 | description = "Model Context Protocol (MCP) server to capture from an OpenCV-compatible webcam"
 5 | readme = "README.md"
 6 | requires-python = ">=3.10"
 7 | dependencies = [
 8 |     "httpx>=0.28.1",
 9 |     "mcp[cli]>=1.4.1",
10 |     "opencv-python>=4.11.0.86",
11 | ]
12 | 
13 | [[project.authors]]
14 | name = "13rac1"
15 | 
16 | [project.scripts]
17 | videocapture_mcp = "videocapture_mcp.main"
```

--------------------------------------------------------------------------------
/videocapture_mcp.py:
--------------------------------------------------------------------------------

```python
  1 | import cv2
  2 | from contextlib import asynccontextmanager
  3 | from collections.abc import AsyncIterator
  4 | from dataclasses import dataclass
  5 | from datetime import datetime
  6 | from typing import Optional, Dict
  7 | from mcp.server.fastmcp import FastMCP, Image
  8 | 
  9 | # Store active video capture objects
 10 | active_captures: Dict[str, cv2.VideoCapture] = {}
 11 | 
 12 | # Define our application context
 13 | @dataclass
 14 | class AppContext:
 15 |     active_captures: Dict[str, cv2.VideoCapture]
 16 | 
 17 | @asynccontextmanager
 18 | async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
 19 |     """Manage application lifecycle with camera resource cleanup"""
 20 |     # Initialize on startup
 21 |     #print("Starting VideoCapture MCP Server")
 22 |     try:
 23 |         # Pass the active_captures dictionary in the context
 24 |         yield AppContext(active_captures=active_captures)
 25 |     finally:
 26 |         # Cleanup on shutdown
 27 |         #print("Shutting down VideoCapture MCP Server")
 28 |         for connection_id, cap in active_captures.items():
 29 |             cap.release()
 30 |         active_captures.clear()
 31 | 
 32 | # Initialize the FastMCP server with lifespan
 33 | mcp = FastMCP("VideoCapture", 
 34 |               description="Provides access to camera and video streams via OpenCV",
 35 |               dependencies=["opencv-python", "numpy"],
 36 |               lifespan=app_lifespan)
 37 | 
 38 | def main():
 39 |     """Main entry point for the VideoCapture Server"""
 40 | 
 41 |     mcp.run()
 42 |     
 43 | @mcp.tool()
 44 | def quick_capture(device_index: int = 0, flip: bool = False) -> Image:
 45 |     """
 46 |     Quickly open a camera, capture a single frame, and close it.
 47 |     If the camera is already open, use the existing connection.
 48 |     
 49 |     Args:
 50 |         device_index: Camera index (0 is usually the default webcam)
 51 |         flip: Whether to horizontally flip the image
 52 |     
 53 |     Returns:
 54 |         The captured frame as an Image object
 55 |     """
 56 |     # Check if this device is already open
 57 |     device_key = None
 58 |     for key, cap in active_captures.items():
 59 |         if key.startswith(f"camera_{device_index}_"):
 60 |             device_key = key
 61 |             break
 62 |     
 63 |     # If device is not already open, open it temporarily
 64 |     temp_connection = False
 65 |     if device_key is None:
 66 |         device_key = open_camera(device_index)
 67 |         temp_connection = True
 68 |     
 69 |     try:
 70 |         # Capture the frame
 71 |         frame = capture_frame(device_key, flip)
 72 |         return frame
 73 |     finally:
 74 |         # Close the connection if we opened it temporarily
 75 |         if temp_connection:
 76 |             close_connection(device_key)
 77 | 
 78 | @mcp.tool()
 79 | def open_camera(device_index: int = 0, name: Optional[str] = None) -> str:
 80 |     """
 81 |     Open a connection to a camera device.
 82 |     
 83 |     Args:
 84 |         device_index: Camera index (0 is usually the default webcam)
 85 |         name: Optional name to identify this camera connection
 86 |     
 87 |     Returns:
 88 |         Connection ID for the opened camera
 89 |     """
 90 |     if name is None:
 91 |         name = f"camera_{device_index}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
 92 |     
 93 |     cap = cv2.VideoCapture(device_index)
 94 |     if not cap.isOpened():
 95 |         raise ValueError(f"Failed to open camera at index {device_index}")
 96 |     
 97 |     active_captures[name] = cap
 98 |     return name
 99 | 
100 | @mcp.tool()
101 | def capture_frame(connection_id: str, flip: bool = False) -> Image:
102 |     """
103 |     Capture a single frame from the specified video source.
104 |     
105 |     Args:
106 |         connection_id: ID of the previously opened video connection
107 |         flip: Whether to horizontally flip the image
108 |     
109 |     Returns:
110 |         The captured frame as an Image object
111 |     """
112 |     if connection_id not in active_captures:
113 |         raise ValueError(f"No active connection with ID: {connection_id}")
114 |     
115 |     cap = active_captures[connection_id]
116 |     ret, frame = cap.read()
117 |     
118 |     if not ret:
119 |         raise RuntimeError(f"Failed to capture frame from {connection_id}")
120 |     
121 |     if flip:
122 |         frame = cv2.flip(frame, 1)  # 1 for horizontal flip
123 |     
124 |     
125 |     # Encode the image as PNG
126 |     _, png_data = cv2.imencode('.png', frame)
127 |     
128 |     # Return as MCP Image object
129 |     return Image(data=png_data.tobytes(), 
130 |                  format="png")
131 | 
132 | @mcp.tool()
133 | def get_video_properties(connection_id: str) -> dict:
134 |     """
135 |     Get properties of the video source.
136 |     
137 |     Args:
138 |         connection_id: ID of the previously opened video connection
139 |     
140 |     Returns:
141 |         Dictionary of video properties
142 |     """
143 |     if connection_id not in active_captures:
144 |         raise ValueError(f"No active connection with ID: {connection_id}")
145 |     
146 |     cap = active_captures[connection_id]
147 |     
148 |     properties = {
149 |         "width": int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
150 |         "height": int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)),
151 |         "fps": cap.get(cv2.CAP_PROP_FPS),
152 |         "frame_count": int(cap.get(cv2.CAP_PROP_FRAME_COUNT)),
153 |         "brightness": cap.get(cv2.CAP_PROP_BRIGHTNESS),
154 |         "contrast": cap.get(cv2.CAP_PROP_CONTRAST),
155 |         "saturation": cap.get(cv2.CAP_PROP_SATURATION),
156 |         "format": int(cap.get(cv2.CAP_PROP_FORMAT))
157 |     }
158 |     
159 |     return properties
160 | 
161 | @mcp.tool()
162 | def set_video_property(connection_id: str, property_name: str, value: float) -> bool:
163 |     """
164 |     Set a property of the video source.
165 |     
166 |     Args:
167 |         connection_id: ID of the previously opened video connection
168 |         property_name: Name of the property to set (width, height, brightness, etc.)
169 |         value: Value to set
170 |     
171 |     Returns:
172 |         True if successful, False otherwise
173 |     """
174 |     if connection_id not in active_captures:
175 |         raise ValueError(f"No active connection with ID: {connection_id}")
176 |     
177 |     cap = active_captures[connection_id]
178 |     
179 |     property_map = {
180 |         "width": cv2.CAP_PROP_FRAME_WIDTH,
181 |         "height": cv2.CAP_PROP_FRAME_HEIGHT,
182 |         "fps": cv2.CAP_PROP_FPS,
183 |         "brightness": cv2.CAP_PROP_BRIGHTNESS,
184 |         "contrast": cv2.CAP_PROP_CONTRAST,
185 |         "saturation": cv2.CAP_PROP_SATURATION,
186 |         "auto_exposure": cv2.CAP_PROP_AUTO_EXPOSURE,
187 |         "auto_focus": cv2.CAP_PROP_AUTOFOCUS
188 |     }
189 |     
190 |     if property_name not in property_map:
191 |         raise ValueError(f"Unknown property: {property_name}")
192 |     
193 |     return cap.set(property_map[property_name], value)
194 | 
195 | @mcp.tool()
196 | def close_connection(connection_id: str) -> bool:
197 |     """
198 |     Close a video connection and release resources.
199 |     
200 |     Args:
201 |         connection_id: ID of the connection to close
202 |     
203 |     Returns:
204 |         True if successful
205 |     """
206 |     if connection_id not in active_captures:
207 |         raise ValueError(f"No active connection with ID: {connection_id}")
208 |     
209 |     active_captures[connection_id].release()
210 |     del active_captures[connection_id]
211 |     return True
212 | 
213 | @mcp.tool()
214 | def list_active_connections() -> list:
215 |     """
216 |     List all active video connections.
217 |     
218 |     Returns:
219 |         List of active connection IDs
220 |     """
221 |     return list(active_captures.keys())
222 | 
223 |     mcp.run(transport='stdio')
224 | 
225 | # For: $ mcp run videocapture_mcp.py
226 | def run():
227 |     main()
228 | 
229 | if __name__ == "__main__":
230 |     main()
231 | 
```