WebKitUI MCP
Native WebKit for Mac. Human approval and persistent sign-ins. Requires the companion app.
Install
Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).
https://github.com/lowrisk75/webkitui-mcp/releases/download/v0.6.24/webkitui-mcp-0.6.24-macos-arm64.mcpbDocumentation
From the project's README on GitHub, refreshed weekly. Links open on GitHub.
Contents
WebKitUI MCP
Native WebKit automation for Apple Silicon, exposed through MCP and designed for an LLM rather than a human.
This repository is a Swift rewrite. The retained TypeScript/Playwright files are prior art only and are not the implementation being extended.
What exists
- Native
WKWebViewruntime using the persistent system WebKit data store for real authenticated sessions. - MCP 2026-07-28 stdio server with legacy initialization compatibility.
- Thirteen bounded tools, including authenticated native downloads, explicit page/element scrolling, and bounded text extraction for rendered virtualized logs.
- Observation-scoped element symbols backed by semantic locator recipes and fresh action-time resolution.
- Five separate addressing counters:
address_resolution_failed,address_now_ambiguous,logical_target_changed,node_replaced_but_semantic_locator_recovered, andcoordinate_invalidated_by_layout_change. - Provenance attached to every serialized page string.
- Observation-time privacy minimization: non-rendered controls are removed before bounding, sensitive/token-like fields never serialize values, and a sensitive select publishes neither its option labels, nor how many it has, nor the one currently chosen in it — a selected label is that control's value. The control is still reported, with its role and accessible name, so it can be handed to a human.
- Checkpoint-plus-delta observation history and Minimal Failure Set coverage metrics.
- Optional local Ollama ranker with
think: false, strict budgets, and deterministic fallback. - Transaction ledger with preconditions, exact post-conditions, idempotency keys, indeterminate outcomes, receipts, and reconciliation without replay.
- Human confirmation before every exposed click and open-world navigation. Navigation defaults to a server-owned native macOS dialog so a client cannot silently decline the round trip; MCP multi-round navigation remains opt-in.
browser_sessioncan set a bounded 320×240–3840×2160 CSS viewport and can navigate back, forward, or reload using WebKit's own history list. History movement is confirmed against the destination WebKit reports; reloading a page produced by a form submission is refused before confirmation to prevent replay.- Camera, microphone, and geolocation requests are always denied by the native
WKUIDelegate. The observation names the requesting origin and permission, with repeated requests counted and distinct records bounded; there is no MCP operation that can grant one. - The confirmation names the origin a submit control's data would reach, computed
server-side from the freshly re-resolved element rather than from its accessible
name, and says so explicitly when that origin differs from the page the operator is
on. A submit button's
formactionoverrides its form'saction, so a control whose accessible name reads harmlessly can post elsewhere. - Confirmations are counted in a rolling 60-second window. From the fifth, the dialog states how many have been requested; the twentieth is refused instead of presented, because a dialog a human clicks twenty times in a row has stopped being a gate.
- Legacy MCP clients receive the same exact-action authority boundary through a server-owned native macOS confirmation dialog; the model cannot supply or forge the approval value.
Local human handoff:
handoff_startimmediately returns an opaque session-bound resume token while the actual WebKit session becomes a visible window.handoff_statusis non-blocking;handoff_resumeconsumes the token only after local confirmation and returns a fresh observation. The agent remains locked out throughout human control.Handoff replies include the localized
return_control_button_label. Returning control confirms the human step, not remote saving. Arguments are specific to each operation:handoff_startaccepts only operation/session ID; status adds the resume token; resume additionally accepts compact/maximum_elements. Timing arguments belong to back/forward/reload, not handoff. Discovery remains composition-free for clients that cannot safely parse conditional schemas.- An active human-control window can be brought forward again with
handoff_focuswithout changing who controls the page. Session status says whether that surface is currently presented, and restricted authentication status exposes only bounded rendering booleans—never page text, field values, private URL components, or network payloads. - The packaged handoff app installs a native Edit menu, so standard first-responder shortcuts such as Command-X/C/V/A work inside WebKit form controls.
- MCP sessions use a loopback SOCKS5 boundary, one per profile and shared by its sessions, with failover disabled: hostnames are resolved once, public addresses are pinned, and private/reserved destinations plus non-TCP SOCKS commands fail closed.
- One process at a time controls the browser for the macOS account: the host lease belongs to that process and is released with its last session or on process death. The production CLI holds one session.
- Private remote clients can use the app-owned broker plus a forced-command SSH relay; WebKit and authenticated profile data remain on the logged-in Mac.
- Multiple relay clients may use the same long-lived broker concurrently, each
in its own session and window, up to three, on the shared default profile. A
session belongs to one client: another client gets
session_in_usewithwait_only=trueand cannot observe, navigate, act, invalidate addresses, or request duplicate user control in it. Ownership transfers after the owning client disconnects, or through a human-confirmedclient_handoff. Native confirmations are shown one at a time across the app, and each names the requesting agent as a quoted, self-reported name. - Each independent task must call
browser_session(operation: "open")and keep its returnedsession_idfor all page operations. On a shared transport, a full broker refuses an open that would return an already-owned page. Status without an ID selects the caller's sole session; when it owns several, it asks for an explicit ID. A new client with spare capacity is directed to open its own page. Host lease metadata reports the broker's remaining session slots. - While a person holds the human control window, a pop-up the page opens (a sign-in
with Google or Apple, say) is a real window sharing the page's profile, protected
proxy and
window.opener, at most four; all close when the agent resumes. Under agent control ordinary new windows stay refused and reported. An explicitly approved download can retain one request-scoped export view, with the original configuration/profile, without replacing the page. Only same-origin GET attachment responses or Blob downloads are accepted there; it is closed at the request's terminal outcome and is never an agent-visible tab. - The app broker owns one live browser across MCP client reconnects. Every reconnect invalidates observations, pending approvals, capabilities, and transaction coordinators before returning the preserved session handle.
- The native host starts the AppKit event loop before MCP work and keeps the live
WKWebViewattached to one opaque window. Agent control orders that window out; human handoff presents the same view without reparenting it. A packaged visual smoke mode checks the actual WindowServer pixels. - The stdio relay stays alive across broker restarts. It reconnects before the next undispatched request and never silently replays a request whose outcome became unknown during a restart.
- Durable multi-client status works without a session ID and reports only
privacy-safe holder metadata (client name/version, PID, age, inactivity and
policy).
open(wait_timeout_ms: ...)can wait up to 60 seconds for the local host lease without stealing it; dead transports release their session owner.browser_session(operation: "client_handoff")can transfer an idle session only after local human confirmation and a final no-active-call check. - A secretless SiliconPass fill can return
credential_not_found; WebkitUIMCP then offers a native human handoff for manual sign-in and addition/update in SiliconPass. No credential value crosses MCP, JSON, logs, or the clipboard. - Web-content termination invalidates every observation immediately; recovery reloads the host-owned last URL without replaying an action. A forced-crash fixture verifies that an
HttpOnlyauthenticated cookie pluslocalStorageandsessionStoragesurvive in the same view/data-store lifetime. - The macOS companion is a regular Dock app with a local activity window. It records only allowlisted MCP tool names, outcome, timestamp, duration and a bounded error type. Parameters, URLs, page content, credentials, cookies, keystrokes and response bodies are structurally absent. Owner-only JSONL files rotate automatically and can be exported or cleared from the app.
Deliberate limits
browser_actexposes click, native submit-control click, bounded non-sensitive input/textarea fill, Enter/Tab/Escape, blur, explicit input commit, and answering the one JavaScriptalert/confirm/promptdialog a page is suspended on (dialog_accept,dialog_dismiss,dialog_accept_value). A pending dialog is reported in the observation as untrusted site content, blocks every other operation until it is answered, is never answered automatically, and resolves to an explicit indeterminate outcome if nobody answers it.beforeunloadis out of scope: the public macOS SDK exposes no before-unload panel to an embedder. Native approval also routes public text fills through AppKit insertion with a measured trustedinputreceipt. Fill verifies both the freshly re-resolved semantic target's exact value and that its live validation state is not invalid. Other actions support exact URL or URL prefix, title, heading, exact/contains semantic text, checked, selected, enabled, semantic target absence (target_absentwithvalue="true"), value, validation state, character count, bounded state attributes, dialog, named panel, or selected-option postconditions. A same-URL SPA mutation returnssame_url_page_state_changedguidance instead of implying success. UI state never proves backend commit.browser_downloadconverts authenticated attachment responses toWKDownloadfrom either a fresh observed control or a protected same-origin URL fallback. It requires exact native confirmation plus a save-panel destination, never overwrites an existing file, and succeeds only after an on-disk receipt reports the HTTP status, suggested/final filename, MIME type, byte count, SHA-256, and decoded provisioning-profile UUID when available. Cookies, headers, and the absolute local destination path stay outside MCP.- macOS file inputs use WebKit's native open-panel delegate. Selected regular files are bounded to 10 files and 50 MiB each; receipts may expose filenames, byte counts and SHA-256 values, but never local paths or file contents. A file-selection receipt is not provider acceptance: the action's independent postcondition must still verify the uploaded preview or saved state.
- Call
browser_uploaddirectly on the freshly observed file input or chooser-opening label/button, including a menu's upload button. Opening its file panel withbrowser_actfirst leaves a pending chooser thatbrowser_uploadrefuses without another click. Use human handoff to finish or cancel that chooser, return control and reconcile before a new upload. Missing gesture evidence remains uncertain even when a separate receipt proves file selection; never replay automatically. - Fill dispatches normal
input/changeevents, so site handlers may autosave or cause server effects. It requires human authorization (exact approval or an explicitly remembered interaction scope); password controls require local human handoff. approval_mode: "native"sends confirmed click/submit and Enter/Tab/Escape through public AppKitNSEventhandling on the freshly re-resolvedWKWebViewtarget. An isolated message handler must observe the matching DOM event withevent.isTrusted == truebefore the receipt reports trust. Missing/mismatched receipts fail indeterminate; no flag is synthesized.approval_mode: "mcp", blur, and commit remain JavaScript-dispatched and report untrusted. Native public-text fill reports trust only when its matching AppKit insertion receipt is observed.- Action results separately expose
confirmation_mode,dispatch_mode, andtrusted_gesture_state. Native confirmation alone never establishes event trust. - Navigation readiness and rendered-content availability are separate. A document can
reach
readyState=completeand mutation quiescence while remaining blank; navigation, observation, and text reads reportempty_or_unusablein that case and explicitly forbid treating the absence as page truth. A rendered textless canvas or media document remainsusable. browser_read_textreads only currently rendered virtualized lines. Use bounded scroll plus another read for additional ranges.- No arbitrary JavaScript, raw CDP escape hatch, coordinate retry, proxy fleet, anti-bot bypass, or headless claim. Exact per-action confirmation is the default; users can explicitly opt into the bounded remembered permissions described below. This changes the default authority boundary only for the site, profile and operations the user selects.
- Viewport resizing changes desktop CSS layout only. It does not emulate a mobile
device—the public macOS SDK exposes no
WKWebViewContentModeAPI—andset_emulated_mediais deliberately absent because it does not help operate a person's site. - Camera, microphone, and geolocation access cannot be granted through WebKitUI MCP. Requests are denied and reported in the next observation instead of leaving a page failure silent; use human control in a complete browser when the workflow requires one of those permissions.
- No multiple agent-visible tabs. A request-bound export view is reserved for managed downloads; ordinary popup refusals and human-controlled sign-in windows remain distinct. One session holds one exclusive host lease, keeping approvals tied to an unambiguous page.
- No subresource or XHR request inspection. It is possible and it is not offered.
WKWebsiteDataStore.proxyConfigurationsis public from macOS 14 and is Apple DTS's own recommendation for reading aWKWebView's request contents, and a bundledWKWebExtensionwith thewebRequestpermission is public from macOS 15.4. Neither is free: the proxy route needs a trusted root certificate to see inside HTTPS, and the extension route reports no headers and cannot block. What is reported instead is narrow — the download receipt's HTTP status, and the egress proxy's accepted, blocked, pinned and timed-out connection counts. A main-frame navigation result carries no HTTP status today, and the proxy does not name the hosts it allowed or refused. - Native AppKit dispatch is not a distinguishing feature and is not claimed as one.
safaridriverdispatchesNSEventthrough[window sendEvent:]exactly as this does, and the WebDriver specification requires every conformant driver to produce trusted events. What differs is the measurement: an action whose trusted DOM receipt is missing or mismatched fails indeterminate here, where Playwright's hit-target interceptor treats an absent event as success. - The
WKFormInfosubmission gate is implemented, unit-tested, and does not fire. WebKit did not callwebView(_:willSubmitForm:submissionHandler:)on either macOS 27.0 build measured here,26A5416band26A5419a— not forsubmit(), not forrequestSubmit(), and not for a native trusted-gesture click. It is therefore not a second live gate and must not be read as one; it will decide if WebKit begins delivering the callback. The live defence against a submit control that posts to another site is the destination line in the confirmation. - Registered cross-origin frames expose bounded rendered semantics with
THIRD_PARTY_EMBEDprovenance and a sanitized origin. Their geometry is explicitly frame-local;frameActionModes(or compactframe_action_modes) names eligible attempts even whenactionable=falsemeans no native pointer geometry. A confirmedhoverorselect_optionruns only as untrusted JavaScript in the exact child frame; non-sensitivepress_keyandfillcan use AppKit only when that frame returns a matching trusted DOM receipt. A native pointer click has no public frame-to-window coordinate transform, refuses before confirmation withcross_origin_native_geometry_unavailable, and points to live human handoff. This is bounded control reach, not proof of hosted checkout completion. Sensitive and authentication controls still require a human. Frames that cannot be evaluated, and frames beyond the bounded native registry, remain explicitly unreadable rather than being treated as empty page content. - Some identity providers require a complete browser surface and do not render
inside an app-embedded
WKWebView(for example a WebAuthn-only step without the browser passkey entitlement). Those returnfull_browser_requiredwith an internalsafari_compatibilityrequirement. Any HTTP(S) page may also usecompatibility_startas an explicit manual fallback for native rendering problems.browser_sessionwithoperation: "diagnose"returns bounded structural loading and error counts, without console messages or request URLs. Script counts describe structure; they do not prove the cause of a stalled load. Usestartorfocusto present the native window;handofftoggles control. The operation opens the exact private URL in Safari only after native confirmation; it never auto-switches merely because a page timed out, never replaces the native handoff, provides no Safari observation/control, and cannot return a Safari login to this MCP session. Complete the blocked workflow manually in the external browser. WebKitUI never copies cookies, passkeys, AutoFill data, or credentials between backends. takeSnapshotmay omit GPU-composited effects.- No exactly-once or rollback claim for an uncooperative website.
- Low concurrency is intentional because WKWebView has no per-view hard memory quota.
- The protected network path is the MCP session registry or
WebKitRuntime(protectedWebsiteDataStore:); the lower-levelWebKitRuntime(websiteDataStore:)initializer is intentionally unprotected for fixtures and embedding. - This is a bounded website-traffic control, not a complete process egress
sandbox. The exact tested transports, exclusions and safe-use rule are in
docs/network-boundary.md.
Compared with the Safari MCP server
Apple's Safari MCP server ships inside safaridriver and is started with
safaridriver --mcp; Apple's own help text for the flag reads "Run as an MCP (Model
Context Protocol) server using stdio transport." It exposes seventeen tools — page
content, screenshots, network requests, console logs, JavaScript evaluation, DOM
interaction, viewport and media emulation, tab management. It needs two settings in two
different panes: "Show features for web developers" in Safari > Settings > Advanced, and
"Allow remote automation and external agents" in Safari > Settings > Developer. Apple's
floor is Safari 27 beta or Safari Technology Preview 247 or later, and Apple documents
Safari 27 beta on macOS 26 and macOS Sequoia as well as macOS 27, so it is not a macOS 27
requirement.
Apple aims it at web developers: it "gives your agent the ability to know how your code actually renders in the browser", and both sets of release notes file it under WebDriver > New Features. Apple also states it "runs entirely on your local machine and makes no network calls of its own" and that it "does not have access to your personal information in Safari (e.g. AutoFill or other browser activity)". For developing a site it is the better tool, it is included with Safari and Apple lists no separate price, and this project does not compete with it.
The difference is what happens when the site is not yours and you are signed in to it:
| Safari MCP server | WebKitUI MCP | |
|---|---|---|
| Purpose | inspect and debug a site you are developing | act on a site you are signed in to |
| Approval | no confirmation step is documented, and none of the seventeen tools requests one | native macOS confirmation before every exposed click and open-world navigation |
| Tool annotations | none: every tool carries only name, description and inputSchema, so a client gets no signal separating page reading from JavaScript evaluation | readOnlyHint, destructiveHint, idempotentHint and openWorldHint on every tool |
| JavaScript evaluation | exposed as a tool | absent, with no CDP or coordinate fallback |
| Session | Apple's WebDriver documentation states automation windows are "isolated from normal browsing windows, user settings, and preferences" and that a session "always starts from a clean slate" | its own WKWebView and its own persistent profile, which is what authenticated work requires |
| Protocol | reports 2024-11-05 | 2026-07-28, the current revision |
| Network detail | full request inspection, including headers, body and timing | no per-request inspection: the download receipt's HTTP status, and the egress proxy's connection counts without host names |
| Verification | inspection tools | an explicit postcondition per action, plus separate confirmation_mode, dispatch_mode and trusted_gesture_state |
The isolation wording above is Apple's WebDriver documentation, about automation sessions generally. Apple has published no equivalent statement about the MCP server itself.
Both can be installed at once, and for most developers both should be.
Two things this project will not say about Apple's server, because Apple does not: that it cannot reach your cookies or your logged-in sessions — Apple's MCP post names AutoFill and "other browser activity" only — and that Apple states there is no confirmation step. The absence is documented; a denial is not.
Build and test
The product targets macOS 15+ on Apple silicon. Building the optional
WKJSHandle probe requires the macOS 27 SDK; those APIs stay feature-gated.
swift build -c release --arch arm64
xcrun swift-format lint --strict --recursive Sources Tests Package.swift
swift test --arch arm64
swift test -c release --arch arm64
For a reproducible verification of the current checkout plus the signed local installation and its two-client transport:
scripts/verify-native-installed.sh
Run the server:
swift run -c release --arch arm64 webkitui-mcp
Before registration, run the secret-free local readiness check:
webkitui-mcp doctor
It checks the native confirmation helper, local owner-authentication policy, architecture, macOS version, and locally stored license state. It does not open a website, read browser data or credentials, authenticate the user, or contact the license service.
webkitui-mcp --help prints the stdio contract without starting the server.
The sibling webkitui-mcp-confirm helper owns legacy native dialogs so closing
an AppKit alert cannot terminate or corrupt the long-lived stdio server.
The process reads newline-delimited JSON-RPC from stdin, writes protocol responses only to stdout, and reserves stderr for diagnostics.
For a disposable direct process, install the CLI and register it normally:
release_bin="$(swift build --show-bin-path -c release --arch arm64)/webkitui-mcp"
release_dir="$(dirname "$release_bin")"
install -m 0755 "$release_bin" "$HOME/.local/bin/webkitui-mcp"
install -m 0755 "$release_dir/webkitui-mcp-confirm" "$HOME/.local/bin/webkitui-mcp-confirm"
codex mcp add webkitui-mcp -- "$HOME/.local/bin/webkitui-mcp"
claude mcp add --scope user webkitui-mcp -- "$HOME/.local/bin/webkitui-mcp"
Install the two together and leave them side by side. Before showing a confirmation the server checks the helper next to it, and what it can demand depends on how the server itself is signed:
- a notarized server pins its Developer ID team and the exact identifier the release script stamps, so nobody can drop a helper of their own beside it;
- a server built from source is ad-hoc signed and has neither, so the check can only require that the helper sits next to it under its own name. That gives nothing away: whoever can write a helper beside an unsigned server can replace that server too.
Mixing the two is refused in both directions. If you re-sign one binary, re-sign the other with the same identity, or the confirmation will not appear and every navigation will fail closed.
Keyboard behaviour of the confirmation
The confirmation uses a nonactivating panel: it can receive keyboard input while the caller remains the active application, without adding a separate helper icon to the Dock. Two preferences, shared by a source build and the notarized app, decide what the keyboard may do:
# Return cancels and Escape closes after 1 s of initial keyboard focus (default).
defaults write com.lorislab.webkitui-mcp ConfirmationKeyboardDefault cancel
# Return/Escape have no default action; click, or Tab to a button then Space.
defaults write com.lorislab.webkitui-mcp ConfirmationKeyboardDefault none
# Arming delay for Return/Escape, 0 to 5 seconds (default 1).
defaults write com.lorislab.webkitui-mcp ConfirmationArmingDelaySeconds -float 2
The arming delay starts after the initial display and key-window acquisition.
Queued Return/Escape input is checked against its original event timestamp. Initially no button has focus:
stray Return/Space press nothing, and Escape
is ignored until armed. Deliberately selecting a button with Tab and Space or
clicking still works. In none mode Escape stays disabled.
Approve is never a keyboard default and cannot be made one. The local Always
Allow… menu lets the person choose navigation only or navigation plus clicks/
typing, either for this client/session or remembered for the exact site and browser
profile. Nothing is remembered by Cancel or Approve Once. Permanent permissions
apply to all agents using that profile, not to a self-reported client identity;
private profiles offer session choices only. An origin includes scheme and port.
Interactions can send, modify or delete data even when a site does not label their
effects. Native form submissions, downloads/uploads, dialogs, authentication and
control transfers retain their separate approval gates. MCP arguments cannot grant
permissions. Use Revoke Remembered Site Permissions in the status menu to clear
session and permanent grants; it affects future actions and cannot undo one already
dispatched. Receipts distinguish remembered authorization from exact confirmation
with authorization_source; event trust still needs its independent receipt.
For authenticated sessions that must survive Codex conversation reconnects, build the self-contained app instead:
scripts/package-preview.sh dist
scripts/verify-package-preview.sh dist
During human control, ⌘F opens local native page search; ⌘G and ⇧⌘G move between matches, and Escape closes search. Queries are not exposed through MCP. Search is disabled outside human control and while a native save sheet is attached.
Managed downloads require approval followed by a native destination choice: select the folder and click Save. This is not proof of transfer completion; only a verified download receipt confirms delivery. Cancellation, request revocation or closing the source window dismisses the destination sheet. Unmanaged manual downloads remain unsupported; do not replay a refused managed download blindly.
Unzip WebKitUI-MCP-0.6.43-preview.zip, move WebKitUI MCP.app to the
Applications folder, and open it. In the status window:
- Enable Launch at Login. macOS may require approval in System Settings.
- Copy and run the Codex setup command. It points to the relay embedded in the app bundle and the owner-only local Unix socket.
- Keep the app in Applications after registration so the saved relay path remains valid.
The app bundle also carries the pinned SiliconPass vault MCP connector at
Contents/Resources/CredentialBroker/siliconpass-vault-mcp. It remains a thin,
secretless client of SiliconPass's separately installed and unlocked credential
broker; WebKitUI does not contain the vault, its keys, or a credential export.
Configure it only after installing the matching SiliconPass broker manager:
This source pins the locally verified manager 0.2.5 (22), vault MCP 0.3.0 contract (ten tools), exact connector bytes and machine-code hash. Package checks list tools and reject an invalid secret argument; they do not read or modify a vault. This is an installed-version pin, not a claim about the latest upstream release.
codex mcp add siliconpass-vault -- \
"/Applications/WebKitUI MCP.app/Contents/Resources/CredentialBroker/siliconpass-vault-mcp"
Report this site… prepares a voluntary report about a site the agent could not
drive: origin, version, load counters and the DOM structure around a label you
type (tags, roles, attribute names; never values or page text), in the
LorisReports envelope format. You read the whole report, then a Mail draft to
support@lorislab.fr opens; the app itself sends nothing. See
docs/privacy-retention.md.
The Activity journal button opens the local privacy-safe event history.
Its files live under ~/Library/Application Support/WebkitUIMCP/Activity with
owner-only permissions. Rotation retains at most seven 5 MiB archives plus the
active file. Clear removes activity files but preserves transaction
receipts; Export writes only the same redacted event schema.
The app uses Apple's Service Management API and does not install a mutable
plist in ~/Library/LaunchAgents. Prepare to uninstall disables Launch at
Login while preserving receipts and license data; then quit the app and move it
to the Trash. The preview archive is unsigned. Signing, notarization, clean-Mac
installation and publication are separate release gates.
Each MCP client receives isolated observations, confirmations, transactions,
and capability grants over the same durable browser. A logical
browser_session close detaches that client authority but deliberately keeps
the live browser; quitting the app destroys that in-memory page session.
Restart conversations opened before registration: MCP tool catalogs are fixed for a running conversation and are not retroactively replaced.
Confirmation timeouts do not prove that a person saw a panel. queue_timed_out
means no helper was launched; otherwise confirmation_presented is unknown
(null) unless the verified helper supplied a bounded native presentation receipt.
The receipt contains only an opaque request ID, process/window IDs, timing and
AppKit visibility/focus/screen flags and lifecycle/decision enums, never the requested action or destination.
Pending metadata is available while the helper runs; samples repeat every five seconds.
Parsing is bounded to 16 KiB and 32 records, with exact request/PID binding.
It is diagnostic evidence, not approval or proof of human perception.
Human-control windows and confirmations show the same short session label;
owned_by_this_client identifies a transport connection, not an individual task.
Tool discovery publishes composition-free typed object schemas for clients that
cannot import JSON Schema intersections/unions. Conditional operation requirements
are still enforced strictly at runtime. For example, a native click needs fresh
session_id, observation_id, element_id, an idempotency_key,
operation: "click", approval_mode: "native", and a postcondition such as
{"type":"checked_equals","value":"true"}. A fill instead supplies
operation: "fill" and value, and forbids a custom postcondition: exact value
and DOM validation are checked automatically. These examples confer no authority
to act on another task's session or to bypass confirmation.
SiliconPass control requests have a 15-second transport budget; credential delivery allows 330 seconds for the provider's native interaction (300 seconds plus a reply margin). Ending an invocation invalidates its callback sink: late callbacks cannot start a fill. Failures expose only a fixed stage/category and conservative recovery guidance, never remote error text or credential bytes. A transport failure does not prove that no fill occurred: inspect or hand off to the person, never retry automatically.
MCP flow
For long dialogs, targeted list scrolling, exact pending-confirmation focus and safe wizard postconditions, see bounded interaction workflows. DOM readiness is not business readiness or proof of a remote save.
- List the secretless persistent profile with
browser_session { operation: "profiles" }, thenbrowser_session { operation: "open", profile_id: "default" }. browser_navigateopens a native exact-destination approval dialog by default. Useapproval_mode: "mcp"only when the client reliably supports multi-round elicitation.browser_observe; useelement_scroll_into_view,browser_scroll, orbrowser_session { operation: "set_viewport", ... }, then observe again when needed.back,forward, andreloadare native-confirmed session operations.- Use the fresh
observationIDandelementIDonce. - For a login form, call
browser_fill_siliconpass. If it returnscredential_not_found, accept the native handoff and add or update the credential directly in SiliconPass. browser_actreturnsinput_required; approve the exact bound action.- Read, export as canonical
ReceiptV1, or reconcile the receipt withbrowser_transaction. One redacted canonical object produces both JSON and Markdown; only the SHA-256 of the idempotency key is exported. Exported evidence never authorizes replay.
Large semantic pages can be read without oversized MCP results: browser_observe
supports role/name filters, maximum_elements, maximum_field_characters, and
element_offset; follow nextElementOffset until it is absent.
The enforced limits and the remaining release measurements are documented in
docs/performance-budgets.md.
The authority boundaries, attack paths, current controls and residual release
gates are maintained in
docs/security-threat-model.md.
Local data lifetimes and explicit-export boundaries are documented in
docs/privacy-retention.md.
The stable diagnostic allowlist and forbidden support data are documented in
docs/support-diagnostics.md.
The manual signed-update, rollback, support and incident rules are documented
in docs/release-maintenance-policy.md.
While a person holds the human control window, its bar offers Fill with
SiliconPass when the SiliconPass broker is installed. The person asks, not the
agent: WebKitUI binds the one visible password form in the frame being used,
including a restricted sign-in frame such as idmsa.apple.com, and SiliconPass
matches the saved sign-in by that frame's exact host. The values are typed into the
two fields through AppKit, so the page sees real input events; nothing is
submitted and nothing reaches MCP, logs or the agent.
SiliconPass credential release is deliberately stronger than ordinary browser
confirmation. After the exact native fill summary is approved, macOS evaluates
deviceOwnerAuthentication uses zero Touch ID reuse and delegates the
available authentication method to macOS. WebKitUI does not infer that Touch ID,
Apple Watch, password fallback, or closed-lid authentication will be available
on a particular Mac. If the Mac is locked or authentication UI cannot be
presented, WebKitUI receives only
user_presence_unavailable: no secret is released and no automatic retry is
performed.
The protected browser never connects to private address space. The one exception
is a service on the owner's Tailscale network: navigating to a name that resolves
only into 100.64.0.0/10 asks for a native Allow Tailscale Access confirmation
for that exact origin and port. Once allowed, the proxy connects to that host on that
port only, pinned against rebinding; every other private address, and the same name
on another port, stays refused. Grants last until the app quits. Any other private
destination returns privateNetworkDestination instead of an opaque WebKit error.
Restricted authentication origins such as idmsa.apple.com return only their
canonical origin and a local handoff requirement. Observe, text, capture,
scroll, credential fill, and actuation remain blocked until the human leaves
that origin. A cross-origin redirect is not reported as an ambiguous WebKit
failure: it returns redirect_requires_human_approval with source and target
origins only.
When App Store Connect embeds that restricted Apple Account origin, the sign-in
takes the native human handoff like any other restricted origin. It used to return
full_browser_required: the frame measured as stalled was a hidden-page defect of
the parked window (fixed 2026-09-23), not an engine limit.
Navigation blocks on an exact native macOS confirmation by default and returns
a normal terminal tool result. Human handoff is
two calls: the first transfers control to the visible window; after completing
the sensitive step, call browser_session { operation: "handoff", ... } again
and approve the native resume dialog. MCP 2026-07-28 clients continue to use
multi-round input_required results.
Use browser_session { operation: "handoff" } when a human must control the same local WebKit session. Declining resume leaves human control active.
If a previous handoff token survives while agent control is active, status and
handoff_start point to handoff for recovery. A local confirmation is required
before invalidating the old token and starting a new human handoff. A confirmed
tokenless resume also invalidates earlier tokens. Another client's handoff remains
excluded, and human work in progress is reported as wait-only.
When a click's cross-origin navigation is refused, the action result and status
report a navigation_blocker containing only the source and destination origins
and a human-handoff instruction. The click is not automatically replayed, and the
private authentication request is not exposed.
Semantic appearance and target absence postconditions require an unfiltered, untruncated baseline:
otherwise postcondition_baseline_incomplete refuses the action before dispatch.
Observe again with enough elements and field characters to establish absence.
Covered controls name the hit-tested covering element in coveredBy (compact:
covered_by). Name filters normalize common apostrophes; visible inline onclick
controls are included even without a pointer cursor. Identically sized link wrappers
without independent state or explicit handlers are omitted in favour of their link.
The handoff window becomes a regular foreground Mac app with a Dock icon and
uses the same rendered view as semantic observation and capture.
Linux headless lane
linux/ is a separate, ephemeral Playwright worker for disposable workloads on
a dedicated Linux VM. It does not inherit Mac cookies or passwords and it does
not replace native WebKit. Route authenticated sessions and human handoff to the
Mac; route public, unauthenticated headless work to Linux. See
linux/README.md and
docs/architecture/linux-headless-worker.md.
Evidence
- Architecture:
docs/architecture/ - Private remote transport and deployment gates:
docs/architecture/private-remote-transport.md - Dated research and NotebookLM audits:
docs/research/ - Same-Mac runtime benchmark:
Benchmarks/README.md - Public/private publication boundary:
docs/architecture/public-private-boundary.md - Vulnerability reporting and release invariants:
SECURITY.md
The dated adversarial corpus measurement records 53 deterministic tests across 57 concrete fixture executions for provenance, dialog truth, destinations, and export leakage. It measures those product boundaries, not human or model attack success.
The first measured local lane uses 30 runs of the same deterministic fixture at 2560×1600. It compares WKWebView with Playwright 1.61.1 driving installed Chrome 151. It does not yet measure full process-tree memory, visible-window behavior, authenticated task success, or Playwright's pinned Chromium binary; no broader superiority claim is made.
Licensing
WebKitUI MCP 0.6.43 Developer Preview is available under the
Business Source License 1.1. The source is readable, auditable and
modifiable; production use is granted for personal noncommercial, qualifying
noncommercial organization and evaluation use, and commercial production use
requires an agreement with the Licensor. This version converts to Apache 2.0 on
2030-08-28. See LICENSING.md for the exact scope, third-party
boundary and treatment of earlier revisions.
Contextual verification and recovery
Use postcondition: {"type":"dialog_contains", "value":"Configure", "item_text":"Device ID"}
when a generic dialog name alone cannot identify the item. The exact accessible name
and case-sensitive rendered substring must occur on the same unique dialog in the
acted frame. The combined condition must be false before dispatch. Incomplete dialog text,
missing scope, or duplicate matching dialogs cannot prove it. This is an observed UI
transition, not proof of causality or backend persistence. Legacy dialog_appears
continues to mean an exact-name appearance only.
Transactional action results include recovery: dispatch certainty, per-predicate
evidence, and an exact read-only browser_transaction reconciliation call using the
original session and key when verification remains unproved. It never authorizes a
replay or a fresh key to repeat a possibly completed write.
Unlabeled textareas do not borrow their editable contents as an accessible name.
An observed, unique HTML name can preserve their semantic identity across a
controlled replacement. Missing or duplicated identity remains unverifiable.
The global privacy inventory accepts at most 512 document/shadow/frame roots and 20,000 element nodes, plus the existing 256 sensitive values and 65,536 sensitive characters. It must finish before any filtered output; exhaustion never returns a partially filtered page. Larger bounds are local capability limits, not a claim that new Reddit or every complex site has been qualified.
Safety
The model cannot mint capability handles. Page content never becomes trusted policy. Password values are omitted from observations. Unknown dispatch is indeterminate and is never automatically retried. No commit, push, deployment, or external account mutation is performed by the project itself.
Navigation actor attribution uses the first main-frame navigation within two seconds of an action. It is diagnostic evidence, not proof of causality or authorization: autonomous or more delayed navigation can be misattributed.
A disappearing or renamed Save/Done control cannot satisfy enabled_equals=false: that predicate requires the original semantic target to remain present and disabled. Use target_absent with value="true" for disappearance, or a heading/text predicate for the resulting page. Absence is proved only by a complete observation; it means the original semantic identity is no longer observed, and does not prove server-side persistence. An indeterminate result after dispatch requires inspection or receipt reconciliation, never automatic replay.
Human handoff recovery
Safari and WebKitUI have separate sign-in sessions; explain that a new login may be required before transferring a workflow. In the visible WebKitUI window, finish the private step and select Done — Return Control (French: Terminé — Rendre le contrôle). Use handoff_focus if hidden. A spoken completion or a finished navigation does not authorize agent control. Navigation only suggests checking whether the step is complete; it does not prove a remote save. Resume still requires local approval and returns a fresh observation.
After a SiliconPass delivery failure, do not automatically retry: delivery may already have occurred. Offer handoff_start immediately so the user can inspect and finish privately. For CAPTCHA, keep resolution with the user and facilitate the site's official accessibility options, including audio, without transcribing or solving the challenge.
The human window includes CAPTCHA / accessibility help and Focus website to move keyboard focus back to the page without activating or submitting anything. Help explains Tab/Shift-Tab and the provider's official audio/accessibility controls, when offered. Known widget navigation URLs can reveal a possible challenge and open this help automatically; this metadata hint is non-exhaustive and never establishes completion. Help remains available for unrecognized or embedded challenges. No widget text, image, audio, response token or answer is extracted by this assistance.
Returning control when privacy extraction fails
An explicitly authorized human return remains effective if the fresh observation exceeds the bounded privacy inventory. Resume then returns resumed=true, control_state=agent_controlled, status=resumed_observation_unavailable, observation_available=false and no observation. Old targets are invalidated. Use browser_navigate with an exact destination and its normal confirmation to leave the document, or request another human handoff. No write is retried and no site save is inferred.
privacy_budget exposes only a fixed category (values, characters, roots, nodes, or unknown), its enforced limit and a saturated lower-bound counter. It never contains scanned values or raw exception text. Closing with browser_preserved=true releases ownership and preserves the live page and control state; reopening a reused=true browser does not reset a handoff or bypass privacy filtering. The human window has no URL address bar; use confirmed agent navigation after returning control, or the site's own links during human control.
Frequently asked questions
What is WebKitUI MCP?
Native WebKit for Mac. Human approval and persistent sign-ins. Requires the companion app.
How do I install WebKitUI MCP?
Follow the installation steps in its documentation, shown on this page and in its repository.
Does WebKitUI MCP need an API key?
Its registry entry lists no required key or secret. Some features may still need an account with the service it connects to.
Is WebKitUI MCP open source?
Its code is public on GitHub, but the repository does not declare a standard license.
Who maintains WebKitUI MCP?
It is published by lowrisk75. AI.tn lists it but does not maintain or endorse it.
From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.