---
title: Confirmation Flow
description: How mcdbus decides when to ask for user confirmation and the three-tier fallback cascade for delivering the prompt.
---

import { Aside } from '@astrojs/starlight/components';

mcdbus asks for user confirmation before performing operations that could change system state. This page explains when confirmation triggers, how the three-tier fallback cascade works, and how environment variables control the behavior.

## When confirmation triggers

Two conditions trigger a confirmation prompt:

1. **System bus method calls.** Any call to `call_method` with `bus="system"`. The system bus can affect the entire machine, so every method call requires explicit approval.

2. **Property mutations on any bus.** Any call to `set_property`, regardless of which bus. Changing a property is always a state mutation, even on the session bus.

Read-only operations never trigger confirmation. This includes `list_services`, `introspect`, `list_objects`, `get_property`, `get_all_properties`, and all the shortcut tools (`battery_status`, `network_status`, `bluetooth_devices`, `list_systemd_units`, `send_notification`, `media_player_control`). The shortcut tools either read properties only or operate on the session bus.

<Aside type="note">
`send_notification` and `media_player_control` are session bus method calls, not property mutations, so they execute without confirmation. The session bus is user-scoped and the risk from these operations is minimal.
</Aside>

## The three-tier cascade

When confirmation is required, mcdbus tries three methods in sequence. If one succeeds, the remaining tiers are skipped. If all three are exhausted, the behavior depends on configuration.

### Tier 1: MCP elicitation

The preferred method. mcdbus calls `ctx.elicit()`, which is a native MCP protocol feature that presents the user with a choice dialog in their MCP client. The prompt shows the operation details (service, method, path, arguments) and offers two options: "Yes, proceed" and "No, cancel."

If the MCP client supports elicitation and the user selects "Yes, proceed", the operation continues. If the user selects "No, cancel", the operation is aborted with a `ToolError`.

If the client does not implement elicitation at the protocol level, `ctx.elicit()` either returns a `CancelledElicitation` or throws an exception. In either case, mcdbus catches this and falls through to Tier 2.

### Tier 2: Desktop notification fallback

This tier is opt-in. It activates only when the `MCDBUS_NOTIFY_CONFIRM` environment variable is set to a non-empty value (e.g., `MCDBUS_NOTIFY_CONFIRM=1`).

When active, mcdbus sends a desktop notification through `org.freedesktop.Notifications` on the session bus. The notification has the urgency set to critical and presents two action buttons: "Approve" and "Deny."

The implementation registers D-Bus signal match rules for `ActionInvoked` and `NotificationClosed` before sending the notification, ensuring no signal can be missed due to a race condition. It then waits up to 60 seconds for the user to respond.

If the user clicks "Approve", the operation proceeds. If they click "Deny", dismiss the notification, or let it expire, the operation is aborted. If the notification service itself is unreachable, mcdbus logs the failure and falls through to Tier 3.

<Aside type="caution">
The notification fallback requires a running notification daemon on the session bus and a graphical environment to display it. It will not work in headless or SSH sessions.
</Aside>

### Tier 3: Proceed with warning

If both Tier 1 and Tier 2 fail or are unavailable, mcdbus logs a warning to stderr and allows the operation to proceed.

This is the default behavior when no confirmation method is available. The rationale is that the operation was initiated by Claude at the user's request, and blocking it entirely when no confirmation channel exists would make the tool unusable in environments that lack elicitation support.

The warning is written to stderr (and to the systemd journal if running as a service), creating an audit trail even when no interactive confirmation was possible.

## The decision tree

```
Confirmation required?
|
+-- No --> Execute immediately
|
+-- Yes --> Try MCP elicitation (ctx.elicit)
    |
    +-- User approved --> Execute
    |
    +-- User denied --> Abort (ToolError)
    |
    +-- Elicitation unavailable
        |
        +-- MCDBUS_REQUIRE_ELICITATION set?
        |   |
        |   +-- Yes --> Abort (ToolError: "Elicitation required
        |   |           but client does not support it")
        |   |
        |   +-- No --> Continue to fallbacks
        |
        +-- MCDBUS_NOTIFY_CONFIRM set?
            |
            +-- Yes --> Send desktop notification
            |   |
            |   +-- User clicked Approve --> Execute
            |   |
            |   +-- User clicked Deny / dismissed / timed out --> Abort
            |   |
            |   +-- Notification service unreachable --> Proceed with warning
            |
            +-- No --> Proceed with warning (log to stderr)
```

## Environment variables

Two environment variables control the confirmation flow:

**`MCDBUS_REQUIRE_ELICITATION`** -- When set to any non-empty value, mcdbus refuses to proceed if the MCP client does not support elicitation. No fallback to notifications or warn-and-proceed. The operation fails immediately with a `ToolError`. Use this in high-security deployments where you need a guarantee that every sensitive operation was explicitly approved through the MCP protocol.

**`MCDBUS_NOTIFY_CONFIRM`** -- When set to any non-empty value, enables the desktop notification fallback (Tier 2). Without this, mcdbus skips directly from Tier 1 failure to Tier 3 (proceed with warning). Use this when your MCP client does not support elicitation but you have a graphical session with a notification daemon.

Both can be set in the systemd unit file:

```ini
[Service]
Environment=MCDBUS_REQUIRE_ELICITATION=1
Environment=MCDBUS_NOTIFY_CONFIRM=1
```

If both are set, `MCDBUS_REQUIRE_ELICITATION` takes priority. The notification fallback is never reached because elicitation failure immediately aborts the operation.

## Audit logging

Regardless of which tier handles confirmation, mcdbus logs every system bus method call and every property mutation to stderr. The log message includes the bus type, service name, interface, method (or property name), object path, and signature. When running under systemd, these messages go to the journal and can be reviewed with:

```bash
journalctl -u mcdbus.service | grep "mcdbus:"
```

This audit trail exists independently of the confirmation mechanism. Even Tier 3 (proceed with warning) produces a log entry, so you can always reconstruct what operations were performed.
