Summary
The macos-mcp server holds an AXObserver registration on the focused
window via AXObserverAddNotification. When that focused window is
Google Chrome and the user navigates to a new page (or switches
tabs), Chrome rebuilds its accessibility tree, fires AX notifications,
and the MCP synchronously walks the new tree. Walking Chrome's AX tree
takes ~1-2 seconds and during that time the entire UI freezes
(WindowServer waits for AX RPCs).
This is reproducible on every page load, every time, when Chrome is
the focused app. Other apps freeze too but Chrome is the worst because
its AX tree is enormous (every tab, every DOM element exposed as AX).
Environment
- macOS 26.4.1 (Build 25E253), Apple M2 (Mac14,5), 32 GB RAM
- Claude Desktop (Responsible PID for both Python instances)
- macos-mcp installed via Claude Extensions
(ant.dir.gh.cursortouch.macos-mcp)
- Chrome 147.0.7727.138
Symptoms
- Whole system freezes for ~1-2s every time Chrome navigates
- Only when Chrome has focus
- No CPU spike in Activity Monitor at the OS level (one Python at ~50%
of one core ≈ 4% on a 12-core machine — invisible at a glance)
- Memory pressure stays green
- Memory footprint grows unbounded over hours: 6.7 GB after ~18h,
3.3 GB after ~2h on a second instance
Diagnostic Evidence
Captured via sudo spindump "Google Chrome" 60 100 -file .... The
heaviest non-idle stack across the system was a worker thread in the
macos-mcp Python process:
Thread 0x569c cpu time 4.859s / 5.0s sample window
pythread_wrapper
thread_run
method_vectorcall
_PyEval_EvalFrameDefault
func_vectorcall (_objc.cpython-312-darwin.so)
ffi_call_int (libffi.dylib)
ffi_call_SYSV
_AXObserverAddNotificationAndCheckRemote (HIServices)
_AXObserverAddNotification
_AXMIGAddNotification
mach_msg → mach_msg2_trap (kernel)
The thread spent ~5 seconds out of 5 sampled (effectively all CPU
time) inside _AXObserverAddNotification on a single core. Chrome's
own browser process was idle (mach_msg waits) — it was waiting on
the AX RPC the MCP held open.
Process info:
- Path:
~/Library/Application Support/Claude/Claude Extensions/ant.dir.gh.cursortouch.macos-mcp/.venv/bin/macos-mcp
- Footprint: 6776 MB on 18.7-hour-old instance, 3346 MB on 2.3-hour-old instance
- Parent:
uv → Responsible: Claude
Hypothesis
The AX observer is registered against the system focused-window
notification, then on each notification callback the MCP walks the
target window's full AX tree synchronously. For Chrome, that tree
includes every tab and every accessible DOM node, which is huge.
Until the walk returns, AX RPCs from WindowServer back-pressure and
the user perceives a system freeze.
Suggested Fixes
-
Throttle / debounce focus-change handlers. Don't react to every
AX notification — coalesce and process at most once per N ms.
-
Lazy AX walk. Don't proactively walk the focused window's tree
on every focus/navigation. Walk on-demand only when an MCP tool
call asks for it.
-
Skip Chrome / browser apps. Maintain a deny-list of bundle IDs
whose AX tree is known to be expensive. Or only walk top-level
AXWindow and shallow children unless explicitly asked deeper.
-
Run AX queries on a background thread with a strict timeout.
Even today, the worker thread is doing AX work — but it appears
to issue many small synchronous AX calls in a tight loop.
Consolidating into batched async queries would let the system
reclaim the AX RPC bus between batches.
Related
This compounds with #4
(multiple instances spawning at launch). On a machine with 4 instances
each polling AX on focused Chrome, the freezes stack and the system
becomes unusable.
Reproduction
- Install macos-mcp via Claude Extensions
- Start Claude Desktop, confirm macos-mcp is enabled
- Open Chrome, focus its window
- Open a complex site (gmail, github, twitter)
- Click any link, or hit Cmd+L and load a new URL
- Observe ~1-2s system-wide freeze immediately after page commit
- Repeat — freezes on every navigation, indefinitely
Disabling the macos-mcp extension (or killing the Python process)
stops the freezes immediately.
Summary
The macos-mcp server holds an
AXObserverregistration on the focusedwindow via
AXObserverAddNotification. When that focused window isGoogle Chrome and the user navigates to a new page (or switches
tabs), Chrome rebuilds its accessibility tree, fires AX notifications,
and the MCP synchronously walks the new tree. Walking Chrome's AX tree
takes ~1-2 seconds and during that time the entire UI freezes
(WindowServer waits for AX RPCs).
This is reproducible on every page load, every time, when Chrome is
the focused app. Other apps freeze too but Chrome is the worst because
its AX tree is enormous (every tab, every DOM element exposed as AX).
Environment
(
ant.dir.gh.cursortouch.macos-mcp)Symptoms
of one core ≈ 4% on a 12-core machine — invisible at a glance)
3.3 GB after ~2h on a second instance
Diagnostic Evidence
Captured via
sudo spindump "Google Chrome" 60 100 -file .... Theheaviest non-idle stack across the system was a worker thread in the
macos-mcp Python process:
The thread spent ~5 seconds out of 5 sampled (effectively all CPU
time) inside
_AXObserverAddNotificationon a single core. Chrome'sown browser process was idle (
mach_msgwaits) — it was waiting onthe AX RPC the MCP held open.
Process info:
~/Library/Application Support/Claude/Claude Extensions/ant.dir.gh.cursortouch.macos-mcp/.venv/bin/macos-mcpuv→ Responsible:ClaudeHypothesis
The AX observer is registered against the system focused-window
notification, then on each notification callback the MCP walks the
target window's full AX tree synchronously. For Chrome, that tree
includes every tab and every accessible DOM node, which is huge.
Until the walk returns, AX RPCs from WindowServer back-pressure and
the user perceives a system freeze.
Suggested Fixes
Throttle / debounce focus-change handlers. Don't react to every
AX notification — coalesce and process at most once per N ms.
Lazy AX walk. Don't proactively walk the focused window's tree
on every focus/navigation. Walk on-demand only when an MCP tool
call asks for it.
Skip Chrome / browser apps. Maintain a deny-list of bundle IDs
whose AX tree is known to be expensive. Or only walk top-level
AXWindowand shallow children unless explicitly asked deeper.Run AX queries on a background thread with a strict timeout.
Even today, the worker thread is doing AX work — but it appears
to issue many small synchronous AX calls in a tight loop.
Consolidating into batched async queries would let the system
reclaim the AX RPC bus between batches.
Related
This compounds with #4
(multiple instances spawning at launch). On a machine with 4 instances
each polling AX on focused Chrome, the freezes stack and the system
becomes unusable.
Reproduction
Disabling the macos-mcp extension (or
killing the Python process)stops the freezes immediately.