IntelliJ MCP and the macOS accessibility tree are two different graphs.
They share the word “tree” and they each ship an MCP server. That is where the resemblance ends. One walks the IDE’s project view inside the JVM. The other walks the OS-level AXUIElement graph of any running app via kAXChildrenAttribute. They are not interchangeable, they don’t compose, and the only reason they keep landing on the same query is that both communities use the word “tree”.
Below: what each one actually exposes, the Swift code that walks an actual AX tree, and the seven attribute keys you need to read off each node if you want to build the second kind of MCP server.
Direct answer, verified 2026-05-07
IntelliJ’s MCP server does not expose the macOS accessibility tree.
It exposes IDE tools only: list_directory_tree, get_symbol_info, find_files_by_glob, search_in_files_by_text, rename_refactoring, and a handful of run-config and database tools. The full list is in JetBrains’ documentation. None of them call into the OS accessibility framework, and the IntelliJ MCP cannot read the accessibility tree of the IntelliJ window itself, let alone any other app.
To walk the actual macOS accessibility tree from an MCP server you need a separate native binary that calls AXUIElementCreateApplication(pid) on the target app and recurses through kAXChildrenAttribute. That is what whatsapp-mcp-macos does at Sources/WhatsAppMCP/main.swift lines 95 to 176.
Authoritative source for the IntelliJ side: jetbrains.com/help/idea/mcp-server.html. Authoritative source for the macOS AX side: developer.apple.com AXUIElement.h.
What IntelliJ’s MCP actually exposes
IntelliJ ships an MCP server starting with version 2025.2. It is a JetBrains plugin running inside the IDE’s JVM, not a native binary. Its tool surface is the IDE: project view, files, PSI symbols, run configurations, refactors. Nothing in this list reaches into AppKit or the macOS accessibility framework.
The closest IntelliJ MCP gets to a “tree” is list_directory_tree (a virtual-file system view) and the implicit PSI tree behind get_symbol_info and rename_refactoring. Both are code-shaped graphs, not UI graphs. Different problem, different MCP.
What “the macOS accessibility tree” actually is
On macOS, every running app exposes a graph of AXUIElement nodes through the OS accessibility framework, originally for VoiceOver and Switch Control. Each node has a role (AXButton, AXTextField, AXWindow, AXGenericElement, AXLink), optional text attributes (description, value, title), and a screen position and size. Children are an attribute too, the same way every other field is. You walk the tree by reading kAXChildrenAttribute on the current node and recursing.
Reaching this graph from your code means calling C-level functions in the ApplicationServices framework, with a process id as the entry point. From the JVM that is JNI; from a Swift or Objective-C binary it is a few function calls. An MCP server that targets this graph has to be the second kind of binary, not the first.
The full function with all the role and text filtering lives in the repo at Sources/WhatsAppMCP/main.swift. The seven attribute constants in the snippet above are the entire shopping list of AX keys you need for an LLM-friendly representation of a window.
Side by side: same word, different graph
| Feature | IntelliJ built-in MCP server | macOS AX-tree MCP (e.g. whatsapp-mcp-macos) |
|---|---|---|
| What 'tree' actually means | The IDE's project view: directories, files, and PSI symbols. A JVM-side data structure rendered into the Project tool window. | The macOS accessibility tree of a running native or Catalyst app. A C-level graph of AXUIElement nodes the OS exposes for VoiceOver, Switch Control, and any AX-aware client. |
| Where the tree lives | Inside the IntelliJ JVM. Lifecycle is bound to the IDE process and its open project. No tree exists when no project is open. | Inside the target app's process address space, exposed via the OS accessibility framework. Lives as long as the app is running, regardless of which IDE the developer is in. |
| How the MCP server reaches it | The MCP server is a JetBrains plugin running in-process inside IntelliJ. It calls IntelliJ APIs (VirtualFile, PsiManager, ProjectRootManager). No IPC to the OS. | The MCP server is a separate native binary. It calls AXUIElementCreateApplication(pid) to attach by PID, then recurses through kAXChildrenAttribute. IPC is through XPC under the hood. |
| What permission the user grants | None beyond installing the MCP Server plugin and trusting it inside the IDE. | macOS Accessibility, granted to the host process that forks the MCP child (Claude.app, Cursor.app, Terminal). Without that toggle, every AX call returns nothing. |
| Cross-app reach | None. The IDE-level MCP cannot read Slack, WhatsApp, Safari, or even another IDE window. It is bound to the JVM project context. | Any running app the user has Accessibility permission for. The same Swift code that walks WhatsApp's AX tree can target /System/Applications/Calculator.app or Linear's Catalyst window. |
| Tree node attributes | PSI fields: file path, line, symbol name, type, references. The 'tree' is a code structure, not a UI structure. | kAXRoleAttribute, kAXDescriptionAttribute, kAXValueAttribute, kAXTitleAttribute, kAXPositionAttribute, kAXSizeAttribute, kAXChildrenAttribute. A UI structure with absolute screen coordinates. |
| Failure modes | IDE not open, project not indexed, plugin disabled, file outside project scope. | Permission denied, TCC cache stale, target app not running, AX tree slow to settle, app uses a non-AX rendering path (an Electron app with a webview, for example). |
What it looks like when each one runs
Same MCP host, two different MCP servers, two completely different sets of system calls. The IntelliJ MCP stays inside the JVM. The AX MCP crosses into the OS through AXUIElementCreateApplication and starts walking nodes.
The bottom probe is what whatsapp_search triggers under the hood: bind to the WhatsApp Desktop PID, set a timeout, recursively read kAXChildrenAttribute, filter to AXButtons sitting in the right half of the window, return a flat list of search-result rows.
How to actually walk the macOS AX tree from an MCP server
If you want to build the second kind of MCP server, this is the shape. Five steps, all of them in main.swift of the WhatsApp MCP repo. There is no extra framework, no SDK, no bridge library. ApplicationServices ships with macOS.
1. Identify the target by bundle id, not by window title
Use NSRunningApplication to look up the PID for the bundle id. Window titles change; bundle ids do not. WhatsApp MCP hardcodes net.whatsapp.WhatsApp.
let bundleId = "net.whatsapp.WhatsApp" let apps = NSRunningApplication .runningApplications(withBundleIdentifier: bundleId) let pid = apps.first?.processIdentifier
2. Attach with AXUIElementCreateApplication and set a messaging timeout
AXUIElementCreateApplication(pid) returns the root element. Always set a messaging timeout: AX calls go through XPC and a hung target will hang the MCP child indefinitely. WhatsApp MCP uses 5.0 seconds.
3. Recurse through kAXChildrenAttribute with a depth cap
Many Catalyst apps have absurdly deep accessibility hierarchies. A maxDepth of around 15 is what works for WhatsApp Desktop without missing leaf elements. Read role, description, value, title, position, and size on each node and keep what is meaningful.
4. Filter by role and text on the way back up
Most nodes are skeleton containers. Keep AXButton, AXTextField, AXTextArea, AXStaticText, AXHeading, AXGenericElement, AXLink, plus anything with non-empty description, value, or title. That filter cuts the tree from thousands of nodes to dozens or hundreds.
5. Verify the permission with both a flag check and a functional probe
AXIsProcessTrustedWithOptions can return true while AX calls silently fail because of a stale TCC database. Always do a real read of kAXChildrenAttribute on the root element as a probe and surface a remediation message that points at System Settings > Privacy & Security > Accessibility.
The permission check that catches stale TCC state
One subtle thing the JetBrains-only mental model misses: macOS silently denies AX calls under conditions where the OS still reports the process as “trusted”. Re-signing an app, moving it between /Applications and ~/Applications, or a macOS update can leave the TCC database stale. The flag check is true; the calls return nothing.
The fix is to do both: the flag check (so you can prompt the user) and a functional probe (so you know whether the AX framework is actually responding for this PID).
If the probe fails while the flag is true, the remediation is to remove and re-add the host app in System Settings > Privacy & Security > Accessibility, then restart. The MCP returns a JSON error with that exact instruction so an LLM can repeat it back to the user without you wiring it up.
Running both MCPs together is the right pattern
MCP hosts like Claude Code, Cursor, and Windsurf accept multiple MCP servers in one config. The IntelliJ MCP gives you tools that operate on code (refactor, search, run a configuration). An accessibility- tree MCP gives you tools that operate on the running OS (read a chat, click a button, type into a field, drag a window). The agent picks tools per request. They occupy different namespaces and do not collide.
A useful end-to-end loop: the agent uses IntelliJ’s MCP to read get_symbol_info on a function and replace_text_in_file to refactor it, then uses the WhatsApp MCP to drop a one-line update into a group chat asking a teammate to review the diff. Neither MCP could do the other’s job. Both running side by side cover the gap.
Wiring up an AX-tree MCP server in your stack?
Happy to walk through the AXUIElement code, the TCC pitfalls, and how the npm + Swift packaging works on a 20 minute call.
Frequently asked questions
Does IntelliJ's MCP server expose the macOS accessibility tree?
No. IntelliJ's built-in MCP server, shipped from IntelliJ 2025.2 onward and documented at https://www.jetbrains.com/help/idea/mcp-server.html, exposes IDE-level tools only: list_directory_tree, find_files_by_glob, find_files_by_name_keyword, get_file_text_by_path, search_in_files_by_text, search_in_files_by_regex, get_file_problems, get_symbol_info, get_run_configurations, get_project_modules, get_project_dependencies, create_new_file, replace_text_in_file, reformat_file, rename_refactoring, plus terminal and database tools. None of those reach into the macOS accessibility framework. There is no AXUIElement call, no PID targeting, no Accessibility permission prompt. The IntelliJ MCP cannot read another app's UI, and it cannot even read the IDE's own UI through accessibility, because it talks to IntelliJ's PSI and VirtualFile APIs in the JVM, not to the OS.
Then why does this keyword put 'IntelliJ MCP' and 'accessibility tree' next to each other?
Because both ideas use the word 'tree' and both have an MCP server, so people conflate them. JetBrains marketing talks about IntelliJ's MCP letting agents 'walk the project tree' and read 'symbol declarations'. Independent tutorials about MCP servers for desktop control talk about 'walking the macOS accessibility tree' to drive apps. Two different graphs. Two different MCP servers. They do not compose. If you want the IntelliJ MCP to be able to click around inside the IntelliJ window, that is not how it works; you would need a separate AX-walking MCP that targets the IntelliJ PID via its bundle id (com.jetbrains.intellij or com.jetbrains.intellij.ce).
What does an MCP server that actually walks the macOS accessibility tree look like?
It looks like Sources/WhatsAppMCP/main.swift in the whatsapp-mcp-macos repo. Lines 95 to 176 define traverseAXTree(pid:, maxDepth: 15). The function calls AXUIElementCreateApplication(pid), sets AXUIElementSetMessagingTimeout(appElement, 5.0), then recursively reads kAXRoleAttribute, kAXDescriptionAttribute, kAXValueAttribute, kAXTitleAttribute, kAXPositionAttribute, kAXSizeAttribute, kAXChildrenAttribute. The recursion has a depth cap because Catalyst apps tend to have very deep hierarchies. A role filter at the leaves keeps AXButton, AXTextField, AXTextArea, AXStaticText, AXHeading, AXGenericElement, AXLink, plus anything with text. Source is at https://github.com/m13v/whatsapp-mcp-macos.
Why do I need a separate native binary instead of a JVM plugin?
AXUIElement is a C API in the macOS ApplicationServices framework. Reaching it from a JVM means JNI, which IntelliJ's MCP plugin does not bother with because that is not what it is for. A native Swift or Objective-C binary running outside the JVM hits the API directly, and it can be invoked over stdio by any MCP host: Claude Code, Cursor, Windsurf, Codex, an LLM-powered shell. The MCP host forks the binary, pipes JSON-RPC over stdin and stdout, and the binary talks to the OS accessibility framework on behalf of whichever LLM is connected.
What exact attribute keys do I need to read off each AXUIElement node?
For a UI tree useful to an LLM agent, seven keys are enough: kAXRoleAttribute (so you can filter to interactable element types), kAXDescriptionAttribute (the human-readable label that VoiceOver would speak), kAXValueAttribute (current text in a field), kAXTitleAttribute (window or button title), kAXPositionAttribute and kAXSizeAttribute (so the agent can click at the center of an element with CGEvent), and kAXChildrenAttribute (to recurse). WhatsApp MCP reads exactly those seven, drops nodes with no text content unless the role is one of AXButton, AXTextField, AXTextArea, AXStaticText, AXHeading, AXGenericElement, or AXLink, and returns a flat array. Flat is easier for an LLM to reason about than a nested tree.
Why a 5.0 second messaging timeout?
AXUIElement calls go through XPC, which is asynchronous IPC under the hood. If the target app is hung, paused under a debugger, or refusing to respond on its main thread, an AX call will block forever. AXUIElementSetMessagingTimeout(element, 5.0) caps that wait at five seconds and turns a hang into a clean error code your MCP server can return as JSON. WhatsApp MCP uses 5.0 seconds for normal calls and 2.0 seconds for the boot-time functional probe at line 576. Without this, a hung WhatsApp window would freeze the MCP child until the host process restarted it.
Why is AXIsProcessTrustedWithOptions returning true not enough?
macOS's TCC database (Transparency, Consent, and Control) caches Accessibility grants per code-signed identity. After macOS updates, app re-signing, or moving an app between /Applications and ~/Applications, that cache can go stale. The flag is true because the user once granted permission to a binary with this bundle id, but the actual AX calls fail because the OS cannot match the running binary to the cached grant. The fix is to remove and re-add the host app in System Settings > Privacy & Security > Accessibility, then restart. Detect this by also doing a functional probe: read kAXChildrenAttribute on the root element and check whether the call actually succeeds. WhatsApp MCP does both at lines 561 to 610 of main.swift.
Can I point IntelliJ's MCP and an AX-walking MCP at the same agent at the same time?
Yes, and that is the most useful pattern. MCP hosts like Claude Code accept multiple MCP servers in one config. Add IntelliJ's MCP server for code-level work (refactor, search, run configurations) and add an accessibility-tree MCP for OS-level work (read a chat in WhatsApp Desktop, click a button in Finder, drag a file out of the IDE into another app). They occupy completely different tool namespaces. The agent picks tools per request. WhatsApp MCP exposes whatsapp_status, whatsapp_start, whatsapp_quit, whatsapp_get_active_chat, whatsapp_list_chats, whatsapp_search, whatsapp_open_chat, whatsapp_scroll_search, whatsapp_read_messages, whatsapp_send_message, and whatsapp_navigate, none of which collide with anything IntelliJ ships.
What about Electron apps and accessibility trees?
This is the honest limitation. Pure Electron apps, Slack and Discord being the canonical examples, render their UI in a Chromium webview. The macOS accessibility framework can see the outer window chrome and a single AXWebArea node, but the actual UI nodes inside are DOM elements behind that webview. You can sometimes coax Chromium into exposing a fuller AX tree by setting --force-renderer-accessibility, but it is unreliable and slow. Catalyst apps like WhatsApp and Apple's own native apps expose a full AX tree and are the right targets for this pattern. If you need to drive an Electron app from an MCP, browser-style automation (CDP, Playwright) is usually the better fit.
Where do I read this code for myself?
https://github.com/m13v/whatsapp-mcp-macos. The accessibility-walking code is in Sources/WhatsAppMCP/main.swift. Specific line ranges referenced on this page: 95 to 176 (struct AXElementInfo and traverseAXTree), 561 to 610 (requireAccessibility and probeAccessibility), 57 (the bundle id constant). The package is also on npm as whatsapp-mcp-macos with a postinstall step that runs xcrun swift build -c release to compile the binary on install.