Guide Mar 26, 2026

The MCP Security Checklist: How to Audit Your AI Agent's Browser Actions

MCP servers run with broad permissions. How do you know what your AI agent actually did? Screenshot, inspect, and record tamper-evident audit trails.

You just gave your AI agent access to your production servers via MCP.

It can:

But you can't see what it actually did.

The problem: MCP servers run with broad permissions. Without visual proof, you have no way to audit whether an agent:

This is a security and governance nightmare. Your compliance team asks: "Show me what happened." You have logs, but no proof.

Here's the MCP security checklist: screenshot, inspect, and audit trail every action.


The MCP Security Problem: Broad Permissions, No Visibility

MCP servers grant agents access to real systems. Unlike constrained APIs, MCP is a general-purpose protocol for browser automation, file access, and system commands.

An agent using MCP can:

# Example: Agent interacts with your dashboard via MCP
result = await mcp_server.use_tool("click", selector="#approve-payment")

But what actually happened? Did the agent find the right button? Was it a payment approval or a fraud alert? Did the page state change as expected?

You don't know. You only have the agent's word.

For production systems handling payments, access control, or data deletion, this is unacceptable.


The Solution: Visual Proof at Every Step

Add PageBolt to your MCP security stack. After every action, capture:

  1. Screenshot — What does the page look like now?
  2. Inspect — What elements exist and what are their states?
  3. Video — Record the entire automation for tamper-evident audit trails

This transforms your agent from a black box into an auditable system.


The MCP Security Checklist

✅ Pre-Deployment Verification

✅ Per-Action Security

For each action (click, fill, navigate):

  1. Take a screenshot before the action — Establish baseline state
  2. Execute the action — Agent performs the operation
  3. Inspect the page — Verify the action had the expected effect
  4. Take a screenshot after the action — Prove the state changed
  5. Compare the two screenshots — Detect unexpected outcomes

✅ Audit Trail


Real Example: Agent Approves a Payment

Here's what secure MCP automation looks like:

import asyncio
import os
import json
from anthropic import Anthropic
import hashlib

client = Anthropic()

PAGEBOLT_API_KEY = os.getenv("PAGEBOLT_API_KEY")
PAGEBOLT_BASE_URL = "https://pagebolt.dev/api/v1"

async def audit_agent_action(url: str, action_description: str, screenshot_before: bytes = None):
    """
    Secure MCP action: Take visual proof before and after every action
    """

    # Step 1: Screenshot BEFORE the action
    if not screenshot_before:
        screenshot_before = await take_screenshot(url)
        screenshot_before_hash = hashlib.sha256(screenshot_before).hexdigest()
        print(f"[AUDIT] Before screenshot hash: {screenshot_before_hash}")

    # Step 2: Inspect the page to find the correct selector
    inspect_response = await inspect_page(url)
    print(f"[AUDIT] Page elements found: {len(inspect_response['elements'])} items")

    # Step 3: Ask Claude to validate the action
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=500,
        messages=[{
            "role": "user",
            "content": f"""
            You are a security auditor validating MCP agent actions.
            TASK: {action_description}
            PAGE INSPECTION: {json.dumps(inspect_response, indent=2)}

            Identify the correct element and whether this action is safe.
            Respond in JSON: {{"selector": "#id", "safe": true, "reasoning": "...", "risks": []}}
            """
        }]
    )

    action_plan = json.loads(response.content[0].text)
    print(f"[AUDIT] Claude validation: {action_plan['reasoning']}")

    if not action_plan["safe"]:
        print(f"[SECURITY] Action blocked: {action_plan['risks']}")
        return None

    # Step 4: Execute the action via MCP
    print(f"[ACTION] Clicking selector: {action_plan['selector']}")
    # await mcp_server.click(action_plan['selector'])

    # Step 5: Screenshot AFTER the action
    await asyncio.sleep(2)
    screenshot_after = await take_screenshot(url)
    screenshot_after_hash = hashlib.sha256(screenshot_after).hexdigest()
    print(f"[AUDIT] After screenshot hash: {screenshot_after_hash}")

    # Step 6: Log the action
    log_entry = {
        "timestamp": "2026-03-26T14:23:45Z",
        "action": action_description,
        "selector": action_plan["selector"],
        "url": url,
        "screenshot_before_hash": screenshot_before_hash,
        "screenshot_after_hash": screenshot_after_hash,
        "status": "SUCCESS" if screenshot_before_hash != screenshot_after_hash else "NO_CHANGE_DETECTED"
    }

    print(f"[AUDIT] Logged: {json.dumps(log_entry, indent=2)}")
    return log_entry

async def take_screenshot(url: str) -> bytes:
    import requests
    response = requests.post(
        f"{PAGEBOLT_BASE_URL}/screenshot",
        headers={"x-api-key": PAGEBOLT_API_KEY, "Content-Type": "application/json"},
        json={"url": url, "format": "png"}
    )
    if not response.ok:
        raise Exception(f"Screenshot failed: {response.status_code}")
    return response.content

async def inspect_page(url: str) -> dict:
    import requests
    response = requests.post(
        f"{PAGEBOLT_BASE_URL}/inspect",
        headers={"x-api-key": PAGEBOLT_API_KEY, "Content-Type": "application/json"},
        json={"url": url}
    )
    if not response.ok:
        raise Exception(f"Inspect failed: {response.status_code}")
    return response.json()

Why it's secure:


Preventing Selector Hallucination with /inspect

Agents hallucinate. They see a page and guess at selectors.

// Agent might hallucinate a selector that doesn't exist
await page.click("#approve-payment-btn");  // Wrong! No such element.

The /inspect endpoint prevents this by providing the actual page structure:

{
  "elements": [
    {
      "type": "button",
      "text": "Approve Payment",
      "selector": "#payment-approve-7q3x",
      "visible": true,
      "x": 450,
      "y": 320
    }
  ]
}

Now Claude knows the real selector. No hallucination.


Tamper-Evident Audit Trails with /video

For compliance audits, a static screenshot isn't enough. You need a video.

import requests, hashlib

response = requests.post(
    "https://pagebolt.dev/api/v1/video",
    headers={"x-api-key": os.getenv("PAGEBOLT_API_KEY")},
    json={
        "steps": [
            {"action": "navigate", "url": "https://dashboard.example.com"},
            {"action": "click", "selector": "#login"},
            {"action": "fill", "selector": "#email", "value": "user@example.com"},
            {"action": "click", "selector": "#approve-btn"}
        ],
        "format": "mp4",
        "cursor": {"visible": True, "style": "classic"}
    }
)

video_bytes = response.content
with open("audit-trail.mp4", "wb") as f:
    f.write(video_bytes)

# Hash for tamper-evidence
video_hash = hashlib.sha256(video_bytes).hexdigest()
print(f"Audit trail hash: {video_hash}")
# Store hash in log — if video is modified, hash won't match

A video provides:


Pre-Deployment Security Check (Executable)

async def pre_deployment_security_check(agent_url: str):
    checks = {
        "can_screenshot": False,
        "can_inspect": False,
        "can_record_video": False,
        "detects_hallucinated_selectors": False,
        "logs_every_action": False
    }

    try:
        await take_screenshot(agent_url)
        checks["can_screenshot"] = True
        print("✅ Screenshot capability verified")
    except Exception as e:
        print(f"❌ Screenshot failed: {e}")

    try:
        inspect_data = await inspect_page(agent_url)
        if "elements" in inspect_data:
            checks["can_inspect"] = True
            print("✅ Inspect capability verified")
    except Exception as e:
        print(f"❌ Inspect failed: {e}")

    try:
        import requests as req
        r = req.post(
            f"{PAGEBOLT_BASE_URL}/video",
            headers={"x-api-key": PAGEBOLT_API_KEY},
            json={"steps": [{"action": "navigate", "url": agent_url}]}
        )
        if r.status_code == 200:
            checks["can_record_video"] = True
            print("✅ Video recording verified")
    except Exception as e:
        print(f"❌ Video failed: {e}")

    if checks["can_inspect"]:
        checks["detects_hallucinated_selectors"] = True
        print("✅ Hallucination detection enabled")

    if all([checks["can_screenshot"], checks["can_inspect"], checks["can_record_video"]]):
        checks["logs_every_action"] = True
        print("✅ Full action logging enabled")

    if all(checks.values()):
        print("\n✅ SECURITY CHECK PASSED: Agent is safe for production")
        return True
    else:
        failed = sum(not v for v in checks.values())
        print(f"\n❌ SECURITY CHECK FAILED: {failed} checks failed")
        return False

Summary: The MCP Security Stack

Layer Tool What It Does
Proof Screenshot Captures page state before & after actions
Prevention Inspect Shows real page structure, prevents hallucination
Evidence Video Records entire sequence for compliance audits
Validation Claude API Approves actions before execution
Logging Audit trail Timestamps, hashes, and logs every action

MCP agents are powerful. But power without visibility is risk.

Add PageBolt to your MCP security stack:

Your agents gain confidence. Your compliance team gets proof.

Audit your MCP agents — free

100 requests/month, no credit card. Screenshot, inspect, and video for every agent action.

Get API key free →