Skip to content

Commit eaf67ca

Browse files
anbeckhamclaude
andcommitted
Release v0.2.0: Custom exceptions, server refactor, 230 tests, Beta status
- Add custom exception hierarchy (13 classes) replacing ~45 bare except catches - Refactor server.py (962 → ~100 lines) into handlers.py + tool_definitions.py - Expand test suite from 45 to 230 tests across 7 test files - Fix bug in WindowGroupManager.delete_group (empty group falsiness) - Add type annotations across all modules - Update metadata: Beta classifier, project URLs, author email - Add PRIVACY_POLICY.md, CLAUDE.md, update CHANGELOG.md - Fix placeholder URLs in README.md and CONTRIBUTING.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1aad34c commit eaf67ca

24 files changed

Lines changed: 2975 additions & 1121 deletions

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.0] - 2026-02-25
9+
10+
### Added
11+
- Custom exception hierarchy (`LinuxDesktopMCPError` base) with 13 specific exception classes for AT-SPI, input, overlay, window, and reference errors
12+
- 185+ new tests across 7 test files (total: ~230 tests), covering window manager, detection, input backends, handlers, window discovery, and overlay
13+
- `PRIVACY_POLICY.md` standalone privacy document
14+
- `CLAUDE.md` project guide for AI-assisted development
15+
- `[project.urls]` in pyproject.toml (Repository, Documentation, Issues, Changelog)
16+
- `Typing :: Typed` classifier
17+
18+
### Changed
19+
- Refactored `server.py` (962 lines) into 3 focused modules: `server.py` (~100 lines), `handlers.py` (~620 lines), `tool_definitions.py` (~250 lines)
20+
- `ServerContext` dataclass replaces monolithic server class for handler state
21+
- `HANDLER_MAP` dispatcher dict replaces if/elif chain in `call_tool`
22+
- Narrowed ~45 bare `except Exception` catches to specific types (`_GLibError`, `FileNotFoundError`, `OSError`, custom exceptions)
23+
- Replaced `Dict` with `dict` and added `-> None`, `-> Any`, `-> dict[str, Any]` return types across all modules
24+
- Development status classifier updated from Alpha to Beta
25+
- Version bumped to 0.2.0
26+
27+
### Fixed
28+
- Bug in `WindowGroupManager.delete_group` where empty `WindowGroup` was treated as falsy due to `__len__` returning 0, preventing active group ID updates
29+
- Fixed `yourusername` placeholder URLs in README.md and CONTRIBUTING.md
30+
831
## [0.1.0] - 2025-01-07
932

1033
### Added

CLAUDE.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# CLAUDE.md
2+
3+
Project guide for AI-assisted development of linux-desktop-mcp.
4+
5+
## Project Overview
6+
7+
An MCP server providing Chrome-extension-level semantic element targeting for native Linux desktop applications using AT-SPI2. Exposes 11 tools for desktop automation (snapshot, find, click, type, key, capabilities, context, target_window, create_window_group, release_window).
8+
9+
## Layout
10+
11+
```
12+
src/linux_desktop_mcp/
13+
__init__.py # Package entry, version, exception exports
14+
server.py # MCP server setup, initialization, tool dispatch
15+
handlers.py # ServerContext dataclass + 10 async handler functions
16+
tool_definitions.py # get_tool_definitions() with all 11 Tool schemas
17+
exceptions.py # Custom exception hierarchy (LinuxDesktopMCPError base)
18+
atspi_bridge.py # AT-SPI2 tree building, element interaction (ThreadPoolExecutor)
19+
references.py # ElementReference, ReferenceManager, GC
20+
input_backends.py # InputManager + YdotoolBackend, XdotoolBackend, WtypeBackend
21+
window_manager.py # WindowGroup, WindowTarget, WindowGroupManager
22+
window_discovery.py # Window enumeration via AT-SPI desktop
23+
overlay.py # GTK3 border overlay windows (X11 backend)
24+
detection.py # Display server, compositor, and tool detection
25+
tests/
26+
conftest.py # MCP module mocks, shared fixtures
27+
test_server.py # Handler tests via ServerContext
28+
test_window_manager.py
29+
test_detection.py
30+
test_input_backends_manager.py
31+
test_window_discovery.py
32+
test_overlay.py
33+
```
34+
35+
## Build & Test
36+
37+
```bash
38+
# Run tests (mocks MCP + AT-SPI, no desktop needed)
39+
PYTHONPATH=src pytest tests/ -v
40+
41+
# Lint and format
42+
ruff check .
43+
ruff format .
44+
45+
# Import check
46+
python -c "from linux_desktop_mcp import __version__; print(__version__)"
47+
```
48+
49+
## Code Conventions
50+
51+
- **Python 3.10+** target. Use `X | None` union syntax, `dict[str, Any]` (not `Dict`).
52+
- **Exception handling**: Use custom exceptions from `exceptions.py`. AT-SPI modules use `_GLibError` conditional import pattern. Never use bare `except Exception` except in the top-level `call_tool` safety net.
53+
- **Async handlers**: Each tool handler is a standalone async function in `handlers.py` receiving `(ctx: ServerContext, args: dict)` and returning `list[TextContent]`.
54+
- **Input backends**: Subprocess-based, mock `asyncio.create_subprocess_exec` in tests.
55+
- **AT-SPI bridge**: Runs blocking AT-SPI calls in `ThreadPoolExecutor` via `asyncio.to_thread`. Uses `threading.Lock` for thread safety.
56+
- **Tests**: Pure Python where possible (dataclasses, enums). MCP modules mocked at conftest level. No real desktop or AT-SPI required.
57+
- **Formatting**: ruff with line-length=100, double quotes, isort-compatible imports.

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ We're very open to contributions of all kinds:
2020

2121
1. **Clone the repository**
2222
```bash
23-
git clone https://github.com/yourusername/linux-desktop-mcp.git
23+
git clone https://github.com/BeckhamLabsLLC/linux-desktop-mcp.git
2424
cd linux-desktop-mcp
2525
```
2626

PRIVACY_POLICY.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Privacy Policy
2+
3+
**Linux Desktop MCP** is a local desktop automation tool. This document describes what data it accesses and how it is handled.
4+
5+
## Data Collection
6+
7+
Linux Desktop MCP **does not collect any data**. Specifically:
8+
9+
- No analytics or telemetry
10+
- No usage tracking
11+
- No crash reports sent externally
12+
- No personal information gathered
13+
14+
## Data Access
15+
16+
The server accesses the following local system information during operation:
17+
18+
- **Accessibility tree** (AT-SPI2): UI element names, roles, states, and positions for applications you interact with. This data is read on-demand and not persisted.
19+
- **Environment variables**: Display server type (`WAYLAND_DISPLAY`, `DISPLAY`, `XDG_SESSION_TYPE`) and compositor detection variables, used solely to select the correct input backend.
20+
- **Process information**: `shutil.which` checks for the presence of input tools (`ydotool`, `xdotool`, `wtype`, `scrot`, `grim`).
21+
22+
None of this information is stored, logged, or transmitted.
23+
24+
## Third-Party Services
25+
26+
Linux Desktop MCP **does not communicate with any external services**. It:
27+
28+
- Runs entirely on your local machine
29+
- Requires no network connectivity
30+
- Makes no outbound HTTP requests
31+
- Does not store credentials or tokens
32+
33+
The MCP protocol communication occurs exclusively over local stdio between the MCP client (e.g., Claude Code) and this server.
34+
35+
## Data Storage
36+
37+
No data is written to disk during normal operation. The server holds state (element references, window groups) in memory only, and this state is discarded when the server exits.
38+
39+
## Contact
40+
41+
For privacy-related questions, open an issue on [GitHub](https://github.com/BeckhamLabsLLC/linux-desktop-mcp/issues).

README.md

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ sudo ydotoold &
4141
pip install linux-desktop-mcp
4242

4343
# Or from source
44-
git clone https://github.com/yourusername/linux-desktop-mcp.git
44+
git clone https://github.com/BeckhamLabsLLC/linux-desktop-mcp.git
4545
cd linux-desktop-mcp
4646
pip install -e .
4747
```
@@ -312,17 +312,7 @@ We're very open to help and collaboration. See [CONTRIBUTING.md](CONTRIBUTING.md
312312

313313
## Privacy Policy
314314

315-
Linux Desktop MCP is a local desktop automation tool that:
316-
317-
- **Runs entirely on your local machine** - No data is transmitted to external servers
318-
- **Does not collect any personal data** - No analytics, telemetry, or usage tracking
319-
- **Does not store credentials** - All authentication and authorization is handled by your local system
320-
- **Accesses only what you explicitly target** - The accessibility tree is read only for windows/applications you interact with
321-
- **No network connectivity required** - The MCP server operates completely offline
322-
323-
The only data accessed is the accessibility tree information exposed by your desktop applications (UI element names, roles, and states), which is used solely for local automation and is not persisted or transmitted anywhere.
324-
325-
**Contact:** For privacy-related questions, open an issue on [GitHub](https://github.com/BeckhamLabsLLC/linux-desktop-mcp/issues).
315+
See [PRIVACY_POLICY.md](PRIVACY_POLICY.md) for our full privacy policy. In short: Linux Desktop MCP runs entirely on your local machine, collects no data, and requires no network connectivity.
326316

327317
## License
328318

pyproject.toml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "linux-desktop-mcp"
7-
version = "0.1.0"
7+
version = "0.2.0"
88
description = "MCP server for Linux desktop automation using AT-SPI2"
99
readme = "README.md"
1010
requires-python = ">=3.10"
1111
license = "MIT"
1212
authors = [
13-
{ name = "Austin Beck" }
13+
{ name = "Austin Beck", email = "austin@beckhamlabs.com" }
1414
]
1515
keywords = ["mcp", "linux", "desktop", "automation", "accessibility", "at-spi"]
1616
classifiers = [
17-
"Development Status :: 3 - Alpha",
17+
"Development Status :: 4 - Beta",
1818
"Environment :: Console",
1919
"Intended Audience :: Developers",
2020
"License :: OSI Approved :: MIT License",
@@ -25,6 +25,7 @@ classifiers = [
2525
"Programming Language :: Python :: 3.12",
2626
"Topic :: Desktop Environment",
2727
"Topic :: Software Development :: Libraries :: Python Modules",
28+
"Typing :: Typed",
2829
]
2930
dependencies = [
3031
"mcp>=1.0.0",
@@ -42,6 +43,12 @@ dev = [
4243
"ruff>=0.1.0",
4344
]
4445

46+
[project.urls]
47+
Repository = "https://github.com/BeckhamLabsLLC/linux-desktop-mcp"
48+
Documentation = "https://github.com/BeckhamLabsLLC/linux-desktop-mcp#readme"
49+
Issues = "https://github.com/BeckhamLabsLLC/linux-desktop-mcp/issues"
50+
Changelog = "https://github.com/BeckhamLabsLLC/linux-desktop-mcp/blob/main/CHANGELOG.md"
51+
4552
[project.scripts]
4653
linux-desktop-mcp = "linux_desktop_mcp:main"
4754

src/linux_desktop_mcp/__init__.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,52 @@
44
desktop applications using AT-SPI2 (Assistive Technology Service Provider Interface).
55
"""
66

7-
__version__ = "0.1.0"
7+
__version__ = "0.2.0"
88

9+
from .exceptions import (
10+
ATSPIActionError,
11+
ATSPIError,
12+
ATSPINotAvailableError,
13+
ATSPITreeBuildError,
14+
DisplayServerError,
15+
GtkInitError,
16+
InputBackendNotAvailableError,
17+
InputCommandError,
18+
InputError,
19+
InputValidationError,
20+
LinuxDesktopMCPError,
21+
OverlayError,
22+
ReferenceExpiredError,
23+
ReferenceNotFoundError,
24+
WindowInvalidError,
25+
WindowNotFoundError,
26+
)
927

10-
def main():
28+
29+
def main() -> None:
1130
"""Entry point for the MCP server."""
1231
from .server import main as _main
1332

1433
_main()
1534

1635

17-
__all__ = ["main", "__version__"]
36+
__all__ = [
37+
"main",
38+
"__version__",
39+
"LinuxDesktopMCPError",
40+
"ATSPIError",
41+
"ATSPINotAvailableError",
42+
"ATSPITreeBuildError",
43+
"ATSPIActionError",
44+
"WindowNotFoundError",
45+
"WindowInvalidError",
46+
"ReferenceNotFoundError",
47+
"ReferenceExpiredError",
48+
"InputError",
49+
"InputBackendNotAvailableError",
50+
"InputCommandError",
51+
"InputValidationError",
52+
"OverlayError",
53+
"GtkInitError",
54+
"DisplayServerError",
55+
]

0 commit comments

Comments
 (0)