Edoras is a local Safari control layer for agents on macOS. It exposes a stdio MCP server plus a native Swift helper so an agent can inspect Safari tabs, attach to a tab, open an agent-owned tab, navigate it, observe page state, and show visible presence in Safari without a Safari extension.
It is built for agents that should use Safari, not a generic headless browser. The browser stays visible, the attached tab is explicit, and foreground actions refuse to run unless Safari is actually frontmost.
The presence UI has two parts:
- A clean static blue marker on the real Safari tab button.
- A glowing animated blue border around the visible page area when the attached tab is also the visible Safari tab.
Edoras is intentionally local-first. It does not collect telemetry, grant macOS permissions for you, enable safaridriver, or install a browser extension.
- macOS with Safari
- Xcode Command Line Tools or SwiftPM
- Node.js 18 or newer
- A local MCP-capable host such as Claude Desktop, Codex, or another stdio MCP client
git clone https://github.com/fschrhunt/Edoras.git
cd Edoras
npm install
npm run build
npm test
npm run doctorThen request the macOS permission prompts:
npm run request-permissionsrequest-permissions can open the right System Settings pane, but macOS still requires you to grant permissions manually.
Edoras uses Apple Events and Accessibility APIs to work with Safari.
Grant permissions to the app or host process that launches Edoras. Depending on how you run it, that may be Terminal, iTerm, Claude Desktop, Codex, node, swift, or the built edoras-helper.
Recommended setup:
- Open System Settings.
- Go to Privacy & Security.
- Enable Accessibility for the host process that will run Edoras.
- Go to Privacy & Security -> Automation.
- Allow the host process to control Safari when macOS asks.
- In Safari, enable Develop -> Allow JavaScript from Apple Events for richer page observation.
If you do not see the Develop menu in Safari:
- Open Safari Settings.
- Go to Advanced.
- Enable "Show features for web developers".
- Use Develop -> Allow JavaScript from Apple Events.
Check your setup at any time:
npm run doctorFor a stable local command path:
npm run install-localThis builds release binaries and creates symlinks in ~/.local/bin:
edoras-helperedoras-menubaredoras-mcp
Use a different destination if needed:
EDORAS_BIN_DIR=/usr/local/bin npm run install-localEdoras runs as a local stdio MCP server.
Use this command:
node <path-to-edoras>/bin/edoras-mcp.jsFor release builds, set EDORAS_HELPER:
EDORAS_HELPER=<path-to-edoras>/.build/release/edoras-helper node <path-to-edoras>/bin/edoras-mcp.js{
"mcpServers": {
"edoras_safari": {
"command": "node",
"args": ["<path-to-edoras>/bin/edoras-mcp.js"],
"env": {
"EDORAS_HELPER": "<path-to-edoras>/.build/release/edoras-helper"
}
}
}
}If you installed locally and ~/.local/bin is on your PATH, you can use:
{
"mcpServers": {
"edoras_safari": {
"command": "edoras-mcp",
"env": {
"EDORAS_HELPER": "<path-to-edoras>/.build/release/edoras-helper"
}
}
}
}Claude Desktop commonly reads MCP config from:
~/Library/Application Support/Claude/claude_desktop_config.json
Add edoras_safari under mcpServers, then fully quit and reopen Claude Desktop.
If your Codex setup supports MCP servers in ~/.codex/config.toml, add:
[mcp.edoras_safari]
command = "node"
args = ["<path-to-edoras>/bin/edoras-mcp.js"]
[mcp.edoras_safari.env]
EDORAS_HELPER = "<path-to-edoras>/.build/release/edoras-helper"Restart Codex or reload its MCP configuration after editing.
The MCP server exposes:
safari_statussafari_attach_currentsafari_list_tabssafari_open_tabsafari_navigatesafari_focus_tabsafari_observesafari_clicksafari_typesafari_keysafari_screenshotsafari_ocrsafari_show_presencesafari_end_sessionsafari_close_agent_tab
Suggested first tool calls:
safari_statussafari_open_tabwith a harmless URL such ashttps://example.comsafari_observesafari_show_presence
- Inspect Safari status and visible tabs.
- Attach to the current tab or open an agent-owned tab.
- Navigate, observe, click, type, and send keys with foreground checks.
- Capture screenshots and OCR through the local helper.
- Show visible tab/page presence so the human can see when Safari is under agent control.
You can test without MCP:
swift run edoras-helper status
swift run edoras-helper doctor
swift run edoras-helper list-tabs
swift run edoras-helper open-tab --url https://example.com
swift run edoras-helper observe
swift run edoras-helper show-presence
swift run edoras-helper overlay-status
swift run edoras-helper end-sessionEdoras includes a small native menu bar controller:
swift run edoras-menubarThe menu bar app provides common actions such as status, attach current tab, open agent tab, show presence, run doctor, and request permissions.
To launch it at login:
npm run launch-agent:installTo remove the launch agent:
npm run launch-agent:uninstallsafari_open_tabopens and tracks an Edoras-owned tab.safari_close_agent_tabonly closes tabs Edoras opened itself.safari_attach_currentattaches to the current Safari tab, so use it only when that tab is meant for the agent.safari_navigatecan navigate the attached Edoras tab without focusing Safari.safari_click,safari_type, andsafari_keyare foreground actions. They refuse to run unless Safari is frontmost and the attached Edoras tab is visible.- Tab presence only uses real Safari tab-button geometry.
- Page presence only draws around the visible page for the attached visible tab.
- Window-level highlighting is diagnostic only through
show-presence --mode window.
Run the full local check:
npm install
npm run build
npm testPresence verification touches live Safari geometry, so run it locally when Safari is available:
npm run verify:presencenpm run verify:presence validates geometry without forcing a long-lived visible overlay. To test the visible overlay:
EDORAS_VISUAL_TEST=1 npm run verify:presenceIf Safari is not open or the attached tab is not visible, presence verification may skip page checks instead of failing.
If tools are missing in your MCP host:
- Confirm the MCP config uses absolute paths.
- Confirm Node.js is installed and available to the host.
- Fully quit and restart the MCP host.
- Run
EDORAS_HELPER=<path-to-edoras>/.build/release/edoras-helper node <path-to-edoras>/bin/edoras-mcp.jsmanually and check for JSON-RPC startup errors.
If Safari control fails:
- Run
npm run doctor. - Recheck Accessibility and Automation permissions.
- Confirm Safari is running.
- Enable Safari Develop -> Allow JavaScript from Apple Events for richer observation.
If presence appears in the wrong place:
- Run
swift run edoras-helper observe. - Check
tabPresence.sourceandpagePresence.source. - Run
swift run edoras-helper overlay-statuswhile the overlay is visible.
- Local state is stored in
~/Library/Application Support/Edoras. - Edoras does not collect telemetry.
- Safari and macOS permissions remain local and user-granted.
safaridriveris optional and is not enabled automatically.
- Safari tab geometry is best-effort because Safari does not expose a stable public tab-rectangle API.
- Exact page-region detection depends on Accessibility. Edoras uses a conservative content-area fallback when needed.
- Background navigation is supported, but background clicking and typing are intentionally refused.
- Edoras is macOS/Safari-only.
MIT