mcpField
A
Apple Safari

Apple Safari

Local
cupertino.mgcrea.ioBrowser Automation5Updated today

About Apple Safari

An MCP server for macOS Safari that reads browsing history, bookmarks, and the Reading List, and can inspect open tabs and page content. Its file, Apple Events, and extension lanes expose different data and actions and require their respective permissions.

Key features

  1. 01Search history and see visit counts
  2. 02List tabs, bookmarks, and Reading List
  3. 03Read page text, HTML, and elements
  4. 04Opt-in page network records
  5. 05Open URLs and act on pages when writes are enabled
  6. 06Optional code extraction from page text

Use cases

  1. 01Find a previously visited page
  2. 02Inspect open-tab content and form fields
  3. 03Review network requests from an allowed page
  4. 04Fill forms or save Reading List items with writes enabled

MCP support

Provides MCP clients with Safari history, tab, bookmark, Reading List, and page tools. Page access, network records, and write actions are governed by separate permissions and configuration gates.

Configuration

Local server config is the same for every client. Copy it into your client’s MCP config file.

mcp.json · Universal config
{
"mcpServers": {
"mgcrea-mcp-apple-safari": {
"command": "npx",
"args": [
"-y",
"@mgcrea/mcp-apple-safari"
],
"env": {
"APPLE_SAFARI_ALLOW_WRITES": "<APPLE_SAFARI_ALLOW_WRITES>",
"APPLE_SAFARI_ALLOW_CODES": "<APPLE_SAFARI_ALLOW_CODES>",
"APPLE_SAFARI_ALLOW_NETWORK": "<APPLE_SAFARI_ALLOW_NETWORK>",
"APPLE_SAFARI_DOWNLOAD_DIR": "<APPLE_SAFARI_DOWNLOAD_DIR>",
"APPLE_SAFARI_NETWORK_CHUNK_CHARS": "<APPLE_SAFARI_NETWORK_CHUNK_CHARS>"
}
}
}
}
Run commandnpx -y @mgcrea/mcp-apple-safari
Environment variables to set
APPLE_SAFARI_ALLOW_WRITESRegister the mutating tools. Off by default, and the gate decides whether the write tools exist at all rather than merely refusing — an agent with writes off cannot see them.
APPLE_SAFARI_ALLOW_CODESReturn one-time codes: a one-time-code field in apple_safari_page_elements, and apple_safari_find_codes for a code a page shows as text. Its own gate rather than allowWrites, because it is a read. Credential fields stay redacted whatever this says.
APPLE_SAFARI_ALLOW_NETWORKRegister apple_safari_network_record, _list and _get, which record what a page you allowed the extension on fetches and read the responses. Its own gate rather than allowWrites, because it is a read; off means the tools do not exist. Headers named like credentials are masked, bodies are not. Reloading a tab to record and saving a body to a file still need APPLE_SAFARI_ALLOW_WRITES.
APPLE_SAFARI_DOWNLOAD_DIRWhere apple_safari_network_get's saveAs writes a file. Defaults to ~/Downloads. A name is kept to its basename and an existing file is never replaced unless asked.
APPLE_SAFARI_NETWORK_CHUNK_CHARSCharacters of a body the server asks the page for per command when it reads a large body. Defaults to 262144, which was measured to work; how far above that Safari's native-message channel goes is unmeasured.

Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.

Packages
npm@mgcrea/mcp-apple-safari1.31.0

Tools

No tool list yet

Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.

Details

From the project README

@mgcrea/mcp-apple-safari

MCP server for the macOS Safari browser: what you have visited, what is open now, and what is on the page in front of you.

{
  "mcpServers": {
    "apple-safari": { "command": "npx", "args": ["-y", "@mgcrea/mcp-apple-safari"] }
  }
}

Three lanes that are not fallbacks for each other

This is the thing to understand before anything else. Every other surface in this bundle has two routes to the same data and the choice is about speed. Safari's lanes see almost disjoint things, and each needs a different permission — so an ungranted Safari server is not a slower Safari server, it is a different and much smaller one.

LaneNeedsAnswers
FileFull Disk Accessthe past: history, bookmarks, the Reading List
Apple Eventsan Automation grant, Safari runningthe present: which tabs are open
Extensionthe extension enabled AND allowed on that sitethe page: its text, its elements, acting on it

The extension lane needs no TCC grant of any kind. Safari consents to it one website at a time, which the user can see and revoke per site.

Tools

Read

ToolWhat
apple_safari_search_historyhistory by text or date range, with visit counts
apple_safari_get_pageone history item by ref
apple_safari_list_tabstabs open right now, optionally enriched from history
apple_safari_list_bookmarksthe bookmark tree
apple_safari_list_reading_listsaved items, and which are unread
apple_safari_read_pagea captured page as text or HTML
apple_safari_page_elementswhat is clickable or typeable on a live page
apple_safari_diagnosticswhich lanes work, and why one does not

Opt-in read — APPLE_SAFARI_ALLOW_CODES

ToolWhat
apple_safari_find_codesa one-time 2FA code in the TEXT of an open page

Opt-in read — APPLE_SAFARI_ALLOW_NETWORK

ToolWhat
apple_safari_network_recordarm, stop, clear or report recording on a page (reload needs writes)
apple_safari_network_listwhat the page fetched: method, URL, status, timing, no bodies
apple_safari_network_getone request's headers and body; saveAs writes a file (needs writes)

Write — APPLE_SAFARI_ALLOW_WRITES

ToolWhat
apple_safari_open_urlopen a URL, in a new tab or the current
apple_safari_add_reading_list_itemsave a URL for later
apple_safari_clickclick an element on a live page
apple_safari_filltype into one
apple_safari_scrollscroll a page

Configuration

VariableDefault
APPLE_SAFARI_ALLOW_WRITESoffRegister the five write tools at all.
APPLE_SAFARI_ALLOW_CODESoffOne-time codes — see below.
APPLE_SAFARI_ALLOW_NETWORKoffNetwork capture — see below.
APPLE_SAFARI_DOWNLOAD_DIR~/DownloadsWhere network_get saveAs writes.
APPLE_SAFARI_NETWORK_CHUNK_CHARS262144Characters per native message when pulling a body.
APPLE_SAFARI_LIVE_TABSonUse the Apple Events lane. Off = no Apple Event.
APPLE_SAFARI_STOREautoExplicit History.db path.
APPLE_SAFARI_BOOKMARKSautoExplicit Bookmarks.plist path.
APPLE_SAFARI_PAGESautoExplicit extension capture directory.
APPLE_SAFARI_ACTION_TIMEOUT_MS12000How long an action waits for the page to answer.
APPLE_SAFARI_LIVE_READ_TIMEOUT_MS2500How long read_page waits on the tab. 0 = capture.
APPLE_SAFARI_READING_LIST_CONFIRM_MS1500Beat before re-reading to confirm a Reading List add.
APPLE_SAFARI_INDEX_MODEautoauto | ro | immutable | off.
APPLE_SAFARI_DEFAULT_RANGE_DAYS30Window when only a start is given.
APPLE_SAFARI_MAX_RANGE_DAYS3660Hard ceiling on a single range.
APPLE_SAFARI_MAX_RESULTS—Default page size.
APPLE_SAFARI_EXPOSE_PROMPTSoffRegister prompts and cupertino:// resources.
APPLE_SAFARI_DEBUGoffVerbose logging.
APPLE_SAFARI_ALLOW_CODES

Two things move together when it is on: find_codes is registered, and page_elements returns the value of a one-time-code field. It is a read, and deliberately not folded into ALLOW_WRITES — reaching a read through the write gate would mean granting the right to click a button in order to see a number.

A credential field is withheld either way. A password, a card number or a CVC comes back redacted: "credential" with no value and no setting that returns it. This flag is named for codes and does not widen past them. In both cases hasValue still says whether the field is filled, which is usually what you needed.

It is weaker than the Messages gate of the same name, and worth knowing which. There, off means the tool does not exist and the alternative is sifting whole threads. Here read_page stays ungated, so off removes the targeted field read and the live DOM scan — not every byte of a page.

See docs/passwords.md in the repo for why the Passwords app itself is unreachable by any lane, and why reading a code a website shows is a different question from reading the vault.

APPLE_SAFARI_ALLOW_NETWORK

Registers network_record, network_list and network_get, which show what a page you have allowed the extension on fetched: fetch and XMLHttpRequest calls from the top frame, console errors, and resource-timing entries. It is a read gate and is not folded into ALLOW_WRITES, for the reason ALLOW_CODES is not: the traffic of a signed-in page is session material, and that is a different exposure from a one-time code.

  • Bodies are not redacted, and neither are URLs. Headers named authorization, proxy-authorization, cookie or set-cookie, or containing token, secret, api-key or apikey, come back as <redacted: N chars>. A body has no names to match, so it is returned as the page saw it, and URLs are returned in full, query strings included, so a token in one comes back too. Turn the gate on knowing that.
  • Opt-in per tab, and it expires. Nothing is wrapped until network_record starts, recording survives a reload for 15 minutes by default (120 at most), and stop ends it at once.
  • Two things need ALLOW_WRITES as well: reload: true on network_record, which re-runs the page, and saveAs on network_get, which writes into APPLE_SAFARI_DOWNLOAD_DIR (a bare filename, and never over an existing file unless overwrite: true). Each fails with an error naming APPLE_SAFARI_ALLOW_WRITES.
  • What it cannot see: subframes, web and service workers, <img>/<script> bodies and WebSockets. Request ids die when the page reloads or navigates. On a page whose Content-Security-Policy refuses inline scripts there is a fallback that loads the recorder by URL instead. It has not been verified in Safari; if it works it arrives late, so the page's first requests can be missing, and if it does not, hook.injection says "blocked".
  • A file inside an API response: network_get with jsonPath to the field, decode: "base64" and saveAs. The bytes go to disk, not into the conversation.

Buffer limits: 1,000 records and 50 MiB of bodies per page; bodies are kept up to maxBodyBytes (10 MiB by default, 50 MiB at most). See docs/safari.md.

Notes that will bite you

  • An unmatched tab is normal, not an error. The only join key between a live tab and history is the URL, and it is trivially lossy: measured match rates were 60.7%, 55.3% and 8.3% across three runs. A single-page app reached by pushState commits no history row at all, so the page was genuinely visited and genuinely is not in the store. Treat history: null as NOT FOUND, never as "never visited".
  • An exact URL match is not proof the row is about that page. One measured tab on http://localhost:4321/ matched a history row for an entirely different project. Any local address carries this.
  • read_page is live when the tab can answer, and a snapshot when it cannot. It asks the open tab first and waits APPLE_SAFARI_LIVE_READ_TIMEOUT_MS; a visible tab answers within a second. A hidden tab checks in only every ten, so it — like a closed one — gets the capture the extension stored at load and as the page settled, with source: "capture", ageSeconds, and liveRead saying why. The acting tools always go to the live page, which is why they cost about a second on a visible tab and up to ten on a hidden one.
  • Read a form with page_elements, not read_page. Field values are not page text. Elements carry value (a select's shown option too), checked on checkboxes and radios, and disabled: true on greyed-out controls, which are listed rather than dropped. Values are cut at 200 characters unless maxValueChars raises it, and a cut one says so with valueTruncated.
  • Set a form with fill and click. fill writes through the element's native setter, which is what makes React register the change — a plain el.value = shows the text and leaves React's state on the old value. On a select it takes the option text page_elements reports. Checkboxes and radios are clicked, not filled, and a click toggles, so read checked first. File inputs cannot be set: a content script has no way to reach a file on disk.
  • A timeout from an action means nothing was listening — the extension off, not allowed on that site, or the page closed. It never means the page refused. Commands are at-most-once: one that is handed out and lost is not retried, because a click that MIGHT have landed must not be repeated.
  • Element ids die on navigation. They come from a page_elements call and are valid for that page only. Never construct one, and re-enumerate after any click that loaded something.
  • A Reading List add hits the network. Safari fetches the URL in the background to build the entry, and overwrites any withTitle you pass with the page's own title. verified has three states and is never false: the file lags the add by about 2 s, and reporting an unconfirmed write as a failed one makes callers retry — into a list with no remove verb.
  • The write lane needs no Full Disk Access. Both Apple Events writes work on a machine where the read lane does not. APPLE_SAFARI_LIVE_TABS=false outranks the write gate, because that flag promises a server that sends no Apple Event at all.
  • open_url accepts http and https only. A javascript: URL would be do JavaScript through the front door — measured: Safari accepts one through the navigation verb — and file: is a navigation verb that reads local files.

Licence

MIT.

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.