Skip to main content

bad-ass-mcp

Every other tool needs a browser or a vision model. This one reads what the OS already knows.

A cross-platform MCP server for desktop GUI automation — driven by accessibility APIs, not screenshots.

Works with Claude, Codex, Gemini, Ollama, or any OpenAI-compatible client that supports MCP.

bad-ass-mcp recording itself interacting with MarkTheCrab bad-ass-mcp recording itself interacting with MarkTheCrab

Why

Most AI desktop automation tools work by taking screenshots and asking a vision model "what do you see?" That's slow, expensive, and fragile.

Every major OS exposes a structured accessibility tree — the same one used by screen readers — that describes every button, text field, combo box, and menu in every running application. bad-ass-mcp speaks that language directly.

  • macOS: AXUIElement via PyObjC
  • Linux: AT-SPI2 via gi.repository.Atspi
  • Windows: UI Automation via comtypes

Actions fire on control objects directly, so the target window doesn't need focus. The user can keep working while automation runs in the background.

Tools

Tool Description
list_windows List all visible application windows
get_tree Full accessibility tree for a window as nested JSON
find_elements Find elements by role and/or name
click Click / invoke an element (foreground-independent)
click_at Click at absolute screen coordinates — fallback for webview/canvas UI
type_text Type text into a field (SetValue → EditableText → key injection)
select_option Select an option in a combo box or list
get_value Get the current value or text of an element
wait_for_window Wait until a window matching a pattern appears
wait_for_element Wait until an element exists and optionally has a given state
screenshot Capture a PNG (write to output_path or return base64)
start_recording Start recording the screen (or a specific window)
stop_recording Stop recording and export as a GIF
learn_layout Resolve semantic names → live handle IDs (one call per session)
run_sequence Execute a list of actions server-side — no per-action round-trips

Webviews: waking lazy accessibility trees

Chromium-based apps (Electron, CEF, and every Chromium browser) have a full accessibility tree — they just don't build it until an assistive technology announces itself. Until then the window looks canvas-only: get_tree returns nothing and screenshot-clicking is the only option.

bad-ass-mcp announces itself. When a window's a11y tree comes back empty and the process looks like a webview, the backend performs the platform's "a screen reader is here" handshake, waits a beat, and re-probes before giving up:

  • Linux: sets org.a11y.Status.ScreenReaderEnabled on the session D-Bus at server start so Chromium/Electron apps launched afterwards enable accessibility from birth. Windows that stay hollow (an application+frame whose phantom child fetches as None) are detected via the AT-SPI toolkit name and reported accessible: false: the app either launched before the flag went up or opts out on its own. Restarting the app with the server running should pick the flag up per Chromium's documented behavior, but note that is not yet verified here: the only Linux app live-tested so far (flatpak Vivaldi) ignores the flag entirely, its own first-run assistive-technology toggle included, and only produces a tree when launched with --force-renderer-accessibility.
  • macOS: sets AXManualAccessibility on the app (Electron's documented switch), falling back to AXEnhancedUserInterface (what VoiceOver sets, watched by plain Chrome/CEF). The attribute set only succeeds on apps that support it, so it's a free no-op elsewhere.
  • Windows: sets the system SPI_SETSCREENREADERRUNNING flag, then queries the Chrome_RenderWidgetHostHWND child window — Chromium hangs its DOM tree off that renderer child, not the top-level HWND (which only shows empty panes). get_tree / find_elements are routed through the renderer child automatically.

One wake attempt per process per server run. If list_windows still reports accessible: false after this, the window is genuinely canvas-only (custom OpenGL/Vulkan surface, immediate-mode toolkit without AccessKit) — fall back to screenshot + click_at.

The tradeoff: a woken app spends some memory/CPU maintaining its tree, which is exactly why Chromium lazy-loads it. The wake targets the app you're automating, not the whole desktop (the Linux flag is technically session-global, but only a11y-aware apps respond, and only on demand).

Installation

Requirements: Python 3.11+, ffmpeg + gifsicle for recording (optional)

# macOS
pip install 'bad-ass-mcp[macos]'

# Linux — install system PyGObject + AT-SPI bindings first, then:
pip install bad-ass-mcp

# Windows
pip install 'bad-ass-mcp[windows]'

Linux: PyGObject and AT-SPI are not on PyPI — install them from your distro:

# Debian/Ubuntu
sudo apt install python3-gi gir1.2-atspi-2.0 at-spi2-core

# Arch
sudo pacman -S python-gobject at-spi2-core

# Fedora
sudo dnf install python3-gobject at-spi2-core

Then make sure AT-SPI is enabled (most desktop environments — GNOME, KDE, XFCE — have it on by default):

# Check
gsettings get org.gnome.desktop.interface toolkit-accessibility

# Enable
gsettings set org.gnome.desktop.interface toolkit-accessibility true

If you're using a venv, create it with --system-site-packages so it can see the distro-installed gi:

python -m venv --system-site-packages venv

Windows: Elevated (admin) applications can only be automated from an elevated Python process. No special permissions are needed for normal apps.

Register with Claude Code

claude mcp add bad-ass-mcp --scope user -- bad-ass-mcp

Or manually in ~/.claude.json:

{
  "mcpServers": {
    "bad-ass-mcp": {
      "type": "stdio",
      "command": "bad-ass-mcp"
    }
  }
}

Usage

list the windows on screen
→ [{ "id": "12345", "name": "Firefox", ... }]

find the search box in window 12345 and type "hello"
→ find_elements(window_id="12345", role="entry")
→ type_text(handle_id="...", text="hello")

Electron/CEF apps get their lazy accessibility tree woken automatically (see Webviews above). For apps that still don't expose a clean tree, type_text falls back to platform-native key injection automatically (AT-SPI on Linux, Quartz events on macOS, PostMessage on Windows).

Architecture

bad_ass_mcp/
├── server.py          # FastMCP tool definitions
├── types.py           # WindowInfo, ElementHandle, ActionResult, StaleHandleError
└── backend/
    ├── base.py        # Abstract DesktopBackend interface
    ├── linux.py       # AT-SPI2 + xdotool
    ├── macos.py       # AXUIElement + Quartz
    └── windows.py     # UI Automation + ctypes Win32

The abstract backend interface means adding a new platform is just implementing one class. The full contract-test suite runs against every backend via FakeBackend.

Releasing

Tag a version and the release workflow builds the sdist and wheel, publishes them to PyPI via trusted publishing, and attaches them (plus the PyInstaller bundles) to the GitHub release:

git tag v1.4.0 && git push --tags

License

MIT

Metadata

Release files for bad-ass-mcp 1.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bad-ass-mcp 1.4.1
File Size Uploaded Provenance
bad_ass_mcp-1.4.1.tar.gz 684.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for bad-ass-mcp 1.4.1
File Interpreter ABI Platform
bad_ass_mcp-1.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 733.3 kB

Release files / bad_ass_mcp-1.4.1.tar.gz

Download URL bad_ass_mcp-1.4.1.tar.gz
Size 684.8 kB
Tags Source
SHA-256 checksum
How to use checksums
454e2503e2c27924146f24c524daab3b476d0d7fb3c5e53a33e4fed49b02fad0
BLAKE2b-256 checksum
How to use checksums
50311f6bbe0058dadff848838c8650e067a6aad43c8a88d607cd35ae530e2319
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / bad_ass_mcp-1.4.1-py3-none-any.whl

Download URL bad_ass_mcp-1.4.1-py3-none-any.whl
Size 48.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bb975d76cc50f9eb9db6ec019f891b6344372925b90d8ae644a33cad5a2f6c81
BLAKE2b-256 checksum
How to use checksums
0f4d8c58d5ef5d92f3349ce773b4e4349928a892054793c11c9c010a96ff227e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for bad-ass-mcp 1.4.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for bad_ass_mcp-1.4.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.4.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page