Skip to content

Run on Safari ​

A Safari dev session runs on the same local bridge as every other browser, so an agent can build, open, reload and read your extension there. Reach for it when you develop on macOS with Xcode and want the Safari build checked while you edit.

Quick start ​

With a project open, ask the agent:

Run my extension in Safari

The agent calls extension_dev with browser: "safari". The build converts your extension into a macOS app, signs it with xcodebuild, and opens it. Turn the extension on once in Safari > Settings > Extensions. From then on every save reloads it, and its background and content lines land in the session log.

To let the agent read pages too, run safaridriver --enable once and turn on Safari > Settings > Developer > "Allow remote automation" (Safari > Develop > Allow Remote Automation on older Safari).

How it works ​

The session needs Extension.js 4.1.21 or newer in the project. On 4.1.33 or newer the run opens the app in the background, leaves your own Safari alone, and quits only that app on stop.

The control channel is on by default, so the bridge tools work as soon as the extension is enabled: extension_storage, extension_reload, extension_open for surfaces, extension_dom_snapshot by tab id, extension_logs, and the assertions.

Safari has no CDP or RDP, and its MV3 background CSP blocks eval. So extension_eval in background fails, and extension_inspect has no Safari path. Read the background through its log lines instead.

Pages ride a safaridriver automation session. On Extension.js 4.1.32 or newer, with remote automation on, the dev session opens one and records webdriverPort and webdriverSessionId in ready.json. extension_open with url and extension_eval with context: "page" use it. extension_doctor reports it as the safari-window check.

Without that session, extension_open with url rides the bridge's navigate verb (Extension.js 4.1.29 or newer), and extension_eval in content or page needs a tab already open at the url.

Apple ships its own MCP server as safaridriver --mcp (Safari 27, or Safari Technology Preview 247 and later). It reads pages in an isolated automation window, has no extension-aware tool, and runs beside a dev session. extension_browsers reports whether your safaridriver has --mcp.

Reference ​

ToolOn SafariExtension.js
extension_dev with browser: "safari"Builds the app, opens it, reloads on every save, streams background and content logs4.1.21 or newer
extension_logsWorks once the extension is enabled4.1.21 or newer
extension_storage, extension_reloadWork over the bridge4.1.21 or newer
extension_open with surfaceOpens the extension's surfaces; the devtools surface is refused4.1.21 or newer
extension_dom_snapshotWorks by tab id4.1.21 or newer
extension_assertcontent-script-injected, background-worker-booted, storage-key-present and console-errors-empty work; surface-rendered reads through the relay and can pass or fail4.1.21 or newer
extension_open with urlOver the safaridriver session; without one, over the bridge's navigate verb4.1.32 (safaridriver), 4.1.29 (navigate)
extension_eval with context: "page"Over the safaridriver session; without one, the tab must already be open at the url4.1.32 or newer
extension_eval with context: "content"Needs a tab already open at the url4.1.21 or newer
extension_eval with context: "background"Fails: Safari's MV3 background CSP blocks evalnone
extension_inspectNo Safari path: Safari has no CDP or RDPnone
extension_doctorAdds the safari-window check: pass, fail when the window is gone, skip with the engine's reason when none was opened4.1.32 or newer
extension_startNot supported: the framework's start and preview commands refuse Safari targetsnone

When it fails ​

  • extension_logs answers E_LOGS_MISSING: the extension is not enabled yet, or the project runs an engine older than 4.1.21. Turn it on in Safari > Settings > Extensions, check the project's extension version, then read again.
  • extension_doctor shows safari-window as skip: the dev session opened no safaridriver session, and the detail carries the engine's reason. Run safaridriver --enable, turn on remote automation, then restart extension_dev. Page eval and open by url fall back to the bridge until then.
  • safari-window is fail: the automation window or the driver is gone. Stop and restart extension_dev with browser: "safari".
  • extension_eval in background fails: Safari's CSP blocks it. Have the background write one line on start and assert background-worker-booted, or read extension_logs.
  • The extension is enabled but a content script does nothing: Safari grants website access separately from the enable switch. Allow the site under Permissions in the same Extensions panel.
  • extension_start refuses the Safari target: the framework does not run start or preview for Safari. Use extension_dev for the loop and extension_build for the app.

Next ​