Write Safety
familiar began "read-only by design" and is now read + write — but writing is gated behind a single, deliberately strict safety layer. This page explains exactly when a write happens and what stops it.
The three conditions
A write only happens when all of these hold:
- The tool is called with
confirm=true(otherwise it returns a dry-run preview and changes nothing). - Writes are enabled globally (on by default; opt out with
familiar writes disable). - The action runs through
writes.guard— the single choke point every mutating tool must use.
Every executed write is appended to an audit log on disk.
The gate: writes.guard
writes.py exposes one function that every write tool calls:
async def guard(action, params, confirm, execute):
# confirm=False -> dry-run preview, execute nothing
# confirm=True, writes off -> raise WriteDisabled
# confirm=True, writes on -> execute(), append to audit log, return result
confirm=false → dry-run preview
The default. Nothing is changed. The tool returns:
{
"dry_run": true,
"action": "set_waypoint",
"params": { "...": "..." },
"note": "Nothing was changed. Pass confirm=true to execute (writes must be enabled)."
}
This is how the AI can show you what it would do before you commit. You confirm by asking it to run the same action with confirm=true.
confirm=true but writes disabled → WriteDisabled
If you've globally turned writes off, a confirmed write still does nothing and raises WriteDisabled telling you to run familiar writes enable to re-allow it.
confirm=true and writes enabled → execute + audit
The action runs, and a line is appended to the audit log (see below).
The global toggle
Writes are on by default. The toggle is controlled three ways, in precedence order:
- Environment variable
FAMILIAR_ENABLE_WRITES— if set, it wins (1/true/yes/on= enabled). - On-disk flag file
writes_disabledin the data dir — created/removed by the CLI. - Default — enabled (no flag file present).
CLI:
uv run familiar writes # show current state
uv run familiar writes disable # create the flag → block all writes
uv run familiar writes enable # remove the flag → re-allow writes
The write_status MCP tool reports the current state to the AI without touching the toggle.
The audit log
Each executed write appends one JSON line to audit.log in the data dir:
{"ts": 1719600000.0, "action": "send_mail", "params": { "...": "..." }}
Dry-runs and blocked (disabled) writes are not logged — only real, executed writes are. The log lives alongside the encrypted token store (see Configuration for the path).
Which tools are writes?
Every tool marked WRITE in the Tool Reference:
- Comms:
send_mail,set_mail_read,respond_calendar_event - Contacts:
add_contacts,remove_contacts - Fittings:
create_fitting,delete_fitting - Fleet:
invite_to_fleet,update_fleet - UI:
set_waypoint,open_window
These are powered by the OAuth write scopes granted at login (see Authentication & Scopes).
For contributors
When adding a write tool:
- Give it a required
characterand aconfirm: bool = Falseparameter. - Route the mutation through
writes.guard— never call a mutating ESI endpoint directly. - Pass a clear
actionstring and the user-facingparamsso the dry-run preview and audit line are meaningful.
familiar
familiar — an EVE Online MCP server · repository · MIT