Skip to content

Serve the CLI over MCP, so an agent can publish without a shell - #13

Merged
Mathieu2301 merged 1 commit into
mathieu/agent-discoverfrom
mathieu/agent-mcp
Sep 14, 2026
Merged

Mathieu2301 merged 1 commit into
mathieu/agent-discoverfrom
mathieu/agent-mcp

Conversation

@Mathieu2301

Copy link
Copy Markdown
Member

Stacked on #12. Base is mathieu/agent-discover, not main: packages/ does not exist on main, 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 mcp serves the existing commands as MCP tools over newline-delimited JSON-RPC on stdio.

Tool Command
miakapp_discover discover read-only, offline
miakapp_check check read-only, offline
miakapp_release release read-only
miakapp_upload upload read-only
miakapp_init init writes miakapp.yaml, never overwrites
miakapp_publish publish moves the pointer — needs confirm: true
miakapp_activate activate moves the pointer — needs confirm: true
miakapp_rollback rollback moves the pointer — needs confirm: true

The 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 dispatch the 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 in COMMAND_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: true on 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 in docs/agent-guide.md §8, whichever surface it drives.

Stdout hygiene

The dispatch runs against a host whose write and writeError are 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 --json is a usage error rather than a no-op, for the same reason.

Bounds

  • reassembly buffer capped at 1 MiB per message; a longer one ends the session instead of growing the buffer;
  • a notification is never answered, not even to say it was not understood;
  • unparseable input is a -32700 that does not end the session;
  • a closed stdin is a clean shutdown with exit 0, since that is how a client stops an MCP server.

Verification

  • bun run check → 281 pass / 0 fail, exit 0
  • bun run check:packages → CLI 93, component 38, template 8, exit 0
  • 37 new tests in packages/cli/test/mcp.test.ts
  • Real handshake against the built binary, not only the injected host: initializenotifications/initializedtools/list over a pipe, then a tools/call of miakapp_discover against a real file on the real filesystem, and an unconfirmed miakapp_publish returning {"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 GET for the component pointer — the pointer lives in the Firestore document components/{homeID} per RFC 0002 §7, read by the host, not by a publisher over REST. So --expected-generation still 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.

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.
@Mathieu2301
Mathieu2301 merged commit 7d076aa into mathieu/agent-discover Sep 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant