Reviewed Components V2 messages
Components V2 messages have no immediate-call path. Set capabilities.interactions: true, list every eligible channel or thread by its own exact ID in scopes.interactionChannelIds, pin the expected application and bot IDs, and enable the application's Message Content privileged intent. The interaction allowlist must remain inside configured read scope. A parent grants neither write scope nor private-thread membership to a child. Grant View Channel, Read Message History, and Send Messages for direct channels, or Send Messages in Threads for threads.
The input is a deliberately small layout DSL, not raw Discord JSON. It supports non-blank text displays, separator nodes with explicit normalized divider and spacing defaults, callback-free link-row nodes, authenticated request-row nodes, and top-level container nodes with optional RGB accent color and spoiler state. Either row is valid at the top level or directly inside a container and contains one to five buttons. Every button has one non-blank single-line label of at most 80 Unicode characters. Link buttons additionally require one normalized absolute HTTPS URL of at most 512 Unicode characters. Request Buttons optionally select primary, secondary, success, or danger, defaulting to secondary. Containers must be non-empty and may contain only text, separators, link rows, and request rows, so recursion stops after one level. Every layout must include a text display and is bounded to 40 total nodes, counting each action row and button, 4,000 aggregate Unicode text characters, and 16 KiB of canonical UTF-8 state. Callers cannot provide numeric component IDs or custom IDs. The connector verifies that Discord assigned unique positive 32-bit IDs to rows and buttons, then removes them before semantic comparison.
Link buttons use Discord's style-5 URL behavior and never carry a custom ID, callback, emoji, SKU, or caller-selected style. Every normalized destination origin must exactly match one canonical HTTPS origin in scopes.componentLinkOrigins; an omitted or empty list disables link buttons for both guild and one-to-one private-message writes. A configured origin has the form https://example.com or https://example.com:8443, with no credentials, path, query, fragment, trailing slash, wildcard, duplicate, or noncanonical spelling. Destination URLs may contain a path, query, and fragment. Policy checks run before Discord access during planning and verification, and every plan exposes the exact normalized URLs and unique sorted origins for review.
The connector does not fetch a link, perform DNS resolution, follow a redirect, inspect a destination, or claim that the allowlisted first-hop origin is the page a Discord client ultimately opens. Treat every destination as untrusted presentation data and review the complete URL, including its path, query, and fragment. Discord clients and destination servers remain outside the connector's control.
Managed request Buttons are the sole custom-ID exception. Callers choose only a visible label and optional visual style. The connector derives each ID from a domain-separated HMAC and never returns it in a plan, activity record, operation receipt, diagnostic, or telemetry record. The danger style changes presentation only; it cannot bypass the broker, authorize a write, select an administration action, or invoke a callback registry. Caller-selected custom IDs, disabled states, emojis, selects, modals, arbitrary callbacks, sections, thumbnails, media galleries, files, attachments, remote-media URL fields, raw numeric component types, arbitrary JSON, remote templates, and arbitrary template variables remain absent. Text Display content may contain ordinary Discord markdown links, which the connector displays in the exact review but never fetches.
Managed request Buttons
Section titled “Managed request Buttons”A request row is available only when the same process has ready native Interaction ingress for the exact guild and channel. Configure capabilities.nativeInteractions: true plus the exact guild, channel, and user allowlists, install the connector's managed guild command through its separate reviewed workflow, and leave the application's outgoing Interaction endpoint unset. Planning and final replanning freshly verify the pinned application and bot, unset outgoing endpoint, and complete guild-command inventory, so publication fails before a Discord write when the paired broker is absent, checking, stopped, identity-drifted, endpoint-conflicted, command-drifted, evidence-unavailable, or outside exact native Interaction scope. The plan exposes verified Gateway delivery, the exact ready guild, authorized user IDs, command ID, command version, phase, and schema, binds all of it into the digest, and becomes stale if that evidence changes before execution.
The generated route binds the pinned application and bot, exact guild and channel, complete normalized layout, and one-shot operation-key hash. Each button tag additionally binds the route, global button index, exact label, and style. IDs stay within Discord's custom-ID limit and need no route database. A normal restart with the same token can authenticate an existing published row. Token rotation intentionally invalidates its routes as well as receipt verification. Old clicks then fail closed, so publish fresh replacement messages after rotating the token rather than attempting to edit a row whose former authentication can no longer be proven.
Click handling is intentionally narrower than a generic component callback. The broker accepts only Button component events with the managed ID prefix, verifies the attached source message before acknowledgement, defers privately, then freshly reads the exact source message and complete managed-command inventory. The fresh message must still be connector-authored Components V2 state in the same application, guild, channel, and message, with the same authenticated route, button index, label, and style. Only an exact allowlisted user can create a pending request. The visible button label becomes transient untrusted request text; no Discord write or administration operation runs automatically. Unrelated custom IDs are ignored, while malformed or stale managed IDs receive a fixed private rejection when possible.
For example, add two private broker requests to a reviewed layout:
[ { "kind": "text", "content": "Choose a private request" }, { "kind": "request-row", "buttons": [ { "label": "Summarize this release", "style": "primary" }, { "label": "Assess blockers", "style": "danger" } ] }]The pending request then follows the native Interaction response lifecycle. Select menus, modals, arbitrary custom-ID routing, public responses, automatic tool calls, and direct moderation actions remain outside this boundary. One-to-one private-message Components V2 deliberately reject request rows because the native broker is exact-guild scoped.
Typed local templates
Section titled “Typed local templates”compile_component_template provides a versioned local authoring layer over that same DSL. It accepts one strict discriminated shape, rejects unknown fields, validates Unicode and single-line labels, chooses semantic accent colors, derives presentation text, and immediately runs the ordinary component-layout and notification review. The compiler does not inspect connector configuration, use the bot token for Discord access, contact a service, write a file or activity record, grant Discord authority, or send a message; the ordinary MCP result boundary still applies secret redaction. The result returns ready-to-copy normalized components, its template name and version, the complete mention and safety review, fixed privacy and authority evidence, and exact plan, execute, and verify tool names. discord://connector/component-templates exposes the data-free catalog and limits to resource-aware clients.
| Template | Required fields | Optional and derived behavior |
|---|---|---|
announcement | headline, body, priority | Priority is information, important, or urgent; it supplies accessible status text and the semantic accent; optional cta has one strict { label, url } link button |
incident-status | title, status, summary | Status is investigating, identified, monitoring, or resolved; impact and single-line nextUpdate are optional; status supplies accessible text and the semantic accent |
poll-results | question, two to ten uniquely labeled { label, votes } options | Vote totals, singular or plural wording, and one-decimal percentages are derived deterministically; callers cannot provide a winner or precomputed chart |
release-notes | releaseName, summary, one to eight changes | Change order is preserved and rendered as a fixed list; optional cta has one strict { label, url } link button |
welcome-card | headline, introduction, one to eight steps | Step order is preserved and rendered as a fixed numbered path |
All template narrative text and CTA destinations remain untrusted transient content. Headlines, questions, release names, option labels, changes, steps, next-update text, and CTA labels are single-line; narrative bodies remain multiline. A visible user mention not repeated in notifyUserIds renders quietly, while a requested notification still requires that exact visible mention and the independently configured user allowlist during planning. The compiler preserves caller text, normalizes CTA URLs, and does not evaluate placeholders, inspect policy, or fetch URLs. Compilation alone grants no link or Discord authority; the reviewed message or private-message workflow enforces scopes.componentLinkOrigins before Discord access.
For example, compile a status card locally:
{ "template": "incident-status", "title": "API latency incident", "status": "monitoring", "summary": "Latency has returned to the normal range.", "impact": "Some requests were delayed.", "nextUpdate": "After the observation window", "notifyUserIds": []}For one typed outbound CTA, compile an announcement and separately allow its exact origin before publication:
{ "template": "announcement", "headline": "Service guide updated", "body": "The reviewed operating guide is ready.", "priority": "information", "cta": { "label": "Read the guide", "url": "https://docs.example.com/guides/service" }, "notifyUserIds": []}Then follow one unchanged reviewed lifecycle:
- Call
compile_component_templatefor one named callback-free template, or callpreview_component_layoutto validate and normalize a custom layout locally. Review the deterministic outline, explicit defaults, recursive action-row, link-button, and request-button counts, exact normalized link URLs and origins, aggregate Unicode length, mentioned and suppressed user IDs, notification projection, and warnings. When compiling, copy the returned exactcomponentsandreview.notificationUserIdswithout reinterpreting them. - Call
plan_component_messagewithaction: "create"oraction: "edit", the exact channel, normalized layout intent, explicit notification users, and a unique one-shot operation key. Create may include one exact reply and a separate reply-author notification choice; edit requires one exact message ID and forbids reply fields. - Review the verified application, bot, guild, channel or thread, parent and private-thread membership where applicable, confirmed intent, complete role and overwrite evidence, required and effective permissions, current already-V2 edit state, target layout and preview, exact link destinations and origins, request-button count and exact ingress readiness when present, notifications, reply, irreversible flag warning, privacy projection, operation-key hash, and keyed digest.
- If an edit plan reports
already-current, the live layout matches and its parsed user-mention state is empty. Call execution with the matching digest to receive a record-free no-op. No confirmation, claim, receipt, activity record, rate budget, or Discord mutation is needed. - For a real change, call
execute_component_messagewith identical intent plus the digest and approve the signed confirmation only if every identity, target, layout, permission, notification, warning, hash, and digest remains intended. - Review the exact returned message ID, jump URL, response and readback match, activity ID, and terminal outcome before any follow-up.
- Retain the exact request. Call
verify_component_messageafter completion or a connector restart to bind it to the durable receipt and check the receipt-bound exact message without supplying a plan digest or trusted create message ID.
Creation sets Discord's irreversible IS_COMPONENTS_V2 message flag and sends a channel-bound deterministic nonce with uniqueness enforcement. It may create either a default message or an exact reply. Editing is narrower: the target must already be a default Components V2 message owned by the verified bot, with no webhook owner, poll, content, attachment, embed, sticker, unsupported component, mismatched identity field, or reply conversion. The connector never turns a legacy message into V2 and never changes message identity, flags, pin state, creation timestamp, or reply reference while editing.
Text Display mentions follow the same deny-by-default notification policy as plain-text sends. Every notified user must be present as a visible <@user-id> mention somewhere in the normalized layout and must be listed in scopes.mentionUserIds. Role, @everyone, and @here notifications remain disabled. Create reply-author notification is a separate reviewed boolean whose exact reply author must be locally authorized. Edits cannot notify a reply author.
The process-keyed digest binds the complete normalized layout and link destinations, fresh request-button command evidence and exact authorized user IDs, live target state, identities, intent, exact channel, link-origin, and native Interaction scope, thread evidence, permission evidence, reply, notifications, operation-key hash, privacy projection, and warnings. The adapter replans before elicitation, and the service replans again before mutation. A real create acquires a durable exact-channel claim; a real edit acquires a durable exact-message claim. Both consume the shared interaction budget, atomically reserve the one-shot key, append pending content-free activity, and send one non-retried POST or PATCH.
The mutation response and a fresh exact GET must both prove the reviewed bot, guild, channel, message type, V2 flag, empty legacy content fields, reply identity, assigned component IDs, authenticated request-button route when present, and semantically exact normalized layout. A known non-rate-limited Discord 4xx before a response is failed. Transport ambiguity, rate limiting, server failure, malformed response evidence, response mismatch, readback failure, or local completion-record failure is uncertain; the key remains spent and the durable target claim remains quarantined for operator review. The connector never retries, rolls back, or deletes a created message as compensation.
Verification recomputes a domain-separated HMAC over the normalized caller-retained request and the pinned application and bot identities. Production derives its receipt-verification and request-button keys from the bot token under separate domains, independently of the process-bound plan-approval key, so a normal restart preserves both bindings while token rotation intentionally invalidates old verification requests and published request-button routes. A request mismatch, nonterminal receipt, or receipt target mismatch stops before Discord access. A matching completed receipt triggers fresh identity, intent, exact scope, thread, reply, notification-policy, and read-permission checks, followed by one exact message GET and request-button authentication when present. The result is content-free and distinguishes verified, live drifted, locally blocked, and not-found outcomes. Verification does not require a ready broker, send permission, inspect the write coordinator, reserve a key, append activity, consume the interaction limiter, scan channel history, or infer a created message from content.
Component text, layouts, link destinations and origins, request-button custom IDs and authenticated routes, notification and parsed-mention IDs, mention profiles, generated numeric component IDs, raw payloads, nonce, and raw operation key never enter activity records, operation receipts, diagnostics, or telemetry. Durable component-publication records contain only exact guild, channel, optional reply, and resulting message IDs, keyed request and plan digests, operation-key hash, timestamps, fixed action, verification and outcome values, activity ID, and a sanitized error category. A click activity may additionally retain the exact source message ID, button index, and fixed style, but never the label, request text, custom ID, or route. See Discord's component overview, component reference, message resource reference, and Interaction response contract for the underlying platform contracts.
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.