ctrlrun mcp-operator exposes the operator’s own commands as MCP tools. The person who has to
answer an approval asks their assistant what is waiting, reads the action and its arguments, and
answers — with no checkout, no shell and no store path. The answer goes through the same two
store calls ctrlrun approve and ctrlrun deny make, so there is one approval record and one
place its state changes.
pip install "ctrlrun[gateway]", and a directory holding the ctrlrun.yaml
and the store your agents already use. It speaks MCP revision 2026-07-28 and accepts
2025-11-25, 2025-06-18 and 2025-03-26.
Start it
127.0.0.1:8901 and there is no flag that changes that. Its read tools answer
without a credential, so it must not be the process that opens a port to a network; put a proxy
in front of it on the same host, terminating authentication there and overwriting both headers
on every request. That proxy is what makes the headers worth anything.
There is no --principal either. A fixed name would attribute every approval to the same
string whoever gave it, and an approver that distinguishes nobody is not attribution. With
--identity-jwt the approver comes from a verified bearer token instead, and
--identity-jwt-user-claim says which claim names the person.
Point a client at it
A transcript
Asking what is waiting needs no credential:What it will not do
- Make an agent act. No tool proposes, executes or resumes an action, and there is no auto-approve, dry-run or development mode.
- Approve an action other than the one the request names. The grant carries the
action_hashstored when the request was created; change an argument and the call is refused. - Take an answer from a machine. A credential naming an agent and no person is
-41013. - Take an expired credential.
-41014, distinct from-41007, because refreshing a token and obtaining one are different fixes. - Decide whether you were allowed to answer. It authenticates who answered and records it.
Any human whose credential the provider verifies can answer any pending request, exactly as
any human who can run
ctrlrun approvecan. That is attribution, not authorization.
If it did not work
- Exits at start naming
--user-headeror--identity-jwt-user-claim: a write tool refuses a credential that names no person, so a configuration that could never write is refused early. - Exits at start naming loopback: there is no
--allow-remote; put a proxy in front instead. -41003with reasonexpired: the request timed out. The agent proposes it again.-32020: a mirrored MCP header disagrees with the body. The body is believed and the request is refused rather than guessed at.