Real-time Gateway events
Set gateway.enabled: true only after identity.applicationId, identity.botId, and at least one exact guild or channel read allowlist are configured. Before opening a native WebSocket, the stdio server calls Discord's authenticated Get Gateway Bot endpoint through the fixed REST origin. It strictly validates the returned root WSS endpoint, recommended shard count, and session-start limit, then keeps the normalized endpoint only in private process memory. Constructing the MCP adapter, running doctor, and running setup never open a connection or perform the startup request. smoke launches the normal stdio runner, so a policy that selects Gateway behavior exercises that startup and shuts it down after verification. Resume URLs received from Discord are accepted only for credential-free wss hosts in Discord's Gateway host family.
After authenticated discovery, startup derives one exact private routing topology before opening any socket. Exact event-feed guild scope, layout-evidence guild scope, and native Interaction guild scope contribute guild IDs directly. Channel-only event scope and every exact voice-status channel are resolved through bounded concurrent authenticated GET /channels/{channel.id} reads that retain only the requested channel ID and returned guild ID. Direct-message channels, mismatched IDs, malformed evidence, oversized responses, redirects, and private failure causes fail closed. The connector then applies Discord's documented (guild_id >> 22) % num_shards formula under the recommended total and opens only the unique shards required by that proven authority. It does not open shard zero solely for direct messages, entitlements, subscriptions, or other non-guild events, and it ignores non-guild dispatches that arrive on a selected shard. This behavior requires no additional configuration or environment variables.
Startup fails without a socket when endpoint discovery fails, route discovery fails, topology evidence is malformed, no session start remains, or the observed remaining allowance is smaller than the selected shard set. The optional Gateway failure does not disable REST tools. One shared coordinator schedules fresh Identifies by Discord's shard_id % max_concurrency key, permits independent keys concurrently, preserves deterministic selected-shard order within a key, enforces the platform spacing rule, and maintains a separate rolling reconnect-loop budget for each shard. The observed remaining allowance is decremented only after an actor sends a fresh Identify; Resume does not consume it. Each shard owns an independent socket, heartbeat, sequence, session, vetted resume endpoint, and reconnect state. READY must prove the configured application, exact bot user, and exact [shard_id, num_shards] pair.
Each shard also owns one rolling outbound-event budget matching Discord's documented 120 events per connection every 60 seconds. Every successful Heartbeat, Identify, Resume, and caller-triggered command write counts against the same connection window. Exact channel-info commands enter a bounded FIFO only below a conservative half-budget ceiling, preserving the other half for lifecycle traffic. A queued command has a fixed short local deadline, caller abort removes it immediately, and disconnect, reconnect, stop, or terminal failure cancels the complete queue and discards its serialized exact-ID payloads. The Discord response timeout and requestedAt timestamp begin only after the command is actually written. If lifecycle traffic reaches the absolute ceiling, the actor reconnects with the fixed outbound-budget-exhausted category before sending another payload rather than inviting Discord's documented 4008 disconnect. This behavior is per connection, process-local, non-configurable, content-free outside the already pending exact evidence request, and adds no environment variable or policy authority.
Aggregate readiness means every selected shard is ready. A recoverable interruption exposes reconnecting for the whole topology while the affected shard attempts Resume and other actors retain their private sessions. Any terminal actor, READY shard mismatch, or consumed guild dispatch arriving through the wrong shard fails the whole topology, so partial coverage is never reported as healthy. Guild-scoped outbound channel-info requests use the same deterministic shard calculation and require the preflight channel-to-guild route to match. Non-resume reconnects use the vetted cached endpoint, valid Resumes use the separately vetted resume endpoint, and replayed dispatches during Resume are normalized instead of dropped.
get_gateway_status distinguishes disabled, discovering, resolving-scope, connecting, authenticating, ready, reconnecting, failed, and stopped, and reports only fixed error categories. Its discovery summary contains the check timestamp, recommended shard count, the total, remainingAtCheck, localStartsSinceCheck, resetAfterMs, and maxConcurrency session-start values, plus content-free topology counts for active shards, resolved channels, and scoped guilds. It never returns selected shard IDs, exact scope IDs, the token, raw errors, WebSocket addresses, application or bot identity, session IDs, resume URLs, or Discord Gateway sequences.
When the enabled feed has an exact guild read allowlist, it also builds one privacy-safe direct-channel layout per allowlisted guild. The layout retains only exact channel ID, numeric type, raw position, nullable parent ID, and whether Discord set CHANNEL_OBFUSCATED; names, topics, permission overwrites, and all unknown fields are discarded immediately. A channel-only read allowlist cannot activate this complete-guild view. get_gateway_status exposes aggregate ready, resuming, pending, unavailable, invalidated, retained-channel, and obfuscated-channel counts but never returns the underlying guild or channel IDs.
Layout readiness begins with one atomically validated Guild Create channel array, not with READY or a REST channel list. Valid Channel Create, Update, and Delete dispatches advance its local revision. A socket interruption moves retained evidence into a non-readable resuming state while replayed dispatches update the private projection; only a successful Resume makes it complete again. Fallback Identify, a known continuity gap, an unavailable or deleted guild, malformed relevant evidence, an unknown direct-channel type, or invalid parent topology clears the affected evidence and makes consumers fail closed until a valid Guild Create reseeds it. This prepares for Discord's channel-obfuscation rollout, under which HTTP guild-channel lists omit channels a bot cannot view starting November 16, 2026 while Gateway channel objects preserve only obfuscation-safe layout fields. The temporary testing capability is deliberately not sent because Discord documents that opt-in mechanism as pre-release and subject to change.
The feed handles guild, channel, channel-pin, thread, role, message, bulk-deletion, reaction, poll-vote, soundboard, and Stage-instance lifecycle changes. Soundboard create, update, and delete dispatches become records containing only guild and sound IDs, while bulk updates retain only a bounded unique list of sound IDs. Sound names, volume, emoji, creator data, audio, and unknown payload fields are discarded. Stage create, update, and delete dispatches become scoped invalidation records containing only guild, Stage channel, and Stage-instance IDs; topics, speaker and audience state, scheduled-event objects, and unknown payload fields are discarded. A pin update is exposed only as a scoped channel-pins-updated invalidation event without message content or Discord's last-pin timestamp. Startup guild and thread synchronization records only a bounded ephemeral channel-to-parent identifier map so an allowlisted parent can grant read scope to child threads. Direct messages, out-of-scope guilds, unknown out-of-scope channels, malformed dispatches, and raw Discord strings are discarded. Public records contain a local receipt time, a fixed event kind, an opaque cursor, and only the relevant guild, channel, parent, role, message, sound, or Stage-instance IDs.
Opaque cursors belong to one running process and never reuse Discord's sequence. If a cursor belongs to another process, predates retained history, crosses a connection gap, is malformed, or points ahead of the local feed, get_gateway_events returns retained events with resetRequired, an exact reset reason, and a new cursor. A successful Resume preserves cursor continuity; fallback Identify, terminal failure, and stopping an established session rotate the cursor generation. Buffer overflow and connection gaps have separate content-free counters instead of pretending uninterrupted delivery.
Both Gateway resources are listed and readable even while the feature is disabled. When enabled, the server advertises resource subscription support. Legacy clients may subscribe to either exact URI through resources/subscribe; modern clients may include the URI in subscriptions/listen. Keyed leading-and-trailing coalescing limits notification traffic while preserving every retained event in the readable buffer. A notification contains only the resource URI and tells the client to read the bounded snapshot.
Canonical source: docs/reference.md
Documentation generated for guildcontrol@0.0.0. Canonical source and edit history remain in the public repository. GuildControl is an independent project and is not affiliated with or endorsed by Discord Inc. Discord is used only to identify the platform that GuildControl connects to.