Configuration
The operational interface is one strict versioned non-secret JSON document plus only the secrets referenced by that document. A typical deployment therefore has one policy file and one bot-token secret. The document covers verified identity, read scope, tool selection, Gateway behavior, every capability and exact feature scope, bounded limits, owned local storage roots, runtime settings, and credential-free observability. Its $schema field points at the published JSON Schema for editor support.
Read scope is explicit. readScope.guildMode and readScope.channelMode accept allowlist or all-visible. An allowlist mode requires at least one effective exact ID, while an all-visible mode requires an empty ID array. all-visible is convenient for a bot whose Discord installation is itself tightly controlled, but a later Discord-side permission or guild-installation expansion becomes readable without another local policy edit. Portable profiles require an exact guild allowlist, and multi-guild policies should normally retain exact allowlists. Existing schema-v2 files that omit these mode fields keep their previous meaning: a non-empty array normalizes to allowlist, while an empty array normalizes to all-visible.
Top-level groups provide typed reusable sets for guilds, channels, users, and roles. A compatible scope may reference a group as @name; validation expands that reference to exact sorted snowflakes before policy use. Groups never resolve Discord names, never cross ID domains, cannot nest, and cannot make duplicate effective IDs valid. Use them to eliminate repeated configuration edits, not to weaken exact targeting.
Notification and thread behavior are also explicit. notifications.userMentions accepts disabled, allowlist, or reviewed. threads.reads and threads.messageWrites accept exact or inherit. Omitted fields preserve the earlier schema-v2 behavior of allowlisted user mentions, inherited thread reads, and exact child-thread writes. New setup and recipe output writes these choices explicitly.
{ "groups": { "channels": { "support": ["CHANNEL_ID"] }, "users": { "on-call": ["USER_ID"] } }, "notifications": { "userMentions": "reviewed" }, "readScope": { "channelIds": ["@support"], "channelMode": "allowlist", "guildIds": ["GUILD_ID"], "guildMode": "allowlist" }, "threads": { "messageWrites": "inherit", "reads": "inherit" }}The selected non-secret policy path may be a protected regular file or an explicit final symbolic link to a protected stable target. A regular target may also have additional hard links. Inspection binds the resolved target and stable file metadata, replacement rejects link retargeting or source drift, and publication preserves a selected symbolic link while atomically replacing its target. This accommodation is limited to non-secret policy. Credential files, webhook capability files, attachment inputs, and other secret or capability-bearing local files retain their stricter topology rules.
limits.mcpReadResponseMaxBytes is the strict application-result boundary. The default is 1 MiB, the minimum is 64 KiB, and the maximum is 8 MiB, leaving transport framing headroom below the pinned MCP SDK's 10 MiB stdio buffer. The connector measures compact JSON.stringify output in UTF-8 only after recursive secret redaction. A larger value is an authority expansion in configuration review because more transient Discord data may cross one MCP response; a smaller value is a reduction.
The boundary applies to every read-only tool result, every resource result including the plan-review MCP App, every rendered prompt, and every pre-write input_required result. An oversized tool read returns a schema-valid fixed response-too-large error with the configured limit and recovery path but no measured byte count, content preview, digest, identifier, argument, or withheld field. Resources and prompts fail with bounded InvalidParams errors. The connector never truncates, compresses, paginates after the fact, spills, caches, or tokenizes an oversized result. Final results and errors from mutation-capable tools bypass the read budget because a Discord mutation may already have occurred and its outcome must not be hidden. Protocol initialization, catalog discovery, list responses, list-change events, and notifications are outside this application-result budget; use risk-separated toolsets and progressive discovery to bound catalog exposure.
Complete tool results retain their concise human-readable summary as the first text block and their exact typed application result in structuredContent. Reviewed plans, reviewed lifecycle outcomes, and fixed safe tool errors with validated continuation evidence add one compact text block beginning with GUILDCONTROL_RECEIPT . Its strict versioned JSON projection may contain only the result schema version, a closed status, validated SHA-256 or HMAC-SHA-256 digests, a closed next action, write-required state, and fixed safe error category, code, and retry state. It never copies Discord content, profile labels, resource names, audit reasons, paths, URLs, raw operation keys, arbitrary error text, or unknown fields from the structured result. The receipt is generated only after recursive secret redaction, participates in the complete-result byte measurement, and gives model-, client-, and harness-neutral text consumers enough content-free evidence to continue reviewed workflows. Ordinary reads and input-required continuations remain unchanged.
Create and verify the first file directly against the caller-owned bot:
export DISCORD_BOT_TOKENprintf 'Discord bot token: 'read -r -s DISCORD_BOT_TOKENprintf '\n'guildctl setup \ --config ./guildcontrol.json \ --preset server-observer \ --guild-id YOUR_GUILD_IDguildctl config validate ./guildcontrol.jsonguildctl doctor --config ./guildcontrol.json --onlineguildctl smoke --config ./guildcontrol.jsonconfig init FILE creates a preset-backed file from caller-supplied public IDs without contacting Discord. config validate FILE uses placeholder secrets to check strict structure and every cross-field policy without reading a token or contacting Discord. config show FILE returns the canonical document and a bounded summary; config explain [PATH] returns schema-backed descriptions for the whole document or one field. Add --json for versioned reports. --force retains a recoverable hidden backup and cannot replace the pinned application or bot identity. There is no environment-policy or automatic configuration-import command; migrate emits offline guidance only.
Offline configuration workbench
Section titled “Offline configuration workbench”config workbench ACTIVE_FILE --html OUTPUT_FILE validates one protected schema-v2 policy without resolving its credential or contacting Discord, then writes a standalone editor with exclusive private permissions. The output parent must be a canonical directory owned by the process user and not writable by a group or the world, and an existing output target is never replaced. Add --json for a versioned export report.
The workbench embeds the complete non-secret active document, schema-derived field metadata, public Discord identity and scope IDs, local paths, and external secret reference names. It never embeds a secret value. Treat the HTML as a private operator artifact because those non-secret details can still be sensitive. The page contains no external navigation target or browser persistence API. Its content security policy permits only the exact embedded style and script while blocking network connections, forms, frames, workers, objects, and media, and the page exposes a candidate only through an explicit local download or copy action. The command does not open a browser, write the active policy, construct a Discord client, create activity state, or grant approval.
The page keeps edits in memory, locks the schema version and application and bot identities, gives every non-secret policy field a schema-derived control, supports search, filters, reset, complete canonical preview, and preliminary local field and authority-impact guidance. These checks intentionally do not duplicate the full cross-field policy engine. Download the candidate to a distinct protected path, then run config replace ACTIVE_FILE CANDIDATE_FILE; it supplies authoritative validation, exact semantic impacts, canonical tool exposure, warnings, identity proof, confirmation, fresh locked recomputation, and atomic replacement. Use config plan and config apply when review and application must occur in separate commands.
Reviewed configuration replacement
Section titled “Reviewed configuration replacement”Use config replace ACTIVE_FILE CANDIDATE_FILE for a deliberate edit that is not covered by an additive recipe. Both paths must identify distinct protected policy files or accepted explicit final links that satisfy the bounded canonical-path, ownership, mode, strict-JSON, schema-v2, target-stability, and complete cross-field policy checks. The application and bot IDs must match exactly. A different Discord identity belongs in a separate configuration rather than a replacement plan.
The versioned plan compares parsed canonical documents while preserving meaningful omitted-versus-explicit optional fields. Each exact path receives a category for read scope, capability, feature scope, scope group, notification policy, thread policy, tool surface, Gateway, limits, storage, observability, runtime, credential reference, or metadata. Its impact is classified as authority expansion, authority reduction, authority redistribution, operational change, or metadata only. Field-specific rules account for explicit all-visible modes, effective expanded group membership, inverse protected-user and write-interval limits, Gateway retention, optional defaults, local roots, telemetry destinations, and mixed allowlist replacement. The report also resolves exact canonical tools added or removed by toolset changes, aggregates impacts, emits deterministic warnings, includes the complete non-secret candidate, and provides structured validation, online-doctor, and smoke commands.
The command first prints that complete plan and asks for the active policy's exact name. After confirmation it recomputes the plan from fresh reads, obtains an exclusive file lock, rejects semantic drift or link retargeting, writes and verifies the exact candidate atomically, and retains a recoverable hidden backup. JSON and other non-interactive use requires both --accept-current-plan and --confirm ACTIVE_NAME. An already-current replacement is a no-write, no-backup result.
Planning and replacement read no secret value, construct no Discord client, contact no Discord endpoint, open no Gateway, start no telemetry exporter, and write no activity state. The domain-labeled SHA-256 digest binds the normalized active and candidate paths, their resolved target evidence, both canonical document digests, exact change set, tool exposure, warnings, and required confirmation. Moving or changing either file therefore requires fresh review.
For a detached review, call config plan ACTIVE_FILE CANDIDATE_FILE, retain its digest, then call config apply ACTIVE_FILE CANDIDATE_FILE --plan-digest DIGEST --confirm ACTIVE_NAME. Application recomputes the plan from fresh reads, checks the exact digest and active-policy-name confirmation, and applies the same locked publication and verification boundary. The candidate is never modified in either flow.
This generic workflow can add, remove, or redistribute authority, so the impact report is evidence for operator review rather than permission by itself. A local policy replacement does not grant Discord permissions, enable Developer Portal intents, install the bot, or prove live access. Run the emitted offline validation, online doctor, and read-only smoke checks after application. Curated recipes remain preferable for supported additive workflows because they also encode Discord permissions, intents, risk, and scope relationships. No environment-policy compatibility, legacy alias, fallback parser, or migration-plan warning participates in either path.
Expanding the user mention allowlist
Section titled “Expanding the user mention allowlist”Writing <@USER_ID> as ordinary visible text does not require a notification allowlist. With no matching ID in notifyUserIds, GuildControl sends the text while suppressing the notification. The policy applies only when a caller deliberately asks Discord to notify that exact user.
notifications.userMentions offers three risk-calibrated modes:
disabledrejects every user notification request, which is appropriate for archival, reporting, and read-mostly deploymentsallowlistpermits only exact IDs already expanded fromscopes.mentionUserIds, which creates a stable unattended automation ceilingreviewedpermits allowlisted IDs directly and lets a supporting write workflow present every otherwise unlisted, visibly mentioned exact user for fresh signed approval
The allowlist remains useful because a notification reaches a person outside the connector and can become spam, phishing, or accidental escalation. Channel permission alone answers whether the bot may post, not whom automation may interrupt. The earlier requirement to edit policy for every occasional recipient, however, made that protection disproportionately expensive. Reviewed mode preserves exact IDs, visible intent, host approval, content and target binding, and fresh confirmation without treating every one-off recipient as permanent automation authority.
Not every write workflow can provide that signed review. A path without conditional MCP elicitation still calls the direct authorization boundary and therefore accepts only configured allowlisted IDs. Role mentions, @everyone, and @here remain suppressed in all three modes. Reply-author notification remains separately explicit and, in reviewed mode, binds the freshly fetched exact reply author into the same confirmation.
If a write returns Discord user USER_ID is outside the notification scope, the selected policy is in allowlist mode or the called path cannot review an exception. Discord user notifications are disabled by connector configuration means the mode is disabled. Discord user USER_ID requires signed interactive notification review means reviewed mode is active but the called path does not implement that review. These boundaries are separate from Discord roles and channel permissions, Developer Portal intents, and ordinary MCP host write approval.
Call search_guildcontrol_docs with that exact error text to retrieve this recovery section at runtime. The search is local, bounded, credential-free, and available regardless of the selected Discord toolsets. It returns excerpts from fixed public files packaged with the running GuildControl version, contacts no service, persists neither query nor result, and grants no authority.
For a recurring automated recipient, resolve the intended person to one exact Discord user ID, create a distinct private candidate from the active non-secret policy, and add the ID to scopes.mentionUserIds without removing existing entries. A typed user group is also valid when several compatible fields share the same stable set:
{ "groups": { "users": { "on-call": ["USER_ID"] } }, "notifications": { "userMentions": "allowlist" }, "scopes": { "interactionChannelIds": ["CHANNEL_ID"], "mentionUserIds": ["@on-call"] }}Names and nicknames are not policy targets. A canonical typed mention can supply its exact user ID through parse_discord_reference, while a separately enabled member directory can resolve a username-or-nickname prefix through search_guild_members followed by exact member review.
Review and apply the candidate through the integrated configuration replacement workflow:
guildctl config replace ACTIVE_FILE CANDIDATE_FILEguildctl config validate ACTIVE_FILEguildctl doctor --config ACTIVE_FILE --onlineguildctl smoke --config ACTIVE_FILEDetached config plan and config apply remain available when separate review is required. In either flow, confirm only after reviewing the complete candidate and effective expanded authority. Applying changes the policy file but not an already running serve process, so reload the MCP host after the checks pass. Call get_connector_status from the reloaded connection and confirm that the notification mode and mentionUserCount reflect the intended policy before relying on it.
For an occasional recipient, set notifications.userMentions to reviewed without adding that user to permanent scope, then use a write path that advertises conditional notification review. The request must still target an authorized channel, include <@USER_ID> visibly in the submitted content, and list the same ID in notifyUserIds. The signed prompt shows the exact target, content evidence, requested recipients, reply author where applicable, and fresh permission evidence. Declining or losing freshness sends nothing. A plain @Name is only text.
Offline doctor also inspects the selected policy when its referenced bot credential is missing or unreadable. Unlike config validate, it reports runtime, credential availability, identity pins, and the complete effective safety posture in one report. An unavailable credential fails only its credential check while independent policy checks continue. doctor --online requires the real credential and makes no Discord request when that credential is unavailable.
Selecting a file with --config FILE or GUILDCONTROL_CONFIG_FILE makes that document the exclusive policy source. The CLI resolves a relative --config path, while the environment selector must contain an absolute canonical path. Startup permits only the selector and the exact secrets referenced by the document. It rejects every other populated GUILDCONTROL_* or OTEL_* variable and every undeclared Discord token variable so ambient state cannot override, extend, or ambiguously combine with file policy. A config file and profile are mutually exclusive.
Run guildctl host --npx --config FILE --html PRIVATE_FILE for a stable exact-version package launch and private interactive activation guide. The descriptor supplies the server name, pinned command and ordered arguments, exact secret environment variables and file paths, and recommended startup and tool timeouts. It also declares that the server should be required, writes should require host approval, and reviewed writes require MCP elicitation. Never copy a secret value into JSON, TOML, or another static host configuration.
The command always projects that verified activation plan into a deterministic guildcontrol.host-adapters.v1 catalog in this fixed order: claude-code, codex, cursor, vscode, gemini-extension, and mcp-json. Every entry includes the activation digest, its own domain-separated adapter digest, exact format-specific configuration and newline-terminated rendered bytes, host server name, destinations, secret strategy and variable names, required-server, approval, elicitation, and timeout contract, instructions, limitations, and the official host-schema source. Verification regenerates the complete catalog from the activation plan and requires canonical equality. No adapter accepts or reads a credential value.
Add --adapter ID to append one adapter's exact configuration and guidance to human output. This selection writes no file and does not change JSON output shape. Add --json to receive the complete activation plan with adapterCatalog; automation therefore retains every projection and digest even when --adapter is also present. The optional mode-0600 guide renders every adapter between the canonical process contract and secret-custody checklist. Its Cursor URI remains copyable text rather than an active link, and the page retains a no-network, no-navigation content security policy.
| Adapter | Exact projection | Credential behavior |
|---|---|---|
claude-code | Top-level mcpServers entry with explicit type: "stdio" for project .mcp.json or a Claude Code-managed scope | Emits ${NAME} references only; Claude Code expands them from protected process state at launch |
codex | Exact mcp_servers.NAME TOML table with command, ordered arguments, named environment forwarding, required-server behavior, write approval, and timeouts | Emits only env_vars names; Codex forwards those variables from protected process state |
cursor | Top-level mcpServers, explicit type: "stdio", and a base64 Cursor MCP install URI containing the exact server entry | Emits ${env:NAME} references only; the private URI is policy-specific and contains local launch arguments |
vscode | Top-level servers and password-masked promptString inputs | Maps each variable to ${input:guildcontrol-credential-N}; omits sandboxEnabled because VS Code auto-approves tools for sandboxed MCP servers; interactive-input servers are not forwarded to Agent Host sessions |
gemini-extension | Complete policy-specific gemini-extension.json with a safe digest-derived extension and server alias | Declares each environment variable as a sensitive: true setting and maps it through ${NAME}; Gemini CLI supplies its sensitive keychain path |
mcp-json | Top-level mcpServers with exact command and argument order | Omits an env field because no secret-reference syntax is portable across the common convention; the protected host process must already contain every named variable |
For a file-backed credential policy, every projection omits environment forwarding, input, and extension-setting fields and records credential-file as the strategy. The policy selector already points the connector to the exact protected file. The generated Gemini manifest is a private policy-specific local extension, not a generic signed distribution artifact. The Cursor URI likewise initiates only a review flow. No projection proves host installation, installed-version compatibility, secret availability, approval behavior, elicitation, process startup, or Discord access.
Reviewed host configuration installation
Section titled “Reviewed host configuration installation”Use host plan and host apply when the selected host stores one supported static JSON document and the operator wants a reviewed merge instead of manual editing. Codex TOML remains an exact manual projection and is rejected before the JSON installer reads a destination:
guildctl host plan --npx --config ./guildcontrol.json --adapter mcp-json --host-file /absolute/path/to/mcp.jsonguildctl host apply --npx --config ./guildcontrol.json --adapter mcp-json --host-file /absolute/path/to/mcp.json --plan-digest PLAN_DIGEST --confirm HOST_SERVER_NAMEBoth actions regenerate the exact activation and adapter from the selected installed release, launcher, policy, optional server name, and adapter. They accept only the explicit host path; neither searches user directories, chooses a host, creates a parent, resolves a connector credential, contacts Discord or another network endpoint, starts a process, creates activity state, or changes connector policy. The parent must already be a canonical directory owned by the process user and not writable by group or world where portable metadata exists. A target may be absent. An existing target must pass the same canonical, regular, single-link, byte- and structure-bounded, stable, duplicate-free strict-JSON, trusted-owner, and private-mode checks as inspection. Non-finite numbers, unsafe integers, and negative zero fail closed because native JSON rewriting cannot preserve them safely.
The guildcontrol.host-change-plan.v1 report returns the activation, adapter, and host-server identities; target state; create, update, or no-op decision; shared-merge or dedicated-replacement strategy; owned server-entry change; generated VS Code input counts; unrelated-state treatment; canonical-rewrite and backup requirements; exact confirmation; fixed limitations; and a domain-separated SHA-256 plan digest. It returns no selected path, observed value, raw document, unrelated entry, credential material, or stable hash of host bytes. The digest binds the normalized target internally, exact activation and adapter, fixed change summary, and stable directory and file identity metadata including nanosecond change and modification times where Node exposes them. An ordinary content edit, replacement, creation, removal, relink, mode change, ownership change, or target switch therefore invalidates the plan. A party with complete candidate activation, adapter, path, and metadata can test already-suspected private references against the digests, so the digests are not anonymity mechanisms. Because private bytes are deliberately excluded, metadata freshness is not a cryptographic content commitment against a privileged filesystem adversary.
Shared Claude Code, mcp-json, Cursor, and VS Code targets preserve every unrelated top-level value and server entry semantically. The owned server collection is created when absent, and the owned server entry is added, replaced, or retained exactly. VS Code additionally preserves unrelated inputs, adds or replaces each generated input ID, and rejects duplicate generated IDs rather than guessing which one to remove. A non-object existing server collection or non-array existing VS Code input collection fails closed. Gemini extension output owns a dedicated manifest, so its plan reports complete-document replacement and no unrelated-state preservation. Every changed document is serialized as bounded UTF-8 JSON with canonical indentation and a trailing newline; JSON whitespace is therefore normalized.
Apply first requires the exact generated server-name confirmation and a syntactically valid plan digest, then acquires a private exclusive sibling lock and recomputes the whole plan. A stale digest fails before destination mutation. An exact destination is a verified no-op with no temporary output or backup. Otherwise apply writes and syncs a private exclusive temporary document, rereads the source bytes and complete stable metadata, and refuses any intervening change. A replacement copies the complete original to an owner-mode sibling backup and rechecks the source before atomically replacing it. Creation uses no-clobber publication. Directory metadata is synced where supported, the exact published bytes are reread, and the ordinary adapter inspector must report match. Failed publication verification restores and rereads the original or removes a newly created target only while the published binding and bytes remain exact. If another writer changes the destination during verification, apply preserves that state and returns uncertainty instead of overwriting it. The connector-owned sibling lock cannot prevent an external editor that ignores it from racing the final publication window. Owner-only modes and directory synchronization have portable proof only where the platform exposes those filesystem semantics.
The guildcontrol.host-change-apply.v1 report returns the reviewed change, exact adapter identities, plan digest, post-apply inspection, changed-or-unchanged status, and whether a backup was created. A successful replacement returns its backup path because recovery requires locating the retained original; no destination path or file content is returned. That backup contains the complete prior file, including any secret a host had already stored. Parsed JavaScript strings cannot be reliably erased from the runtime heap, although temporary byte buffers are cleared where practical. Remove the backup through the operator's normal recoverable file workflow only after the host reload and read-only checks succeed. A process interruption may retain the target's hidden lock, temporary artifact, or backup. Never treat the lock as stale automatically: first establish that no apply operation is running, retain every backup, move only the exact stale lock or temporary artifact through a recoverable file workflow, and create a new plan rather than reusing the earlier digest.
Successful installation proves only one exact static publication. It does not prove the host uses that path, accepts that schema, retained the file, resolved a secret, honors approval or elicitation, started the server, negotiated MCP, or accessed Discord. Reload the host, run static inspection, run smoke, and complete one read-only host request.
Host configuration drift inspection
Section titled “Host configuration drift inspection”After manual merge or reviewed installation, compare the destination file with the exact adapter regenerated from the installed release and selected policy:
chmod 600 /absolute/path/to/mcp.jsonguildctl host --npx --config ./guildcontrol.json --adapter mcp-json --inspect-host-file /absolute/path/to/mcp.jsonReplace mcp-json and the destination with the adapter actually installed. --inspect-host-file requires --adapter; it never searches a home directory, chooses a host, or changes the selected file. The default launcher checks the installed entrypoint, --npx checks the package's exact current version, and --command COMMAND checks that exact custom launcher. A file produced for another release, policy selection, server name, command, or argument order therefore reports drift rather than being compared with a mutable registry tag.
The inspector accepts one explicit canonical regular single-link JSON file between two bytes and 1 MiB, refuses symbolic path components, and verifies a safe canonical parent plus stable file identity, size, and timestamps around an exact read. On platforms with portable ownership and mode metadata it requires the parent to be process-owned and not writable by group or world, requires the process user or root as file owner, and forbids every group or world file permission. Other platforms report file ownership and access as platform-unverified instead of claiming a private boundary. JSON must be one complete duplicate-free, structure-bounded, safely representable document; malformed, oversized, ambiguous, missing, linked, exposed, or unstable input fails with a fixed path-free error.
For mcp-json and cursor, only the exact named mcpServers entry is compared. For vscode, the named servers entry and each generated password input ID must match exactly once. Other servers, root fields, and input records in those shared files are deliberately ignored. A gemini-extension destination is a dedicated manifest, so its complete document is compared. The owned projection detects missing or invalid structure and exact command, ordered arguments, transport, environment reference, server option, sensitive-input, and extension-field drift.
Human and JSON reports expose only the adapter and activation identities, domain-separated inspection digest, fixed difference categories, match or drift state, file-check booleans, safe counts, limitations, and privacy assertions. They never return the selected host path, raw document, observed values, unrelated state, credential material, or a digest derived from private host bytes. Activation, adapter, and inspection digests can confirm already-suspected private launcher references and are not anonymity mechanisms. The command may read credential material already present in the explicitly selected file, but it does not resolve the connector's credential, contact a network or Discord endpoint, launch a process, create activity state, or persist anything.
Exact match returns status 0. Drift returns status 1 with the same complete report and leaves the destination byte-for-byte unchanged. Usage, file-safety, or parsing failure returns status 2 through the ordinary redacted CLI error contract. For drift, rerun host plan, review and apply or manually merge only the owned projection while preserving unrelated shared-host entries, reload the host, and rerun inspection. A match proves only that one static file snapshot; finish with smoke --config FILE to prove the real serve child, MCP negotiation, catalog, configured startup behavior, and read-only Discord identity path.
A standalone descriptor runs serve --config ABSOLUTE_FILE and sets no identity or policy environment values. A profile descriptor runs serve --profile NAME with the same secret-only boundary. Profiles use private managed storage and load the same complete saved policy directly; only its exact referenced secrets are resolved from caller-owned sources. Unsupported schema versions fail closed; no older profile or environment-policy shape is accepted.
Credential delivery
Section titled “Credential delivery”The configuration stores exactly one Discord bot credential reference, never its value. The portable default names an environment variable:
{ "credential": { "provider": "environment", "variable": "DISCORD_BOT_TOKEN" }}This works with MCP host secret settings and external secret launchers. For example, 1Password op run and Bitwarden Secrets Manager bws run inject secrets only into the child process environment, so the connector needs no provider SDK, account session, or permission to execute a secret-manager command. Treat the launched command as trusted because same-user process inspection and command injection remain outside the connector's boundary.
Runtimes that project secrets as files can use the file provider:
{ "credential": { "path": "/run/secrets/discord_bot_token", "provider": "file" }}The path must be absolute and canonical in the policy. The connector resolves a projected-secret symlink, opens the final target without following another link, and requires a stable regular file with one hard link, a bounded non-empty size, root or process-user ownership, and no group or world write bits. It then performs one bounded read, requires valid UTF-8 containing one control-free token, and verifies path and metadata stability again before accepting the value. Token content never appears in errors, reports, launch descriptors, logs, or persistent state. An ambient DISCORD_BOT_TOKEN conflicts with a file reference instead of becoming a fallback.
This shape works with Kubernetes Secret volumes and Docker secrets when the projected target satisfies those ownership and mode checks. Use config init --token-file ABSOLUTE_FILE when creating a policy offline from public IDs, or setup --preset ... --token-file ABSOLUTE_FILE when the file is already mounted and the bot can be verified online. The credential is read once during configuration activation, so restart the connector after rotating either secret input. The MCP Registry entry keeps the environment provider as its default because it is the portable host interface; operators can replace the generated credential reference with a file-backed policy without changing connector behavior.
Private webhook credential custody
Section titled “Private webhook credential custody”Webhook creation and every webhook message capability require one additional non-secret path in the same strict policy:
{ "storage": { "webhookCredentialRoot": "/var/lib/guildcontrol/webhook-credentials" }}Create that canonical directory before startup with ownership matching the connector process and exact 0700 mode. The connector never creates or relaxes the root. A successful reviewed Incoming-webhook creation writes one newline-terminated credential as {webhookId}.token with exact 0600 mode and exclusive creation, synchronizes the file, verifies it again, and synchronizes the containing directory before reporting custody. Verified removal also synchronizes the directory before reporting success. Every read reopens that exact file without following links and verifies its canonical path, owner, type, link count, size, mode, identity, and stable root before returning the token only to the private Discord transport call. Existing files are never overwritten, credentials are never accepted through MCP inputs, and there is no token alias, URL parser, discovery scan, or environment fallback.
The path is operational policy rather than a credential value, so it may appear in the local configuration and offline workbench. It is deliberately absent from MCP results, errors, diagnostics, telemetry, activity records, and operation receipts. Back up and permission this directory as bearer-secret storage. Moving or sharing it changes credential custody, not Discord or connector authorization; exact capability gates and channel scope remain mandatory.
When using the published package directly, configure the stdio command as npx with arguments --yes, guildctl@0.3.1, serve, --config, and the absolute file path. Pinning the package version prevents an unreviewed update from replacing the executable. The Registry's npm entry prompts for the config path as a file argument and only for the bot-token secret. Its OCI entry additionally supplies the hardened runtime flags, prompts for one read-only bind mount, and fixes the in-container path. The canonical descriptor remains host-neutral while the separate verified adapters project only documented host schemas; no projection edits or replaces a destination file.
Restart or reload the MCP host after changing its configuration, inspect the negotiated server, and confirm that required-server behavior, write approval, elicitation, and timeouts match the descriptor before enabling reviewed write policies. A host without MCP elicitation can use read-only and plan-only capabilities but must not execute reviewed writes.
Canonical source: docs/reference.md
Documentation generated for guildctl@0.3.1. 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.