Serve the CLI over MCP, so an agent can publish without a shell - #13
Merged
Merged
Conversation
An agent that speaks MCP natively had to spawn a shell, quote arguments and parse output to publish a component. `miakapp mcp` serves the same commands as tools over newline-delimited JSON-RPC on stdio. The server is a translation layer and nothing else: a tool call becomes the exact argv a person would have typed, then runs the same dispatch. There is no second implementation of an option, a default or a validation rule, and a test proves the tool surface exposes every CLI option and invents none. Two deliberate departures from the command line: - publish, activate and rollback refuse to run without `confirm: true`. The guard is checked before anything else and never reaches the argv, so a model that hallucinated a publication spends the mistake on an argument check rather than on a generation; - a command failure comes back as a tool result carrying `isError`, not as a JSON-RPC error. A protocol error means the call never happened; a publication that reached the control plane and failed did happen, and only `kind` tells the caller whether to reconcile. Nothing but framed JSON-RPC reaches stdout: the dispatch runs against a host that captures its own rendering, because one stray line would desynchronize the stream for the rest of the session. Zero new dependencies. 37 tests, including a live handshake shape, a message split across chunks, and a guarded publish proving the control plane was never contacted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #12. Base is
mathieu/agent-discover, notmain:packages/does not exist onmain, and this extends the CLI that PR #10 introduces. Merge order stays #10 → #11 → #12 → this one.Atelier F, livrable 4 (« outils CLI/MCP pour maisons, composants, releases, état coordinateur, tests »), MCP half.
What this adds
miakapp mcpserves the existing commands as MCP tools over newline-delimited JSON-RPC on stdio.miakapp_discoverdiscovermiakapp_checkcheckmiakapp_releasereleasemiakapp_uploaduploadmiakapp_initinitmiakapp.yaml, never overwritesmiakapp_publishpublishconfirm: truemiakapp_activateactivateconfirm: truemiakapp_rollbackrollbackconfirm: trueThe one design decision worth arguing about
The server is a translation layer, not a second front end. A tool call becomes the exact argv a person would have typed, and then runs the same
dispatchthe CLI runs. No option, default or validation rule is implemented twice.Two tests hold that line rather than a comment:
no tool hides an option the command accepts— every entry inCOMMAND_OPTIONS[command]is an exposed argument;no tool invents an option the command would reject— no argument maps to an option the parser would refuse.Add an option to a command and forget the tool, and the CLI package fails to test.
Two deliberate departures from the command line
confirm: trueon the three pointer-moving tools. Checked before anything else, and never appended to the argv — the command line keeps exactly the shape it had. A model that hallucinated a publication spends the mistake on an argument check instead of on a generation. This is the smallest useful piece of livrable 6 (approval policy for physically consequential actions); the real policy is still ahead.A command failure is a tool result with
isError: true, not a JSON-RPC error. The distinction is load-bearing: a protocol error means the call never happened, while a publication that reached the control plane and failed did happen. The result carries the same closed object the CLI prints —kind,exit_code,message,remedy— so an agent branches on the same table documented indocs/agent-guide.md§8, whichever surface it drives.Stdout hygiene
The dispatch runs against a host whose
writeandwriteErrorare discarded. One stray rendered line would desynchronize the stream for the rest of the session, so the protocol owns stdout outright. A test asserts a session that performs a tool call emits exactly one frame and nothing on stderr.mcp --jsonis a usage error rather than a no-op, for the same reason.Bounds
-32700that does not end the session;Verification
bun run check→ 281 pass / 0 fail, exit 0bun run check:packages→ CLI 93, component 38, template 8, exit 0packages/cli/test/mcp.test.tsinitialize→notifications/initialized→tools/listover a pipe, then atools/callofmiakapp_discoveragainst a real file on the real filesystem, and an unconfirmedmiakapp_publishreturning{"ok":false,"kind":"usage"}with the control plane untouched.Zero new dependencies, consistent with the rest of this package.
What this is not
Not a read of live coordinator state. RFC 0004 §13.2 exposes no
GETfor the component pointer — the pointer lives in the Firestore documentcomponents/{homeID}per RFC 0002 §7, read by the host, not by a publisher over REST. So--expected-generationstill has to come from the operator's own records, and inventing an endpoint to make that nicer was not on the table. That gap is real and belongs in a control-plane RFC, not in the CLI.