Reaction lifecycle
Reaction access is split by privacy and risk instead of treating every endpoint as one broad capability. list_message_reactions and the matching exact resource use ordinary readable-channel scope and return only strict aggregate state: normal count, burst count, total count, emoji identity, and whether the verified bot owns a normal or burst reaction. They omit message content, authors, user identities, profiles, burst colors, and unknown raw fields, and they never cache, journal, or persist the response.
list_reaction_users is independently disabled by default because identities are more sensitive than counts. Set capabilities.reactionUserAudit: true and list each eligible channel or thread by its own exact ID in scopes.reactionChannelIds. The feature gate and exact-ID allowlist are enforced before any Discord request, the allowlist must fit inside the read channel allowlist when one exists, and a parent never grants a child thread reaction scope. The tool accepts one Unicode grapheme with an emoji code point or one exact name:snowflake custom emoji, supports separate normal and burst pages, proves strict ascending user-ID order, and returns only IDs and bot flags through a bounded after cursor. Usernames, display names, avatars, profile fields, queries, and raw payloads are discarded and never persisted.
The bot's own normal reaction remains an interaction rather than a moderation action. add_reaction, add_reactions, and remove_own_reaction require capabilities.interactions: true plus the exact interaction-channel allowlist, share the interaction rate budget, validate the same strict emoji grammar, read the exact message first, return a journaled no-op when the requested state already holds, require Discord's exact no-content success status for a write, and read the message again to verify the bot's own state. A mismatched or unreadable postcondition is uncertain rather than reported as success.
add_reactions accepts one ordered set of two to ten unique logical emoji. After pinned identity verification, it validates the complete set before target-channel access or any write, rejects custom emoji aliases that share one ID, resolves the exact channel and message once, and reuses each fresh verified message readback as the next precondition. Every absent reaction must pass its own limiter admission, then receives its own pending record, idempotent PUT operation, and postcondition; every already-owned reaction receives its own no-op record without consuming write budget. Every fresh snapshot must still contain the complete processed prefix, so a concurrently removed earlier reaction stops the set as uncertain instead of producing a false aggregate success. Processing stops at the first failure and never compensates or continues best-effort. The content-free error reports a failed zero-based item index or aggregate-drift boundary plus verified progress counts. Retry the identical ordered set after resolving any reported delay or uncertainty: prior successes become verified no-ops and remaining items continue in the same order. The connector adds no fixed inter-item sleep because Discord's rate-limit headers and the stricter local interaction limiter govern pacing.
Reaction moderation has no immediate-call path. Set capabilities.reactionModeration: true, configure pinned application and bot IDs, list every eligible channel or thread in scopes.reactionChannelIds, and place every ineligible user in scopes.protectedUserIds. The moderation gate, exact-ID allowlist, and protected-user check are enforced before any Discord request. After identity verification, the connector-owned target exclusion is enforced before target-channel or message access. Planning then verifies the exact guild, channel, message, connector identity and membership, complete roles and overwrites, private-thread membership when applicable, and complete channel-level VIEW_CHANNEL, READ_MESSAGE_HISTORY, and MANAGE_MESSAGES evidence. Voice-channel messages additionally require CONNECT; unknown or incomplete permission evidence fails closed.
- Call
plan_reaction_moderationwith exact channel and message IDs, one strict scope-specific request, a local review reason, and a unique one-shot operation key. - Review the verified identities, exact message, complete reaction snapshot, target, permission evidence, privacy guarantees, warnings, operation-key hash, and keyed digest.
- Call
execute_reaction_moderationwith identical intent plus the digest. - Approve the signed MCP confirmation only if every identity, scope, target, permission, local reason, hash, and digest remains intended.
- Review target absence, exact-snapshot verification, activity ID, and outcome before any related operation.
The user scope removes one exact user's normal reaction to one exact emoji and cannot target the connector bot or a protected user. The emoji scope clears every reaction of one exact emoji, including normal and burst reactions. The all scope clears every reaction from the exact message. The two bulk scopes are identity-blind and can remove reactions from locally protected users; scopes.protectedUserIds guards only an exact user target. This limitation is prominent in the signed plan and confirmation. An absent target is a record-free no-op that needs no confirmation, claim, receipt, activity record, or Discord write. The process-keyed digest binds the complete aggregate state, strict scope-specific target, application and bot identities, guild ownership, exact message metadata, complete permission evidence, local reason, privacy projection, warnings, and operation-key hash. Any relevant change invalidates approval.
A real execution rebuilds the plan before coordination, acquires a durable exact-message claim across connector processes sharing the same local activity-state root, rebuilds the plan again inside that claim, atomically reserves the one-shot key, appends pending content-free activity, and issues one exact non-retried DELETE. It then proves target absence and compares the complete aggregate snapshot with the expected post-state. Unrelated concurrent reaction changes produce completed-with-drift; a surviving target, rate limit, server or transport error, malformed evidence, or failed readback is uncertain and retains the exact-message claim for operator review. A known Discord 4xx rejection received without a success response, other than request timeout or rate limit, can settle as failed. The workflow never retries, compensates, restores reactions, or infers a target from a display name.
Discord does not document audit-log reason support for reaction endpoints, so the required reason is local review context and is not sent as a misleading audit header. Durable records contain exact Discord IDs, scope, custom emoji ID when present, a domain-separated keyed emoji fingerprint, plan digest, operation-key hash, timestamps, outcome, and verification only. They never contain Unicode emoji text, custom emoji names, message content, authors, usernames, profiles, burst colors, audit reasons, raw keys, response bodies, or transport causes. After uncertainty, inspect the exact message and Discord audit context before resolving the quarantined claim or forming a new intent. See Discord's reaction endpoint reference for the underlying API operations.
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.