When MCP elicitation is unavailable (most clients), fall back to
org.freedesktop.Notifications with Approve/Deny action buttons.
Opt-in via MCDBUS_NOTIFY_CONFIRM=1. Silence (timeout) is denial.
Fixes signal race where NotificationClosed stomped ActionInvoked
result in the same event loop iteration.
ctx.elicit() throws an exception (not CancelledElicitation) when the
MCP client doesn't implement the elicitation/create JSON-RPC method.
Wrap the call in try/except to treat protocol-level rejection the
same as CancelledElicitation. Found during live testing with Claude
Code CLI which doesn't support MCP elicitation yet.
Three-pillar fix from Hamilton review:
Code quality — validate_signature() for D-Bus spec compliance,
MCDBUS_TIMEOUT env var, replace 13 error-as-success returns with
ToolError, monotonic clock deadline on tree walks, sanitize D-Bus
error messages, fix resource connection leak via module-level
BusManager, hasattr guards in conftest.
Elicitation — ctx.elicit() confirmation for system bus call_method
and all set_property calls, graceful degradation when client lacks
elicitation support, MCDBUS_REQUIRE_ELICITATION for hard-fail mode.
Permission docs — four-layer guide (systemd sandboxing, dbus-broker
policy, polkit rules, xdg-dbus-proxy) with ready-to-deploy example
configs validated against xmllint, bash -n, and systemd-analyze.
Fix get_mgr() to use ctx.lifespan_context (works in both request
and standalone Client mode). Add autouse fixture to reset
event-loop-bound singleton state per test, preventing cross-test
asyncio.Event contamination.
21 new tests exercise all 14 tools through FastMCP's in-memory
transport. New shortcuts: network_status (NetworkManager),
battery_status (UPower), bluetooth_devices (bluez),
kwin_windows (KRunner WindowsRunner). 53 tests pass.
- Add asyncio.wait_for timeout (30s) to all D-Bus calls
- Add asyncio.Lock to BusManager.get_bus to prevent race conditions
- Convert recursive tree walk to bounded BFS with visited set, max depth (20), max nodes (500)
- Add input validation for D-Bus names and object paths at tool boundary
- Standardize error handling (RuntimeError, TimeoutError) across all tools
- Catch JSONDecodeError in deserialize_args and set_property
- Check arg count matches signature in deserialize_args
- Add explicit variant wrapping via {"signature": "ai", "value": [1,2,3]}
- Narrow resource exception handling from Exception to (RuntimeError, OSError)
- Guard disconnect_all per-bus to avoid partial cleanup on error
- Audit log system bus calls and all set_property calls to stderr
- Consolidate _get_mgr into get_mgr in _bus.py
- Sort MPRIS players for deterministic auto-discovery
- Fix progress reporting (pass None as total when unknown)
- Add 7 new tests for validation, arg count, and JSON error paths (32 total)
Bridge Linux D-Bus IPC into MCP with tools for service discovery
(list_services, introspect, list_objects), method calls and property
access, plus convenience shortcuts for notifications, systemd units,
and MPRIS media player control. All 25 tests passing.