One CLI does work in Mission Control, whoever holds it
Mission Control is reached three ways: the web app for people, the API behind it, and an MCP server
of its own tools for agents. The last is the wrong shape. ADR 0023 found that agents do their best
work with the tools their training holds, and named a CLI in the image as the next step; and the
MCP server serves agents only, so a person whose tasks live in Mission Control reaches them through
the web app or raw API calls, and a coding agent on a laptop, working as that person, reaches them
not at all. We decided that there is one CLI, mission-control (mc for short), for doing work in
a running server, and that it does not care who holds it: a person who signed in, a role's run with
its token in the environment, or a coding agent on a laptop acting as the person. The CLI is a
client of the API and nothing more; who the caller is, and what they may do, is the token's and the
server's business, as it is for the web app (ADR 0001, ADR 0005). It covers what Mission Control's
MCP tools did, so the MCP server goes, and one thing more: mission-control wait blocks until the
caller has a turn on a task, which is how a person or a local agent takes the place a role's run
would have had. What was mission-control, the server with the web app, becomes
mission-control-server.
Considered Options
- Keeping the MCP server beside the CLI, a runtime choosing either: rejected; two ways to carry the same tools to instruct, test and keep, and the one we stop believing in is the one that would stay (ADR 0023's reasoning on two models). The MCP endpoint goes in the change that switches the agent image to the CLI, after parity, not before. Organizations' own MCP servers (ADR 0018) are unaffected; they are theirs, not ours.
- The CLI as a subcommand of the server's binary: rejected; the server binary carries the server and the web app, and the client has to be light enough to bake into the agent image and install on a laptop. Two packages, two names.
- A client for agents and another for people: rejected; that is the shape we are leaving. The
same command on a laptop, in a container and in a script, with the same
--help, is the point: a person can read what an agent does and an agent can do what a person does. - The CLI deciding when its holder has a turn, from a stream of raw task events: rejected; the run driver already decides that for roles, and the rule would then exist twice and drift. The rule moves to the server, where the driver and the CLI both read it.
mcas the primary name: rejected; Midnight Commander and the MinIO client both install asmc, and in a terminal the word most often means the first.mission-controlis the name in help, instructions and records;mcis an alias for whoever has nothing competing, which includes the agent image.- Long-lived personal tokens, or a refresh flow, for people: deferred. A person's token lives seven days as it does for the web app, so a person signs in about weekly. Comparable tools do not ask for that, and a refresh flow such as OAuth is the intended follow-up; nothing here depends on how a token is obtained.
Consequences
- A turn is the server's notion. A party, role or person, has a turn on a task when the task is
assigned to them in a working status, is not blocked, and something happened since they last
looked: a handoff, a comment, a subtask or a blocker finishing. That rule is lifted out of the
task run driver into one place the driver and a new API read. The driver answers a role's turn
by starting or continuing a run, as now;
waitanswers a person's turn by printing it and exiting 0; nothing starts a run for a person, as before. Where to look from is the consumer's: a run carries its window as it does now, and the CLI keeps a cursor of its own on disk, so the web app and a local agent reading the same tasks do not fight over what has been seen. waitis a command a script can build on. With no argument it waits for any turn of the caller's; with a task id it waits on that task, which is how a local agent on a task waits for a subtask it handed out to come back. It exits 0 with the turn as JSON on stdout, and non-zero on the--timeoutit is given, so a loop around it is plain shell.- One audience, and what a token may do is in its claims. The
mcpaudience goes; every token is accepted by the API, and the authentication hook validates what its claims say before anything behind it sees a principal. A run's token claims its role and its run, and the hook checks the run is still live, so it stops working when the run stops, as now; what a run alone may do, such assign_offon the task it works on (ADR 0022), is unchanged since the run is still in the context. Tighter claims are welcome where they pay: a run token may also name the task or conversation it serves, and a person's token a scope, each checked in the same hook. The CLI never reads a claim; it carries the token. - The token reaches a run through its environment, issued at launch. Today it is issued when
the session opens and travels in the session's MCP configuration. With a CLI it must be in the
process environment, as
GH_TOKENis forgh, so it is issued with the environment (ADR 0019's layers) together with the server's URL. The 24 hour life of a run token is as before. - Signing in mirrors
tea.mission-control logintakes a server URL and credentials and stores the token under a named server in the user's config, one of which is the default, so a laptop that reaches two servers (ADR 0021's cluster and a local one) picks with a flag. The environment wins over the file, which is how a run, or anything scripted, is pre-authenticated. - Attribution follows the token. What a person's token does appears as the person, comments and sign-offs included, whichever program held it. A sign-off given on a person's token is a person's review in ADR 0022's terms, not a role's. A role's run token acts as the role. The CLI adds no principal of its own.
- Parity is the work. Mission Control's MCP tools are a layer of their own over the services;
most have routes because the web app needs them, and what has none gets one: reading and posting in
Slack, a role's sign-off, following tasks and asking roles from a conversation (ADR 0011). The tool
descriptions become
--help, which the role instruction and the working-in-mission-control skill then point at instead of describing tools. Every command takes--jsonso an agent or a script reads it and a person gets a table. - The CLI is built from the API's own description, the OpenAPI document the web app already generates its client from, so the two clients cannot disagree about the API.
- The rename touches the bin,
pnpm start, the service install, the server image, the cluster manifests and the docs. The agent image gains the client, pinned likeghandtea.