Governed MCP server for IP cameras (VAPIX + ONVIF SOAP): fail-closed per-agent policy, hash-chained signed receipts on every call — including denials. First reference producer for the AAR spec.
0
stars
12
commits
TypeScript
primary language
Aug 30, 2026
updated
Every tool call passes a fail-closed policy gate and emits a hash-chained,
signed receipt — including denials.
The audit trail records what agents tried, not just what they did.
AAR specification · Conformance plan · Quick start · Report an issue
Agents are getting wired into everything. Nobody has shipped agent access to cameras and video systems that a security director could approve. This is that attempt.
list_cameras, get_snapshot, ptz_move, ptz_preset, get_receipts, plus the config-drift triobun install
Describe your cameras in cameras.json:
{
"lobby": { "base": "http://192.168.1.33", "user": "root", "credKey": "cam-lobby",
"ptz": true, "protocol": "vapix" },
"gate": { "base": "http://192.168.1.32", "user": "root", "credKey": "cam-gate",
"ptz": true, "protocol": "onvif", "profile": "profile_1_jpeg" }
}
Grant agents authority in policy.json (anything not granted is denied):
{
"agents": {
"claude-main": {
"tools": ["list_cameras", "get_snapshot", "ptz_move", "get_receipts"],
"cameras": ["lobby", "gate"],
"ptz": { "maxStep": 30 }
}
}
}
Register with an MCP client (Claude Code shown):
claude mcp add cameras -- env AGENT_ID=claude-main bun /path/to/onvif-mcp/index.ts
Audit the receipt chain any time:
bun index.ts --verify
# chain OK — every hash linked + signature valid
| Tool | Does | Policy checks |
|---|---|---|
list_cameras | Live device info for cameras this agent may see | agent known, tool granted |
get_snapshot | Capture a JPEG, return path + SHA-256 | + camera granted |
ptz_move | Relative pan/tilt/zoom in degrees | + camera granted, camera is PTZ, step within maxStep |
ptz_preset | Recall a named PTZ preset (VAPIX); emits an AAR wire bundle | + camera granted, camera is PTZ, ptz grant |
get_receipts | Tail the signed receipt chain | agent known, tool granted |
Every call — allowed or denied — appends a receipt. A denial looks like this:
{ "seq": 19, "profile": "aar-0.2-draft-alignment",
"principal": { "role": "agent", "type": "service", "id": "claude-main" },
"enforcement_point": "onvif-mcp/0.1.0", "node_kind": "authorization",
"action": { "tool": "ptz_move", "params": { "camera": "gate", "pan": 90 } },
"decision": "deny", "detail": "step exceeds policy maxStep 30°",
"prev": "8fb7…", "hash": "8322…", "sig": "jRld…" }
Receipt semantics follow the Agent Action Receipts (AAR)
v0.2 vocabulary: principals, enforcement points, node kinds (observation,
action_attempt, authorization), and calibrated outcome-evidence levels
(device_acknowledged, independently_sensed, unknown).
Honesty note: wire conformance to AAR v0.2 (deterministic CBOR, detached
COSE_Sign1 ES256) is not claimed yet. The current chain is a draft
transport. The gap analysis and conformance plan live in
docs/aar-alignment.md.
Set protocol per camera: "vapix" (AXIS HTTP CGI) or "onvif" (SOAP
services). Verified live against AXIS hardware: on AXIS OS 12.9.57 the admin
user works for ONVIF over HTTP digest; older 12.x firmware requires a separate
ONVIF account provisioned in the web UI. ONVIF RelativeMove uses the generic
translation space — pan converts as degrees/360 (measured exact on hardware);
tilt/zoom mapping is linear-approximate.
Experimental (v0.1.0). Verified live against three AXIS cameras (two PTZ, one
fixed dome) through real MCP client round-trips: physical PTZ motion with
before/after frame proof, all denial paths exercised and receipted, and
tamper-detection confirmed by mutating a receipt and watching --verify flag
the exact sequence. Not production software — see the roadmap.
receipts-aar/
emits deterministic-CBOR + COSE_Sign1 ES256 bundles for the pinned-ontology
actions (camera.stream.view, camera.ptz.preset), offline-verified conformant
by the spec's independent pyref verifier: bun index.ts --verify-aar.
A conformant verdict proves wire integrity + binding, not receipt-body truth — scope, remaining narrative residue, and same-operator disclosures: docs/aar-alignment.md. Policy denials wire-emit too
(decision deny, real refusal reason); receipt bodies carry real narrative (timestamps,
agent identity, policy.json digest)ONVIF® is a trademark of ONVIF, Inc. This project is not affiliated with, endorsed by, or certified by ONVIF, Inc. The name is purely descriptive — this server speaks the ONVIF protocol as published in the open specifications. No ONVIF conformance is claimed or implied. No ONVIF logos are used and no WSDL files are redistributed; the SOAP envelopes are hand-authored.
MIT © 2026 Matthew Visher.
The AAR specification this project aligns with is separately licensed: spec text CC BY 4.0, reference code Apache-2.0.
12 commits
TypeScript
100.0%
Governed MCP server for IP cameras (VAPIX + ONVIF SOAP): fail-closed per-agent policy, hash-chained signed receipts on every call — including denials. First reference producer for the AAR spec.
0
stars
12
commits
TypeScript
primary language
Aug 30, 2026
updated
Every tool call passes a fail-closed policy gate and emits a hash-chained,
signed receipt — including denials.
The audit trail records what agents tried, not just what they did.
AAR specification · Conformance plan · Quick start · Report an issue
Agents are getting wired into everything. Nobody has shipped agent access to cameras and video systems that a security director could approve. This is that attempt.
list_cameras, get_snapshot, ptz_move, ptz_preset, get_receipts, plus the config-drift triobun install
Describe your cameras in cameras.json:
{
"lobby": { "base": "http://192.168.1.33", "user": "root", "credKey": "cam-lobby",
"ptz": true, "protocol": "vapix" },
"gate": { "base": "http://192.168.1.32", "user": "root", "credKey": "cam-gate",
"ptz": true, "protocol": "onvif", "profile": "profile_1_jpeg" }
}
Grant agents authority in policy.json (anything not granted is denied):
{
"agents": {
"claude-main": {
"tools": ["list_cameras", "get_snapshot", "ptz_move", "get_receipts"],
"cameras": ["lobby", "gate"],
"ptz": { "maxStep": 30 }
}
}
}
Register with an MCP client (Claude Code shown):
claude mcp add cameras -- env AGENT_ID=claude-main bun /path/to/onvif-mcp/index.ts
Audit the receipt chain any time:
bun index.ts --verify
# chain OK — every hash linked + signature valid
| Tool | Does | Policy checks |
|---|---|---|
list_cameras | Live device info for cameras this agent may see | agent known, tool granted |
get_snapshot | Capture a JPEG, return path + SHA-256 | + camera granted |
ptz_move | Relative pan/tilt/zoom in degrees | + camera granted, camera is PTZ, step within maxStep |
ptz_preset | Recall a named PTZ preset (VAPIX); emits an AAR wire bundle | + camera granted, camera is PTZ, ptz grant |
get_receipts | Tail the signed receipt chain | agent known, tool granted |
Every call — allowed or denied — appends a receipt. A denial looks like this:
{ "seq": 19, "profile": "aar-0.2-draft-alignment",
"principal": { "role": "agent", "type": "service", "id": "claude-main" },
"enforcement_point": "onvif-mcp/0.1.0", "node_kind": "authorization",
"action": { "tool": "ptz_move", "params": { "camera": "gate", "pan": 90 } },
"decision": "deny", "detail": "step exceeds policy maxStep 30°",
"prev": "8fb7…", "hash": "8322…", "sig": "jRld…" }
Receipt semantics follow the Agent Action Receipts (AAR)
v0.2 vocabulary: principals, enforcement points, node kinds (observation,
action_attempt, authorization), and calibrated outcome-evidence levels
(device_acknowledged, independently_sensed, unknown).
Honesty note: wire conformance to AAR v0.2 (deterministic CBOR, detached
COSE_Sign1 ES256) is not claimed yet. The current chain is a draft
transport. The gap analysis and conformance plan live in
docs/aar-alignment.md.
Set protocol per camera: "vapix" (AXIS HTTP CGI) or "onvif" (SOAP
services). Verified live against AXIS hardware: on AXIS OS 12.9.57 the admin
user works for ONVIF over HTTP digest; older 12.x firmware requires a separate
ONVIF account provisioned in the web UI. ONVIF RelativeMove uses the generic
translation space — pan converts as degrees/360 (measured exact on hardware);
tilt/zoom mapping is linear-approximate.
Experimental (v0.1.0). Verified live against three AXIS cameras (two PTZ, one
fixed dome) through real MCP client round-trips: physical PTZ motion with
before/after frame proof, all denial paths exercised and receipted, and
tamper-detection confirmed by mutating a receipt and watching --verify flag
the exact sequence. Not production software — see the roadmap.
receipts-aar/
emits deterministic-CBOR + COSE_Sign1 ES256 bundles for the pinned-ontology
actions (camera.stream.view, camera.ptz.preset), offline-verified conformant
by the spec's independent pyref verifier: bun index.ts --verify-aar.
A conformant verdict proves wire integrity + binding, not receipt-body truth — scope, remaining narrative residue, and same-operator disclosures: docs/aar-alignment.md. Policy denials wire-emit too
(decision deny, real refusal reason); receipt bodies carry real narrative (timestamps,
agent identity, policy.json digest)ONVIF® is a trademark of ONVIF, Inc. This project is not affiliated with, endorsed by, or certified by ONVIF, Inc. The name is purely descriptive — this server speaks the ONVIF protocol as published in the open specifications. No ONVIF conformance is claimed or implied. No ONVIF logos are used and no WSDL files are redistributed; the SOAP envelopes are hand-authored.
MIT © 2026 Matthew Visher.
The AAR specification this project aligns with is separately licensed: spec text CC BY 4.0, reference code Apache-2.0.
12 commits
TypeScript
100.0%