A Model Context Protocol server for Discord. It discovers every server visible to one or more configured bots, reads channels and ticket threads, resolves Discord message URLs, and returns safe image attachments as MCP image content. It also lists server emojis and stickers and can send messages as a configured bot when explicitly requested. An on-demand Nova listener answers owner mentions across accessible servers or owner DMs using the logged-in Codex CLI. It supports source-linked research, images, voice notes, reactions, progress displays and owner controls.
Reading uses Discord REST. Sending, reactions, progress edits and research-thread creation occur only when requested or within explicitly enabled conversations. The listener starts on demand; it does not auto-start with the operating system. Version 2.11.0 exposes 42 MCP tools, or 45 with approved project roots. Existing MCP tool names and arguments remain compatible.
- Node.js 18 or newer.
- A Discord application with a bot. One bot can be invited to multiple servers.
- Enable Message Content Intent under Developer Portal > Bot > Privileged Gateway Intents. Discord applies this intent to message content, embeds, and attachments returned to verified apps, including REST message reads.
- Grant the bot View Channel and Read Message History only where it should read. Add it to private threads that it needs to inspect.
- For sending, grant Send Messages in the target channel, or Send Messages in Threads for a thread. Reading tools do not need these permissions. Optional reactions, attachments and research threads need Add Reactions, Attach Files and Create Public Threads respectively. Slash commands use the application command installation scope; text controls remain available if slash commands have not propagated.
For a LonglenAI setup, invite the same reader bot to longlenai,
longlenai-creator, and longlenai-dev. Discord server and channel permissions
remain the access boundary.
The Codex plugin bundles the same server and a use-discord skill. It requires
Node.js 22 or newer. Build and install it from this checkout:
npm ci
npm run build:plugin
codex plugin marketplace add .
codex plugin add discord@yuruzuu-discordRestart Codex after installation, then enable Discord in Plugins and use
its tools or the bundled skill. The build outputs a self-contained plugin under
plugins/discord, with its runtime dependencies bundled. The checkout's
node_modules directory and credentials are not part of that plugin.
Store credentials outside the plugin at ~/.config/discord-mcp/.env, or set
DISCORD_ENV_FILE to a credentials file. XDG_CONFIG_HOME is respected when set.
The file supports the same DISCORD_TOKEN and multi-account environment variables
as the standalone server. Keep it private, for example with chmod 600.
Directly supplied environment variables take precedence over file values.
Avoid enabling both this plugin's server and a standalone Codex discord MCP
registration at once. Disable the standalone entry after verifying the plugin:
[mcp_servers.discord]
enabled = falseThe standalone entry points remain available for Claude Code and other MCP
clients. After server changes, run npm run build:plugin and refresh the plugin
installation so its bundled runtime picks up the new code. The build derives the
Codex compatibility manifests from the portable manifests; edit plugin.json
and mcp.json in plugins/discord rather than those generated compatibility
files.
This is a local Codex plugin. Public directory distribution requires a separate submission and a supported deployed MCP connection. Packaging a plugin does not grant Discord permissions or change a chat's network policy.
npm install
DISCORD_TOKEN=your_bot_token npm startDISCORD_TOKEN is the v1-compatible single-bot configuration. For named or
multiple bots, keep token values in environment variables and point account
definitions at those variable names:
export DISCORD_TOKEN_LONGLENAI_READER='your_bot_token'
export DISCORD_ACCOUNTS_JSON='[{"id":"longlenai-reader","tokenEnv":"DISCORD_TOKEN_LONGLENAI_READER","priority":100}]'
node index.mjsDISCORD_ACCOUNTS_FILE can contain the same JSON array. Never put a token value in
that file; tokenEnv is an environment variable name, not a token.
| Tool | Purpose |
|---|---|
discord_list_servers |
List all servers visible across configured bot accounts and report account health. |
discord_list_channels |
List categories, channels, active threads, and optionally archived threads for one server. |
discord_list_tickets |
Find forum posts, threads, and optionally text-channel tickets with parent/category/name/time filters. |
discord_read |
Read a message/channel URL or IDs, auto-select the bot with access, and inline image attachments. |
discord_search_messages |
Search indexed server messages and return up to 250 matches with links and continuation arguments. |
discord_read_activity |
Read everything posted in a server (or chosen channels and their threads) today, yesterday, in the last N hours or a since/until window up to 7 days. Reads channel history directly, so it is complete and fresher than search; idle channels are skipped, recently archived threads are included, and results are compact per-channel transcripts with participants and an optional keyword filter. |
discord_search_batch |
Run up to 10 keyword/channel/author searches in one call (one small page each) and get deduplicated, compact hits with per-search continuations. |
discord_find_members |
Resolve server usernames and nicknames to author IDs. |
discord_research_topic |
Collect bounded topic matches, nearby context and source links. |
web_read_link |
Read public HTTP(S) text with private-address and redirect controls. |
read_tool_result |
Retrieve omitted worker/tool-result pages by expiring conversation-local handle. |
discord_nova_control |
Apply explicit owner conversation controls, research jobs and opted-in digests. |
project_list, project_search, project_read_file |
Read explicitly configured project roots; worker access is owner-DM-only. These three tools are absent when roots are empty. |
discord_add_reaction |
React with any Unicode emoji or a custom emoji the bot can use; Discord checks access and availability. |
discord_message_context |
Jump to a message and read its surrounding conversation. |
discord_browse_messages |
Browse up to 250 messages at a time with older/newer cursors. |
discord_start_direct_messages |
Start private DM conversations for the fixed owner only. |
discord_stop_direct_messages |
Stop DM replies independently of server listeners. |
discord_direct_message_status |
Inspect the DM listener, owner ID and reply statistics. |
discord_start_server_mentions |
Opt in to owner mentions/replies across all accessible server channels. |
discord_stop_server_mentions |
Stop server-wide watching independently of DMs. |
discord_server_mentions_status |
Inspect server coverage and per-conversation statistics. |
discord_fetch_attachment |
Fetch one selected image from a message or a direct Discord CDN/media URL. |
discord_check_access |
Diagnose which configured bot can read a guild, channel, or message. |
discord_list_expressions |
List custom emojis and stickers, with ready-to-use emoji markup, sticker IDs, availability, and role restrictions. |
discord_send_message |
Send bot messages with text, custom emojis, and up to three server stickers. |
discord_reply |
Use Discord's native reply feature for a specific message, with optional reply notifications. |
discord_send_messages |
Send one to five short message bubbles in order, reporting partial receipts if a send fails. |
discord_create_channel / discord_create_category |
Create a text, voice, announcement, stage or forum channel (optionally under a category) or a category. Needs Manage Channels. |
discord_create_role |
Create a role with optional color, hoist, mentionable and permission bitfield. Needs Manage Roles. |
discord_add_role / discord_remove_role |
Give or take away a role for a member. Needs Manage Roles with the role below the bot's highest role. |
discord_forward_messages |
Natively forward one to ten existing messages, with their attachments, into a channel, thread or DM in order. Nova can also forward up to five messages per reply within its reading scope. |
discord_user_info |
Get a public profile, avatar and creation date, plus server nickname, join date and roles when requested. |
discord_start_proactive |
Start a background channel listener powered by the logged-in Codex CLI. |
discord_stop_proactive |
Stop that listener and cancel pending replies. |
discord_proactive_status |
Read listener state, mode, queue, reply counters and errors. |
The four v1 tools remain as compatibility aliases:
discord_get_message, discord_read_messages, discord_get_channel, and
discord_get_server_info.
discord_search_messages uses Discord's guild search endpoint. Supply a
guildId and optional query; the default result limit is 250, newest first.
It supports channel/author/mention filters, reply targets, attachment and embed
types, pinned state, snowflake bounds, and timestamp/relevance sorting.
Only messages visible to the selected bot are returned.
Discord's search pages contain at most 25 results, so the tool gathers pages
until it reaches the requested limit. It retries index-building responses and
does not treat short search pages as completion. Results include totalResults,
nextOffset, continuation, indexing state and offsetLimitReached. Discord's
offset window ends at 9975; the tool reports that boundary instead of silently
claiming a complete search.
Pass a result's message URL to discord_message_context to jump into its
conversation. Context is returned oldest first, with the anchor message and
older/newer navigation arguments. Use those arguments with
discord_browse_messages to continue browsing, up to 250 messages per call.
Images are opt-in with includeImages; search results provide attachment
references without downloading them. A deleted anchor remains an error.
Call discord_list_expressions with a guildId to discover that server's
expressions. Its kind can be all, emojis, or stickers. Custom emoji entries
include markup such as <:wave:123456789012345678> or animated
<a:wave:123456789012345678>. Include that exact markup in
discord_send_message.content. Include sticker IDs in stickerIds:
{
"guildId": "100000000000000001",
"channelId": "200000000000000001",
"content": "Hello <:wave:123456789012345678>",
"stickerIds": ["300000000000000001"]
}Messages come from the selected bot account. Content is limited to 2000
characters; a sticker-only message may omit content. Mention notifications are
disabled by default; explicitly set allowMentions: true when wanted. Discord
still enforces channel permissions, emoji role restrictions, and sticker
availability.
Sending is marked as a write tool and should only be called when the user asks
to send. It shares the bounded request queue and rate-limit handling, but sends
are never combined with other sends. Each send uses Discord's enforce_nonce
deduplication. Supply the same nonce when retrying a send; deduplication applies
within Discord's recent-message window. Failed rate-limited sends can retry;
ambiguous network or server failures are returned without automatically resending.
Use discord_reply with channelId, messageId and message content to reply
natively. mentionRepliedUser controls the reply ping separately from other
mentions, and defaults to false. discord_send_message.replyToMessageId also
supports native replies. A deleted target causes the reply to fail instead of
silently becoming an unrelated message.
Use gifUrl to send an existing HTTPS GIF or GIF page URL, with or without text.
Discord handles its preview. Content and the appended URL together must fit in
2000 characters. discord_send_messages accepts a messages array of message
objects and spaces them by intervalMs (650 ms by default). Its optional
replyToMessageId applies to the first bubble. A stable batchId gives each
message a stable nonce; partial failures include sentMessages and
failedMessageIndex so a retry can avoid duplicate sends.
The channel and bot route are resolved once per batch; each send still goes
through Discord's permission checks, rate limits and nonce deduplication.
Start the listener only when you want the bot to participate in a channel:
{
"guildId": "100000000000000001",
"channelId": "200000000000000001",
"mode": "mentions"
}Call discord_start_proactive with those arguments. It checks Codex CLI
availability and login, starts a detached local server, and reports running state
after the Gateway is ready. No startup greeting is posted. mentions responds
to owner bot mentions and native replies to the bot. questions also considers
owner questions; all considers every owner message. Messages from bots and
webhooks are ignored, and repeated Gateway messages are deduplicated.
Automatic responses in every mode are restricted to owner
291140236979732480. Other users are rejected at both the Gateway and engine
before lookups, typing, context loading or model generation.
The bot appears online while its Gateway listener is active. Enable Message
Content Intent, View Channel, Read Message History, and the relevant
Send Messages / Send Messages in Threads permission. Only the selected
channel is processed. The server continues running when its calling MCP process
closes, until you call discord_stop_proactive or stop the computer/process.
It does not auto-start on login or when Codex opens.
Nearby message bubbles from one author are grouped before generating a reply.
Responses are generated one at a time, defaulting to a five-second cooldown and
at most six model attempts per minute. Nova defaults to gpt-6.1-sol,
reasoningEffort: "low" (Light) and serviceTier: "priority" (Fast).
The defaults are independent of the global Codex model settings. Startup can
override model, reasoningEffort or serviceTier; "default" selects Standard
service. See the Codex configuration reference.
The worker uses your Codex quota and existing saved CLI login. Each listener
daemon pools one warm Codex app-server process while keeping a separate
ephemeral, in-memory thread for each conversation.
Later messages continue that thread, with nearby context sent incrementally.
This preserves conversation state and allows prompt-cache reuse; actual cache
hits depend on the service and are reported in conversation.cachedInputTokens.
DM and server threads are separate. Stopping/restarting a listener discards its
ephemeral conversation; approved memory survives independently.
Native shell, filesystem, plugin, hook, hosted web-search and external MCP tools are disabled in the worker. A shared registry supplies 10 host-executed reading tools: seven Discord discovery/search/context/profile operations, public link reading, a bounded topic-research playbook, and omitted-result retrieval. The three project tools are added only for explicitly approved roots in owner DMs. Owner DMs can research any server the bot can access; server conversations read only their own server. Other private conversations remain unavailable.
Searches return up to 250 matches with continuation pages and source links.
Large worker results become bounded previews with opaque handles for
read_tool_result; handles expire after ten minutes and retain at most eight
results per tool instance. Tools have individual deadlines, a configurable
whole-turn budget, a call limit, and repeated-result detection. Context loading
hydrates old reply parents and includes forwarded text as untrusted evidence.
Current attachments and explicitly referenced messages can supply bounded images
and voice notes, including forwarded attachments attributed to the forwarding
message. Recent-history audio is not repeatedly transcribed.
The host validates arguments and owns Discord credentials; the worker receives no Discord token. Its temporary read-only permission profile does not edit global Codex configuration. Tool definitions and operations are shared with the MCP surface, while worker-specific guards retain conversation scope.
Substantial work uses one editable progress message and temporary status reactions for queued, working, finished or failed states. Updates describe actual searches, image reads, voice transcription and other tool activity. Details shows tool status, counts and timings; raw internal reasoning, tool arguments and source message contents are not activity text. Ordinary chat needs no progress filler. Typing continues during actual preparation and delivery.
Complete reply bubbles are validated and sent as the model streams them. The
first bubble gives a short useful answer; later bubbles add details. Raw partial
tokens and reasoning are never posted. Each bubble has a stable nonce, the first
keeps any selected native reply reference, and confirmed bubbles are not sent again at
turn completion. A failure after an early bubble stops the rest of the reply. Source bubbles can
contain up to 16,000 characters; delivery splits them into Discord-safe chunks
of at most 2000 characters while preserving code fences. Generated UTF-8 text
files are kept in memory for delivery, with at most three files, 128 KiB each
and 256 KiB total. This does not grant arbitrary filesystem attachment access.
statistics.lastFirstResponseMs measures the most recent first-send delay from
local message receipt, and statistics.streamedMessages counts streamed sends.
DMs and standalone server mentions use ordinary messages. In servers, native replies identify ongoing reply chains, batched questions and answers where another message has arrived after the trigger. Only the first answer bubble uses that reference, with reply pings disabled. Longer answers can still use a few short bubbles.
Nova's persona and rules are plain Markdown in instructions/nova/ (see
instructions/README.md); edits apply to the next conversation or nova reset.
Nova writes like a conversational assistant: plain sentences and short
paragraphs, with headings, bold emphasis and bullet lists kept for content that
genuinely needs them. Server names are always bold and numbers italic (396
messages in Anime Vanguards Dev). While it works, its progress message says what it is doing
in plain words, such as "I'm currently searching #balancing for messages with
"fate" and found 25 results." It refers to channels as <#channelId> and people as
<@userId> using IDs from the messages and tool results it has seen; these render
as clickable names and never notify anyone, because Nova sends with mentions
disabled.
For "what happened today" or "summarize the day in the dev server", Nova uses
discord_read_activity, which works like DiscordChatExporter's date-bounded
export. It turns the window into a snowflake, skips every channel whose last
message is older, reads the rest in parallel (100 messages per request), and
checks recently archived threads so a thread that auto-archived this afternoon
is still included. It returns compact transcripts that keep the most recent
lines when a day is too large, and it stops at a 20-second deadline with a
partial result that names any quieter channels it skipped, rather than timing
out. Recent reads are cached for ten minutes, so drilling into one channel
right after an overview is nearly instant. day: "today" uses the owner time
zone (timeZone in nova.json, otherwise the host's zone), and Nova writes
dates and times as Discord timestamps (<t:unix:f>, <t:unix:R>) so they show
in each reader's own time zone.
From the owner DM, you can ask Nova to post in a server channel or thread ("tell Hand good job in #av-slop-chat"). It posts at most three messages per reply, only to server text channels and threads the bot can access, and pings only the users named in the message, and only when you ask. Roles, @everyone and @here never ping. Nova refuses to post after reading your connected apps in the same answer, and the host confirms each post, or explains a failure, in your DM.
Nova can choose up to three emoji reactions to supplied messages in the current
conversation, with or without a written answer. Unicode emoji are unrestricted;
custom emoji must be usable by the bot. Discord enforces availability and
permissions. Reactions are validated before the host sends them; a failed
reaction does not prevent a written answer. Status reports reactions,
reactionErrors and lastReactionError separately from message counts.
Gateway startup handles both client and shard errors, makes up to three attempts
with backoff, and safely terminates pending handshakes during teardown. Its
45-second readiness deadline allows the SDK's 30-second handshake timeout to
settle first. The socket cleanup workaround pins @discordjs/ws to 1.2.3.
An on-demand supervisor additionally retries unexpected process exits up to five
times with bounded backoff and detects prolonged Gateway disconnection. Invalid
credentials or disallowed intents require attention rather than endless retries.
It never starts model health-check turns or installs operating-system startup
jobs. After connecting, the SDK handles reconnect/resume; status exposes
gateway.connected, connectionAttempts and reconnects so a running process
is distinguishable from an active Gateway connection.
The bot shows a typing indicator while gathering context, generating a reply
and sending its message batch, in server channels and owner DMs. It refreshes
the indicator every seven seconds while working and stops on completion,
failure or cancellation. Typing failures do not prevent a reply. Discord lets
the last typing indication expire after ten seconds; idle listeners do not
keep an indicator running. See Discord's typing documentation.
Available server emojis, stickers, optional gifUrls, recent channel GIF links,
and animated emoji GIFs can provide expressions. Replies never ping users or
roles automatically. An uncertain send is not blindly retried.
Use discord_proactive_status with channelId to inspect a listener, and
discord_stop_proactive with channelId to stop it. Stop before changing an
active listener's routing scope. accountId selects a specific configured bot.
DISCORD_CODEX_COMMAND can point to a Codex executable when it is absent from
PATH. Private control/status files and logs live under
~/.local/share/discord-mcp/proactive (or XDG_DATA_HOME).
Bursty statistics updates share a status-file write within a 250 ms window.
Lifecycle changes flush immediately, and live status requests use the current
in-memory state.
Call discord_start_server_mentions to explicitly enable watching all servers
the bot belongs to. No server or channel ID is needed. One Gateway connection
receives events from accessible channels and threads, including new channels
and servers joined while running. Discord channel permissions and private-thread
membership still determine visibility.
Only owner 291140236979732480 can trigger a response, by mentioning Nova or
replying natively to one of its messages. Ordinary chatter and other users'
pings do not create workers or use Codex quota. Conversation runtimes are created
lazily, with separate ephemeral threads and memory.md files per channel.
Server conversations share a single reply scheduler, the configured cooldown, and the model-attempt limit (six per minute by default). Up to eight idle conversation runtimes are retained; older idle runtimes close as needed, while active/queued work is kept. Approved memory survives eviction.
Stop channel-specific listeners before enabling server-wide watching to prevent
duplicate replies. The controller rejects overlapping scopes, including
concurrent starts. discord_server_mentions_status reports watchedGuildCount
and per-channel statistics. discord_stop_server_mentions stops this mode;
owner DMs continue independently. This mode does not auto-start after the
computer or supervisor stops.
Call discord_start_direct_messages to enable private conversations with the
bot. This installation is fixed to owner ID 291140236979732480; no tool or
environment option can change that recipient. The listener opens or reuses the
bot's one-to-one DM with that owner, and checks the sender and channel before
context lookup or reply generation. Other senders, group DMs and server messages
are ignored by this listener.
Open the bot's profile in Discord and select Message. Owner messages trigger
replies without a mention or question mark. Replies use the logged-in Codex CLI,
ordinary message bubbles and the same cooldown/attempt limits as server
conversations. DMs use standard emojis and supplied or recent GIFs, without
fetching server expression catalogs. The DM Gateway uses the DIRECT_MESSAGES
intent and supports uncached DM channels; Message Content Intent is not needed
for messages sent directly to the bot. See Discord's Gateway documentation.
discord_direct_message_status reports the fixed owner, running state and reply
statistics. discord_stop_direct_messages stops DM replies independently of
server listeners. DM mode stays active while its background process and computer
are running. The on-demand supervisor can recover an unexpected daemon exit;
it does not start after a computer restart. It only sees the owner's
conversation with the bot, not personal conversations with other Discord users.
Each conversation has its own private memory.md under
~/.local/share/discord-mcp/memory/<account-and-conversation>/ (or
XDG_DATA_HOME). DM memories are not injected into server threads. You can edit
the file directly; Nova reads the current contents before replying. New files
start empty, and ordinary chat is never automatically saved.
Unchanged notes reuse their snapshot and hash. File metadata checks still
detect manual edits and atomic replacements; changed contents reload before the
next reply. Consolidation skips replacement when the result is unchanged.
Only owner 291140236979732480 can issue these text commands:
remember this: I prefer short repliessaves exactly the approved note.- Reply natively to a message with
remember thisto approve its text. show memoryshows the saved notes, with a bounded preview for long files.consolidate memoryformats and deduplicates approved text without inventing facts or inferring memories from chat. Consolidation runs only when requested.
In a mentions-only server channel, mention Nova or reply to Nova while issuing
the command. In the owner DM, no mention is required. Notes are limited to 2000
characters each and the file to 16 KiB. Saves use private permissions, serialized
writes and atomic replacement, with a check for concurrent manual edits. Status
includes memoryFile so the owner can locate the live file.
Owner 291140236979732480 can use /nova slash commands, or the equivalent
nova <action> text commands while a listener is active. In a mentions-only
server, mention Nova or reply to it when using text controls. Commands are handled
by the host; other users cannot operate them.
| Command | Effect |
|---|---|
/nova status, /nova details |
Inspect the conversation, actual tool activity, errors and available Codex account usage windows. |
/nova stop |
Cancel the current answer; keep listening. |
/nova pause, /nova resume |
Pause or resume automatic answers in this conversation. |
/nova model, /nova effort, /nova fast |
Inspect or set the conversation model, effort or requested Fast tier. |
/nova budget |
Set the whole-turn budget in seconds, from 1 to 900. |
/nova steer |
Send an explicit correction to a currently running turn. |
/nova reset, /nova compact |
Reset ephemeral context or ask Codex to compact it; approved memory remains separate. |
/nova voice, /nova projects |
Inspect the configured speech backend and approved project list. |
/nova deliveries |
Inspect durable delivery receipts and uncertain operations. |
/nova research, /nova jobs, /nova digest |
Start/inspect research jobs or manage opted-in digests. |
For example, nova fast on requests priority service and nova budget 300
allows a five-minute turn. Model, effort, budget, tier and pause settings persist
per conversation. Requested settings do not guarantee provider availability or
service-tier fulfillment. Usage values can be unavailable; they are not inferred
from token counts.
Progress offers Details and Stop answer buttons. The first answer can offer Remember message, Read more, Retry request and Details. Buttons are bound to their owner and conversation, single-use and valid for 30 minutes. Remembering a message is an explicit memory approval; retries are blocked when an earlier delivery is still uncertain.
Private settings live in ~/.config/discord-mcp/nova.json (or XDG_CONFIG_HOME).
Project access is disabled by default. Approved roots are absolute directories,
exposed only through read-only list/search/file-range tools in owner DMs; hidden
secrets and paths escaping the approved roots are rejected. Example configuration:
{
"version": 1,
"projectRoots": [
{ "id": "approved-project", "name": "Approved project", "root": "/absolute/path/to/project" }
],
"voice": {
"backend": "local",
"executable": "/absolute/path/to/whisper-cli",
"model": "/absolute/path/to/ggml-tiny.bin",
"ffmpeg": "/absolute/path/to/ffmpeg"
},
"web": true,
"media": true,
"playbooks": true,
"apps": true,
"webSearch": "auto"
}Nova can search the web with Codex's built-in web search, and each search
appears in the progress log ("I've searched the web for "…""). With
"webSearch": "auto" (the default), server conversations search live. The
owner DM uses OpenAI's search cache while connected apps are enabled, because
live search can open arbitrary pages and would bypass the rule that blocks
link reading after private app data has been read. Set "live", "cached",
"indexed" or "disabled" to choose a mode yourself.
Owner DMs can also use the apps connected to your Codex/ChatGPT account (for
example Gmail, Google Drive, GitHub and Linear) through apps_list_tools and
apps_call_tool. Ordinary calls remain read-only. apps_list_tools with
access: "action" discovers write actions; apps_prepare_action sends an exact
proposal for separate owner approval. Only the host can execute the approved
proposal, once, in the owner DM. Payment tools (PayPal, invoices, billing,
and similar) are always refused. The reply model never gets native app access;
the listener calls tools on a hidden Codex thread that never runs model turns.
Server conversations never see these tools. After app data is read, public link
reading is blocked for the rest of that answer so private data cannot leak
through a URL. When an app returns an image (a Google Slides thumbnail, a Figma
screenshot, or an image attachment from Gmail or Linear), Nova can post it in
the DM. Live screenshots of Gmail, Drive or spreadsheet pages are not
supported. Connect or disconnect apps in ChatGPT/Codex, and set
"apps": false to turn the feature off.
Nova reads PDFs attached to an owner request or its native reply context. Text
is extracted page by page; scanned pages are rendered locally for model vision.
discord_read_pdf opens a PDF by authorized channel/message/attachment IDs and
returns nextPage for continued reading. Parsing has a 30-second deadline,
16 MiB input limit, 300-page document limit, at most 20 selected pages and three
rendered pages per call. The plugin includes its isolated PDF worker, fonts and
the build host's native rendering dependency. Set "pdf": false to disable it.
In your owner DM, ask naturally for a reminder or conditional alert. Nova prepares its exact schedule and sends Approve once / Decline buttons. Explicit commands can also create a schedule directly:
nova reminder add {"content":"Review the hotbar changes","runAt":"2026-10-06T09:00:00+08:00"}
nova reminder list
nova reminder remove <id>
nova alert add {"content":"New hotbar discussion","condition":{"type":"discord","guildId":"SERVER_ID","query":"hotbar"},"intervalMinutes":15}
nova alert remove <id>
Reminders support one-time or recurring delivery. Alerts check new Discord
messages or a predicate on a discovered read-only app result. Checks stay quiet
without model calls; notifications go only to the owner DM. Schedules survive
restart, and uncertain deliveries halt until explicitly resolved. Relative times
use timeZone in private nova.json; use an ISO timestamp with an explicit
offset in commands. Set "reminders": false to disable scheduling.
Coding handoffs use the running local T3 Code app and its connected Codex/Claude
Code harnesses. Ask Nova for its T3 catalog, choose an existing project and exact
supported model, and approve the prepared task. nova tasks catalog,
nova tasks list, nova tasks status <id> and nova tasks stop <id> inspect or
stop Nova's own tasks. Explicit nova handoff start accepts JSON with projectId,
harness (codex or claude-code), model, title and request. Tasks work in
that selected project's root and use T3's approval-required mode. Command/tool
activity is shown in temporary fenced blocks; specific harness approvals use
owner-only buttons. Nova never exposes private reasoning. T3 keeps running tasks
when Nova stops, and Nova resumes observation without relaunching them.
Connect using the supported configureT3Connection() helper in
src/proactive/t3-client.mjs. Its dedicated 30-day session is kept in private
~/.config/discord-mcp/t3-session.json. T3 must remain running; reconnect after
session expiry. Set "handoffs": false to disable the integration. See
the handoff guide and schedule guide.
Prepared app actions never execute before your separate approval. Large
proposals include a complete JSON attachment to review. Use nova approvals,
nova approve <id> or nova decline <id> if buttons expired. Uncertain app
outcomes are never automatically retried. Set "appActions": false to disable
write proposals while keeping connected-app reads.
Restart the relevant listener and MCP client after editing global capability settings. Owner conversation controls apply their settings while listening.
Nova admits two independent server conversations at once while keeping each conversation serial. Its default input batching window is 500 ms; existing explicit pacing settings still apply. Progress and status rendering run through bounded background queues, so slow Discord decoration does not hold up tools or real replies.
Healthy native threads survive confirmed cancellation and recoverable native failures. Unknown shutdowns and invalid replies retire only the affected thread; uncertain sends and written native requests are never automatically replayed. The RPC transport has method-specific budgets, frame/pending-byte limits and late-response cleanup. Stable context uses transactional revisions, and model tool previews default to 32 KiB with conversation-local handles for full sources. Trusted-local MCP result budgets remain unchanged. Native Codex owns compaction.
Status includes bounded numeric stage p50/p95 measurements for queueing, preparation, worker startup, native output, tools, delivery and settlement. They contain no message bodies or arguments. See harness performance verification for reproducible synthetic comparisons and their limits.
Local voice transcription uses whisper.cpp and FFmpeg, installed separately from
Git and the plugin. Discord Opus notes need conversion to 16 kHz mono PCM WAV.
Downloads are limited to approved Discord media hosts and 16 MiB; containers are
checked, decoding is bounded, and temporary audio is deleted after the decoder
stops. Up to three current or explicitly referenced notes are processed per turn;
additional notes are reported. A multilingual tiny model is a lightweight default,
but names, accents and project jargon may be misheard. Check important details.
Use /nova voice to diagnose a missing executable, model or conversion backend.
An optional explicitly configured speech API uses backend: "api", an HTTPS
endpoint, selected model and apiKeyEnv naming a private environment
variable. It sends audio to that provider and may incur separate charges; Codex
subscription login does not supply speech-provider credentials. No API backend
is selected by default. Public link reading is text-only, refuses credentials and
private/reserved addresses, pins validated DNS, and rechecks redirects.
A private delivery journal stores IDs, nonces and confirmed receipts rather than chat bodies, retaining at most 2000 operations for seven days. It prevents replay of known work after restart. An uncertain send is halted until inspected and explicitly resolved; neither a restart nor a Retry button blindly resends it. This operational journal does not create lasting conversational memories.
/nova research starts an owner-requested investigation in a dedicated public
Discord thread. From DMs, provide the target server and parent channel IDs;
thread creation requires the relevant Discord permissions. The thread keeps its
own ephemeral conversation and memory, and accepts owner follow-ups. /nova jobs
lists, inspects or stops jobs. A restarted process marks unfinished jobs failed;
it does not automatically replay their research requests.
Digests are off by default and require explicit opt-in. Start one from the owner DM with a topic, author IDs or channel IDs and an interval of 15 minutes to one week, for example:
nova digest add {"guildId":"100000000000000001","query":"hotbar","intervalMinutes":60}
nova digest list
nova digest run <digest-id>
nova digest remove <digest-id>
The listener searches only new matches since the saved cursor and summarizes useful changes, decisions and unresolved questions into the owner's DM using Codex and the same conversation quota. No matches produce no notification. Cursor advancement requires a confirmed delivery receipt. An uncertain delivery halts that schedule until verified. Enabled schedules persist while the owner DM listener is running; stopping it pauses execution. Digests never automatically save chat into approved memory.
External periodic callers can still persist their own cursor and pass it back explicitly, independently of Nova’s opted-in digest manager:
- Use
discord_list_tickets.updatedAfterto find recently active tickets. - Use
discord_read.afterwith the newest message snowflake saved by the caller. - Save
cursors.newestonly after the caller has processed the returned messages.
The MCP process keeps only disposable routing caches. Restarting it does not lose caller state or change the periodic contract.
Each bot makes at most ten concurrent network requests. Archived-thread scans run with bounded concurrency and preserve channel and warning order. Ticket category filters also limit which parents are scanned for archived threads.
Overlapping identical requests share one fetch; later channel and message reads
still fetch fresh data. Server discovery retains its process cache and supports
discord_list_servers.refresh to discover newly invited servers. Discord bucket
headers and global cooldowns delay requests when rate limits are exhausted, and
retries honor the full Retry-After interval.
Transient connection failures on reads retry with bounded backoff. Failed or
partial server discovery is not cached, so a subsequent call can recover without
requiring refresh: true or a client restart. If every bot fails discovery, the
tool reports an error with account diagnostics instead of a successful empty
server list. DNS, timeout, network-policy, and TLS failures include their cause
codes. Network-policy and certificate failures are not retried, and failed sends
are never automatically retried after a connection error.
Duplicate image downloads are reused within a read or while a matching fetch is in progress. Image output counts and byte limits still apply to every returned image. Rejected download streams are cancelled rather than left open. MCP text results use compact JSON alongside the same structured results.
Codex can forward a named environment variable without storing its value in TOML:
[mcp_servers.discord]
command = "node"
args = ["/absolute/path/to/discord-readonly-mcp/index.mjs"]
env_vars = ["DISCORD_TOKEN_LONGLENAI_READER"]
[mcp_servers.discord.env]
DISCORD_ACCOUNTS_JSON = '[{"id":"longlenai-reader","tokenEnv":"DISCORD_TOKEN_LONGLENAI_READER","priority":100}]'Launch Codex from a shell where DISCORD_TOKEN_LONGLENAI_READER is exported.
Project .mcp.json:
{
"mcpServers": {
"discord": {
"command": "node",
"args": ["/absolute/path/to/discord-readonly-mcp/index.mjs"],
"env": {
"DISCORD_TOKEN_LONGLENAI_READER": "${DISCORD_TOKEN_LONGLENAI_READER}",
"DISCORD_ACCOUNTS_JSON": "[{\"id\":\"longlenai-reader\",\"tokenEnv\":\"DISCORD_TOKEN_LONGLENAI_READER\",\"priority\":100}]"
}
}
}
}Restart the client after changing MCP configuration. Both Codex and Claude use the same stdio server and tool schemas.
Images are fetched only over HTTPS from Discord CDN/media hosts. Redirect targets, declared MIME types, file signatures, and byte limits are checked before returning base64 MCP image blocks.
| Environment variable | Default | Maximum |
|---|---|---|
DISCORD_MAX_IMAGE_BYTES |
8 MiB per image | 25 MiB |
DISCORD_MAX_IMAGES |
4 per call | 10 |
DISCORD_MAX_TOTAL_IMAGE_BYTES |
20 MiB per call | 50 MiB |
npm test
npm pack --dry-runTests use mocked Discord REST responses and never need a real bot token.
MIT (c) Vorakorn Kosidphokin