Change procedures (MOPs)
A MOP is a change written down as API steps that Claude runs for you in order, checking each one. You approve it once for the whole run instead of confirming every write. If a step fails, the run stops by default, and the whole run can be rolled back, newest write first. MOPs are MCP-only: there is no dashboard page to list or run them, so you ask Claude.
What is in a MOP?
Section titled “What is in a MOP?”| Part | What it is |
|---|---|
Pre-checks (phase pre) | Reads that confirm the starting state. They must not write |
Steps (phase step, the default) | Reads and writes, on either surface (wifi or switches) |
Post-checks (phase post) | Reads that confirm the result. They must not write |
expect | The HTTP status expected (default any 2xx) and up to 20 checks on the response, such as list.length gte 1 |
capture | Values kept for later steps, used as {{name}} |
forEach | Runs a step once per element of a captured list, as {{item}} |
rollback | How a rollback reverses the step: auto (default: the write’s recorded undo), none, or a request of its own |
params | Values you give when a run starts |
window | The change window: alert downtime on the listed devices or containers while the run is live |
Check operators are eq, ne, gt, gte, lt, lte, contains,
exists, absent, matches and in.
How do I run one?
Section titled “How do I run one?”Describe the change to Claude. It uses these functions:
| Function | What it does | Who |
|---|---|---|
mop.save | Creates a MOP, or replaces one (a new version each save) | Engineer and above |
mop.preview | Lints it and shows what a run would do, sending nothing | Any role |
mop.start | Starts a run. A MOP with writes is approved here | Any role for a read-only MOP; engineer and above with writes |
mop.run | Runs the next steps until the run ends, a step fails, or the code-mode time limit is near (then call again) | Anyone who can start it; only the person who started it sends its writes |
mop.step | Runs exactly one step | As mop.run |
mop.status | The run so far: every step’s evidence, its writes and their rollback, and post-change verification | Any role |
mop.abort | Stops a run. What it already changed stays | Engineer and above |
mop.rollback | Reverses what a run changed | Engineer and above, and only the person who started the run |
mop.list, mop.get, mop.runs | The MOPs, one MOP, recent runs (default 20) | Any role |
mop.remove | Deletes a MOP and its runs; refused while a run is active | Engineer and above |
A viewer or operator who tries a write-side function gets write_blocked: “Your
role in this organisation (operator) can read and preview MOPs but not (what);
an engineer or admin can.”
What does the preview check?
Section titled “What does the preview check?”Errors stop a run; warnings don’t.
| Errors | Warnings |
|---|---|
| A pre- or post-check that writes (“A pre-check must only read; this one changes the controller”) | A write checked only by its HTTP status |
| A write to an operation the API spec doesn’t know | A step that can’t be undone automatically, or is marked rollback: none |
A {{variable}} used before any step captures it or any parameter declares it | A rollback that recreates a deleted object with a new id |
A {{variable}} in the path instead of pathParams | A dangerous action (“starting the run needs the second factor”) |
| A bad JSON path or regular expression | No pre-checks, no post-checks, or no change window |
A MOP that has errors or is missing parameters can’t start: not_ready, “The
MOP has lint errors or missing params; fix them (mop.preview shows them) before
starting.”
How does the approval work?
Section titled “How does the approval work?”You approve the whole run once, and its writes are then sent without further
confirms. The first mop.start returns confirm_required with a preview of
every write step: its body, its rollback and the change window. A MOP with a
dangerous step also needs your authenticator code or, without one, a dashboard
approval (see Approving dangerous changes).
The approval covers this version of the MOP, with these parameters and this
onFail, and nothing else. After it:
- Only the person who started the run sends its writes. Anyone else gets
not_armed: “Step N changes the controller, and run X was armed by someone else; only they can send its writes. You can abort it.” - The writes are approved for 12 hours from the start. After that:
arming_expired, “Run X was armed N h ago; its writes are no longer approved. Abort it (undo what it wrote with history.undo if needed), then start a new run.” - Writes are still refused for a read-only role or organisation, and a step that has since become a dangerous action is refused: “start a new run”.
Every write lands in the write history under the run’s approval.
What happens when a step fails?
Section titled “What happens when a step fails?”With the default onFail: 'stop', the run stops at the failed step and waits
for you. You then roll it back, or leave it. With onFail: 'rollback', the
run reverses what it changed straight away.
A run starts running. It ends passed when every step passes, failed when
one fails, or aborted when someone stops it (a failed run can be aborted too).
A rollback can start from passed, failed, aborted or rollback_failed; it
is automatic only for a failed run with onFail: 'rollback'. While reversing,
the run is rolling_back, then rolled_back, or rollback_failed if any write
could not be reversed. A rollback interrupted by the time limit stays
rolling_back and continues on the next mop.rollback.
A rollback reverses each write newest first, using the step’s declared rollback
or the write’s recorded undo. A write whose target was changed since is left
alone unless you say force: true.
| Rollback refused | Message |
|---|---|
| The run is still running | ”Run X is still running; abort it first, or let it fail.” |
| Someone else started the run | ”Only the person who started (armed) the run can roll it back; others can undo its writes one by one with history.undo.” |
| The 12 hours are over | ”Run X’s approval has expired, so it can no longer be rolled back as a whole; undo its writes one by one with history.undo (each is confirmed).” |
| Already done | ”Run X is already rolled back.” |
What does the change window do?
Section titled “What does the change window do?”It puts what the MOP touches into alert downtime while the run is live. The
window opens when a run with writes starts, with the comment MOP run
<id>: <title>, and lasts minutes (default 60, at most 24
hours). It closes when the run passes, is aborted or is rolled back. After a
failure it stays open, since a rollback usually follows, and ends on its own.
What are the limits?
Section titled “What are the limits?”| Limit | Value | At the limit |
|---|---|---|
| MOPs per organisation | 100 | ”An org keeps at most 100 MOPs; remove one first.” |
| Steps per MOP | 200 | Refused when saved |
| Parameters per MOP | 30 | Refused when saved |
Items a forEach repeats over | 200 | The step fails |
| Runs kept | 20 per MOP, 90 days | Older finished runs are deleted |
| Evidence kept per step | 600 characters of the response | Truncated |
| Active runs per MOP | 1 | run_active: “Run X of this MOP is still active; finish or abort it first.” |
If the MOP was saved again after you previewed it, mop.start refuses with
changed: “MOP N changed (now version V) since it was previewed; preview
and start it again.”