CLI & MCP Server
Command-line interface (src/main.rs)
Section titled “Command-line interface (src/main.rs)”The xmsg binary exposes five subcommands: serve, mcp, register, unregister, and
check-peers.
#[derive(Parser, Debug)]#[command( name = "xmsg", version, about = "Fast, lightweight local HTTP bridge into live agent sessions")]struct Cli { #[command(subcommand)] command: Commands,}
#[derive(Subcommand, Debug)]enum Commands { /// Start the HTTP bridge server Serve(Box<ServeArgs>),
/// Run the stdio MCP server for agent harnesses Mcp(McpArgs),
/// Register credentials with running xmsg server Register(RegisterArgs),
/// Explicitly revoke or unregister a session (e.g. `--codex-session <P>`) Unregister(UnregisterArgs),
/// Load a peers file with every rule `serve` applies, and exit 0 or name the failure CheckPeers(CheckPeersArgs),}
#[derive(Parser, Debug)]pub struct UnregisterArgs { /// Deliberately revoke a Codex root session ID (`RevokedByOperator`, sticky across resume) #[arg(long = "codex-session")] pub codex_session: String,
/// Agent socket path (defaults to $XDG_RUNTIME_DIR/xmsg/agent.sock) #[arg(long, env = "XMSG_AGENT_SOCK")] pub sock: Option<PathBuf>,}
#[derive(Parser, Debug)]pub struct CheckPeersArgs { /// Path to the peers file (JSON) pub peers_file: PathBuf,
/// This node's federation certificate (PEM); with it, the peers file is also checked /// against this node's own name and key #[arg(long)] pub fed_cert: Option<PathBuf>,}xmsg serve
Section titled “xmsg serve”Starts the local IPC and optional mTLS federation daemon:
#[derive(Parser, Debug)]pub struct ServeArgs { /// Optional TCP listen address (IP:PORT), for environments like Kubernetes pods where loopback is a private netns. With no flag, no TCP socket is bound. #[arg(long, env = "XMSG_LISTEN")] pub listen: Option<String>,
/// Run as a leaf node (in-pod sidecar, loopback HTTP only, outbound-only federation client) #[arg(long, env = "XMSG_LEAF")] pub leaf: bool,
/// Principal to attribute all local requests to in leaf mode (e.g. "svc:matrix") #[arg(long, env = "XMSG_LEAF_PRINCIPAL")] pub leaf_principal: Option<String>,
/// Path to unix domain socket for HTTP server (defaults to $XDG_RUNTIME_DIR/xmsg/http.sock; on macOS without it, the Darwin user temp dir) #[arg(long, env = "XMSG_HTTP_SOCK")] pub http_sock: Option<PathBuf>,
/// Path to session metadata directory (repeatable, or separated by ':' in env XMSG_SESSIONS_DIR; defaults to ~/.claude/sessions) #[arg( long = "sessions-dir", env = "XMSG_SESSIONS_DIR", value_delimiter = ':' )] pub sessions_dirs: Vec<PathBuf>,
/// Maximum request body size in bytes #[arg( long, env = "XMSG_MAX_BODY", default_value_t = storage::DEFAULT_MAX_BODY_BYTES )] pub max_body: usize,
/// Path to SQLite database #[arg(long, env = "XMSG_DB_PATH")] pub db_path: Option<PathBuf>,
/// Reply TTL in seconds (default 7 days: 604800) #[arg(long, env = "XMSG_REPLY_TTL", default_value_t = 604800)] pub reply_ttl: u64,
/// Idempotency key retention window in seconds (default 24h: 86400) #[arg(long, env = "XMSG_IDEMPOTENCY_TTL", default_value_t = 86400)] pub idempotency_ttl: u64,
/// Per-service pending queue capacity (default: 100) #[arg( long, env = "XMSG_SVC_QUEUE_CAP", default_value_t = storage::MAX_SVC_QUEUE_PER_SESSION )] pub svc_queue_cap: usize,
/// Registration socket path (defaults to $XDG_RUNTIME_DIR/xmsg/register.sock; on macOS without it, the Darwin user temp dir) #[arg(long, env = "XMSG_REGISTER_SOCK")] pub register_sock: Option<PathBuf>,
/// Agent socket path (defaults to $XDG_RUNTIME_DIR/xmsg/agent.sock; on macOS without it, the Darwin user temp dir) #[arg(long, env = "XMSG_AGENT_SOCK")] pub agent_sock: Option<PathBuf>,
/// Leaf register socket path (defaults to $XDG_RUNTIME_DIR/xmsg/register.sock) #[arg(long, env = "XMSG_LEAF_REGISTER_SOCK")] pub leaf_register_sock: Option<PathBuf>,
/// Trusted Antigravity executable paths (comma-separated or multiple flags) #[arg(long = "agy-exe", env = "XMSG_AGY_EXE", value_delimiter = ',')] pub agy_exes: Vec<PathBuf>,
/// Trusted Codex executable paths (comma-separated or multiple flags) #[arg(long = "codex-exe", env = "XMSG_CODEX_EXE", value_delimiter = ',')] pub codex_exes: Vec<PathBuf>,
/// Trusted Codex home directories (repeatable, or separated by ':' in env XMSG_CODEX_HOME; defaults to $CODEX_HOME or ~/.codex) #[arg(long = "codex-home", env = "XMSG_CODEX_HOME", value_delimiter = ':')] pub codex_homes: Vec<PathBuf>,
/// Trusted Svc daemon executable paths per name (format: NAME=PATH, repeatable or comma-separated) #[arg(long = "svc-exe", env = "XMSG_SVC_EXE", value_delimiter = ',')] pub svc_exes: Vec<String>,
/// Path to peers configuration file (JSON) #[arg(long, env = "XMSG_PEERS_FILE")] pub peers_file: Option<PathBuf>,
/// Federation listen address (IP:PORT), e.g. <ts-ip>:7788 #[arg(long, env = "XMSG_FED_LISTEN")] pub fed_listen: Option<String>,
/// Path to federation TLS certificate (PEM) #[arg(long, env = "XMSG_FED_CERT")] pub fed_cert: Option<PathBuf>,
/// Path to federation TLS private key (PEM) #[arg(long, env = "XMSG_FED_KEY")] pub fed_key: Option<PathBuf>,
/// Dynamic federation TLS credential and CA reload interval in seconds (default: 30) #[arg(long, env = "XMSG_FED_RELOAD_INTERVAL_SECS", default_value = "30")] pub fed_reload_interval_secs: u64,}xmsg mcp
Section titled “xmsg mcp”Starts the stdio Model Context Protocol (MCP) server for agent harnesses:
#[derive(Parser, Debug)]pub struct McpArgs { /// Path to session metadata directory (repeatable, or separated by ':' in env XMSG_SESSIONS_DIR; defaults to ~/.claude/sessions) #[arg( long = "sessions-dir", env = "XMSG_SESSIONS_DIR", value_delimiter = ':' )] pub sessions_dirs: Vec<PathBuf>,
/// URL of xmsg HTTP server #[arg(long, env = "XMSG_URL", default_value = "http://127.0.0.1:7787")] pub xmsg_url: String,
/// HTTP unix socket path #[arg(long, env = "XMSG_HTTP_SOCK")] pub http_sock: Option<PathBuf>,
/// Agent socket path (defaults to $XDG_RUNTIME_DIR/xmsg/agent.sock; on macOS without it, the Darwin user temp dir) #[arg(long, env = "XMSG_AGENT_SOCK")] pub agent_sock: Option<PathBuf>,
/// Expose only the reply tool (no list, no send) #[arg(long, env = "XMSG_REPLY_ONLY")] pub reply_only: bool,}xmsg register
Section titled “xmsg register”Registers credentials or handles lifecycle hooks for Antigravity, Claude Code, and Codex:
#[derive(Parser, Debug)]pub struct RegisterArgs { #[command(subcommand)] pub harness: RegisterHarness,}
#[derive(Subcommand, Debug)]pub enum RegisterHarness { /// Register Antigravity session credentials Agy(RegisterAgyArgs),
/// Claude Code subagent hook handler (PreToolUse, SubagentStart, PostToolUse, SubagentStop) Claude(RegisterClaudeArgs),
/// Codex CLI lifecycle and subagent hook handler Codex(RegisterCodexArgs),}
#[derive(Parser, Debug)]pub struct RegisterCodexArgs { /// Hook mode for Codex hooks #[arg(long)] pub hook: bool,
/// Hook event when `--hook` is passed #[arg(long, default_value = "")] pub event: String,
/// Deliberately revoke a Codex root session ID (`RevokedByOperator`, sticky across resume) #[arg(long, conflicts_with = "unrevoke")] pub revoke: Option<String>,
/// Clear `RevokedByOperator` on a Codex root session ID back to `StaleAfterResume` #[arg(long, conflicts_with = "revoke")] pub unrevoke: Option<String>,
/// Agent socket path (defaults to $XDG_RUNTIME_DIR/xmsg/agent.sock) #[arg(long, env = "XMSG_AGENT_SOCK")] pub sock: Option<PathBuf>,
/// Optional label override to assign to the registered Codex session #[arg(long, env = "XMSG_CODEX_LABEL")] pub label: Option<String>,}
#[derive(Parser, Debug)]pub struct RegisterClaudeArgs { /// Hook mode for Claude Code hooks #[arg(long)] pub hook: bool,
/// Agent socket path (defaults to $XDG_RUNTIME_DIR/xmsg/agent.sock) #[arg(long, env = "XMSG_AGENT_SOCK")] pub sock: Option<PathBuf>,}
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]pub enum AgyHookEvent { PreInvocation, PreToolUse, Stop,}
#[derive(Parser, Debug)]pub struct RegisterAgyArgs { /// Registration socket path (defaults to $XDG_RUNTIME_DIR/xmsg/register.sock; on macOS without it, the Darwin user temp dir) #[arg(long, env = "XMSG_REGISTER_SOCK")] pub sock: Option<PathBuf>,
/// Agent socket path for subagent hook frames (defaults to the directory of `--sock` or $XDG_RUNTIME_DIR/xmsg/agent.sock) #[arg(long, env = "XMSG_AGENT_SOCK")] pub agent_sock: Option<PathBuf>,
/// Hook mode for agy hooks #[arg(long)] pub hook: bool,
/// Hook event when `--hook` is passed #[arg(long, value_enum, default_value_t = AgyHookEvent::PreInvocation)] pub event: AgyHookEvent,
/// URL of xmsg HTTP server (for checking session credentials in hook mode) #[arg(long, env = "XMSG_URL", default_value = "http://127.0.0.1:7787")] pub url: String,
/// Label to assign to the registered Antigravity session #[arg(long, env = "XMSG_AGY_LABEL", default_value = "agy")] pub label: String,}xmsg check-peers
Section titled “xmsg check-peers”Validates a peers.json file against all naming, trust-model (pin vs. ca + identities), and
non-overlap rules that xmsg serve enforces, exiting 0 on success or non-zero naming the failure:
xmsg check-peers /path/to/peers.json --fed-cert /path/to/node.crtMCP server tools (xmsg mcp)
Section titled “MCP server tools (xmsg mcp)”xmsg mcp exposes five tools over stdio JSON-RPC (or [reply, replies] when started with
--reply-only / XMSG_REPLY_ONLY=1):
list(name?, node?, kind?, status?, within?)
Section titled “list(name?, node?, kind?, status?, within?)”Lists recipients the caller may see on this node and every reachable peer:
status:"live"(default, each entry carriesregistered_atin epoch seconds) or"inactive"(showsparkedandclosedinstances seen withinwithinseconds, default86400= 24 hours, capped at the reply TTL, each carryinglast_seen).kind: Filter by"service","instance", or"subagent".name: Filter by service name or instance label.node: An exact canonical name or local nickname queries one node. A bare-host selector queries all matching nodes in the current view; see node selectors.- Returns:
{ recipients: [{ address, kind, harness?, label?, status, node, user, registered_at | last_seen }], nodes: [{ node, status, detail? }] }.
send(to, text, as_agent?)
Section titled “send(to, text, as_agent?)”Sends a message to <id or name>[@<node>] (at most one @):
to: An instance UUID (optionally~n), a delegated sub-address (<P>/<child>), a service name, or a unique live instance label, optionally followed by@node. Passingref,from, or a label containing' · 'is refused with a migration hint.as_agent: Set automatically by the harnessPreToolUsehook when called by a subagent.- Returns:
{ message_id, address, outcome }whereoutcomeis"delivered"or"queued". Use the returnedaddressfor follow-ups.
reply(message_id, text, as_agent?)
Section titled “reply(message_id, text, as_agent?)”Answers message_id’s other party symmetrically (if you received the message, answers the sender;
if you sent it, sends a follow-up to the recipient in the same exchange):
- Returns:
{ bytes, messageId, inReplyTo, outcome }, wheremessageIdis the new reply’s ULID andinReplyToismessage_id.
replies(message_id, wait?)
Section titled “replies(message_id, wait?)”Reads stored replies to message_id, optionally waiting up to wait seconds (clamped to 0..=60)
for the first reply.
inbox(wait?, as_agent?)
Section titled “inbox(wait?, as_agent?)”Drains unexpired messages queued for a bound subagent’s <P>/<child>@<node> turn mailbox while its
turn is active, waiting up to wait seconds (clamped to 0..=60) for the first message, and marks
drained messages delivered:
- Returns:
{ address, messages: [{ id, from, body, created_at }] }.
Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).