Native Discord Interaction ingress
Native Interaction ingress lets an exact allowlisted Discord user submit one private request through either /guildcontrol request:... or a connector-authenticated request Button and receive one ephemeral response without exposing an Interaction token to MCP. The command name is configurable, but its contract is not: one guild-only chat-input command, administrator-only by default through default_member_permissions: "0", one required string option named request, no NSFW use, and the connector's fixed request-length ceiling. A request Button uses its reviewed visible label as the request, creates no Discord mutation, and does not inherit the slash command's Administrator requirement. Guild commands propagate immediately according to Discord's application-command documentation.
Command management is a separate reviewed write boundary. Set capabilities.nativeCommandChanges: true and list every eligible guild in scopes.nativeInteractionGuildIds. plan_native_interaction_command reads the exact guild and complete bounded command inventory, hard-blocks any same-name drift or duplicate, binds the entire inventory plus exact fixed contract and one-shot key hash into a process-keyed plan, and returns a true no-op when the desired install or removal state already exists. execute_native_interaction_command rebuilds the plan before signed confirmation and again before one non-retried POST or DELETE, then proves the complete inventory changed by exactly the reviewed command. A known pre-response 4xx can settle as failed; transport ambiguity, server errors, malformed responses, contradictory inventory transitions, and readback failures are uncertain and quarantine the exact guild command target.
Enable ingress only after the exact managed command is installed in every selected guild. Set capabilities.nativeInteractions: true plus non-empty exact guild, channel, and user allowlists. Startup verifies the pinned application and bot, requires the application's outgoing Interaction endpoint to be absent, and requires exactly one contract-matching managed command in every guild before opening the Gateway. Discord delivers Interactions through either Gateway events or an outgoing HTTP endpoint, not both. Interaction-only connections use intents 0; enabling the separate content-free event feed adds only its documented nonprivileged intents. See Discord's Interaction receiving and response contract.
The broker accepts two closed INTERACTION_CREATE shapes. A chat-input request must match the application, authorizing guild installation, command ID, command name and type, exact guild, direct channel, exact member, Administrator permission, one request option, and local scopes. A request-button event must match the application, installation, exact guild, direct channel, exact allowlisted user, Button component type, attached connector-authored Components V2 source message, and authenticated managed custom ID; it never accepts a caller-selected ID or grants write or administration authority. Unknown commands and unrelated custom IDs are ignored for another handler. A managed malformed, over-capacity, out-of-scope, or unverifiable request receives a fixed ephemeral rejection when possible. A duplicate Interaction ID is never admitted twice.
Every owned candidate is acknowledged or rejected within Discord's initial-response boundary before slower work. An accepted candidate is deferred ephemerally, then the broker freshly verifies the exact channel and complete managed-command inventory. A request Button additionally triggers one exact source-message GET and must reproduce the same authenticated application, bot, guild, channel, message, layout route, button index, label, and style seen in the attached event. A changed, deleted, re-signed, cross-scope, or token-rotation-invalidated source never enters the queue.
Accepted request text lives only in a bounded process-local queue. The configurable global and fixed per-user capacity is shared by pending requests, open continuations, and in-flight responses, so continuation work cannot silently expand broker authority. Each request expires at its configured lifetime, which is capped below Discord's token lifetime, or connector shutdown. discord://interactions/pending and list_pending_discord_interactions return only the source kind, exact IDs, timestamps, request text, slash-command version or request-button index and style evidence, and an opaque one-shot reference. Request text, custom IDs, authenticated routes, and labels never enter activity records, operation receipts, logs, telemetry, or persisted state. The raw Interaction token stays inside the broker and never enters an MCP resource, tool input, result, error, diagnostic, notification, or durable record.
respond_to_discord_interaction accepts only an available opaque reference, one bounded plain-text response, and an explicit keepOpen choice that defaults to false. A pending content-free activity record must succeed before the response leaves the process. The broker removes the reference before its one non-retried edit and requires an exact ephemeral application-owned Interaction response with matching source, guild, channel, user, content, and empty attachments, embeds, and components. Slash-command responses must carry exact application-command metadata. Request-button responses must carry exact message-component metadata, interacted_message_id, and a reference to the authenticated source message. The broker durably records completion before retaining any continuation. The default path discards the token. When keepOpen is true and time and capacity remain, the result contains one process-local icref_... continuation instead of the token.
discord://interactions/continuations and list_discord_interaction_continuations expose only the rotating reference, exact verified IDs, opening and expiry times, and completed and remaining follow-up counts. They contain no request text, response text, token, profile, raw payload, or inferred purpose. send_discord_interaction_followup accepts one available continuation, one bounded plain-text response, and another explicit default-off keepOpen choice. It durably records pending content-free activity, consumes the old reference, sends one non-retried ephemeral follow-up with mentions and rich content disabled, validates the exact direct response, performs an independent exact GET readback, and durably records completion. Only that complete success can return a different rotated reference, and the fixed sequence ends after three follow-ups even if the caller keeps requesting another.
The safe operator sequence is:
- Read
discord://interactions/status, then reviewdiscord://interactions/pendingor calllist_pending_discord_interactions. - Call
respond_to_discord_interactionwith the exact pending reference and reviewed response. OmitkeepOpenunless a later update is genuinely intended. - If the result contains a continuation, retain only that returned reference. The original pending reference is spent.
- Before a later update, read
discord://interactions/continuationsor calllist_discord_interaction_continuations, supply the intended conversational context because the connector stores no prior response text, and review expiry and remaining allowance. - Call
send_discord_interaction_followuponce with the exact current continuation and response. SetkeepOpenonly when another update is intended, then requireresponse-and-readback-matchbefore using the newly rotated reference.
Expiry and shutdown drop a continuation without editing or deleting the already completed response. A known pre-application Discord refusal is failed; transport ambiguity, rate limiting, server errors, malformed direct evidence, unavailable or drifting readback, and local completion-record failure return no new reference and never make the old one reusable. The broker deliberately provides no follow-up edit, deletion, public response, attachment, embed, component, poll, mention, token-passthrough, arbitrary-webhook, or automatic-generation path. Resource subscriptions cover status, pending requests, and continuations even when the content-free Gateway event feed is disabled.
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.