Skip to content

CLI

The discord-mcp binary is the single entry point with ten sub-commands routed by commander:

Terminal window
discord-mcp <command> [options]

serve is the default sub-command - running discord-mcp with no command boots the local stdio MCP server, equivalent to discord-mcp serve. Pass --http to serve a bearer-protected Streamable HTTP endpoint instead. The other nine (catalog, setup, activity, update, profile, doctor, smoke, init, migrate) are discovery or operator tools.

Source: packages/mcp-server/src/cli.ts.

Flag Description
--version Print the package version and exit.
--help Print the help summary for the command.

--help works at every level - discord-mcp --help, discord-mcp doctor --help, etc. Each sub-command has its own option list described below.

Terminal window
discord-mcp serve [--gateway]
discord-mcp serve --http [--host <host>] [--port <port>]
discord-mcp serve --profile <name>
discord-mcp [--gateway] # default sub-command, stdio

Start the local stdio MCP transport by default. With --http, start a stateless Streamable HTTP endpoint at /mcp; it requires DISCORD_MCP_ACCESS_TOKEN and is designed to sit behind an HTTPS reverse proxy. The endpoint negotiates MCP 2026-07-28 and retains stateless 2025-era compatibility. Both forms read configuration from process.env and wire the audit sink. The HTTP listener also provides an authenticated GET /healthz readiness probe; it reports startup readiness only (and is not a Discord liveness check).

Flag Type Default Description
--gateway boolean false Enable Discord Gateway resource subscriptions for stdio only. Lazy-imports discord.js so cold-start without this flag stays minimal.
--http boolean false Serve Streamable HTTP MCP at /mcp. Requires DISCORD_MCP_ACCESS_TOKEN; cannot be combined with --gateway.
--host <host> string 127.0.0.1 HTTP listen host. Applies with --http; leave on loopback behind a TLS reverse proxy.
--port <port> number 3000 HTTP listen port. Applies with --http.
--profile <name> string unset Load non-secret bot/guild/tool settings from a caller-owned profile before startup. DISCORD_TOKEN must still exist in the launch environment.
Terminal window
# Bare invocation - equivalent to `discord-mcp serve` (commander default).
discord-mcp
# Explicit serve.
discord-mcp serve
# With Gateway subscriptions enabled.
discord-mcp serve --gateway
# Start the bot and safety boundary saved by guided setup.
discord-mcp serve --profile devbot
# Bare form with the same flag (forwarded to serve through commander's
# default-subcommand passthrough).
discord-mcp --gateway
# Remote MCP endpoint. Publish /mcp through an HTTPS reverse proxy.
DISCORD_MCP_ACCESS_TOKEN="replace-with-a-long-random-secret" \
discord-mcp serve --http --host 127.0.0.1 --port 3000
Code Meaning
0 Process never returns under normal stdio or HTTP operation. The transport runs until it is stopped.
1 Boot failure - typically invalid config (missing DISCORD_TOKEN, unparseable env). Error message goes to stderr.
Terminal window
discord-mcp catalog
discord-mcp catalog --check [--json]

Start a credential-free stdio MCP server for schema discovery. It exposes the same 209 tool contracts and six static resources as an unrestricted full server, but every tools/call—including an unknown name—fails closed with CATALOG_ONLY before validation or dispatch.

This command does not load profiles or runtime configuration, does not require DISCORD_TOKEN, and does not contact Discord or another network service. Use it for MCP registry scanners, security inspection, or contract generation. It cannot read or change a Discord server; use serve with a caller-owned bot for operational work.

The root Dockerfile defaults to catalog for safe automated introspection. If you build that image from source for operational use, explicitly override the command with serve and provide the caller-owned token and safety settings.

--check runs a bounded in-process MCP handshake and exits. It requires the packaged catalog to expose exactly 209 tools and six static resources, reads the six Components V2 JSON resources, and proves both a listed and unknown tool call fail with CATALOG_ONLY. It does not read DISCORD_TOKEN, contact Discord or another network service, write a profile or activity journal, prove AI-host connectivity, or create Activity Evidence. Add --json for the stable discord-mcp.catalog-check.v1 result; --json without --check fails closed.

Flag Type Default Description
--check boolean false Validate the installed credential-free catalog contract and exit.
--json boolean false Emit a machine-readable result for --check; invalid without it.
Code Meaning
0 The catalog transport shut down normally, or the bounded check passed.
1 Long-running catalog startup or shutdown failed; the message is written to stderr.
2 The bounded catalog contract failed or --json was used without --check.
Terminal window
discord-mcp doctor [--profile <name>] [--json] [--online]
discord-mcp doctor --access [--guild-id <id>] [--channel-id <id>] [--profile <name>] [--json]
discord-mcp doctor --profile <name> --client codex [--config <path>] [--json]
discord-mcp doctor --profile <name> --client antigravity-cli [--config <path>] [--json]
discord-mcp doctor --profile <name> --client cursor-cli [--config <path>] [--json]
discord-mcp doctor --profile <name> --client grok-cli [--config <path>] [--json]

Diagnose configuration, token, and connectivity issues. Runs a registry of checks (token format, env-var parse, audit sink, OTel config, etc.) and aggregates results into a single structured report. With --online --profile for a generated Codex profile, it also checks the pinned launcher version against npm latest.

Flag Type Default Description
--json boolean false Emit machine-readable JSON instead of pretty TTY output. Use this in CI.
--online boolean false Run online checks against Discord (requires a working DISCORD_TOKEN). With a generated Codex profile, also performs one read-only npm version check. Without this flag only offline checks run, so doctor stays cheap.
--access boolean false Run the read-only bot identity, application, guild membership, permission, and privileged-intent preflight. Implies --online; it is advisory and does not mutate Discord or block tool calls yet.
--guild-id <id> string profile/allowlist when unique Select the guild to evaluate with --access; it must be inside the configured allowlist when one exists.
--channel-id <id> string unset Optionally fetch one channel and evaluate its permission overwrites. Requires --access.
--profile <name> string unset Activate a saved profile before checks, including its expected bot identity and guild allowlist. A generated Codex profile enables launcher update discovery with --online.
--client <id> string unset Audit a saved MCP client configuration locally. Supports codex, antigravity-cli, cursor-cli, grok-cli, and gemini-cli; each requires --profile and does not call npm or Discord. Antigravity, Cursor Agent, and Grok Build reject persisted MCP credentials; Gemini rejects a materialized token.
--config <path> string selected client’s user config Override the Codex or Grok Build config.toml, Antigravity mcp_config.json, Cursor Agent MCP JSON, or Gemini settings.json path; useful for a nonstandard client home or CI fixture.
Terminal window
# Quick offline health check (no network calls).
discord-mcp doctor
# Same but JSON for CI.
discord-mcp doctor --json
# Verify the token is accepted by Discord.
DISCORD_TOKEN=Bot.xxx discord-mcp doctor --online
# Full online + JSON report - typical pre-deploy gate.
discord-mcp doctor --online --json | jq .
# Verify a saved caller-owned bot profile, its identity lock, and (for a
# generated Codex launcher) whether an explicit update is available.
discord-mcp doctor --profile devbot --online
# Explain the bot's verified guild access before a planned write.
discord-mcp doctor --profile devbot --access --guild-id <guild-id> --json
# Inspect the saved Codex launcher without a network call. This reports only
# normalized state: launcher/version, enabled status, startup timeout,
# dry-run, write-mode, and OTel enabled state.
discord-mcp doctor --profile devbot --client codex --json
Code Meaning
0 All checks ok.
1 At least one check returned warn, no fails (including an available launcher update).
2 At least one check returned fail (e.g. invalid token, missing env).

The exit-code policy is uniform across setup, profile, doctor, smoke, init, migrate, and update: 0 = success, 1 = success-with-work-left, 2 = couldn’t run.

The update-status check never reads or sends DISCORD_TOKEN, bot identity, guild IDs, or profile contents to npm. It never modifies config.toml; use update --apply only after reviewing the reported version. Custom or ambiguous launchers are marked caller-managed and left alone.

--access is a read-only capability report. It verifies /users/@me, the current application, guild membership, roles, and (when supplied) channel overwrites. The JSON result keeps ready, missing_permissions, missing_intents, bearer_required, opaque_required, consent_required, delegated_required, conditional, and unknown distinct; unknown evidence is never treated as permission to write. The current report covers all 208 tools and is intentionally advisory; users_create_dm is user-scoped and still requires its separate explicit consent policy before contacting a recipient.

--client codex does not change configuration or Discord state, but it is deliberately local: it reads the saved generated launcher and never returns the config text, token, other env values, OTel endpoint, or absolute config path. Like other doctor runs, it appends only predefined outcome signals to the privacy-safe local activity journal. It describes the configuration that Codex will use on its next start; restart Codex before treating a launcher change as active runtime state. Custom or ambiguous launchers remain caller-managed and produce a warning instead of being guessed or modified.

Terminal window
discord-mcp smoke [--profile <name>] [--json]
discord-mcp smoke --confirm-write [--profile <name>] [--guild-id <id>] [--json]
discord-mcp smoke --confirm-write --confirm-template-lifecycle [--profile <name>] [--guild-id <id>] [--json]

Verify the real MCP-to-Discord path without loading all 209 tool schemas into an AI model. The default run is read-only: it starts an in-memory MCP client/server pair, validates the bot identity, and lists the guilds visible to the bot.

--confirm-write opts into one bounded lifecycle through the same MCP schemas, middleware, audit, and Discord REST path: create a uniquely named temporary text channel, send and edit one marker message, then delete the message and channel. The command enables destructive execution only inside its own process and still passes __confirm:true to both delete calls. It does not change the environment or configuration used by the long-running server.

--confirm-template-lifecycle extends the write smoke with a temporary native Guild Template: create, inspect, introduce one temporary channel drift, detect that drift, sync the template, prove the drift is clean, then delete the template. It is deliberately rejected without --confirm-write; it needs MANAGE_GUILD in addition to the normal write-smoke permissions.

Flag Type Default Description
--confirm-write boolean false Execute the self-cleaning create/send/edit/delete lifecycle. Without it, the command is read-only.
--confirm-template-lifecycle boolean false Extend a confirmed write smoke with a self-cleaning Guild Template create/inspect/diff/sync/delete lifecycle. Requires --confirm-write.
--guild-id <id> string sole guild Select the write target. Required when the bot can see multiple guilds; the ID must already be visible to it.
--json boolean false Emit the shared machine-readable result envelope, including step and cleanup state.
--profile <name> string unset Activate a saved profile before opening the in-memory MCP session.

The write smoke needs View Channels, Manage Channels, and Send Messages in the test guild. It also needs the users, channels, and messages categories when MCP_CATEGORIES is restricted. If a step fails after channel creation, cleanup is attempted before the command exits; a cleanup failure is reported explicitly.

Terminal window
# Cheap, read-only end-to-end check.
discord-mcp smoke
# Read-only verification through a saved bot/guild boundary.
discord-mcp smoke --profile devbot
# The bot is installed in exactly one private test guild.
discord-mcp smoke --confirm-write
# The bot can see multiple guilds - make the target explicit.
discord-mcp smoke --confirm-write --guild-id 111122223333444455
# Prove the native Guild Template lifecycle on a disposable test guild.
discord-mcp smoke --confirm-write --confirm-template-lifecycle --guild-id 111122223333444455
# CI or agent-readable evidence.
discord-mcp smoke --confirm-write --guild-id 111122223333444455 --json
Code Meaning
0 The read-only check passed, or every write step passed and all temporary artifacts were removed.
2 Startup, targeting, a tool call, or cleanup failed. No write begins when the target is ambiguous.
Terminal window
discord-mcp setup --profile <name> [--client <id>]
[--tool-surface <full|progressive>]
[--allowed-guilds <id,id,...>] [--gateway]
[--categories <name,name,...>] [--write-mode <allow|preview>]
[--output <path>] [--force] [--json]

Recommended onboarding for one caller-owned bot. setup has no token flag: it resolves DISCORD_TOKEN from the caller’s environment, verifies the bot through Discord, selects or validates its guild allowlist, saves non-secret profile metadata, and generates a client snippet that starts serve --profile <name>. Use --categories messages,guild to persist a narrow tool-category allowlist; category spelling is checked when the server boots. Guided setup defaults to --write-mode preview, while --write-mode allow opts into ordinary mutation execution explicitly.

Flag Type Default Description
--profile <name> string prompt if TTY; required otherwise Stable lowercase profile name. Allows letters, digits, dots, underscores, and hyphens.
--client <id> string prompt if TTY, else generic One of claude-desktop, claude-code, codex, antigravity-cli, cursor-cli, gemini-cli (enterprise/API-key compatibility), cursor, deepseek-harness, generic.
--tool-surface <mode> string progressive Store full or progressive as the runtime presentation layer.
--allowed-guilds <ids> string sole guild or interactive choice Verify and store an explicit comma-separated guild allowlist. Ambiguous non-interactive selection fails closed.
--categories <names> string all categories Store comma-separated lowercase category names such as messages,guild; the running server validates names against its installed tool registry.
--write-mode <mode> string preview Store preview (guided setup default) or allow for ordinary mutations. Destructive tools retain their separate dry-run and confirmation gates.
--gateway boolean false Store Gateway subscriptions for stdio startup.
--output <path> string stdout Write the generated client snippet. Existing files are protected.
--force boolean false Update metadata for the same verified bot and overwrite --output. It cannot reassign the profile to another bot ID.
--json boolean false Emit the shared structured result, including profile name/path and snippet content.
Terminal window
export DISCORD_TOKEN="Bot YOUR_DISCORD_BOT_TOKEN"
discord-mcp setup --profile devbot --client codex
discord-mcp doctor --profile devbot --online
discord-mcp smoke --profile devbot

Profiles store only versioned metadata and the provider reference env:DISCORD_TOKEN; neither setup output nor the profile contains the token value. Selected guilds with Administrator still produce exit code 1 and a least-privilege warning.

Terminal window
discord-mcp activity [--json] [--report]

Show the local evidence journal created by setup, doctor, smoke, and blueprint plan/apply/evidence calls through stdio or authenticated HTTP. It is an operator-facing record, not telemetry: discord-mcp never sends it anywhere. Each record contains only a timestamp plus predefined command or blueprint-stage, status, outcome, transport, and signal values. It never contains bot tokens, Discord identities, guild IDs, configuration paths, command arguments, result payloads, or raw errors.

The journal is capped at 200 recent records and lives beside the profile folder: %APPDATA%\discord-mcp\activity.jsonl on Windows, ~/Library/Application Support/discord-mcp/activity.jsonl on macOS, and ${XDG_CONFIG_HOME:-~/.config}/discord-mcp/activity.jsonl on Linux. Delete that local file to erase the journal; it does not change profiles or Discord. Set DISCORD_MCP_ACTIVITY=off for a per-run opt-out; this affects only the local journal and never the MCP server’s Discord behavior.

Terminal window
# Human-readable recent outcomes.
discord-mcp activity
# Use the stable summary in automation or when sharing a redacted diagnosis.
discord-mcp activity --json
Terminal window
discord-mcp activity --report
discord-mcp activity --report --json

--report prints the fixed URL for the voluntary verified outcome form without reading or exporting the journal. It is deliberately URL-only: the command never opens a browser, accesses the network, pre-fills fields, creates an issue, or submits a report. --report --json exposes the URL with submitted: false and network_accessed: false for scripts.

The operator must review and complete the form manually. The journal cannot establish the furthest verified outcome, AI host, transport, community job, time-to-outcome, repeat use, or showcase consent, so the CLI never guesses those fields. Local records contain only timestamps plus predefined command or blueprint-stage, status, outcome, transport, and signal values. They exclude tokens, Discord IDs, names, message content, configuration paths, and raw errors. A public report is optional and is not product telemetry.

Use this before changing the onboarding flow: it distinguishes completed setup, token/discovery failures, the safe administrator-warning least-privilege signal, doctor check results, and whether the first smoke path reached identity/guild reads and cleaned up its own temporary write artifacts.

Terminal window
discord-mcp update --profile <name> [--check]
discord-mcp update --profile <name> --apply

Check the npm latest release against the exact version pinned in one generated Codex launcher. A normal check does not modify configuration. --apply updates only the matching npx ... @discord-mcp/cli@<version> serve --profile <name> array and writes the result atomically.

The command loads only the non-secret local profile to confirm that it belongs to Codex. It never resolves or sends DISCORD_TOKEN, bot identity, guild IDs, or profile contents to npm. Custom wrappers, multiline TOML, and multiple matching launchers fail closed and are left untouched.

Flag Type Default Description
--profile <name> string required Saved caller-owned profile generated for Codex.
--check boolean true Explicit spelling of the default read-only check. Cannot be combined with --apply.
--apply boolean false Explicitly update exactly one generated Codex launcher after a newer release is found.
--config <path> string $CODEX_HOME/config.toml or ~/.codex/config.toml Override the user-level Codex config location.
--json boolean false Emit the structured version/status result.
Terminal window
# Check only. Exit 1 means an update is available, not that the bot failed.
discord-mcp update --profile devbot
# Apply the already-reviewed choice, then restart Codex.
discord-mcp update --profile devbot --apply
# First use from a launcher pinned before v0.14.7.
npx --yes @discord-mcp/cli@0.16.1 update --profile devbot --apply

update never uses @latest in the generated launcher and never installs a version silently. A successful JSON --apply result includes data.restart_required: true: the launcher is pinned, but the running Codex stdio process cannot load that version until Codex restarts. The explicit apply step preserves caller control over bot behavior and Discord permissions.

When an older generated launcher lacks tool_timeout_sec, --apply also adds the recommended tool_timeout_sec = 180 and reports it in data.settings_migrated. An existing caller-defined value is preserved; custom or ambiguous launchers still fail closed without modification.

Terminal window
discord-mcp profile list [--json]
discord-mcp profile show <name> [--json]
discord-mcp profile remove <name> [--yes] [--json]

Manage local non-secret profile metadata. Profiles live under %APPDATA%\discord-mcp\profiles on Windows, ~/Library/Application Support/discord-mcp/profiles on macOS, and ${XDG_CONFIG_HOME:-~/.config}/discord-mcp/profiles on Linux.

  • list reports the bot identity, guild count, client, and tool surface.
  • show prints the complete versioned metadata and provider reference, never the resolved token.
  • remove deletes exactly one local profile. Non-interactive use requires --yes; deleting metadata does not revoke or reset the Discord token.

Rerun setup --profile <name> --force to update a profile for the same bot. To assign the name to a genuinely different bot, first remove the old profile and then run setup again with that bot’s caller-owned token.

Terminal window
discord-mcp init [--client <id>] [--token <token>] [--gateway]
[--tool-surface <full|progressive>]
[--allowed-guilds <id,id,...>]
[--categories <name,name,...>] [--write-mode <allow|preview>]
[--discover-guilds]
[--output <path>] [--force] [--json]

Generate a stateless MCP client config snippet for a supported client. Either prints to stdout or writes to --output <path>. Prefer setup when you want a reusable bot profile and safer credential lifecycle. deepseek-harness emits a Cordis YAML patch for the official MCP client plugin, including an explicit !!js process.env.DISCORD_TOKEN reference when no token is supplied. See DeepSeek Harness client setup for loading the patch.

Flag Type Default Description
--client <id> string prompt if TTY, else generic Target client id. One of claude-desktop, claude-code, codex, antigravity-cli, cursor-cli, gemini-cli (enterprise/API-key compatibility), cursor, deepseek-harness, generic.
--token <token> string ${env:DISCORD_TOKEN} placeholder Legacy explicit Discord bot token. Warning: clients without inherited secret forwarding write it unredacted. Antigravity CLI and Cursor Agent CLI deliberately omit it and inherit the launch environment instead. Prefer setup, which has no token flag.
--gateway boolean false Append --gateway to the generated snippet so the server enables Gateway subscriptions on boot.
--tool-surface <mode> string full Set progressive to add MCP_TOOL_SURFACE=progressive to the generated client snippet; useful for hosts without native deferred MCP loading.
--allowed-guilds <ids> string unset Add a normalized ALLOWED_GUILDS server-side guild allowlist to the generated client snippet. Values must be comma-separated Discord snowflake IDs.
--discover-guilds boolean false Use the current DISCORD_TOKEN to verify the bot and enumerate its real guilds. A sole guild is selected automatically; TTY runs require an explicit choice and default to cancel; ambiguous non-interactive runs must pass --allowed-guilds. Selected guilds with Administrator produce a warning.
--categories <names> string all categories Add a lowercase comma-separated MCP_CATEGORIES allowlist; installed category names are validated at server boot.
--write-mode <mode> string allow Set MCP_WRITE_MODE=preview to make ordinary mutations return previews; allow preserves legacy execution behavior.
--output <path> string (stdout) Write the snippet to a file instead of stdout.
--force boolean false Overwrite the --output path if it already exists. Required to overwrite.
--json boolean false Emit machine-readable JSON instead of pretty output. The snippet body is delivered under data.content.
Terminal window
# Interactive - prompts for client + gateway when stdin is a TTY.
# Omitted tokens always remain an environment reference.
discord-mcp init
# Non-interactive: print a Claude Desktop snippet with the token placeholder.
discord-mcp init --client claude-desktop
# Non-interactive: print a Codex TOML fragment that forwards DISCORD_TOKEN.
discord-mcp init --client codex
# Non-interactive: print Antigravity MCP config with inherited credentials.
discord-mcp init --client antigravity-cli
# Non-interactive: print secret-free Cursor Agent MCP config.
discord-mcp init --client cursor-cli
# Compatibility: print Gemini settings with an explicit, non-secret env reference.
discord-mcp init --client gemini-cli
# Recommended: verify the bot and derive a progressive, guild-scoped Codex fragment.
export DISCORD_TOKEN="Bot YOUR_DISCORD_BOT_TOKEN"
discord-mcp init --client codex --tool-surface progressive --discover-guilds
# Non-interactive bot with multiple guilds: make the choice explicit and verify it.
discord-mcp init --client codex --tool-surface progressive --discover-guilds \
--allowed-guilds 111122223333444455
# Bake a real token into the snippet (NOT recommended for committed files).
discord-mcp init --client cursor --token "Bot.xxx.yyy.zzz"
# Write directly to Claude Desktop's config (force overwrite).
discord-mcp init --client claude-desktop \
--output ~/Library/Application\ Support/Claude/claude_desktop_config.json \
--force
# JSON for tooling.
discord-mcp init --client generic --json | jq .data.content
Code Meaning
0 Snippet generated (and written, if --output was used).
1 Snippet generated, but a selected guild grants the bot Administrator.
2 Couldn’t run - invalid options, failed Discord verification, ambiguous guild selection, or protected output path.
Terminal window
discord-mcp migrate --list [--json]
discord-mcp migrate --from <adapter> [--source <path>] [--json]

Migrate from another Discord MCP setup. Four adapters ship today:

Adapter id Source Upstream tool estimate
pasympa PaSympa Discord MCP (TypeScript) ~91
quadslab quadslab Discord MCP (@quadslab.io, TypeScript) ~139
discord-ops discord-ops by bookedsolidtech (TypeScript) ~49
hubdustry-go-mcp Hubdustry-shaped Go MCP - non-Discord reference fixture unavailable

The original Hubdustry upstream is no longer publicly reachable, so --list deliberately omits its homepage and tool-count estimate instead of presenting unverifiable metadata. The bundled synthetic fixture contains 5 tools; running the adapter against it produces 0 mapped, 5 unmapped, and 0 manual review by design. Use one of the other three adapters for an actual migration.

Flag Type Default Description
--list boolean false List every registered adapter with its description, languages, tool-count estimate, and homepage. Informational - always exits 0. Takes precedence over --from.
--from <id> string (none) Source adapter id - one of the four above. Required to run a migration.
--source <path> string cwd Path to the source repo to migrate from. Defaults to the current working directory.
--json boolean false Emit machine-readable JSON instead of pretty output.
Terminal window
# Discover adapters - exits 0, safe in CI.
discord-mcp migrate --list
# Same, machine-readable.
discord-mcp migrate --list --json | jq '.data.adapters[].id'
# Migrate from a PaSympa checkout in the current directory.
discord-mcp migrate --from pasympa
# Migrate from a sibling directory.
discord-mcp migrate --from quadslab --source ../quadslab-discord-mcp
# JSON output for tooling - capture the full migration report.
discord-mcp migrate --from discord-ops --json > migrate-report.json
Code Meaning
0 --list (always), OR an adapter ran AND every source tool was mapped cleanly (no unmapped, no manual review).
1 Adapter ran but produced unmapped tools or items needing manual review. Successful run with work left - hand-edit the output.
2 Couldn’t run - bare migrate with no flags, unknown --from, source not detected at --source, IO failure.

setup, profile, doctor, smoke, init, and migrate share the same emitResult(...) envelope. In pretty mode you see a TTY-friendly summary. In --json mode you get a single line of structured JSON with this shape:

{
"ok": true,
"exitCode": 0,
"summary": "<one-line summary>",
"details": ["<line>", "..."],
"warnings": [],
"errors": [],
"data": { /* command-specific payload */ }
}

This is the contract pipelines should consume. data is per-command (setup’s data.profile, profile’s metadata collection, doctor’s data.checks, smoke’s data.steps, init’s data.content, migrate’s data.result) and stable across versions within a major.