Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Claude Verifier Safari

An experimental take on replicating the Claude Verifier Chrome extension for Safari. This is my approach to bringing the same browser verification capabilities to Safari using a native Safari Web Extension and WebDriver.

MCP server that verifies web pages in Safari via a native Safari Web Extension or WebDriver.

Table of Contents

Overview

Claude Verifier Safari is a Model Context Protocol (MCP) server that lets AI assistants verify web pages in Safari. It navigates to a URL, runs JavaScript assertions against the page, captures screenshots, and returns structured verification reports.

The server communicates over MCP stdio transport and is macOS-only.

Two verification modes are available:

Mode How it works Safari setup
Extension (default) Safari Web Extension with toolbar badge and popup UI Install and enable the extension
WebDriver Controls Safari externally via safaridriver Enable "Allow Remote Automation"

Architecture

Extension Mode (default)

Claude Desktop / AI Assistant
  |  MCP (stdio)
  v
Node.js MCP Server
  |  HTTP + WebSocket bridge (localhost:52678)
  v
Swift Host App (SafariWebExtensionHandler)
  |  Native Messaging
  v
Safari Web Extension (background.js)
  |  browser.scripting / browser.tabs
  v
Safari Browser --> assertions + screenshot --> report

The MCP server runs an HTTP bridge on localhost:52678 with WebSocket support. The Safari Web Extension connects via WebSocket for instant task delivery, falling back to HTTP polling when the WebSocket is unavailable. When a task arrives, the extension navigates to the URL, runs assertions via browser.scripting.executeScript(), captures a screenshot, and posts the result back.

WebDriver Mode

Claude Desktop / AI Assistant
  |  MCP (stdio)
  v
Node.js MCP Server
  |  W3C WebDriver HTTP (localhost:4444)
  v
safaridriver --> Safari Browser --> assertions + screenshot --> report

The MCP server talks directly to safaridriver using the W3C WebDriver protocol. The extension and Swift layers are bypassed entirely.

Prerequisites

  • macOS (any recent version)
  • Node.js 18+ (required for native fetch())
  • Safari installed

Additional requirements per mode:

Requirement Extension Mode WebDriver Mode
Xcode (to build the extension) Yes No
Safari extension enabled Yes No
"Allow Remote Automation" in Safari No Yes

Quick Start

Three one-time steps:

  1. Install the Safari extension — build via Xcode or install from the Mac App Store (when available)
  2. Enable the extension in Safari > Settings > Extensions
  3. Register the MCP server:
npx claude-verifier-safari setup

That's it. All 24 MCP tools are now available in every Claude Code session.

Installation

Option A: npm (recommended)

npx claude-verifier-safari setup

This registers the MCP server globally in Claude Code (user scope). Under the hood it runs:

claude mcp add -s user claude-verifier-safari -- npx claude-verifier-safari

To remove:

npx claude-verifier-safari uninstall

Option B: From source (development)

cd webdriver
npm install

Then register manually:

claude mcp add claude-verifier-safari -s local -- npx tsx /absolute/path/to/webdriver/src/cli.ts

Safari Extension Setup

The extension must be built and installed via Xcode before using extension mode.

1. Build the Xcode project

open xcode/Safari\ Verifier/Safari\ Verifier.xcodeproj

In Xcode:

  • Select the Claude Verifier Safari (macOS) scheme
  • Build and run with Cmd+R

This installs both the host app and the Safari Web Extension.

2. Allow unsigned extensions (development builds)

Since the extension is not signed for distribution, Safari hides it by default:

  1. Open Safari > Develop > Allow Unsigned Extensions (enter your password when prompted)
  2. This setting resets every time you quit Safari, so you'll need to re-enable it each session during development

If you don't see the Develop menu: Safari > Settings > Advanced > Show features for web developers.

3. Enable the extension in Safari

  1. Open Safari > Settings > Extensions (or Preferences > Extensions on older macOS)
  2. Check the box next to Claude Verifier Safari
  3. Leave website access set to Ask (the default) — Safari will prompt you to allow access for each site when the extension tries to verify it

4. Verify it works

After enabling, you should see the Claude Verifier Safari icon in the toolbar. Click it to open the popup, which shows the current status (idle/running/pass/fail).

Running the Server

The MCP server starts automatically when Claude Code invokes the verify_safari tool. You don't need to start it manually.

For development, from the webdriver/ directory:

npm start

This starts the MCP server via tsx src/index.ts. On startup it:

  1. Launches the HTTP bridge on port 52678
  2. Connects to MCP stdio transport
  3. Logs Bridge listening on port 52678 to stderr

The server keeps running until you stop it with Ctrl+C or send SIGINT/SIGTERM. On shutdown it cleans up the bridge and any active safaridriver process.

Client Configuration

Claude Code (CLI)

Recommended: Use the setup command (registers globally, available in every project):

npx claude-verifier-safari setup

Manual registration from source (local to one project):

claude mcp add claude-verifier-safari -s local -- npx tsx /absolute/path/to/webdriver/src/cli.ts

Note: npx claude-verifier-safari only works when the package is installed from npm. For local development, always use the npx tsx .../src/cli.ts form above. The entry point must be cli.ts (not index.ts).

Verify the server connects:

claude mcp list

You should see claude-verifier-safari: ✓ Connected. All tools will be available in your next Claude Code session.

Claude Desktop

Add the following to your Claude Desktop config file (claude_desktop_config.json):

{
  "mcpServers": {
    "claude-verifier-safari": {
      "command": "npx",
      "args": ["claude-verifier-safari"]
    }
  }
}

After saving, restart Claude Desktop. All tools will be available in your conversations.

Verification Modes

Extension Mode (default)

Best for most use cases. The extension runs assertions inside Safari's own scripting context, which gives the most accurate browser behavior. No special Safari settings required beyond enabling the extension.

mode: "extension"

Features:

  • Toolbar badge shows verification status (idle/running/pass/fail)
  • Popup UI with details
  • Screenshots via browser.tabs.captureVisibleTab()
  • HTML dump support
  • DOM overlay banner
  • WebSocket push transport (instant task delivery)
  • Disconnected state detection with badge indicator
  • No "Allow Remote Automation" needed

Limitations:

  • Requires building and installing the Xcode project

WebDriver Mode

Useful for CI/headless scenarios or when you don't want to install the extension.

mode: "webdriver"

Features:

  • Full HTML dump support
  • DOM overlay banner during verification
  • No extension installation needed

Limitations:

  • Requires Safari > Develop > Allow Remote Automation to be enabled
  • safaridriver must be available in PATH (ships with macOS)

Tool Reference — verify_safari

Parameter Type Required Default Description
url string yes Page URL (http:// or https:// only)
assertions string[] yes JavaScript expressions (1–50, each non-empty)
screenshot boolean no true Capture a PNG screenshot
htmlDump boolean no false Capture raw page HTML (both modes)
timeout number no 30 Timeout in seconds (1–300)
overlay boolean no true Show DOM overlay banner (both modes)
notifications boolean no true Send macOS notifications on start/finish
mode string no "extension" "extension" or "webdriver"

The tool returns:

  • A markdown summary with pass/fail status per assertion
  • An inline screenshot image (when enabled)
  • Report artifacts saved to disk

Browser Control Tools

Beyond verify_safari, the server exposes 23 individual tools for fine-grained browser control. These work in extension mode only and operate on the active Safari tab.

Navigation

Tool Description
navigate Navigate to a URL and wait for load
navigate_history Go back or forward in browser history

Interaction

Tool Description
click Click an element by CSS selector
hover Hover over an element by CSS selector
fill Fill a single input field
fill_form Fill multiple form fields at once
scroll Scroll the page or a specific element
handle_dialog Accept or dismiss alert/confirm/prompt dialogs

Page Inspection

Tool Description
evaluate_script Run arbitrary JavaScript in the page context
take_screenshot Capture a PNG screenshot of the visible tab
dom_snapshot Get a simplified accessibility tree of the page
wait_for Poll until a JavaScript condition becomes truthy
resize_window Change the browser window dimensions

Tab Management

Tool Description
new_page Open a new tab (optionally with a URL)
list_pages List all open tabs with URLs and titles
select_page Switch to a tab by ID
close_page Close a tab by ID

Monitoring

Tool Description
list_console_messages Retrieve captured console messages (log, warn, error, info)
list_network_requests List captured fetch/XHR requests (auto-injects capture hook)
get_network_request Get details of a specific request by index or URL pattern
performance_start_trace Start collecting performance entries via PerformanceObserver
performance_stop_trace Stop tracing and return all collected entries
performance_analyze_insight Analyze performance data: page load timing, FCP/LCP, slow resources

Network monitoring details

Network capture works by monkey-patching fetch and XMLHttpRequest in the page context. It captures metadata only (method, URL, status, headers, timing, body size) — not response bodies. The capture hook is injected automatically on first call to list_network_requests or get_network_request.

Performance tracing details

Performance tracing uses PerformanceObserver to collect entries. Safari supports: resource, navigation, paint, largest-contentful-paint, mark, measure. The performance_analyze_insight tool runs analysis server-side and returns structured output including page load timing, render metrics (FP, FCP, LCP), resource summary with slow resources (>500ms), and custom marks/measures.

Writing Assertions

Each assertion is a JavaScript expression evaluated in the page context. It must return a truthy value to pass.

Examples

// Check page title
document.title === "My App"

// Check an element exists
document.querySelector("h1") !== null

// Check text content
document.querySelector(".hero-title").textContent.includes("Welcome")

// Check element count
document.querySelectorAll(".product-card").length >= 3

// Check visibility
getComputedStyle(document.querySelector(".modal")).display !== "none"

// Check form state
document.querySelector("#email-input").value !== ""

// Check meta tags
document.querySelector('meta[name="viewport"]')?.content.includes("width=device-width")

// Check link href
document.querySelector('a.cta-button')?.href.includes("/signup")

Keep assertions simple and focused. Each assertion should test one thing.

Report Artifacts

Each verification run writes artifacts to .claude-verifier-reports/<timestamp>/ (relative to the current working directory):

File Always created Description
report.json Yes Structured report with status, assertions, and metadata
screenshot.png When screenshot: true Full-page PNG screenshot
page.html When htmlDump: true Raw HTML source
log.txt Yes Human-readable run summary

Report JSON structure

{
  "target": "safari-extension",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "status": "success",
  "summary": "All 3 assertions passed",
  "assertions": [
    { "expression": "document.title === 'Example'", "passed": true, "error": null },
    { "expression": "document.querySelector('h1') !== null", "passed": true, "error": null },
    { "expression": "document.querySelectorAll('a').length > 0", "passed": true, "error": null }
  ],
  "artifacts": {
    "screenshots": ["screenshot.png"],
    "html_dump": null,
    "logs": "log.txt"
  },
  "metadata": {
    "url": "https://example.com",
    "git_sha": "8de16c2",
    "safari_version": "18.1",
    "macos_version": "15.2"
  }
}

Port Configuration

The HTTP bridge between the MCP server and the Safari extension uses a fixed default port.

Component Default port Purpose
HTTP bridge 52678 MCP server <-> extension communication
safaridriver 4444 WebDriver protocol (WebDriver mode only)

The bridge port can be overridden by passing a custom port to startBridge() in the code. The port is also written to ~/Library/Application Support/claude-verifier-safari/port for diagnostics.

Development

All commands run from webdriver/:

npm start          # Run the MCP server (dev mode via tsx)
npm run build      # Compile TypeScript to dist/
npm run lint       # ESLint with strict TypeScript rules
npm test           # Vitest unit tests
npx tsc --noEmit   # Type-check without emitting

Tech stack

Tool Version Purpose
TypeScript 5.7 Type-safe source code
ESM import/export, "type": "module"
MCP SDK 1.12 Model Context Protocol server
Zod 3.24 Input validation
ESLint 9.x Linting (flat config, strict type-checked rules)
Vitest 4.x Unit testing
tsx 4.x TypeScript execution (no compile step)

Code style

  • Strict TypeScript — no any, strict: true in tsconfig
  • ESM onlyimport/export, no require
  • ESLint stricttypescript-eslint strict type-checked rules
  • Small functions — single responsibility, under 30 lines
  • Minimal comments — explain why, not what

Running tests

npm test

Tests are co-located with source files as *.test.ts (excluded from tsconfig and ESLint). Currently covers error classes, feedback script generation, and WebDriver response parsing.

Adding a new test

Create a file next to the module you want to test:

webdriver/src/
  mymodule.ts
  mymodule.test.ts   <-- add this

Vitest picks up any src/**/*.test.ts file automatically.

Project Structure

safari/
  README.md
  CLAUDE.md                 Development guidelines

  extension/
    manifest.json           Web extension manifest (Manifest V3)
    background.js           Polls bridge for tasks, runs assertions
    popup.html/js/css       Toolbar popup UI
    icons/                  Extension icons (48-256px)

  xcode/
    Claude Verifier Safari/
      Claude Verifier Safari.xcodeproj
      Shared (App)/         Shared app resources and view controller
      Shared (Extension)/
        SafariWebExtensionHandler.swift   Native messaging bridge (HTTP proxy)
      macOS (App)/          macOS host app target
      macOS (Extension)/    macOS extension target
      iOS (App)/            iOS host app target
      iOS (Extension)/      iOS extension target

  webdriver/
    package.json            Dependencies, scripts, version, bin field
    tsconfig.json           TypeScript config (ES2022, strict)
    eslint.config.js        ESLint flat config
    vitest.config.ts        Test runner config
    dist/                   Compiled output (npm publish ships this)
    src/
      cli.ts                CLI entry point (setup/uninstall/start)
      index.ts              MCP server setup, orchestration
      bridge.ts             HTTP bridge for extension communication (port 52678)
      webdriver.ts          W3C WebDriver HTTP client
      safaridriver.ts       safaridriver process lifecycle
      errors.ts             Custom error classes
      feedback.ts           DOM overlay and macOS notifications
      types.ts              TypeScript interfaces
      tools/
        register.ts         Imports and registers all tool modules
        verify.ts           verify_safari (full-page verification)
        navigate.ts         navigate, navigate_history
        click.ts            click
        fill.ts             fill, fill_form
        scroll.ts           scroll
        wait.ts             wait_for
        evaluate.ts         evaluate_script
        screenshot.ts       take_screenshot
        resize.ts           resize_window
        tabs.ts             new_page, list_pages, select_page, close_page
        hover.ts            hover
        dialog.ts           handle_dialog
        snapshot.ts         dom_snapshot
        console.ts          list_console_messages
        network.ts          list_network_requests, get_network_request
        performance.ts      performance_start/stop/analyze tools

Troubleshooting

Claude Code: "Failed to reconnect" or "Failed to connect"

The MCP server is registered but Claude Code cannot start it. Common causes:

  1. Server not registered — run npx claude-verifier-safari setup to register globally
  2. Node.js not found — ensure node (18+) and npx are on your PATH
  3. Check server status — run claude mcp list to see connection status, or claude mcp get claude-verifier-safari for full details
  4. Re-register — if using a source install with absolute paths, verify the path points to cli.ts (not index.ts):
    claude mcp remove claude-verifier-safari -s local
    claude mcp add claude-verifier-safari -s local -- npx tsx /absolute/path/to/webdriver/src/cli.ts

Extension mode: "Extension did not respond"

The extension is not polling the bridge. Check:

  1. Is the extension enabled? Safari > Settings > Extensions > Claude Verifier Safari must be checked
  2. Is the server running? npm start should show Bridge listening on port 52678
  3. Is the extension loaded? Look for the Claude Verifier Safari icon in the toolbar
  4. Check the extension console: Safari > Develop > Web Extension Background Pages > Claude Verifier Safari. Look for errors in the console

WebDriver mode: "safaridriver failed to start"

  1. Verify safaridriver exists: which safaridriver (should be /usr/bin/safaridriver)
  2. Enable remote automation: Safari > Develop > Allow Remote Automation
  3. If the Develop menu is not visible: Safari > Settings > Advanced > Show features for web developers

Port conflict on 52678

If another process is using port 52678, you can override the bridge port by modifying the startBridge() call in src/index.ts. The extension's Swift handler will fall back to reading the port from ~/Library/Application Support/claude-verifier-safari/port.

Build fails in Xcode

  1. Ensure you're building the macOS scheme, not iOS
  2. Check that signing is set to "Automatically manage signing"
  3. Clean the build folder: Product > Clean Build Folder (Shift+Cmd+K)

Assertions fail unexpectedly

  • Assertions run in the page context after navigation completes, but dynamic content may not be loaded yet. Use assertions that check for specific elements rather than timing-dependent state
  • In extension mode, navigation waits for tabs.onUpdated with status: "complete" before running assertions
  • In WebDriver mode, the timeout parameter controls how long to wait for page load

Future Enhancements

  • Task queuing — Queue incoming verification requests instead of rejecting with a conflict error when a task is already running. This would let the AI assistant fire multiple verifications without waiting for each one to complete
  • Configurable bridge port via environment variable — Allow setting the bridge port through an env var (e.g. BRIDGE_PORT) instead of requiring a code change, making it easier to avoid port conflicts
  • Persistent verification history — Store past run summaries in a lightweight database (SQLite) so the AI assistant can query trends, compare runs, or detect regressions across multiple verifications
  • Multi-tab parallel verification — Run assertions on multiple URLs concurrently using separate Safari tabs, returning a combined report. Useful for verifying cross-page flows or comparing environments

About

MCP server that verifies web pages in Safari via a native Safari Web Extension or WebDriver.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages