AXIS MCP Server
Enables Gemini to control Axis cameras via the MCP server, providing tools for device management, imaging, event monitoring, analytics, and application control.
Enables ChatGPT to control Axis cameras via the MCP server, providing tools for device management, imaging, event monitoring, analytics, and application control.
Enables Perplexity to control Axis cameras via the MCP server, providing tools for device management, imaging, event monitoring, analytics, and application control.
Allows controlling YouTube live streams via CamStreamer integration, enabling AI to start/stop streams and manage playback.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@AXIS MCP Servercapture a snapshot and describe the scene"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
EdgeMCP for Axis (on-camera ACAP)
A standalone ACAP (.eap) that runs a Model Context Protocol server directly on an Axis camera
(ARTPEC-8, AXIS OS 12.10–13 — e.g. Q1656). It exposes VAPIX-backed tools over Streamable HTTP so
Claude Desktop / Claude Code can connect to the camera and inspect/control it.
It speaks both MCP protocol revisions from one endpoint: 2025-era clients over the stateless legacy
path, and 2026-07-28 clients — which dropped the initialize handshake and protocol sessions
entirely — natively, including server/discover and cacheable tools/list results.
Built with the ACAP Native SDK (Node.js 20 bundled into the package). No Docker runs on the camera —
Docker is only used to build the .eap on your Mac.
2.0.0 removed the on-camera agent. 1.x bundled a 45M language model, speech-to-text and a neural voice alongside the MCP server — 89 % of the download and nine of the thirteen endpoints, inside a product called EdgeMCP. This is an MCP server and nothing else. The agent continues as its own project; its source was moved to
Needle LLM on AXIS/src-extracted-from-edgemcp/.The package id changed with it,
needle_mcp→edgemcp, so existing installs are not upgraded in place and client URLs move to/local/edgemcp/mcp.
Links: Project page · Releases (prebuilt .eap)
Layout
EdgeMCP-for-Axis/
├── Dockerfile # ACAP Native SDK build (aarch64)
├── build.sh # docker build + docker cp -> .eap
├── dev-loop.sh # fast iteration: build, deploy, ask, tail
├── release.sh # build + verify + notes -> releases/
└── app/
├── manifest.json # schemaVersion 2.2.0; appName edgemcp
├── edgemcp # launcher (filename == appName)
├── Makefile # no-op (acap-build runs make)
├── LICENSE
├── package.json # main: dist/bootstrap.js; `npm test` runs the suite
├── tsconfig.json
├── test/ # node --test: guardrail/annotation invariants,
│ # manifest schema-v2 checks, and both protocol
│ # eras exercised over real HTTP
├── src/
│ ├── bootstrap.ts # HTTP servers: /mcp, /status.cgi, /settings.cgi;
│ │ # createMcpHandler per listener, origin guard
│ ├── mcpServer.ts # tool catalog (built once) + per-request server
│ ├── guardrails.ts # access levels, per-caller rate limits,
│ │ # allow-lists, and the tool annotations
│ ├── version.ts # app version, read from package.json
│ ├── vapix.ts # Digest auth client (MD5 + SHA-256)
│ ├── settings.ts # persisted config (PERSISTENT_DATA_PATH)
│ └── tools/ # device, imaging, events, apps, system, ptz,
│ # io, storage, streaming, camstreamer,
│ # camswitcher, camoverlay, aoa, lpv, vmd4,
│ # queuemonitor, guardApps, legacyAnalytics,
│ # demographics, peoplecounter, overlay,
│ # zipstream, viewareas, imagetuning, ptzAdvanced,
│ # diagnostics, netops, streamingApis, audio,
│ # deviceConfig, recordingPipeline, pairing,
│ # dataHub, eventPull (shared ONVIF helper),
│ # controlCgiAcapFactory + legacyAcapFactory
│ # (shared ACAP factories) + deviceConfigApi
│ # (shared REST helper)
└── html/
└── index.html # settings UI (light default + theme switcher)Related MCP server: Device MCP Server
Requirements
Camera | ARTPEC-8 (or ARTPEC-7 for the armv7hf build) |
AXIS OS | 12.10 – 13 — the floor comes from ACAP manifest schema v2, which the package uses because AXIS OS 13 requires it |
Build SDK | ACAP Native SDK 12.11.0 (schema 2.2.0 needs 12.11; older SDKs reject the manifest) |
Runtime | Node.js 20, bundled into the package |
Cameras below AXIS OS 12.10 are not supported. That is a consequence of schema v2, not a choice —
schema v2 is mandatory for AXIS OS 13, and an ACAP that does not declare compatibleOsVersions is
rolled back when the device is upgraded.
Protocol support
2025-era client | 2026-07-28 client | |
Handshake |
| none — |
Sessions | stateless | removed from the spec |
| plain result |
|
Served by | the SDK's stateless legacy path | natively |
Both arrive at the same /mcp endpoint; nothing needs configuring per client.
Tools (269 across 34 modules)
Tool | VAPIX |
| basicdeviceinfo.cgi (getAllProperties / getProperties) |
| param.cgi (Network/Brand/Firmware) + temperaturecontrol.cgi |
| time.cgi |
| viewarea/info.cgi (param.cgi fallback) |
| jpg/image.cgi → MCP image content |
| param.cgi ImageSource/Image |
| opticscontrol.cgi getOptics (ids + capabilities) |
| cascade: opticscontrol.cgi performAutofocus → startFocusSearch → opticssetup.cgi → ptz.cgi?autofocus=on (caches the working method) |
| opticscontrol.cgi setMagnification / autofocus cascade |
| SOAP GetEventInstances |
| applications/list.cgi (filtered) |
| /local/objectanalytics/control.cgi getConfiguration |
| /local/objectanalytics/control.cgi getOccupancy / getAccumulatedCounts |
| SOAP pull-point subscription (CreatePullPointSubscription / PullMessages) filtered to ObjectAnalytics topics |
| applications/list.cgi, control.cgi |
| param.cgi (writes allowlisted) |
| apidiscovery.cgi, param.cgi Properties.API/PTZ |
| firmwaremanagement.cgi getProperties |
| firmwaremanagement.cgi (legacy CGI fallback); both require |
| com/ptz.cgi |
| com/ptz.cgi?query= / ?info=1 |
| com/ptz.cgi?query=presetposcamdata (falls back to presetposcam) — presets grouped per view area, with pan/tilt/zoom when reported |
| com/ptz.cgi presets |
| io/output.cgi (word + symbol action fallback), io/port.cgi |
| record/list.cgi, record/record.cgi |
| URL builders (no request) |
| param.cgi AudioSource/AudioDevice/AudioOutput |
CamStreamer suite (require the respective ACAP installed)
Tool | Endpoint |
| /local/camstreamer/stream_list.cgi (param-tree fallback) |
| /local/camstreamer/get_streamstat.cgi |
| /local/camstreamer/set_stream_enabled.cgi (start/stop) |
| /local/camswitcher/playlists.cgi?action=get |
| /local/camswitcher/playlist_switch.cgi |
| /local/camswitcher/playlist_queue_push.cgi |
| playlist_queue_*.cgi |
| output_info.cgi, clips.cgi |
| /local/camoverlay/api/services.cgi |
| services.cgi?action=set |
| customGraphics.cgi?action=update_text |
| infoticker.cgi |
These call the CamStreamer/CamSwitcher/CamOverlay CGIs on the same camera using the same digest auth. If the corresponding ACAP isn't installed, the tool returns a clear "not installed" error.
Analytics ACAPs (require the respective ACAP installed)
Tool | Endpoint |
| /local/fflprapp/cloud[N].cgi (flat XML, base64 url/password decoded) |
| /local/fflprapp/config_hb.cgi |
| SOAP pull-point subscription filtered to ALPV.* topics (listens a few seconds — most ALPV events are one-shot, not stateful) |
| /local/fflprapp/tools.cgi?action=getImage → MCP image content |
| /local/vmd/control.cgi getConfiguration |
| SOAP pull-point subscription filtered to CameraApplicationPlatform/VMD/ topics |
| /local/queue/.api?live-sum-people.json |
| /local/queue/.api?params.json |
| /local/queue/.api?export-csv-minutes / export-csv-people (CSV parsed to rows) |
aoa_get_triggered_alerts, vmd4_get_triggered_profiles and lpv_get_recent_plates all share one ONVIF
pull-point helper (tools/eventPull.ts) — a stateful event topic (AOA/VMD4's "active" flag) reports its
current value immediately on subscribe, so a short-lived subscription doubles as a one-shot state read.
ALPV plate events are mostly stateless pulses, so that tool is a genuine "listen for N seconds" call instead.
Analytics ACAP siblings (Fence/Loitering/Motion Guard, legacy VMD/tracking apps, people counting)
Tool | Endpoint |
| /local/fenceguard/control.cgi (control.cgi factory) |
| /local/loiteringguard/control.cgi |
| /local/motionguard/control.cgi |
| /axis-cgi/vaconfig.cgi (legacy XML; stateless event, listens a few seconds) |
| /axis-cgi/vaconfig.cgi (legacy XML; stateful) |
| /axis-cgi/vaconfig.cgi (legacy XML; stateful) — deprecated, superseded by VMD4 |
| /axis-cgi/vaconfig.cgi (legacy XML; stateful) — deprecated, superseded by VMD4 |
| /local/demographics/.api |
| /local/tvpc/.api |
| /a3dpc/api/... (AXIS P8815-2 3D People Counter) |
The Fence/Loitering/Motion Guard trio shares tools/controlCgiAcapFactory.ts (same control.cgi JSON-RPC
shape as VMD4/AOA). The four legacy apps (Cross line detection 1.1, Digital autotracking, VMD 2.1, VMD 3)
share tools/legacyAcapFactory.ts — their config is arbitrarily-nested XML, so the config tools return raw
XML rather than guessing at a generic nested mapping.
Overlay / Zipstream / View Area (native VAPIX, no extra ACAP required)
Tool | Endpoint |
| /axis-cgi/dynamicoverlay/dynamicoverlay.cgi (list, getOverlayCapabilities) |
| dynamicoverlay.cgi (addText/addImage/setText/setImage/remove) |
| /axis-cgi/zipstream/getstatus.cgi |
| liststrengths.cgi / setstrength.cgi |
| listgopmodes.cgi / setgop.cgi |
| listfpsmodes.cgi / setfpsmode.cgi / setminfps.cgi |
| listprofiles.cgi / setprofile.cgi |
| /axis-cgi/viewarea/info.cgi, configure.cgi |
Camera / image tuning
Tool | Endpoint |
| /axis-cgi/daynight.cgi |
| /axis-cgi/lightcontrol.cgi |
| /axis-cgi/imagestabilization.cgi (EIS/OIS) |
| param.cgi ImageSource.I#.{Rotation,AutoRotationEnabled} |
| /axis-cgi/orientation/getlongitudinalvalue.cgi + getlateralvalue.cgi (gyro/accelerometer) |
| param.cgi Image.I#.RateControl (VBR/MBR/ABR) |
| /axis-cgi/capturemode.cgi |
| param.cgi Image.*/ImageSource.I0.CameraTiltOrientation (360°/180° cameras) |
PTZ / patrol
Tool | Endpoint |
| param.cgi GuardTour.G# (preset tours) |
| param.cgi GuardTour.G#.Tour.T# |
| /axis-cgi/recordedtour/*.cgi (manually-steered tour recordings) |
| /axis-cgi/ptz-autotracking/admin.cgi |
| /axis-cgi/ptz-orientationaid.cgi (compass overlay) |
| PTZ Control WS API ( |
Ops / diagnostics
Tool | Endpoint |
| /axis-cgi/auditlog.cgi |
| /config/rest/log/v1/... (Device Configuration API) |
| /config/rest/network-diagnostics/v1/netstats/... |
| /axis-cgi/systemready.cgi (no auth required) |
| /axis-cgi/serverreport.cgi, systemlog.cgi, accesslog.cgi |
| /axis-cgi/findmydevice.cgi |
| /axis-cgi/featureflag.cgi |
| /axis-cgi/regionalsettings.cgi |
| /axis-cgi/mdnssd.cgi |
| /axis-cgi/geolocation/get.cgi, set.cgi |
| /axis-cgi/ntp.cgi |
| /axis-cgi/network_settings.cgi (getNetworkInfo — read-only by design) |
The Log API and Network diagnostics API use a newer REST-ish "Device Configuration API" framework
(/config/rest/<api>/v1/..., PATCH/POST bodies wrapped as {"data": ...}) instead of the
{apiVersion, context, method, params} JSON-RPC shape used everywhere else — tools/deviceConfigApi.ts
is the shared helper for it. User account management (pwdgrp.cgi) and network write methods
(static IP / 802.1X / WLAN config) were deliberately left out — see ROADMAP.md's Tier 2 notes and the
network_get_info description for why.
Streaming
Tool | Endpoint |
| /axis-cgi/streamprofile.cgi |
| /axis-cgi/streamstatus.cgi (running RTSP streams) |
| /axis-cgi/media.cgi (URL builder — Matroska/MP4 container stream) |
| /axis-cgi/mqtt/client.cgi |
| /axis-cgi/mqtt/event.cgi (Event Service ⇄ MQTT bridge) |
| param.cgi Image.I#.MPEG.SignedVideo.Enabled |
| /axis-cgi/analyticsmetadataconfig.cgi |
Audio
Tool | Endpoint |
| param.cgi Audio.*, AudioSource.* |
| /axis-cgi/audiodevicecontrol.cgi |
| /axis-cgi/audioanalytics.cgi |
| /axis-cgi/audiomixer.cgi |
| /axis-cgi/mediaclip.cgi, playclip.cgi, stopclip.cgi (upload/download binary transfer not covered) |
Audio control service API, Audio relay service API (both deprecated since AXIS OS 10.12), Audio Multicast Controller (BETA, multi-speaker paging networks), Auto speaker test service API (speaker calibration hardware), and the AXIS Audio Manager Edge/Pro APIs (separate site-management software products, not a camera-resident CGI) were surveyed and deliberately excluded — see ROADMAP.md.
Device configuration
All of these use the newer /config/rest/<api>/<version>/... "Device Configuration API" REST framework
(tools/deviceConfigApi.ts), not the {apiVersion, context, method, params} JSON-RPC shape.
Tool | Endpoint |
| /config/rest/param/v2beta/... (BETA — generic param.cgi wrapper) |
| /config/rest/device-mode/v1beta (BETA) |
| /config/rest/object-snapshot/v1 |
| /config/rest/coordinate-conversion-api/v1 |
| /config/rest/event-schedules/v2 |
| /config/rest/recording-group/v2 (cloud recording pipeline, separate from storage.ts's local SD recording) |
| /config/rest/remote-object-storage/v1 (S3/Azure destinations for recording groups) |
| /config/rest/networkpairing/v1beta (BETA — pair with e.g. an AXIS D4100-E siren/light) |
| /config/rest/camera-pairing/v1beta (BETA — pull video from one paired external camera/intercom) |
| /config/rest/analytics-mqtt/v1 |
| /config/rest/data-transformation/v1beta (BETA — JQ transforms between Device Data Hub topics) |
Autopilot API, Directional audio detection API, Cellular routing API (all require hardware this box
camera doesn't have — a paired remote PTZ, an audio direction-sensing array, or a cellular modem), and
Speaker display settings/notification (a physical display screen on speaker/intercom products) were
surveyed and excluded. Network time synchronization configuration API was skipped as a newer duplicate
of the already-covered ntp_get_info/ntp_set_client_configuration. See ROADMAP.md.
See ROADMAP.md for what's next.
Security & guardrails
See SECURITY.md for the full model. In short:
No user management — the MCP cannot create users or touch credentials/network config (
set_paramwrites are allowlisted to Image/ImageSource/Brand/Time/AudioSource/Event/Overlay).Access levels — operator-set switch: Read-only / Operate (default) / Full. Applies immediately (no restart). See the map below.
Rate limits — 60 tool calls/min, 20 writes/min; PTZ presets capped at 20 via MCP.
Direct port off by default — enabling it auto-generates a bearer token.
Full audit trail — every call and every guardrail refusal in the Live Log + AXIS system log.
What the client actually sees
Three things follow from the access level, and they are visible from the client side:
tools/listis filtered. The server does not advertise what it would refuse. At Operate that is 232 of 269 tools; at Read-only, 154. This is whyreboot_camerasimply is not in the list on a default install — the tool exists, the camera declines to offer it.Side effect worth knowing: a client holding a list from before you lowered the level gets
-32602 Tool not foundrather than the guardrail's "disabled by the camera operator" explanation. Still refused, less informative.
Every tool carries annotations —
readOnlyHint,destructiveHint,idempotentHint,openWorldHint— derived from the same classification the guardrails enforce, so a tool cannot advertise itself as read-only while being treated as a write. Clients use these to decide when to ask you before calling. 154 are read-only; 19 are destructive.Rate limits are per caller, keyed by listener and credential — an agent looping on the direct LAN port cannot exhaust the budget for your own client on the authenticated proxy.
Access-level map
Every tool is classified in src/guardrails.ts (explicit operate/full sets + a
read-only naming heuristic; unknown tools fail closed to Full). Each level
includes everything below it.
Level | Tools | What's included |
Read-only | 154 | Pure inspection — nothing changes on the camera. Device info & health, snapshots, view areas, param reads, PTZ position/status/limits, event topics, analytics reads (AOA, people counter, queue, demographics, LPV), audio/imaging/zipstream status, stream URLs & profile lists, recordings list, audit/system/access logs, MQTT/pairing/storage status, CamStreamer/CamSwitcher/CamOverlay listings |
Operate (default) | +78 | Day-to-day control. PTZ moves & presets, guard/recorded tours, autotracking, orientation aid, focus/zoom, image tuning (day-night, stabilizer, rotation, rate control, dewarp), view-area geometry, native & CamOverlay overlays, audio operation & media clips, relay outputs, start/stop recordings, stream & profile control, zipstream tuning, event schedules, |
Full | +37 | Persistent configuration & maintenance. |
Full list of Full-level tools (37):
analyticsmqtt_add_publisher, analyticsmqtt_remove_publisher, camerapairing_add, camerapairing_remove, camerapairing_update, capturemode_set_mode, control_app, datatransform_create, datatransform_remove, datatransform_update, dcparam_import, devicemode_set_mode, factory_default, featureflag_set, geolocation_set_position, log_clear_persistent, log_set_persistent_enabled, mdnssd_set_configuration, mqtt_activate_client, mqtt_configure_client, mqtt_configure_event_publication, mqtt_configure_subscription, mqtt_deactivate_client, netpairing_add, netpairing_remove, netpairing_set_capability_enabled, ntp_set_client_configuration, reboot_camera, recordinggroup_create, recordinggroup_delete, regionalsettings_set, remotestorage_add, remotestorage_remove, remotestorage_set_failover, remotestorage_update, set_param, signedvideo_set.
These counts and this list come from the code, not from editing this file:
npm testprints269 tools across 34 modules (readonly 154, operate 78, full 37)on every run. They were wrong here for a while — the table said 138/+76/+38 and listedaudio_set_global_settingsas Full, which it is not.

Tests
cd app && npm test # tsc && node --test44 tests, no framework. They exist because the tool table is 269 entries wide and every one of them
is classified by guardrails.ts, so the failure modes are silent rather than loud — a tool nobody
classified is not a compile error, it just fails closed and gets refused at the default access level
with no clue why. That is exactly how ptzws_continuous_move was broken.
File | Covers |
| every tool is deliberately classified (no silent fail-closed); no duplicates; annotations agree with the enforced level; the destructive set; deterministic ordering; the catalog is built once; access levels nest; rate-limit budgets are per caller |
| schema is v2 with every required field; removed fields absent; |
| spawns the real app and speaks both protocol revisions to it over HTTP — 2025 |
| the direct listener's gates: a no- |
Every check was validated by breaking the thing it guards — reverting a classification, dropping
vendorId, renaming appName, pointing the rate limiter back at one global bucket. A test that
does not fail when the bug returns is not protecting anything.
The suite also prints the tool counts on every run, which is where the numbers in this file come from.
Build
Prefer not to build? Grab a prebuilt .eap from
Releases and skip to Install.
Requires Docker Desktop on your Mac.
sh dev-loop.sh build # dev package -> build-arm64/
sh release.sh # release package + notes -> releases/release.sh refuses to build if the manifest's vendor is not your My Axis account email — the
signing portal rejects anything else with ACAP000045, and since the manifest is baked in at build
time, a rejected upload means a full rebuild. It also verifies the built package: the launcher must
be named after appName, dist/ and the node runtime must be present, and anything Needle-shaped
must not be.
Install
Camera UI → System → Apps → enable Allow unsigned apps → Add app → upload the aarch64 .eap →
Start. Open the app's settings page, enter VAPIX admin credentials, click Run self-test.
Connect an MCP client
Two endpoints:
Reverse-proxied (camera enforces admin digest auth):
http://<camera-ip>/local/edgemcp/mcpDirect LAN port (for clients without digest; optional bearer token):
http://<camera-ip>:8000/mcp
claude mcp add --transport http axis-q1656 http://<camera-ip>:8000/mcpThe voice console lives at http://<camera-ip>/local/edgemcp/console.html.
Inspect with: npx @modelcontextprotocol/inspector
Verification checklist
npm testpasses (44) before building anything.eapinstalls and starts on AXIS OS 12.10+ without manifest errorsSettings page loads at
http://<ip>/local/edgemcp/index.html— proves the schema-v2reverseProxysurvivedstatus.cgireports"vapix":{"ok":true}after credentials are enteredtools/listreturns 232 at the default Operate level, andreboot_camerais NOT among themcurl -X POST http://<ip>:8000/mcpwith aninitializebody returns server info (2025 path)The same endpoint answers
server/discoverwith a 2026-07-28_metaenvelope (modern path)MCP Inspector lists all tools and each returns live data
take_snapshotreturns a viewable JPEGset_paramrefuses non-allowlisted groupsApp survives respawn (save settings → app restarts → MCP still answers)
Clean SIGINT exit
The settings page loads no off-origin resource: DevTools → Network shows nothing outside the camera
The package contains nothing Needle-shaped:
tar tzf <eap> | grep -i needleis empty
Notes
Digest auth honours the
algorithmdirective (MD5 and SHA-256 +-sess) — an MD5-only client gets a silent 401 on modern AXIS OS.The in-app server binds
process.env.HTTP_PORT(AXIS OS assigns it; 32554 only for local dev).Direct-port reachability from the LAN depends on the camera firewall; the reverse-proxied path is the sanctioned route. Keep a bearer token set if the direct port is enabled.
The app version is single-sourced from
package.json(src/version.ts); bump it there and inmanifest.jsonwhen releasing.reboot_cameraandfactory_defaultare exposed over MCP but always requireconfirm: true.settings.cgirejects adirectPortoutside 1–65535 or colliding with the primary port.
Two hard-won notes about the speech model and the neural voice moved out with the agent — see
Needle LLM on AXIS/src-extracted-from-edgemcp/README-EXTRACTION.mdand this repo's history at tagv2.0.0. They are about ONNX Runtime builds colliding andkokoro-jshard-coding a Hugging Face URL; neither applies to an MCP server.
This server cannot be deployed
Maintenance
Related MCP Connectors
Enable secure connectivity between Sentry issues and debugging data, and LLM clients, using a Model Context Protocol (MCP) server.
Model Context Protocol server for the Apideck Unified API. Connect any MCP-compatible agent framework to 100+ accounting systems, HRIS platforms, file storage providers, and more through one integration. More information https://www.apideck.com/mcp-server
A Model Context Protocol server for Wix AI tools
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceA generic, modular server for implementing the Model Context Protocol (MCP).50 npm45ISC
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol (MCP) server for seamless integration with peripheral devices connected to your computer. Control, monitor, and manage hardware devices through a unified API.5MIT
- AlicenseNot gradedqualityDmaintenanceModel Context Protocol server that standardizes tool discovery, execution, and context management for AI applications.MIT
- AlicenseAqualityCmaintenanceMCP server for configuring Axis IP cameras via VAPIX, supporting device info, snapshots, image settings, overlays, network, PTZ, and system management.3715 npm1GPL 3.0