Historical note (2026-04-18): This guide documents the first dual-mode cutover and predates the current
AM_INTERFACE_MODEruntime switch. UseREADME.md,AGENTS.md, anddocs/SPEC-interface-mode-switch.mdfor the current operator/server invocation rules.
How to update your workflows for the dual-mode architecture (MCP server + CLI operator binary).
Bead: br-21gj.6.2
Agent Mail now ships two binaries:
| Binary | Name | Purpose |
|---|---|---|
mcp-agent-mail |
MCP server | Agent communication (stdio/HTTP) |
am (mcp-agent-mail-cli) |
Operator CLI | Admin commands (share, guard, doctor, etc.) |
The MCP binary denies all CLI-only commands with exit code 2 and a remediation hint. This is enforced at the binary level — there is no environment variable or flag to override it.
If you are upgrading an existing Python-era installation with mailbox data, use the dedicated legacy import runbook:
docs/RUNBOOK_LEGACY_PYTHON_TO_RUST_IMPORT.md
Quick path:
am legacy detect --json
am legacy import --auto --dry-run --yes
am legacy import --auto --yes
am legacy status --jsonOr run the orchestrated upgrade command:
am upgrade --yes# Before (if you used a combined binary):
mcp-agent-mail serve --host 0.0.0.0 --port 8765
# After (unchanged — server commands stay in the MCP binary):
mcp-agent-mail serve --host 0.0.0.0 --port 8765
# Or use the native HTTP server command:
am serve-http# Before (if you mistakenly used the server binary):
mcp-agent-mail share export --project my-proj
# After (use the CLI binary):
am share export --project my-proj
# Guard operations
am guard install my-proj
am guard check my-proj
# Doctor
am doctor check --json
# Archive
am archive create my-proj
# Schema migration
am migrateThese commands are new — they previously required MCP tool calls:
# Messaging
am mail send -p my-proj --from BlueLake --to RedFox --subject "Hello" --body "Hi"
am mail reply -p my-proj --message-id 42 --body "Got it"
am mail inbox -p my-proj -a BlueLake --json
am mail read -p my-proj --message-id 42
am mail ack -p my-proj --message-id 42 --json
am mail search -p my-proj -a BlueLake --query "keyword" --json
am mail summarize-thread -p my-proj <thread-id>
# Contacts
am contacts request -p my-proj --from BlueLake --to RedFox --reason "coordination"
am contacts respond -p my-proj -a RedFox --from BlueLake --accept
am contacts list -p my-proj -a BlueLake --json
am contacts policy -p my-proj -a BlueLake contacts_only
# File reservations
am file_reservations reserve my-proj BlueLake "src/**" --ttl 7200
am file_reservations release my-proj BlueLake --paths "src/**"
am file_reservations list my-proj --json
# Agents
am agents list --project my-proj --json
am agents register --project my-proj --name BlueLake --program claude-code --model opus-4.6
# Tooling
am tooling directory --json
am tooling schemas --jsonFind and replace the binary name:
# Before:
mcp-agent-mail share export --project "$PROJ"
mcp-agent-mail guard install "$PROJ"
mcp-agent-mail doctor check --json
# After:
am share export --project "$PROJ"
am guard install "$PROJ"
am doctor check --jsonLegacy script paths are transitional shims only; native am paths are authoritative.
Example mapping:
# Native authoritative path:
am check-inbox --agent "$AGENT_NAME" --rate-limit 120
# Legacy compatibility shim (deprecated fallback only):
legacy/hooks/check_inbox.sh --agent "$AGENT_NAME" --rate-limit 120Deprecation flow:
- Announce with before/after mapping and runtime warning.
- Shift all CI/runbook examples to native
am. - Keep shim for a bounded fallback window, then remove after migration audit.
Rollback trigger (shim-first guidance temporarily reinstated) is allowed only for
confirmed native-path regressions with reproducible artifacts (stdout/stderr/exit/timing).
Update your CI to build and test both binaries:
# GitHub Actions example
- name: Build
run: |
cargo build -p mcp-agent-mail # MCP server
cargo build -p mcp-agent-mail-cli # Operator CLI
- name: Test dual-mode
run: am ciNo changes needed. MCP clients should already point to mcp-agent-mail
(stdio or HTTP). This binary continues to accept serve, config, and
no-arg (stdio default).
{
"command": "mcp-agent-mail",
"args": []
}No new environment variables are required. The mode is determined by which
binary you execute — there is no INTERFACE_MODE or MCP_MODE variable.
All existing environment variables (DATABASE_URL, STORAGE_ROOT,
HTTP_HOST, HTTP_PORT, HTTP_PATH, etc.) work the same in both binaries.
$ mcp-agent-mail share export
Error: "share" is not an MCP server command.
Agent Mail MCP server accepts: serve, config
For operator CLI commands, use: mcp-agent-mail-cli share
Fix: Use am share export instead.
If your CI script runs mcp-agent-mail with a CLI command and gets exit
code 2, the command was denied by the dual-mode gate.
Fix: Replace mcp-agent-mail <command> with am <command> in your CI
scripts.
The am binary is built from the mcp-agent-mail-cli crate:
cargo build -p mcp-agent-mail-cli
# Binary: target/debug/amAdd the target directory to your PATH, or use the full path:
./target/debug/am doctor check --jsonIf an MCP agent's sessions show exit code 2 errors, the agent is mistakenly invoking CLI commands through the MCP binary. MCP agents should only use MCP tool calls (via JSON-RPC), never subcommands.
Fix: Check the agent's configuration — it should use MCP tool calls, not shell command invocations.
# MCP binary should deny CLI commands:
mcp-agent-mail share 2>&1
# Expected: stderr error + exit 2
# CLI binary should accept all commands:
am share --help
# Expected: help text + exit 0
# MCP binary should accept server commands:
mcp-agent-mail serve --help
# Expected: help text + exit 0
mcp-agent-mail config
# Expected: config dump + exit 0# Full CI suite (all gates):
am ci
# Quick (skip E2E):
am ci --quick
# Individual suites:
cargo test -p mcp-agent-mail-cli --test mode_matrix_harness
cargo test -p mcp-agent-mail-cli --test semantic_conformance
cargo test -p mcp-agent-mail-cli --test perf_security_regressions
am e2e run --project . dual_modeTest artifacts are saved under tests/artifacts/:
tests/artifacts/
├── cli/
│ ├── mode_matrix/ # Routing decision logs
│ ├── semantic_conformance/ # Parity drift reports
│ ├── perf_security/ # Latency + security reports
│ └── help/ # Snapshot diff artifacts
└── dual_mode/ # E2E step logs + failure bundles
├── steps/ # Per-step JSON logs
├── failures/ # Failure bundles with reproduction
└── summary.json # Aggregate summary
Q: Can I use an environment variable to make the MCP binary accept CLI commands? A: No. The mode boundary is enforced at the binary level (compile-time separation). This is intentional — see ADR-001.
Q: Do both binaries share the same database?
A: Yes. Both binaries read/write the same SQLite database specified by
DATABASE_URL. They share the same storage layer.
Q: What commands does the MCP binary accept?
A: serve (HTTP mode), config (show configuration), and no subcommand
(stdio MCP mode). Everything else is denied.
Q: How do I start the HTTP server?
A: Use am serve-http to start the HTTP server with TUI. For CLI operator
commands, use the am subcommands directly (e.g., am share, am doctor).