Skip to main content

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.
  • mc as the primary name: rejected; Midnight Commander and the MinIO client both install as mc, and in a terminal the word most often means the first. mission-control is the name in help, instructions and records; mc is 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; wait answers 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.
  • wait is 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 --timeout it is given, so a loop around it is plain shell.
  • One audience, and what a token may do is in its claims. The mcp audience 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 as sign_off on 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_TOKEN is for gh, 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 login takes 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 --json so 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 like gh and tea.