You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
operator approve is an MCP tool, so an agent can approve its own proposal without a shell #35
decisions approve is kept off the MCP surface (mcp=False, "A person approves; no agent tool stands in for it"). operator approve is not: on a project, cloudfall mcp serve serves it as a tool, because operator.approve is in the project command set in sdk/src/cloudfall/agent_server.py (line 311), and the command in sdk/src/cloudfall/app.py (operator_approve, around line 2534) has no mcp=False and no person check.
So an agent connected over MCP to a project can approve an operator proposal and run it, with no shell needed. This is the same hole as #25, on the surface the docs call the agent's.
Expected
An agent can't approve its own proposal over MCP. At least: operator approve off the MCP surface, as decisions approve is. Whether it also gets the person-only confirmation from #25 (ctx.attest) belongs with #25's design.
operator approve (sdk/src/cloudfall/app.py, operator_approve) has no mcp=False, and operator.approve is in PROJECT_COMMANDS (sdk/src/cloudfall/agent_server.py), so cloudfall mcp serve on a project serves it as a tool
It runs the proposal's playbook and verifies it with no person check, so an agent connected over MCP can approve and run its own proposal
decisions approve already sets the rule: mcp=False, "A person approves; no agent tool stands in for it"
Design. This removes a tool from the MCP surface, a contract change, so it went through the design gate. It follows the rule decisions approve already states, so there is no new choice to make:
operator approve gets mcp=False with the same comment as decisions approve, and operator.approve leaves PROJECT_COMMANDS
A test checks it is not in the MCP tool list, beside the decisions approve one
Docs that list operator_approve as a tool, or tell an agent to call it, are updated; the CHANGELOG says it under Security
Compatibility: an MCP client that called operator_approve gets "unknown tool". That is the point of the fix; a person approves with cloudfall operator approve from a shell
The PR says "Part of #35". On hold for the rest: the person-only confirmation (requires_person and ctx.attest, as in #25) waits on #37, which brings treaty rc38 and is waiting on decision A there. Once #37 merges, operator approve gets the same treatment in a follow-up PR.
Labelled release-blocker and put in the 0.6.0 milestone, for the reason #25 was: the site says an agent can't go around the gate, and this is a way around it on the agent's own surface. I'll link the PR here.
PR: #38 (Part of #35). It takes operator approve off the MCP surface (mcp=False, out of PROJECT_COMMANDS), adds a test that the tool is gone and a call to it gets UNKNOWN_TOOL, and rewrites the operator guide, sdk/README.md and the server's MCP instructions to say a person approves from a shell.
Trade-off: the four --gateway-* flags of cloudfall mcp serve fed only that tool and are removed with it, which breaks a server started with them. That waits on the decision asked on #38. The person-only confirmation still waits on #37, as above.
Triage: the person check is unblocked. #37 merged (964d889), so main now has treaty rc38 with requires_person and ctx.attest, and decisions approve uses them.
Plan for the rest of this issue, in its own PR, separate from #38:
operator approve is declared requires_person=True, the same way as decisions approve. The person types the decision id at a terminal, and no flag answers that prompt, --yes included. Off a terminal it exits 4 with PERSON_REQUIRED and runs nothing. A mistyped id exits 4 with ATTESTATION_MISMATCH
The MCP side stays in Take operator approve off the MCP server #38, which still waits on its --gateway-* question. Whichever of the two PRs lands second gets rebased onto the other
PR: #42 (draft, Part of #35, stacked on #38). It declares operator approverequires_person=True, so the person types the proposal id at a terminal and no flag answers that prompt. Off a terminal it exits 4 with PERSON_REQUIRED, and a mistyped id exits 4 with ATTESTATION_MISMATCH. Either way the playbook and the verify step don't run. A person's approval records approval.via: terminal. 416 tests pass on the stack.
It has to land after #38. treaty turns requires_person into mcp=False, so on main alone cloudfall mcp serve --project would still bind the --gateway-* flags to a tool that's gone, and it would refuse to start with MCP_BIND_UNKNOWN. #38 removes those flags, and that still waits on the question asked there.
Require a person at a terminal for operator approve #42 (20b33c7): operator approve is requires_person=True. A person types the proposal id at a terminal, off a terminal it exits 4 with PERSON_REQUIRED, and the approval records approval.via: terminal
The review of #42 found a small schema gap, filed as #46: the schema accepts approval.via on an autonomous approval. It doesn't reopen this hole.
Activity
shipmill-romamo commentedon Oct 8, 2026
Triage: implement the MCP part now; the person check waits on #37.
Confirmed on
main(9cb777c):operator approve(sdk/src/cloudfall/app.py,operator_approve) has nomcp=False, andoperator.approveis inPROJECT_COMMANDS(sdk/src/cloudfall/agent_server.py), socloudfall mcp serveon a project serves it as a tooldecisions approvealready sets the rule:mcp=False, "A person approves; no agent tool stands in for it"Design. This removes a tool from the MCP surface, a contract change, so it went through the design gate. It follows the rule
decisions approvealready states, so there is no new choice to make:operator approvegetsmcp=Falsewith the same comment asdecisions approve, andoperator.approveleavesPROJECT_COMMANDSdecisions approveoneoperator_approveas a tool, or tell an agent to call it, are updated; the CHANGELOG says it under Securityoperator_approvegets "unknown tool". That is the point of the fix; a person approves withcloudfall operator approvefrom a shellThe PR says "Part of #35". On hold for the rest: the person-only confirmation (
requires_personandctx.attest, as in #25) waits on #37, which brings treaty rc38 and is waiting on decision A there. Once #37 merges,operator approvegets the same treatment in a follow-up PR.Labelled
release-blockerand put in the 0.6.0 milestone, for the reason #25 was: the site says an agent can't go around the gate, and this is a way around it on the agent's own surface. I'll link the PR here.shipmill-romamo commentedon Oct 8, 2026
PR: #38 (Part of #35). It takes
operator approveoff the MCP surface (mcp=False, out ofPROJECT_COMMANDS), adds a test that the tool is gone and a call to it getsUNKNOWN_TOOL, and rewrites the operator guide,sdk/README.mdand the server's MCP instructions to say a person approves from a shell.Trade-off: the four
--gateway-*flags ofcloudfall mcp servefed only that tool and are removed with it, which breaks a server started with them. That waits on the decision asked on #38. The person-only confirmation still waits on #37, as above.shipmill-romamo commentedon Oct 8, 2026
Triage: the person check is unblocked. #37 merged (964d889), so
mainnow has treaty rc38 withrequires_personandctx.attest, anddecisions approveuses them.Plan for the rest of this issue, in its own PR, separate from #38:
operator approveis declaredrequires_person=True, the same way asdecisions approve. The person types the decision id at a terminal, and no flag answers that prompt,--yesincluded. Off a terminal it exits 4 withPERSON_REQUIREDand runs nothing. A mistyped id exits 4 withATTESTATION_MISMATCHapproval.via: terminal, as from Require a person at a terminal to approve a decision #37decisions show, as decided fordecisions approveon Require a person at a terminal to approve a decision #37 (A1). Ifoperator approvecan't follow that design exactly, the PR stops and asks--gateway-*question. Whichever of the two PRs lands second gets rebased onto the othershipmill-romamo commentedon Oct 8, 2026
PR: #42 (draft, Part of #35, stacked on #38). It declares
operator approverequires_person=True, so the person types the proposal id at a terminal and no flag answers that prompt. Off a terminal it exits 4 withPERSON_REQUIRED, and a mistyped id exits 4 withATTESTATION_MISMATCH. Either way the playbook and the verify step don't run. A person's approval recordsapproval.via: terminal. 416 tests pass on the stack.It has to land after #38. treaty turns
requires_personintomcp=False, so onmainalonecloudfall mcp serve --projectwould still bind the--gateway-*flags to a tool that's gone, and it would refuse to start withMCP_BIND_UNKNOWN. #38 removes those flags, and that still waits on the question asked there.Trade-offs, also listed in #42:
operator approvestop working. Runs licensed by anOperatorPolicyare unaffectedcloudfall.operator.approvetakes a keyword-onlyvia, and the proposal schema gains an optionalapproval.via--gateway-*material is missing, the command reportsPRECONDITIONbefore it asks the person to type the id#38's branch predates #37, so #42's diff also shows #37's files until #38 is rebased onto
main.Take operator approve off the MCP server
Say operator_approve is gone in the earlier MCP server entries
Require a person at a terminal to approve an operator proposal
shipmill-romamo commentedon Oct 9, 2026
Closed: #38 and #42 both landed; see the next comment.
shipmill-romamo commentedon Oct 9, 2026
Both parts have landed on
main, so I'm closing this:operator approveis off the MCP surface, and the--gateway-*flags ofcloudfall mcp serveare removed, as decided on Take operator approve off the MCP server #38operator approveisrequires_person=True. A person types the proposal id at a terminal, off a terminal it exits 4 withPERSON_REQUIRED, and the approval recordsapproval.via: terminalThe review of #42 found a small schema gap, filed as #46: the schema accepts
approval.viaon an autonomous approval. It doesn't reopen this hole.shipmill-romamo commentedon Oct 9, 2026
Shipped in 0.6.0rc7 (#38 and #42): uv tool install cloudfall==0.6.0rc7