Skip to main content
Glama

axis-camera-mcp

An MCP (Model Context Protocol) server that lets an LLM (Claude Code/Desktop) talk to an Axis network camera over VAPIX (Axis's HTTP/JSON camera API): view the live stream in VLC, control pan/tilt/zoom, adjust image sensor settings (brightness, exposure, gain, white balance), capture snapshots, add text/image overlays, get/set the clock, manage local camera user accounts, and control the light.

Built and live-verified against an AXIS M3057-PLVE Mk II on a local network. See docs/live-verification-checklist.md for exactly what's been tested live vs. not, and docs/open-questions.md for a confirmed limitation of this specific camera: PTZ commands succeed and the reported position updates, but produce no real visual pan/tilt/zoom effect on this fixed-camera model.

Architecture

axis_camera_mcp/
  config.py            Loads camera host/username/password from config.json
  vapix/
    session.py          requests.Session + HTTP Digest auth
    jsonrpc.py           Client for VAPIX's modern JSON-RPC-style APIs
    legacy.py             Client for VAPIX's legacy query-param/plain-text CGIs
    client.py              VapixClient facade - the only thing tools/* imports
    exceptions.py
  vlc_launcher.py       Starts/stops the local VLC install to view the RTSP stream
  rag/                  Local knowledge base: VAPIX docs + this project's own
                        live-verified findings, hybrid BM25+embedding search
                        (see "Knowledge base (RAG)" below)
  app.py / context.py  Shared FastMCP instance + lazy VapixClient/VlcLauncher/
                        HybridRetriever singletons
  tools/                One module per capability, each registering @mcp.tool()s
  server.py             Entrypoint: import tools, preload_rag(), mcp.run()
tests/
  test_*.py             Unit tests, mocked HTTP, no live camera needed
  manual/                Plain scripts (not pytest) exercised against the real camera
docs/
  live-verification-checklist.md   One row per tool - what's actually been tested live
  open-questions.md                Confirmed limitations and things not yet tried

Two VAPIX request styles exist side by side on this camera, and the client layer handles both without leaking the difference into tool code:

  • Modern JSON-RPC style (api-discovery, basicdeviceinfo, streamprofile, lightcontrol, time.cgi, dynamicoverlay): POST JSON body {"apiVersion", "method", "params"}, response {"data"} or {"error"}.

  • Legacy query-param/plain-text CGIs (com/ptz.cgi, com/ptzconfig.cgi, pwdgrp.cgi, usergroup.cgi, jpg/image.cgi): plain GET with query params, responses are plain text (sometimes key=value lines) or, for the snapshot endpoint, raw JPEG bytes.

Setup

Credentials live in a config file, never in code or environment variables:

copy config.example.json config.json
notepad config.json   # fill in host/username/password

config.json is gitignored - it never gets committed.

py -m venv .venv
.venv\Scripts\python -m pip install -e ".[dev]"

Running

.venv\Scripts\python -m axis_camera_mcp.server

Knowledge base (RAG)

A local, offline knowledge base lets the LLM search the real VAPIX API docs and this project's own hard-won, live-verified findings (undocumented required fields, real parameter values confirmed only through live trial-and-error, etc.) instead of guessing or re-deriving them each time.

All open-source, all local - no paid API, no vector-DB service:

  • Crawler (rag/crawler.py): plain requests + BeautifulSoup against developer.axis.com (a server-rendered Docusaurus site - no headless browser needed), plus this project's own README/docs/vapix/*.py as local sources.

  • Chunking (rag/chunker.py): paragraph-aware, ~700 chars with 100 overlap.

  • Retrieval (rag/retriever.py): hybrid - BM25 (rank-bm25, exact keyword matches like streamProfileName) and dense embeddings (sentence-transformers, all-MiniLM-L6-v2, CPU-only, for paraphrased questions), combined via reciprocal rank fusion. A plain numpy .npz file serves as the vector store (a few hundred chunks makes brute-force cosine search microseconds-scale - not worth a real vector-DB dependency).

  • Tracking (rag/tracking.py): a local SQLite file logs every ingestion run (per-source chunk/char counts, ok/error status) and every query (text, results, scores) - inspectable directly or via the rag_stats tool.

Setup: sentence-transformers pulls in torch, a genuinely heavy install. On a CPU-only machine, install the CPU-only wheel explicitly first to keep it much smaller than the default resolved build:

.venv\Scripts\python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
.venv\Scripts\python -m pip install -e ".[dev]"

Build/refresh the index (a separate, manually-run step - never runs automatically at server startup, so startup stays fast and network-call-free):

.venv\Scripts\python -m axis_camera_mcp.rag.ingest

Edit axis_camera_mcp/rag/sources.json to add/remove doc pages or local files, then re-run the command above. The embedding model (~80MB) downloads once from Hugging Face Hub on first run and is cached afterward; everything else is fully offline. Generated index files (rag/data/*.jsonl, *.npz, *.sqlite3) are gitignored.

Query it with the search_knowledge and rag_stats tools (see Tool reference below).

Register with Claude Code/Desktop

{
  "mcpServers": {
    "axis-camera": {
      "command": "<path-to-repo>\\.venv\\Scripts\\python.exe",
      "args": ["-m", "axis_camera_mcp.server"]
    }
  }
}

Restart Claude Code/Desktop after adding this for the tools to appear.

Testing

.venv\Scripts\python -m pytest tests/ --ignore=tests/manual   # unit tests, mocked HTTP
.venv\Scripts\python tests/manual/live_smoke_test.py           # read-only checks against the real camera

The tests/manual/ scripts talk to the real camera in config.json - review what each one does before running it against a camera you care about. live_smoke_test.py is read-only. The mcp_harness_*.py scripts exercise the full MCP tool-call path (including mutations like PTZ moves, clock/timezone changes, and user add/remove) - these were this project's own live-verification scripts; treat them as reference, not something to re-run casually against a production camera.

Tool reference

Device/API info (read-only): get_device_info, get_supported_apis, get_camera_overview

Streaming (view the live stream in the local VLC install): start_stream, stop_stream, get_stream_status, list_stream_profiles

Snapshot (single still frame, separate from the video stream): save_snapshot (file only, the LLM never sees the image), get_snapshot (returned directly to the LLM so it can describe what's in view)

PTZ (pan/tilt/zoom - see docs/open-questions.md re: no visual effect on this specific camera): ptz_move_absolute, ptz_move_relative, ptz_get_position, ptz_save_preset, ptz_goto_preset, ptz_list_presets

Image sensor (brightness/contrast/saturation/sharpness, exposure, gain, shutter, white balance - the actual sensor/ISP settings, distinct from PTZ's framing/crop and light's physical illuminator): get_image_settings, set_image_appearance, set_exposure_mode, set_exposure_value, set_manual_gain, set_manual_shutter, set_white_balance

Overlays (text/image burned into the video stream): add_text_overlay, set_text_overlay, add_image_overlay, set_image_overlay, list_overlays, remove_overlay, get_overlay_capabilities

Clock: get_clock_info, set_clock, set_timezone

Light (this specific camera has no light hardware - confirmed via get_light_status, which reports that clearly): get_light_status, activate_light/deactivate_light, enable_light/disable_light, set_light_intensity, get_valid_intensity, set_light_auto_mode

Users - mutations require confirm: true, the one area with real security consequence: list_users (returns every account with its privilege groups, for a privileged caller), add_user, update_user, remove_user

Knowledge base (see "Knowledge base (RAG)" above): search_knowledge (hybrid BM25+embedding search over VAPIX docs and this project's own live-verified findings, with source citations and scores), rag_stats (what's indexed, from where, when, and recent query history - states plainly if the index needs rag.ingest run)

Safety

User-account mutations (add_user, update_user, remove_user) require an explicit confirm: true tool argument and raise a clear error without it - this is the one area where a careless call has real security consequence (unauthorized access or an accidental lockout). Everything else (PTZ, light, clock, streaming, overlays, snapshots) is unconfirmed by design: it's freely reversible and this is a single camera the user owns directly, not a shared production system.

-
license - not tested
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • MCP server for Pentest-Tools.com: run scans, manage findings and reports via your preffered LLM.

  • MCP server for Clipkit — gives AI agents a video toolbox via the Clipkit schema.

  • MCP server for AI dialogue using various LLM models via AceDataCloud

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cdp/GSX2026-axis-camera-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server