@ajmarquez99/discord-cli
v0.3.1
Published
Discord CLI for coding agents — post, read, react, reply, and manage threads via the Discord REST API, with a fail-closed channel allowlist. Ships an MCP server too.
Maintainers
Readme
discord-cli
A Discord CLI for coding agents — the gh/gmail/gsc sibling. Post messages,
read channel history, react, reply, and create threads via the Discord REST API. All channel
content access (reads, reactions, writes) is gated by a fail-closed allowlist.
Install
Global (recommended)
npm install -g @ajmarquez99/discord-cli
discord --versionThis installs both the discord CLI and the discord-mcp MCP server.
From source
git clone https://github.com/AJMarquez99/discord-cli.git
cd discord-cli && npm install && npm install -g .Discord setup
- Create an application + bot at https://discord.com/developers/applications.
- Enable the Message Content privileged intent (Bot → Privileged Gateway Intents) — required to read message content via
discord read. Without it, the Discord API returns emptycontentfor messages the bot didn't author and isn't mentioned in. - Invite the bot (OAuth2 → URL Generator, scope
bot) with these permissions: View Channel, Read Message History, Send Messages, Send Messages in Threads, Create Public Threads, Add Reactions. - Enable Developer Mode in Discord (Settings → Advanced) to copy channel/server IDs (right-click → Copy ID).
Credentials
The quickest path is discord login, which prompts for the bot token (hidden input) and writes
~/.config/discord-cli/credentials.json at chmod 600:
discord login # paste the token at the hidden prompt
discord login --force # overwrite an existing credentials fileOr do it by hand — set DISCORD_BOT_TOKEN, or create ~/.config/discord-cli/credentials.json:
{ "botToken": "your-bot-token" }Override the config path with DISCORD_CLI_CONFIG. Token precedence: env var first, then file.
Run discord init first to scaffold the config files and print the full setup steps.
Allowlist
~/.config/discord-cli/allowlist.json (override with DISCORD_ALLOWLIST):
{
"channels": ["123456789012345678"],
"servers": ["987654321098765432"]
}channels: array of channel-ID strings. All content access (post,read,react,reply,thread create) requires an allowlisted channel. A thread is gated by its parent channel.servers: array of guild-ID strings. Used by open mode (see below) to permit writes to any visible channel in an allowlisted server.- No allowlist file → nothing is accessible (fail-closed). Missing file returns
{ channels: [], servers: [] }. --channelaccepts a channel ID only — aliases have been removed. Usediscord channelsto look up name→ID mappings.- Backward compatible: the loader still accepts the v0.1.0 alias-object form (
{ alias, channelId, serverId }) — it extractschannelIdand ignoresalias. A bare top-level array is treated aschannelswithservers: [].
Config
~/.config/discord-cli/config.json (override with DISCORD_CLI_SETTINGS):
{ "mode": "restricted", "auditLog": { "enabled": true, "logBody": false } }All fields are optional. Defaults: mode: "restricted", auditLog.enabled: true, auditLog.logBody: false.
Mode (restricted vs open)
restricted (default, fail-closed): content access is limited to channels explicitly listed in allowlist.channels. Any other channel ID is blocked with exit 3.
open: content access extends to any channel visible to the bot, as long as the channel belongs to a server in allowlist.servers. Channels already in allowlist.channels are always permitted without an extra API lookup. Channels not in allowlist.channels incur one GET /channels/{id} call to verify the server membership. If servers is empty, open mode grants nothing beyond already-allowlisted channels.
Mode precedence (highest to lowest):
--unrestrictedflag (per-command)DISCORD_MODE=openenvironment variablemodeinconfig.json- Default:
restricted
Audit log
Every successful post, react, and thread create appends a JSONL line to ~/.config/discord-cli/audit.jsonl (override with DISCORD_AUDIT_LOG):
{ "ts": "2026-…Z", "action": "post|react|thread", "channelId": "…", "messageId": "…",
"mode": "restricted|open", "allowlisted": true }- The message body is excluded unless
auditLog.logBody: trueinconfig.jsonor--log-bodyis passed. - Auditing is controlled by
auditLog.enabled(defaulttrue); pass--no-auditto skip a single action. - A failed audit write warns to stderr but never fails the operation.
- Dry-runs are not logged.
- View recent entries with
discord audit [--limit N].
Commands
All commands print JSON by default; add --format table for a human summary.
Global flags: --format json|table (default json), --profile <name> (reserved; single identity in v1).
discord post
discord post --channel <id> --message "..."Post a message to an allowlisted channel. Options:
--channel <id>— target channel ID--message <text>— message content (or pipe on stdin)--reply-to <messageId>— reply to this message (pings the author)--thread <threadId>— post into an existing thread (gated by its parent channel); takes precedence over--channel--unrestricted— open mode: allow any visible channel in an allowlisted server--dry-run— preview without sending or logging (exit 0 even if it would be blocked)--no-audit— skip writing this action to the audit log--log-body— include the message body in the audit entry--allow-everyone— permit@everyone/@herepings (suppressed by default)--allow-roles— permit role pings (suppressed by default)
Mention safety (default-on): @everyone, @here, and role pings are stripped by default. Only user @mentions resolve. Pass --allow-everyone or --allow-roles to opt back in. Replying with --reply-to always pings the replied-to author.
The message body can be piped on stdin when --message is omitted.
Examples:
discord post --channel 123456789012345678 --message "Deploy complete."
echo "Nightly report ready." | discord post --channel 123456789012345678
discord post --channel 123456789012345678 --message "test" --dry-rundiscord read
discord read --channel <id> [--limit N] [--before <messageId>] [--after <messageId>]
discord read --thread <threadId> [--limit N] [--before <messageId>] [--after <messageId>]Fetch recent messages from an allowlisted channel or thread (blocked in restricted mode if channel is not allowlisted). Options:
--channel <id>— channel ID (must be allowlisted in restricted mode)--thread <threadId>— read from a thread; gated by its parent channel (symmetric withpost --thread)--limit <n>— max messages to return (1–100, default 25)--before <messageId>— only messages before this ID--after <messageId>— only messages after this ID--unrestricted— open mode: any visible channel in an allowlisted server
Either --channel or --thread is required.
discord react
discord react --channel <id> --message <messageId> --emoji 👍
discord react --thread <threadId> --message <messageId> --emoji 👍Add a reaction to a message (allowlist-gated). Options:
--channel <id>— channel ID (must be allowlisted in restricted mode)--thread <threadId>— react to a message in a thread; gated by its parent channel (symmetric withpost --thread)--message <messageId>— target message ID--emoji <emoji>— unicode emoji, or custom emoji asname:id--unrestricted— open mode: any visible channel in an allowlisted server--dry-run— preview without reacting or logging--no-audit— skip writing this action to the audit log
Either --channel or --thread is required.
discord thread create
discord thread create --channel <id> --from <messageId> --name "..."Start a thread from an existing message (allowlist-gated by parent channel). Options:
--channel <id>— parent channel ID--from <messageId>— message to start the thread from--name <name>— thread name--auto-archive <minutes>— auto-archive duration:60|1440|4320|10080--unrestricted— open mode: any visible channel in an allowlisted server--dry-run— preview without creating or logging--no-audit— skip writing this action to the audit log
discord channels
discord channels [--server <guildId>] [--type <type>]List a server's channels (name→ID, type, category). Marks allowlisted channels with *. Metadata only — never fetches message content. Ungated; visibility follows the bot's Discord permissions.
--server <guildId>— guild/server ID. Defaults to the single entry inallowlist.servers; required ifserverscontains 0 or 2+ entries.--type <type>— filter by channel type:text|voice|category|announcement|stage|forum
Example:
discord channels --server 987654321098765432
discord channels --server 987654321098765432 --type textdiscord allow list
discord allow listShow allowlisted channel IDs and server IDs. Edit ~/.config/discord-cli/allowlist.json by hand to add or remove entries.
discord audit
discord audit [--limit N]Show recent audited actions, newest first.
--limit <n>— max entries to show (default 20)
discord doctor
discord doctorVerify the bot token; report active mode, bot identity (username, ID, credential source), allowlist channel count, and server count.
discord init
discord initScaffold ~/.config/discord-cli/ with starter allowlist.json (empty — fail-closed) and config.json, report whether credentials are present, and print the remaining setup steps. Non-clobbering: existing files are reported as exists and left untouched. Never writes secrets — use discord login for the token.
discord login
discord login [--force]Set up the bot token: prompts for it with hidden input and writes credentials.json at chmod 600. The token flows prompt → file only — it is never echoed, returned, logged, or accepted as a flag. Refuses to overwrite an existing credentials file unless --force is passed.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (including dry-run, even when it reports blocked: true) |
| 1 | Discord API error, network failure, or unexpected exception |
| 2 | User-fixable config: missing token, malformed config file, bad flags |
| 3 | Blocked by allowlist or mode (non-allowlisted channel in restricted mode, or channel not in an allowlisted server in open mode) |
MCP server
discord-cli ships a stdio Model Context Protocol server — the portable counterpart to the CLI, so MCP-aware clients (Claude Code, Claude Desktop) can drive Discord as structured tools without shell invocations.
Install
npm install -g . installs both the discord CLI and the discord-mcp MCP server binary.
Register with Claude Code
claude mcp add discord -- discord-mcpThe server reads the same ~/.config/discord-cli/ credentials, config, and allowlist files
as the CLI. No extra setup needed.
Tools
| Tool | Description |
|---|---|
| discord_post | Post a message to an allowlisted channel (or thread). @everyone/@here/role pings are stripped unless allow_everyone/allow_roles. |
| discord_read | Read recent messages from an allowlisted channel or thread. |
| discord_react | Add a reaction to a message in an allowlisted channel or thread. |
| discord_create_thread | Create a thread from a message in an allowlisted channel. |
| discord_channels | List a server's channels (names → ids, type; marks allowlisted). Metadata only. |
| discord_audit | Show recent audited actions (newest first). |
| discord_doctor | Verify the bot token; report mode, identity, allowlist + server counts. |
| discord_allowlist | Show the allowlisted channel ids and server ids. |
Safety
Every tool delegates directly to the same run*(opts, deps) command functions used by the
CLI. The allowlist gate, restricted/open mode, mention-safety, dry-run, and audit log all
apply unchanged. A blocked channel returns an MCP error result (the tool call succeeds at
the protocol level, but isError: true is set in the response) — the server never crashes
on a blocked request.
Related tools
discord-cli is one of a small family of fail-closed CLIs built for AI coding agents —
each a focused wrapper around a single service, in the spirit of gh:
