Skip to content

Capability-safe guild invite creation, audit, and revocation

Invite creation has its own channel-level gate and does not depend on invite audit. Set capabilities.inviteCreation: true, list every eligible direct channel in scopes.inviteCreationChannelIds, add the invites toolset, and configure one or more dedicated directories in storage.inviteCapabilityRoots. The channel scope must stay inside a nonempty outer channel allowlist when one is configured. Each root must already exist, be canonical, be owned by the process user, sit below the filesystem root, and not be writable by a group or the world. Persistent role assignment is independently disabled: set capabilities.inviteRoleAssignment: true, list every eligible role in scopes.inviteRoleIds, and set gateway.enabled: true. The role gate requires the creation gate, a nonempty exact role allowlist, pinned identities, and the nonprivileged Gateway layout.

The workflow accepts only an exact guild and channel, a required tagged acceptance mode, a required tagged role-assignment mode, a schema-bounded finite lifetime, a schema-bounded positive use limit, explicit temporary-membership intent, an audit reason, a one-shot operation key, a bearer-capability acknowledgement, and an absolute output file. { "kind": "bearer" } creates a finite capability usable by anyone who obtains it. { "kind": "exact-users", "userIds": ["..."] } additionally restricts acceptance to one bounded nonempty canonical set of unique exact Discord user IDs. { "kind": "none" } creates no role grant. { "kind": "grant", "roleIds": ["..."], "acknowledgePersistentGrants": true } requests one bounded nonempty canonical set of separately allowlisted exact role IDs and explicitly acknowledges that Discord does not remove those roles when the invite expires or is deleted. Existing guild members can accept the invite and receive those roles, so role grants cannot claim temporary membership and must never be described as new-member-only access. Text, announcement, voice, Stage, forum, and media channels are supported. Categories, directories, direct messages, every thread type, voice-stream and embedded-application targets, scheduled-event projection, guest flags, unlimited lifetimes, unlimited uses, reusable invite selection, caller-provided CSV, and other invite variants are outside this intentionally narrow contract. Every request forces Discord's unique flag so a pre-existing capability is never returned as if the connector had created it.

Planning verifies the pinned application and bot, exact guild ownership, bot membership, complete bounded role and visible channel evidence, the target's complete permission overwrites, and effective channel-level VIEW_CHANNEL plus CREATE_INSTANT_INVITE. Exact-user acceptance additionally requires guild-level MANAGE_GUILD as specified by Discord's targeted-invite tutorial, while bearer acceptance keeps the narrower permission set. Role assignment additionally requires guild-level MANAGE_ROLES, one unambiguous highest connector role, and a complete ready Gateway layout that exactly reconciles with every direct channel returned over HTTP; any obfuscated channel, missing metadata, topology mismatch, or layout change blocks planning. Every selected role must exist, be standard and unmanaged, exclude @everyone, sit strictly below the connector, grant no ADMINISTRATOR or unknown bit, and contain only permissions the connector itself can grant. Every selected-role channel overwrite must contain no unknown bit, and the connector must hold every channel permission the selected roles can confer. The plan projects a minimum new ordinary member before and after the role set across every direct channel, reports exact guild and changed-channel permission decisions plus high-risk gains, and blocks an impact too large for human review. This is a point-in-time proof rather than a permanent ceiling: later role permission or channel-overwrite edits can alter the authority attached to the granted role before or after invite acceptance. Planning also proves that the output path is an absent direct child of a configured private root. The digest and signed confirmation bind all identities, the exact canonical acceptance and role sets, complete role, overwrite, hierarchy, Gateway, and permission-impact evidence, finite intent, output path and root review, privacy boundary, warnings, and one-shot key hash. Exact target user IDs and Discord names remain transient plan evidence and never enter lifecycle records.

Execution requires the identical request, fresh matching plan, write approval, and signed interactive confirmation. It durably coordinates the exact channel, guild invite collection, and every selected role, reserves the operation key, records pending content-free activity, and exclusively creates the absent output target with no-follow semantics and mode 0600 before contacting Discord. Bearer mode sends one non-retried JSON create request. Exact-user mode generates a UTF-8 CSV with the exact user_id header and canonical reviewed rows, then sends one non-retried multipart create request whose payload_json contains the finite invite intent. Either transport includes role_ids only for the exact reviewed role set. No caller-supplied bytes, role payload, or multipart field can enter that request.

The connector strictly validates the returned invite, including the exact assigned-role set, before writing the file. Bearer mode performs an independent unauthenticated exact identity and role readback. Exact-user mode requires the created code to appear exactly once with matching metadata and roles in the authenticated bounded guild inventory, then polls the authenticated target-user job under a fixed local bound, requires strict completed counts, fetches the authenticated bounded CSV, and requires its canonical set to match every reviewed ID. Only then does the connector write and sync one bounded discord-invite-capability.v3 JSON document. The file contains the code, fixed-origin URL, guild and channel IDs, creation and expiration timestamps, reviewed limits, temporary-membership value, acceptance kind and target count, role-assignment kind and exact role IDs, and a fixed warning that grants persist while role and channel authority remains mutable. It omits the exact target user IDs so sharing the file with one intended recipient does not disclose the others. The MCP result contains only the caller-supplied output path, an irreversible opaque reference, exact public guild, channel, and assigned-role IDs, acceptance and role counts, digest, and verification state. Codes, URLs, exact target-user lists, role names, and CSV never enter MCP lifecycle records, errors, logs, telemetry, operation receipts, or activity review.

A known non-rate-limited Discord client refusal before an invite can exist settles as failed and removes the connector's empty reservation. Transport ambiguity, rate limiting, server failure, malformed response, identity or assigned-role mismatch, target-user job failure or timeout, target-user count or CSV mismatch, or file-write failure after dispatch is uncertain; the key remains spent and the exact channel stays quarantined for operator review. Verification failures occur before capability material is written, so the empty local reservation is discarded while the remote invite can remain with an undisclosed code and may grant roles if accepted. A failure after the final private write retains that file because deleting it cannot revoke an invite Discord accepted. The connector never retries, creates a replacement, updates the target or role set, revokes automatically, removes roles, or treats local cleanup as remote compensation.

Discord exposes no conditional primitive that freezes a target-user set between authenticated verification and private-file delivery. The durable claims prevent overlapping connector writes, but operators must prevent external invite administration during exact-user issuance. Every exact-user plan discloses this residual race and the possibility that an uncertain asynchronous job leaves a remote invite requiring manual inventory review.

Use the workflow in this order:

  1. Prepare a dedicated private directory and add its canonical path to storage.inviteCapabilityRoots.
  2. Enable capabilities.inviteCreation, add the exact target to both the outer read-channel scope and scopes.inviteCreationChannelIds, expose the invites toolset, and grant the bot View Channel plus Create Instant Invite on that channel. Grant Manage Server only when exact-user acceptance is needed. For role assignment, also enable capabilities.inviteRoleAssignment and gateway.enabled, list every exact eligible role in scopes.inviteRoleIds, grant Manage Roles, and keep the connector above every selected role with every permission those roles can confer.
  3. Call plan_invite_creation with explicit bearer or exact-user acceptance, explicit no-role or acknowledged role-grant intent, the exact finite intent, absent direct-child output file, audit reason, acknowledgement, and unique operation key.
  4. Review every exact target user and role when applicable, persistent-grant warning, hierarchy, guild and channel permission impact, complete Gateway evidence, file-boundary evidence, finite intent, external-administration race, warnings, operation-key hash, and digest.
  5. Call execute_invite_creation with the identical request and digest, then approve the signed confirmation only if every displayed field remains intended.
  6. Read the resulting private file through the trusted local consumer that needs the bearer capability. Exact-user mode confirms only the acceptance kind and target count in that file; the reviewed plan is the transient exact-user record. Role-grant mode includes the exact assigned role IDs and persistence warning because accepting the capability changes durable membership authority. Do not paste its code or URL back into an MCP conversation or activity record.

list_guild_invites, get_guild_invite, and get_guild_vanity_url use the existing invite-audit authority for a separately allowlisted guild. Set capabilities.inviteAudit: true and list every eligible guild in scopes.inviteGuildIds. The invite allowlist must be a subset of readScope.guildIds when the outer read allowlist exists. Every read verifies the expected application and bot, exact guild and owner, connector membership, complete bounded roles, and effective guild-level MANAGE_GUILD permission. Inventory reads additionally require visibility-bounded channel evidence. An invite whose channel Discord omits is rejected rather than reconstructed. The connector deliberately does not offer a reduced VIEW_AUDIT_LOG mode because Discord includes complete invite metadata only for callers with MANAGE_GUILD.

An invite code or URL is a bearer capability. The REST client therefore keeps the code only in transient private state and immediately projects every raw response to bounded metadata. MCP results expose a process-keyed HMAC reference, exact channel identity and type, inviter user ID without a profile, creation and expiration time, usage limits and counts, temporary-membership state, target kind and ID, known and unknown flags, and any granted role IDs with named, unknown, and high-risk permission evidence. Risk flags call out prior use because Discord documents that deleting an invite does not remove roles granted by earlier uses. Guild objects, invite URLs, inviter and target profiles, role names and visuals, scheduled-event and stage objects, application metadata, target-user acceptance sets, approximate counts, and unknown raw fields are omitted. Guild inventory does not expose the exact-user CSV, so audit results identify that omission instead of guessing whether an invite is restricted. The opaque reference cannot be converted back into a code and expires when the connector process restarts.

Discord's guild-invite endpoint is not documented as paginated. The connector fetches one complete inventory under a fixed local safety ceiling, validates every invite against the complete channel and role evidence, sorts the opaque references, and then pages locally. Each continuation cursor is authenticated and binds the exact guild, inventory digest, and next offset. Following a cursor fetches and validates another complete fresh inventory; any addition, removal, use-count change, metadata change, cursor edit, or process restart rejects the page and requires pagination to restart. Exact lookup likewise resolves a known opaque reference only through a complete fresh inventory. The exact discord://guilds/{guildId}/invites/{inviteRef} resource uses the same policy, validation, and projection.

get_guild_vanity_url audits the guild's persistent custom invite without adding configuration. It reuses capabilities.inviteAudit, scopes.inviteGuildIds, and the invites toolset because a vanity code is a public bearer capability. The tool reports whether Discord's VANITY_URL guild feature is present, whether a code is configured, the documented usage count when eligible, complete owner or MANAGE_GUILD evidence, and count-only unknown response fields. A guild without the feature returns a successful ineligible result without calling the vanity endpoint.

The code is transient untrusted data. includeCode defaults to false, and the result's codeDisclosure field distinguishes redaction from an unconfigured vanity URL. Setting includeCode: true returns the exact code but never a full URL. The discord://guilds/{guildId}/vanity-url resource always forces code disclosure off. Codes never enter tool summaries, resource reads, logs, metrics, traces, errors, receipts, activity records, or any other persistent state.

The audit validates bounded unique guild features and the guild object's nullable vanity_url_code, then calls Discord's documented GET /guilds/{guild.id}/vanity-url route only when the feature is present. It requires the endpoint code to match the guild object and fails closed on concurrent change, malformed evidence, or incomplete permission evidence. Discord's public guild resource reference documents the read endpoint and MANAGE_GUILD requirement but no vanity mutation route, so the connector does not offer an undocumented write.

Invite revocation has no immediate-call path. Keep capabilities.inviteDeletions disabled unless permanent capability removal is needed; enabling it also requires invite audit to remain enabled and uses the same exact guild allowlist. The MCP schema accepts an opaque reference rather than a code or URL, and the audit reason rejects the target code and invite URLs before mutation.

  1. Call list_guild_invites and identify the intended capability by its opaque reference, channel, lifetime, use limits, target, granted roles, permission evidence, and risk flags.
  2. Call plan_invite_deletion with the exact guild ID, opaque reference, Discord audit-log reason, and unique one-shot operation key.
  3. Review the verified application and bot IDs, exact guild and channel, target metadata, complete MANAGE_GUILD evidence, inventory bounds, privacy omissions, risk warnings, operation-key hash, and keyed digest.
  4. Call execute_invite_deletion with identical inputs plus the digest.
  5. Approve the signed MCP confirmation only if every exact identity, omission, permission, risk, audit reason, operation-key hash, and digest remains intended.
  6. Review the returned opaque reference, channel ID, verified-absence result, activity ID, and outcome before any follow-up.

The process-keyed HMAC digest binds the normalized request, one-shot operation-key hash, verified application and bot identities, exact guild and owner, complete projected invite inventory, complete channels and roles, connector membership, effective permission evidence, privacy projection, inventory bounds, and warnings. A connector restart invalidates both the reference and digest. The MCP adapter rebuilds the plan before approval, and the service rebuilds it immediately before mutation. Normal invite use changes the reviewed snapshot, so an active invite may require a new plan rather than silently revoking state that no longer matches the review.

Execution atomically reserves the operation-key hash and appends a pending content-free activity record before one non-retried bot-authenticated DELETE. The code-bearing route is replaced with a fixed diagnostic route, response bodies and transport causes cannot enter errors, and observability receives only the fixed operation name. A successful response must identify the exact reviewed code, guild, channel, and invite type. The connector then fetches the complete inventory again and requires the opaque reference to be absent. Verified absence is completed; a valid inventory in which the reference remains is completed-with-drift; a known pre-write Discord 4xx is failed; transport failure, Discord 5xx, malformed success, failed identity validation, or failed readback is uncertain. Every reserved key remains permanently spent, and the connector performs no automatic retry, compensating write, or capability disclosure.

Discord deletes an invite by its secret code and offers no conditional deletion primitive that can atomically bind the preceding inventory review. The connector narrows this unavoidable race with a complete fresh inventory immediately before deletion, a plan digest covering that inventory, returned-target validation, and complete absence readback. Prevent external invite administration during a high-risk revocation. Same-reference executions serialize inside one service process, while the production facade acquires a durable exact guild invite-collection claim that excludes overlapping connector processes sharing the activity-state root. An uncertain outcome spends the key and retains that claim for operator review.

Activity and operation records contain only the exact guild and channel IDs, opaque invite reference, plan digest, operation-key hash, timestamps, fixed verification and outcome values, activity ID, and sanitized error category. Created invite codes and URLs, Discord-returned target-user CSV and projected readback, guild and user profiles, role names, channel names, audit reasons, raw operation keys, raw responses, and transport causes from code-bearing routes never enter MCP results, resources, persistent records, diagnostics, or telemetry. Caller-supplied exact target IDs appear only in the transient plan and signed confirmation. Tool and prompt schemas expose no invite-code or URL field. Operators must not paste a bearer capability into the free-text audit reason; the connector rejects invite URLs locally and rejects the exact target code after fresh lookup. See Discord's create channel invite contract, invite target-user contract, and permission flags.

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.