MCP server over the real Discord REST API -- 5 read-only tools always on, plus 7 write tools (create/edit channel, create/edit role, edit guild, delete channel, create message) gated behind DISCORD_MCP_ENABLE_WRITE=1 (default OFF)
discord-mcp presents a competent read-only tool suite (5 tools with solid naming and descriptions) complemented by 7 write tools that are gated and documented. However, there are consistent gaps in schema completeness, parameter descriptions, and error handling guidance. Tool names follow verb_noun conventions well (list_channels, create_role, delete_channel), and descriptions are generally clear about what each tool does. The main weaknesses: (1) missing input schema documentation in the codebase, parameters are declared as Python function arguments but no explicit JSON Schema is visible, forcing inference from type hints; (2) parameter descriptions in function signatures lack the required detail about formats, constraints, and ranges; (3) no documented output schemas, responses are returned as dicts with no schema definition; (4) error handling does not guide LLM recovery (patterns return {"ok": false, "error": {...}} but the error structure is not documented); (5) no schema validation rules (e.g., 'name' must be 1-100 characters, 'type' must be an integer in a specific range). The write tools appropriately gate behind DISCORD_MCP_ENABLE_WRITE=1 and declare their destructive/write nature in descriptions, which is strong. However, destructive tools like delete_channel lack confirmation/dry-run support. Overall, this is a B-/C+ server, functional and reasonably organized, but missing the rigor expected of production tools.
[WRITE, gated behind DISCORD_MCP_ENABLE_WRITE=1] Create a channel (text/voice/category -- any Discord-valid `type` int) in a guild.
[WRITE, gated behind DISCORD_MCP_ENABLE_WRITE=1] Post a message to a text channel. content must be a non-empty string of at most 2000 characters (Discord's real message content limit).
[WRITE, gated behind DISCORD_MCP_ENABLE_WRITE=1] Create a role in a guild.
[WRITE, gated behind DISCORD_MCP_ENABLE_WRITE=1] Delete a channel. Genuinely destructive and irreversible against a real guild -- gated behind the exact same write gate as every other write tool, no separate/lower bar.
[WRITE, gated behind DISCORD_MCP_ENABLE_WRITE=1] Rename/re-topic/re-parent/reorder an existing channel. Only the fields explicitly passed are changed.
Input schemas not formally documented in codebase. Parameters are declared as Python type hints (e.g., `guild_id: str`, `type: int`) but no explicit JSON Schema definitions are visible. This forces reliance on FastMCP's automatic schema inference, which may not capture constraints (e.g., integer ranges, string length limits, enum values). LLMs cannot read type hints, they need structured JSON Schema with descriptions and constraints.
Parameter descriptions are missing or minimal in function signatures. For example, `type: int` parameter in create_channel lacks a description explaining that it accepts Discord channel type values (0=text, 2=voice, 4=category, etc.). LLMs cannot infer valid ranges or meanings from parameter names alone.
Output schemas are not documented. Tools return dicts (e.g., from routes.list_channels, routes.create_channel) but the structure of these responses is not specified. LLMs cannot plan multi-step tool chains without knowing what fields downstream tools expect. For example, does list_channels return channel IDs that can be passed to edit_channel? What is the field name?
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 51 | 2026-07-28+ | v2 |
[WRITE, gated behind DISCORD_MCP_ENABLE_WRITE=1] Update guild-level identity (name/icon/banner/description). icon_base64/banner_base64 are base64-encoded image bytes (assumed PNG unless already a full `data:` URI) -- see README's honest-limitations section.
[WRITE, gated behind DISCORD_MCP_ENABLE_WRITE=1] Edit an existing role. Only the fields explicitly passed are changed.
Get the permission overwrites (role/member allow+deny bitfields) set on a single channel.
Get the role ids currently assigned to one guild member.
List only the category channels (Discord channel type 4) in a guild -- filtered client-side, since Discord has no dedicated categories-only endpoint.
List all channels (every type, including categories and voice channels) in a Discord guild.
List all roles in a Discord guild.
Error handling does not guide LLM recovery. Routes normalize failures to `{"ok": false, "error": {...}}` but the error structure (error code, message, retryability classification) is not documented. LLMs receive an error response with no guidance on whether to retry, ask the user, or abandon the operation. Per pattern:recovery-guide, errors should state what to do next.
Destructive tool (delete_channel) lacks confirmation/dry-run support. Per pattern:confirmation-request, irreversible operations should support a confirmation step or dry-run before executing. delete_channel can permanently destroy a channel with no undo, the tool should return a confirmation token that must be explicitly confirmed, or offer a separate 'confirm_delete_channel' step.
Tool annotations are missing. FastMCP and the MCP protocol support readOnlyHint, destructiveHint, and idempotentHint in tool definitions. These hints help LLMs reason about safety (e.g., list_channels is read-only, delete_channel is destructive). The server declares these properties informally in descriptions ('[WRITE, gated...]') rather than via structured tool annotations.
Optional parameters lack clear guidance on mutual exclusivity and dependencies. For example, edit_channel accepts name, topic, parent_id, and position as optional fields, the description states 'Only the fields explicitly passed are changed' but does not explain what happens if conflicting combinations are passed, or whether position is relative or absolute.
Pagination and result limits are not addressed. list_channels, list_roles, and list_categories return all results with no pagination, limit, or offset parameters. Per pattern:paginated-result, large result sets blow the context window. These tools should accept limit and offset/cursor parameters and return a total_count.
No validation error messages documented. Per review:actionable-errors, tools should return detailed error messages (e.g., 'Invalid channel name: must be 1-100 characters, lowercase letters and hyphens only') rather than generic failures. The server returns {"ok": false, "error": {...}} but the error payload structure is not documented.
Constraints on string and numeric parameters are not specified. For example, create_message accepts 'content' (string) but the description only states 'non-empty, max 2000 characters', what about minimum length? Are emoji and Unicode allowed? Similarly, create_role 'color' parameter accepts an integer but does not specify the valid range (0 - 16777215 for 24-bit RGB, presumably).