使用说明
Dahua MCP Server
MCP server for managing Dahua/Amcrest IP cameras via their CGI HTTP API. Supports multiple cameras from a single instance, with both stdio and HTTP transports.
Quick Start
1. Create a cameras config file
Create ~/.config/dahua-mcp/cameras.yaml (or cameras.json) with your camera credentials. This is the default config location — no env var needed.
JSON (cameras.json):
{
"cameras": [
{
"name": "front-door",
"host": "192.168.1.108",
"port": 80,
"username": "admin",
"password": "secret"
},
{
"name": "backyard",
"host": "192.168.1.109",
"port": 80,
"username": "admin",
"password": "secret"
}
]
}
YAML (cameras.yaml):
cameras:
- name: front-door
host: 192.168.1.108
port: 80
username: admin
password: secret
- name: backyard
host: 192.168.1.109
port: 80
username: admin
password: secret
Each camera entry supports:
| Field | Required | Default | Description |
|---|---|---|---|
name |
Yes | — | Unique camera name used in all tool calls |
host |
Yes | — | Camera hostname or IP address |
port |
No | 80 |
HTTP port (443 auto-enables HTTPS) |
username |
Yes | — | Camera username |
password |
Yes | — | Camera password |
verify_ssl |
No | false |
Verify SSL certificates |
2. Add the MCP server
Option A: stdio (Claude Code / Claude Desktop)
Using the Claude Code CLI (cameras config auto-discovered from ~/.config/dahua-mcp/):
# From PyPI
claude mcp add dahua-mcp -- uvx dahua-mcp
# From a local clone
claude mcp add dahua-mcp -- uv run --directory /path/to/dahua-mcp dahua-mcp
# With a custom config path
claude mcp add dahua-mcp -e DAHUA_CAMERAS_CONFIG=/path/to/cameras.json -- uvx dahua-mcp
Or manually add to your Claude Code settings (~/.claude.json) or Claude Desktop config:
{
"mcpServers": {
"dahua-mcp": {
"type": "stdio",
"command": "uvx",
"args": ["dahua-mcp"]
}
}
}
Or run from a local clone:
{
"mcpServers": {
"dahua-mcp": {
"type": "stdio",
"command": "uv",
"args": ["run", "--directory", "/path/to/dahua-mcp", "dahua-mcp"]
}
}
}
To override the config location, add an env block:
{
"env": {
"DAHUA_CAMERAS_CONFIG": "/custom/path/cameras.yaml"
}
}
Option B: Docker container (HTTP transport)
Run the container with your config file volume-mounted:
docker run -d \
-v /path/to/cameras.json:/config/cameras.json:ro \
-p 8000:8000 \
ghcr.io/brianegge/dahua-mcp:latest
Then add the HTTP server to your MCP client config:
{
"mcpServers": {
"dahua-mcp": {
"type": "http",
"url": "http://localhost:8000/mcp"
}
}
}
To use a YAML config with Docker:
docker run -d \
-v /path/to/cameras.yaml:/config/cameras.yaml:ro \
-e DAHUA_CAMERAS_CONFIG=/config/cameras.yaml \
-p 8000:8000 \
ghcr.io/brianegge/dahua-mcp:latest
To add bearer token authentication:
docker run -d \
-v /path/to/cameras.json:/config/cameras.json:ro \
-e MCP_HTTP_BEARER_TOKEN=your-secret-token \
-p 8000:8000 \
ghcr.io/brianegge/dahua-mcp:latest
{
"mcpServers": {
"dahua-mcp": {
"type": "http",
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer your-secret-token"
}
}
}
}
Environment Variables
All settings beyond camera credentials are configured via environment variables:
| Variable | Default | Description |
|---|---|---|
DAHUA_CAMERAS_CONFIG |
~/.config/dahua-mcp/cameras.yaml |
Path to cameras config file (JSON or YAML). Auto-discovers from ~/.config/dahua-mcp/ if not set. |
DAHUA_TIMEOUT |
20 |
HTTP request timeout in seconds |
DAHUA_SNAPSHOT_DIR |
system temp dir | Directory take_snapshot writes JPEGs to |
READ_ONLY_MODE |
false |
Disable all write tools (reboot, set_config, etc.) |
DISABLED_TAGS |
— | Comma-separated tags to disable (e.g. destructive,write) |
LOG_LEVEL |
INFO |
Logging level |
RATE_LIMIT_ENABLED |
false |
Enable rate limiting |
RATE_LIMIT_MAX_REQUESTS |
60 |
Max requests per window |
RATE_LIMIT_WINDOW_MINUTES |
1 |
Rate limit window in minutes |
MCP_TRANSPORT |
stdio |
Transport type: stdio, sse, or http |
MCP_HTTP_HOST |
0.0.0.0 |
HTTP bind host |
MCP_HTTP_PORT |
8000 |
HTTP bind port |
MCP_HTTP_BEARER_TOKEN |
— | Optional bearer token for HTTP auth |
Available Tools
Discovery
| Tool | Description | Read-Only |
|---|---|---|
list_cameras |
List all configured cameras (name, host, port) | Yes |
System Info
| Tool | Description | Read-Only |
|---|---|---|
get_system_info |
Get full system info (device type, serial, firmware, etc.) | Yes |
get_device_type |
Get the device type/model | Yes |
get_software_version |
Get firmware version | Yes |
get_machine_name |
Get the camera's machine name | Yes |
get_serial_number |
Get the serial number | Yes |
get_hardware_version |
Get the hardware version | Yes |
get_vendor |
Get the vendor/manufacturer | Yes |
Config Read
| Tool | Description | Read-Only |
|---|---|---|
get_config |
Get any config section by name | Yes |
get_motion_detection |
Get motion detection config | Yes |
get_video_in_mode |
Get video input mode (day/night profile) | Yes |
get_exposure |
Get exposure mode, shutter range and gain per day/night profile | Yes |
get_encoding_config |
Get encoding/streaming config | Yes |
get_network_config |
Get network config (IP, gateway, DNS) | Yes |
get_ntp_config |
Get NTP time sync config | Yes |
Config Write
| Tool | Description | Destructive |
|---|---|---|
set_config |
Set arbitrary config key-value pairs | Yes |
enable_motion_detection |
Enable/disable motion detection | No |
set_shutter_range |
Cap the slowest shutter (e.g. 1/60) to cut motion blur |
No |
set_exposure_auto |
Hand exposure back to the camera | No |
set_record_mode |
Set recording mode (auto/manual/off) | No |
System Control
| Tool | Description | Destructive |
|---|---|---|
reboot |
Reboot the camera | Yes |
take_snapshot |
Take a JPEG snapshot, saved to disk (returns the path) | No |
Storage / Recordings
| Tool | Description | Read-Only |
|---|---|---|
get_storage_info |
Hard drive status, capacity and health | Yes |
find_recordings |
List recorded files, to confirm a recorder is recording | Yes |
Logs
| Tool | Description | Read-Only |
|---|---|---|
search_logs |
Search camera logs by time range and type | Yes |
Firmware quirks
The device protocol lives in aiodahua, which this server is a thin MCP layer over. That is where the digest/basic auth negotiation, the CGI parsing and the firmware quirks are implemented and tested, so the Home Assistant integration and this server share one implementation instead of two.
Behaviours confirmed against real hardware, handled for you:
- Clearing a config field. Sending
Key=with an empty value returnsOKand changes nothing. Pass""toset_configand it is sent as a single space, which the device trims back to"". &cannot appear in a config value. The firmware percent-decodes the whole query string before splitting it on&, so an encoded&still terminates the value — the request either returns HTTP 400 or the value is silently truncated.set_configraises a clear error instead.#,+,%,=,/and'are all fine and round-trip correctly.used == totalon a healthy disk. A recorder pre-allocates the whole drive into fixed-size blocks when it formats, so even a brand new disk reports 100% used.get_storage_infoflags this so it is not mistaken for a full disk; usefind_recordingsto confirm writes are landing.find_recordingschannels are 1-based. Channel0is rejected.- A missing endpoint is an error body, not a 404. Older firmware answers
Bad Request!, 2024 builds answerNot Implemented!with HTTP 501, and some failures arrive as HTTP 200 with an error body that would otherwise parse into a plausible-looking dict. - Some devices only accept basic auth. Digest is tried first and the fallback is automatic.
- A Lorex E891AB reboots if asked for
audio.cgi, dropping HTTP and RTSP for ~105 seconds. The library refuses that request on the brands known to do it.
Development
git clone https://github.com/brianegge/dahua-mcp.git
cd dahua-mcp
uv sync
uv run pre-commit install # Install ruff format/lint hook
uv run pytest # Run tests
uv run ruff check . # Lint
License
MIT License - see LICENSE file for details.