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.
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'guildcontrol setup \ --config ./guildcontrol.json \ --preset server-observer \ --guild-id YOUR_GUILD_IDguildcontrol config validate ./guildcontrol.jsonguildcontrol doctor --config ./guildcontrol.json --onlineguildcontrol 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 plan ACTIVE_FILE CANDIDATE_FILE; only that command supplies authoritative validation, exact semantic impacts, canonical tool exposure, warnings, identity proof, and a fresh digest for config apply.
Reviewed configuration replacement
Section titled “Reviewed configuration replacement”Use config plan ACTIVE_FILE CANDIDATE_FILE for a deliberate edit that is not covered by an additive recipe. Both paths must identify distinct protected regular files that satisfy the bounded canonical-path, ownership, link, mode, strict-JSON, schema-v2, 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, 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 the empty channel list's all-visible-channels boundary, 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.
Planning reads no secret value, constructs no Discord client, contacts no Discord endpoint, opens no Gateway, starts no telemetry exporter, and writes no configuration or activity state. Its domain-labeled SHA-256 digest binds the normalized active and candidate paths, both canonical document digests, exact change set, tool exposure, warnings, and required confirmation. Moving or changing either file therefore requires a new plan.
Use config apply ACTIVE_FILE CANDIDATE_FILE --plan-digest DIGEST --confirm ACTIVE_NAME only after reviewing the complete plan. Application recomputes the plan from fresh reads, checks the exact digest and active-policy-name confirmation, and rejects malformed evidence, identity drift, either stale document, concurrent target changes, removal, or unsafe publication state. The existing exclusive private writer compares the active policy again under its lock, writes and verifies the exact candidate atomically, and retains a recoverable hidden backup. The candidate is not modified. An already-current plan is a no-write, no-backup result. Application still reads no secret and contacts no Discord endpoint.
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.
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 guildcontrol 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 the JSON document 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: mcp-json, cursor, vscode, and gemini-extension. Every entry includes the activation digest, its own domain-separated adapter digest, exact canonical JSON 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 JSON and guidance to human output. This selection writes no file and does not change JSON 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 |
|---|---|---|
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 |
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 |
For a file-backed credential policy, every projection omits env, input, and extension-setting secret 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. Neither 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:
guildcontrol host plan --npx --config ./guildcontrol.json --adapter mcp-json --host-file /absolute/path/to/mcp.jsonguildcontrol 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 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.jsonguildcontrol 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, guildcontrol@0.1.2, 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 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.