Safety classes
Every tool carries exactly one safety class in meta.safety_class, and the class determines the required parameters. The authoritative rule text lives in .opencode/rules/mcp-tools.md; the machine-checked matrix is docs/tool-contracts.md.
The model
| Class | Risk | Extra params | UndoRedo |
|---|---|---|---|
read_only | Never mutates | none | n/a |
mutating | Reversible editor change | dry_run: bool = False | wrapped |
destructive | Deletes/overwrites; maybe irreversible | dry_run and confirm: bool = True | wrapped (captures prior state) |
runtime | Controls game execution | varies | varies |
dry_run
Runs preconditions and returns what would happen. Key properties:
- Nothing is sent to the editor (asserted by contract tests —
"cmd_…"absent from the bridge traffic). - The preview carries the same persistence verdict the real run will stamp (the
cmd_node_persistenceprobe runs the identical rule). - Batch previews report the projected
undoableflag. - A dry-run must never be a containment bypass: the same server-side path containment (
res://-only) applies to the preview's existence probe.
confirm
Required for every destructive tool, without exception. The refusal is a structured precondition, so an agent can react:
{ "error": "PRECONDITION_FAILED",
"hint": "This call discards unsaved changes. Set confirm=True to proceed.",
"required": "confirm" }The recommended flow: dry_run=true first (read what will be affected), then confirm=true when the preview is acceptable. Destructive tools that remove state capture that state for undo (e.g. remove_audio_bus restores the whole bus + effects on undo; delete_resource_file restores exact bytes).
Where the classes come from
The class is metadata on the tool registration; mcp_server/safety.py owns MUTATING / READ_ONLY / DESTRUCTIVE / RUNTIME constants and the enforce_preconditions decorator that checks them. The addon never decides whether an action is allowed — it honors the flags defensively (e.g. the add-on-side confirm check on cmd_delete_node).
The matrix rules
The full class × tool matrix is machine-checked (docs/tool-contracts.md + tests). Invariants:
- No mutating/destructive tool lacks
dry_run. - No destructive tool lacks
confirm. - No tool merges classes (delete is never folded into a fat "node" tool — the gate would be buried).
- Read-only tools never mutate — a
read_onlytool that writes is a bug the contract tests catch.
godot_undo
Every UndoRedo-wrapped mutation is reversible by the human's own undo stack; godot_undo (always-on core) steps back one action. The documented exception: batch sets above 20 nodes bypass the stack for performance — and the result says so via undoable: false (the scene is still marked dirty, #643).
Approval & the fail-closed default
confirm is an accident guard the agent sets itself; real human oversight is the opt-in approval gate (GODOT_MCP_APPROVAL_WEBHOOK, or the elicitation guard with GODOT_MCP_APPROVAL_REQUIRE). mutating/destructive calls are routed to it, plus runtime tools flagged writes_disk/executes_code (export_project, evaluate_expression). With a webhook configured an unreachable webhook now fails closed — it denies — by default (#638); set GODOT_MCP_APPROVAL_FAIL_OPEN=true to opt into fail-open.