CLI, JSON, stdin, Flags, and MCP: The AxiOwl Interface Model

AxiOwl's interface model is deliberately split across a few surfaces instead of forcing every operation through one overloaded command shape. Human operators get a direct CLI. Long or fragile message bodies can go through stdin. Machine-facing flows use JSON where structure is actually needed. Provider sessions reply through MCP, because replies need sender identity that shell flags alone cannot safely provide.

That division is visible in the current C++ implementation. The active build target is the Windows desktop app, and the root CMake project dispatches into apps/windows-desktop. The executable starts in main.cpp, collects argv, and passes the resulting argument vector into run_cli. From there, cli.cpp routes commands such as send, create, rename, discover, list, node, registry, mcp-server, mcp-self-test, delivery-worker, relay-session, license, and status.

Sources Read

The CLI Is The Operator Surface

The CLI is the shortest path for a person or script that already knows what it wants to do. The current help output exposes these core shapes:

axiowl send --to <agent> --body <message>
axiowl send --to <agent> --stdin
axiowl create --provider <provider> --name <agent> [--body <message> | --stdin]
axiowl discover <provider|all> [--target <agent>] [--json]
axiowl list agents
axiowl mcp-server [--host <host>] [--provider <provider>]
axiowl relay-session --stdio
axiowl status
axiowl --version

The actual built executable reports axiowl 0.1.0. Its help output also includes registry and node maintenance commands, a provider smoke test, license commands, and internal delivery worker entry points.

The flag implementation is intentionally simple. In cli.cpp, option_value scans the argument vector for an exact flag and returns the next value. has_flag scans for an exact boolean flag. Each command then validates what it needs. For example, send reads --to or --target, --body, --run-id, --strict, --delivery-method, --from, and --from-node. create reads --provider, --name, --body, --model, and --cwd.

That makes the CLI predictable. It is not a hidden JSON RPC interpreter. It is a command line with explicit verbs and flags.

stdin Is The Safe Message Body Path

The most important shell detail is --stdin. Both send and create support it. When --stdin is present, the command calls read_all_stdin, which reads the whole std::cin buffer into the message body. In both commands, stdin overrides the --body value.

That behavior matters because AxiOwl messages are often not single-line strings. They can include code, lists, quotes, JSON fragments, or instructions that would be damaged by shell quoting. With stdin, the operator can pass the body as a stream and keep flags for routing:

@'
Review this patch and reply through AxiOwl MCP with the run id included.

Do not summarize unrelated files.
'@ | axiowl send --to "Target Agent" --stdin --run-id "run-20260708-128"

MCP uses the same idea internally. In mcp_server.cpp, axiowl_send_message does not rebuild the body as a shell argument. It calls the CLI handler with send --to ... --stdin and passes the MCP body argument through captured stdin. That keeps message transport separate from routing flags.

JSON Exists, But It Is Not One Single CLI Input Mode

AxiOwl does use JSON heavily, but the current implementation uses it in specific places.

Discovery has a JSON output mode. axiowl discover <provider> --json prints one JSON object per discovered row with type discovered_agent, followed by a discovery_summary object. Those rows include fields such as display_name, provider, provider_session_id, node_id, status, sendable, has_enrollment_proof, source, evidence, last_seen_at, and last_error.

MCP is JSON-RPC. The MCP server reads JSON requests and returns JSON results. It supports both newline-delimited JSON messages and Content-Length framed messages. The MCP conformance test covers both modes and verifies that the server exposes required tools such as axiowl_whoami, axiowl_send_message, axiowl_create_agent, and axiowl_version. Framed input is capped: mcp_server.cpp rejects a Content-Length above 16 MiB.

The relay path is also JSON over stdio. axiowl relay-session --stdio starts with a hello frame and then accepts newline-delimited frames such as {"type":"deliver", ...}. It replies with JSON deliver_result or error frames.

The delivery worker uses JSON request files. The user-facing command shape is axiowl delivery-worker --request <request-json-file> [--validate-only], but that is an internal handoff path. message_pipeline.cpp accepts a send, writes a provider request for the delivery worker, and returns a receipt before waiting for provider delivery.

There is also limited JSON parsing inside the C++ code. json_util.cpp implements small helpers for flat object string and boolean extraction, not a general-purpose JSON document model. mcp_server.cpp has a raw object parser that can keep nested values intact for JSON-RPC fields. Provider edges also parse provider output where needed, such as Claude JSON output, OpenCode JSON session lists, discovery JSONL, or Codex app-server frames.

So the accurate statement is this: AxiOwl has structured JSON surfaces for MCP, discovery output, relay frames, worker handoff, logs, provider configs, and provider output parsing. It does not currently expose a single universal axiowl send --json request body contract.

Flags Carry Routing Intent

Flags are still important because they carry operator intent cleanly.

For a send, --to names the target agent. --from and --from-node can supply an explicit sender when a CLI-only test needs one. --strict is carried into the message request and then into provider delivery. --run-id gives the pipeline a correlation value. --delivery-method is available for provider-specific delivery selection, including Cursor method names or method letters shown in help.

For create, --provider selects the provider edge, --name is the requested agent name, --model and --cwd are passed to provider creation where relevant, and the initial body can come from --body or --stdin.

For discovery, the first positional value is the provider scope. The current CLI accepts codex, codex-cli, vscode-native, vscode-copilot, copilot-cli, antigravity, antigravity-cli, cursor, cursor-cli, claude-code-cli, opencode-cli, and all. Remote discovery is explicitly excluded in this local-provider remediation build.

The registry model explains why flags name agents instead of requiring every provider ID every time. The durable registry stores display names, aliases, providers, provider session IDs, node IDs, sendability, source, timestamps, and last error state. A send request names a target, then MessagePipeline resolves it through the registry, runs targeted discovery once if needed, and dispatches to the provider edge only after target and sender checks pass.

MCP Is The Provider Reply Surface

The MCP server is the most structured AxiOwl interface because provider replies need identity. The command is:

axiowl mcp-server --host <host> --provider <provider>

The host and provider can also come from environment variables such as AXIOWL_MCP_HOST, AXIOWL_MCP_PROVIDER, AXIOWL_PROVIDER_SESSION_ID, and AXIOWL_SENDER_NAME. Command flags override those defaults.

The MCP tool list includes:

The most important tool is axiowl_send_message. Its schema requires to and body, with optional strict, runId, receiptForMessageId, and sourceContext. Its description in code is careful: success is an MCP-to-AxiOwl handoff receipt only. Provider delivery and reply are not implied.

That is not just wording. MessagePipeline sets accepted_by_axiowl and state = "accepted_by_axiowl" after target resolution and before delivery worker or provider work. The CLI prints: provider delivery, provider wake-up, and provider reply are not implied. Tests assert that this receipt boundary stays separate from provider delivery proof.

Identity Is Why MCP Matters

For provider-to-provider communication, "who sent this?" cannot be a guess. The developer docs and provider matrix require provider-owned MCP metadata, a provider patch, or an equivalent provider-supported mechanism. Environment-only sender identity is not accepted as final CLI provider support.

mcp_server.cpp resolves identity by host/provider. It looks for provider-specific metadata keys such as Codex, VS Code, Cursor, Antigravity, Claude, Copilot CLI, and OpenCode session identifiers. When it has a usable session ID and a real display name, it can register the sender in the registry with source mcp_sender_identity. Then axiowl_send_message calls the CLI with --from set to the stable sender address and --from-node set to the resolved node.

The final visible body builder uses that identity too. When a sender is resolved, AxiOwl appends an MCP reply instruction targeting the sender:

mcp__axiowl.axiowl_send_message({
  "to": "<resolved sender>",
  "body": "<full reply>"
})

If a run ID or receipt message ID is available, those values are included in the reply instruction. This is why AxiOwl is more than a shell wrapper around provider CLIs. The interface model is designed to carry reply routing and correlation through the provider session.

CLI Provider Status Is Deliberately Uneven

The current provider matrix separates supported surfaces from target surfaces. Codex CLI is listed as supported with response-backed proof, and its provider page says replies must come back through AxiOwl MCP with Codex CLI session identity.

Several other CLI surfaces have code paths or historical proof but are not described as final supported surfaces under the current bar. Copilot CLI has create/resume code and MCP config writing, but the docs say local testing has been auth-blocked and metadata patch proof is outstanding. Claude Code CLI uses documented print/resume behavior against discovered JSONL session state, but current final support requires provider-owned MCP metadata or a robust patch. Antigravity CLI and OpenCode CLI are also target surfaces until that metadata requirement is satisfied.

This is the right distinction for operators. AxiOwl can expose flags, write config, or call a provider CLI before the full end-to-end support bar is met. The support bar is discovery, installation/config, send, provider receive, MCP reply, and correct provider-owned sender identity.

The Practical Model

The clean way to think about AxiOwl's interface is:

That model keeps the operator path simple while preserving machine-readable boundaries where they matter. A person can type axiowl send --to ... --stdin. A provider can call axiowl_send_message with JSON arguments over MCP. The pipeline can record an AxiOwl handoff receipt without pretending it is provider proof. Discovery can emit JSON lines without turning every command into a JSON API.

AxiOwl's current interface is therefore not "CLI versus MCP" or "flags versus JSON." It is a layered contract: flags for routing, stdin for safe body transfer, JSON for structured protocols, and MCP for identity-bearing provider collaboration.

Image prompt:

Create a polished graphic image related to: CLI, JSON, stdin, flags, and MCP as layered technical interfaces in AxiOwl.

Subject: a clean technical interface stack made of a terminal pane, structured data blocks, a stdin pipe, small flag toggles, and a protocol connector hub, all as abstract non-readable UI elements with no text.
Style: halfway between a clean symbolic icon and a realistic product/technical illustration; professional SaaS/technical marketing style; crisp edges; high detail; no text; no logos.
Background: solid #00ff00 chroma key green screen background covering the full canvas edge to edge.
Restrictions: no owl, no axolotl, no birds, no animals, no mascot, no text, no watermark.