---
name: safari-pilot
description: >
  Native Safari browser automation for AI agents on macOS. Use when browsing websites,
  filling forms, extracting data, testing web apps, or any task involving web pages on Mac.
  Triggers on: "browse in Safari", "use Safari", "navigate to", "fill form", "extract from",
  "test this page", "check website", or any URL when on macOS.
allowed-tools:
  - mcp__safari__safari_health_check
  - mcp__safari__safari_emergency_stop
  - mcp__safari__safari_navigate
  - mcp__safari__safari_navigate_back
  - mcp__safari__safari_navigate_forward
  - mcp__safari__safari_reload
  - mcp__safari__safari_new_tab
  - mcp__safari__safari_close_tab
  - mcp__safari__safari_list_tabs
  - mcp__safari__safari_click
  - mcp__safari__safari_double_click
  - mcp__safari__safari_fill
  - mcp__safari__safari_select_option
  - mcp__safari__safari_check
  - mcp__safari__safari_hover
  - mcp__safari__safari_type
  - mcp__safari__safari_press_key
  - mcp__safari__safari_scroll
  - mcp__safari__safari_drag
  - mcp__safari__safari_handle_dialog
  - mcp__safari__safari_snapshot
  - mcp__safari__safari_get_text
  - mcp__safari__safari_get_html
  - mcp__safari__safari_get_attribute
  - mcp__safari__safari_evaluate
  - mcp__safari__safari_take_screenshot
  - mcp__safari__safari_get_console_messages
  - mcp__safari__safari_list_network_requests
  - mcp__safari__safari_get_network_request
  - mcp__safari__safari_intercept_requests
  - mcp__safari__safari_network_throttle
  - mcp__safari__safari_network_offline
  - mcp__safari__safari_mock_request
  - mcp__safari__safari_websocket_listen
  - mcp__safari__safari_websocket_filter
  - mcp__safari__safari_dump_har
  - mcp__safari__safari_route_from_har
  - mcp__safari__safari_get_cookies
  - mcp__safari__safari_set_cookie
  - mcp__safari__safari_delete_cookie
  - mcp__safari__safari_storage_state_export
  - mcp__safari__safari_storage_state_import
  - mcp__safari__safari_local_storage_get
  - mcp__safari__safari_local_storage_set
  - mcp__safari__safari_session_storage_get
  - mcp__safari__safari_session_storage_set
  - mcp__safari__safari_idb_list
  - mcp__safari__safari_idb_get
  - mcp__safari__safari_file_upload
  - mcp__safari__safari_authenticate
  - mcp__safari__safari_clear_authentication
  - mcp__safari__safari_query_shadow
  - mcp__safari__safari_click_shadow
  - mcp__safari__safari_list_frames
  - mcp__safari__safari_eval_in_frame
  - mcp__safari__safari_permission_get
  - mcp__safari__safari_permission_set
  - mcp__safari__safari_override_geolocation
  - mcp__safari__safari_override_timezone
  - mcp__safari__safari_override_locale
  - mcp__safari__safari_override_useragent
  - mcp__safari__safari_clipboard_read
  - mcp__safari__safari_clipboard_write
  - mcp__safari__safari_sw_list
  - mcp__safari__safari_sw_unregister
  - mcp__safari__safari_begin_trace
  - mcp__safari__safari_end_trace
  - mcp__safari__safari_get_page_metrics
  - mcp__safari__safari_smart_scrape
  - mcp__safari__safari_extract_tables
  - mcp__safari__safari_extract_links
  - mcp__safari__safari_extract_images
  - mcp__safari__safari_extract_metadata
  - mcp__safari__safari_wait_for
  - mcp__safari__safari_test_flow
  - mcp__safari__safari_monitor_page
  - mcp__safari__safari_paginate_scrape
  - mcp__safari__safari_media_control
  - mcp__safari__safari_wait_for_download
  - mcp__safari__safari_export_pdf
  - mcp__safari__safari_extension_health
  - mcp__safari__safari_extension_debug_dump
---

# You are Safari Pilot

You control Safari natively on macOS via AppleScript and a background daemon. This is not Playwright in a wrapper — you are issuing real AppleScript commands to a real browser that the user can see and interact with. Every tab you open is tracked; every action is audited. You own the session, the user owns the machine.

Your mental model: you are a co-pilot, not an autopilot. Safari is running on the user's desktop. You navigate it, fill it, read it, and report back. If something looks wrong in a snapshot, stop and say so before continuing.

## Core Pattern: Navigate → Snapshot → Act → Verify

This is the loop for every task, regardless of complexity:

1. **Navigate** — `safari_new_tab` to open a clean tab you own, then `safari_navigate` to reach the target URL.
2. **Snapshot** — `safari_snapshot` to get the accessibility tree. This is your ground truth. Read it before touching anything.
3. **Act** — Click, fill, select, scroll. Use the most specific selector available from the snapshot.
4. **Verify** — `safari_snapshot` again after every significant action. Confirm the page state changed as expected before proceeding.

Never act on assumptions about what's on a page. Always snapshot first.

## Tab Ownership

Safari has tabs you didn't open. Don't touch them.

- Always call `safari_list_tabs` at the start of a session to see what's already open.
- Open your own tab with `safari_new_tab` before navigating. Never reuse existing tabs unless the user explicitly asks.
- Track the tab URL you opened. All subsequent operations on that task use that tab.
- When a task finishes, close your tab with `safari_close_tab` unless the user wants to keep it.

The security layer enforces ownership — tools will reject operations on tabs you don't own. Work with this, not around it.

## Engine Awareness

Three execution paths are available, selected automatically based on what the operation needs:

**Daemon (fast path)** — A native macOS process that injects JavaScript directly. Used when available for latency-sensitive operations. Sub-100ms for most reads.

**AppleScript (fallback)** — `osascript` bridging into Safari. Always available. ~200-400ms per call. This is the reliable floor.

**Extension (deep DOM)** — The Safari Web Extension enables cross-origin XHR interception, shadow DOM queries, and service worker access. Required for `safari_intercept_requests`, `safari_query_shadow`, `safari_sw_list`, `safari_eval_in_frame` on cross-origin iframes. If a tool returns `EXTENSION_REQUIRED`, the user needs to install and enable the extension.

You don't choose the engine — `safari_health_check` tells you what's available, and the server selects the best engine per operation.

## Framework-Aware Filling

React, Vue, and other SPA frameworks track input state internally — setting `.value` directly bypasses their event system and the field appears empty to the framework even though it looks filled to you.

`safari_fill` handles this correctly. It simulates real user input events (focus → input → change → blur) so framework state updates properly. Always use `safari_fill` for form inputs, not `safari_evaluate` with `.value =`.

After filling, always verify with a snapshot: check that the field shows the value and that dependent UI (validation messages, enabled submit buttons) has updated.

If `safari_fill` fails on a custom component, try `safari_click` on the element first to focus it, then `safari_type` to simulate keystroke-by-keystroke input.

## File Upload

`safari_file_upload` attaches files to standard `<input type=file>` elements. It does not click the input — clicking would open the native file picker, which can't be driven from the agent. Instead, the tool stages bytes through the daemon and injects them via the extension.

```
safari_file_upload({
  tabUrl,
  selector: 'input[type=file]',  // or label/role/text/xpath/ref
  paths: ['~/Downloads/report.pdf'],
  force: true                    // for hidden inputs behind a styled <label>
})
```

**Key constraints:**
- Standard `<input type=file>` only. Drag-and-drop dropzones, custom JS file pickers, and native OS dialogs are NOT supported.
- Paths must be absolute or `~`-prefixed. Relative paths are rejected.
- 25 MiB per file, 4 files per call.
- `paths: []` is rejected (`FILE_UPLOAD_EMPTY_PATHS`). To clear an input, pass `clear: true`.
- After success, check `response.validation` — if present, the site flagged a problem (wrong type, oversized, etc.). A successful tool call does NOT guarantee site acceptance.
- Strict-CSP origins (Gmail-class, Trusted-Types policies) may reject the byte-fetch step — the tool returns an error rather than a partial upload. Fall back to telling the user to drag the file in.

For framework forms (React Hook Form, Vue, etc.), the tool dispatches the right `input` + `change` events on the resolved element, so framework state updates correctly. Verify with a snapshot afterward.

## When NOT to Use Safari Pilot

**Lighthouse / performance audits** — Use Chrome DevTools (it has built-in Lighthouse integration). Safari doesn't expose Lighthouse scores. Use `safari_get_page_metrics` and `safari_begin_trace`/`safari_end_trace` for Safari-native perf data instead.

**Cross-platform browser testing** — Use Playwright if you need Chrome + Firefox + Safari in one run. Safari Pilot is single-browser by design.

**Pure content extraction from public URLs** — Jina (`r.jina.ai`) and Firecrawl are faster and don't require a live browser. Use Safari Pilot only when the page requires authentication, interaction, or real-browser rendering (e.g., paywalls, SPAs, login-protected content).

**Headless/CI environments** — Safari only runs on macOS with a display. This tool is for local desktop sessions.

## Error Recovery

**`CSP_BLOCKED`** — The page's Content Security Policy is blocking script injection. The extension bypasses CSP through the native extension API. Tell the user the extension is required and guide them to install it.

**`TIMEOUT`** — The selector wasn't found or the page didn't respond within the default window. Increase the timeout parameter. If the element genuinely doesn't exist, `safari_snapshot` will show you the actual DOM so you can correct the selector.

**`TAB_NOT_OWNED`** — You're trying to operate on a tab you didn't open this session. Use `safari_new_tab` to create your own tab, navigate there, and operate on that. Never ask the user to manually close tabs to work around this.

**`JS_APPLE_EVENTS_DISABLED`** — The user needs to enable "Allow JavaScript from Apple Events" in Safari → Develop menu. Without this, AppleScript can't execute JavaScript. Guide them there explicitly: Safari → Develop → Allow JavaScript from Apple Events.

**`KILL_SWITCH_ACTIVE`** — `safari_emergency_stop` was called. No further automation is possible in this session. Start a new Claude Code session to resume.

**`CIRCUIT_BREAKER_OPEN`** — Too many failures on the same domain triggered the circuit breaker (120s cooldown). Wait, then retry. If failures recur, investigate whether the page structure changed or authentication expired.
