orca
Orca — ATK Accessibility MCP Server
Captures the ATK (Accessibility Toolkit) widget tree from running GTK applications on Linux, normalizes it to ARIA roles/types, applies a configurable declarative security policy, and exposes the result as MCP tools to any standard MCP client (Claude, Cursor, Windsurf, etc.).
Quick Start
cd orca
just shell # enter nix-shell with all dependencies
just server # start the MCP server on stdioOr manually:
nix-shell
PYTHONPATH=src python3 -m srcArchitecture
orca/
├── shell.nix # nix-shell environment
├── pyproject.toml # package config
├── Justfile # task runner
├── docs/
│ ├── README.md # this file
│ ├── usage.md # client integration guide
│ ├── policy.md # policy engine reference
│ ├── atk.md # ATK capture internals
│ └── contribute.md # development guide
└── src/
├── __init__.py
├── __main__.py # entry point
├── atk.py # ATK tree capture
├── normalize.py # ATK→ARIA normalization
├── policy.py # declarative security policy
├── server.py # MCP server
└── default_policy.yaml # ship-default policyMCP Tools
Tool | Params | Description |
| none | Full ARIA-normalized tree, policy-filtered |
|
| Tree scoped to an app (fnmatch glob) |
|
| Single node lookup by |
| none | Top-level app objects (name, pid, role) |
Configuration
Policy
Policy files are loaded in this priority:
~/.config/atk-mcp/policy.yaml(user override)src/default_policy.yaml(bundled default)
If neither exists or either fails to parse, the server starts with
default_action: allow and no user rules.
Policy is loaded once at startup — restart the server to pick up changes.
See docs/policy.md for the full schema and examples.
Nix Environment
All dependencies are managed via shell.nix. No uv, no virtualenv.
Key packages:
python314— runtimepython314Packages.pyatspi— ATK tree accesspython314Packages.pygobject3— GI introspectionpython314Packages.mcp— MCP SDK v2python314Packages.pydantic-settings— policy configpython314Packages.pyyaml— policy parsingat-spi2-core,at-spi2-atk,atk,gtk3— runtime libs
Usage
With Cursor
Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"atk-accessibility": {
"command": "nix-shell",
"args": ["--run", "python -m src"],
"cwd": "/path/to/orca"
}
}
}With Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"atk-accessibility": {
"command": "nix-shell",
"args": ["--run", "python -m src"],
"cwd": "/path/to/orca"
}
}
}With Windsurf
Add to .mcp.json in your project:
{
"mcpServers": {
"atk-accessibility": {
"command": "nix-shell",
"args": ["--run", "python -m src"],
"cwd": "/path/to/orca"
}
}
}From the command line (interactive test)
just shell
python -m src # runs indefinitely on stdioPipe a raw MCP request to test individual tools:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
| python -m srcPolicy Engine
See docs/policy.md for the full reference.
Quick example — deny all heading nodes and redact textbox names:
default_action: allow
built_in_deny:
aria_roles:
- "password"
state_keywords:
- "hidden"
- "invisible"
rules:
- id: deny-headings
conditions:
role: "heading"
action: deny
- id: redact-forms
conditions:
role: "textbox"
action: redact
redact_fields:
- "name"
- "description"ATK Capture
See docs/atk.md for internals. Key points:
Walks
gi.repository.Atspidesktop root recursivelyFail-closed: subprocess isolation prevents GLib abort from crashing the server when no AT-SPI bus is available
Each node captures:
obj_id,role(int),role_name,name,description,state_set,attributes,child_count,index_in_parent,app_name,pid
Normalization
See docs/normalize.md for the role map.
ATK integer roles (0–132) are mapped to ARIA role strings. Unmapped
roles pass through as their role_name string. State names are translated
(e.g. FOCUSED → focused, CHECKED → checked).
Development
See docs/contribute.md for the development guide.
just shell # enter dev environment
just test # run verification suite
just compile # syntax check
just lint # import + smoke check
just server # start server for manual testingLimitations
Wayland apps without AT-SPI: Some Wayland-native GTK apps don't expose AT-SPI interfaces.
get_tree_for_appreturns[]for those apps. Expected, not a bug.No hot-reload: Policy is loaded once at startup.
Requires AT-SPI bus: Without a running accessibility bus (e.g.
at-spi-bus-launcher), the ATK module returns[]gracefully.Python 3.14+: No
typing-extensionsdependency.
License
MIT