The most complete open-source MCP server for Discord.
Give any MCP client (Claude, Cursor, custom agents) full control over Discord — messages, moderation, channels, roles, and more. Born from production use at delfus.app, open-sourced for the community.
- Why mcp-discord?
- Getting Started (for MCP/Claude Users)
- Getting Started (for Bot Developers)
- Usage Examples
- Tools
- Troubleshooting
- Architecture
- Integration Guide
- Configuration
- 80+ tools across 8 categories — servers, channels, messages, reactions, members, roles, moderation, and monitoring
- Dual-mode — run standalone (own process) or integrate as a plugin into your existing discord.js bot
- REST-only or Gateway — choose between lightweight REST-only mode or full WebSocket gateway for real-time features
- Two transports — stdio (default, for Claude Desktop / Claude Code) or HTTP with Bearer token auth
- Production-proven — built and used in production at delfus.app
- Node.js 18 or higher
- A Discord bot token
Install via npm:
npm install @goul4rt/mcp-discordAdd to your MCP config (usually ~/Library/Application\ Support/Claude/claude_desktop_config.json on Mac):
{
"mcpServers": {
"discord": {
"command": "node",
"args": ["-e", "require('@goul4rt/mcp-discord').runStandalone()"],
"env": {
"DISCORD_TOKEN": "your-bot-token-here"
}
}
}
}Restart Claude, then ask:
"List all Discord servers I have access to"
Claude will automatically invoke the list_servers tool and show you the results.
To enable real-time Discord gateway features (like monitoring new messages as they arrive), add this to your config:
{
"mcpServers": {
"discord": {
"command": "node",
"args": ["-e", "require('@goul4rt/mcp-discord').runStandalone()"],
"env": {
"DISCORD_TOKEN": "your-bot-token-here",
"DISCORD_USE_GATEWAY": "true"
}
}
}
}- Node.js 18 or higher
- A Discord bot token
- Existing discord.js bot (or create one from scratch)
# Clone the repository
git clone https://github.com/goul4rt/mcp-discord.git
cd mcp-discord
# Install dependencies
npm install
# Build
npm run buildCopy the example environment file:
cp .env.example .envEdit .env and add your Discord bot token:
DISCORD_TOKEN=your-bot-token-hereRun the MCP server as a separate process (stdio transport):
npm startOr HTTP transport (for remote clients):
npm run start:httpUse IntegratedProvider to embed mcp-discord into your existing discord.js bot:
import { IntegratedProvider, createMcpServer } from '@goul4rt/mcp-discord';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const provider = new IntegratedProvider({ client: myDiscordClient });
await provider.connect();
const server = createMcpServer({ provider });
const transport = new StdioServerTransport();
await server.connect(transport);Ask Claude naturally — it will automatically invoke the right tool:
Example 1: Send a message
> "Send a message to the #general channel saying 'Hello everyone!'"
Claude invokes: `send_message` with `channelId`, `content`
Example 2: Search for a member
> "Find all members in my server with 'admin' in their username"
Claude invokes: `search_members` with `username` filter
Example 3: Moderate a user
> "Timeout the user @Spam for 24 hours with reason 'Spam'"
Claude invokes: `timeout_user` with userId, duration, reason
Example 4: View audit log
> "Show me the last 10 bans in the server"
Claude invokes: `get_audit_log` with action filter
Use these examples as a starting point for your own MCP server or bot integration:
Example 1: Send a message
import { IntegratedProvider } from '@goul4rt/mcp-discord';
const provider = new IntegratedProvider({ client: myBot });
await provider.connect();
const result = await provider.sendMessage(channelId, {
content: 'Hello @members! New announcement:',
embeds: [{ title: 'Update', description: 'System online' }]
});Example 2: Search members
const members = await provider.searchMembers(serverId, {
username: 'admin'
});
console.log(\`Found \${members.length} members matching search\`);
members.forEach(m => console.log(\`\${m.user.username} - roles: \${m.roles.length}\`));Example 3: Moderate a user
await provider.timeoutUser(serverId, userId, {
duration: 24 * 60 * 60 * 1000, // 24 hours in ms
reason: 'Spam',
moderatorId: botId
});Example 4: Fetch audit log
const auditLog = await provider.getAuditLog(serverId, {
limit: 10,
actionType: 'BAN' // Filter by specific action
});
auditLog.entries.forEach(entry => {
console.log(\`\${entry.action}: \${entry.targetId} by \${entry.executorId}\`);
});Example 5: Create a role
const newRole = await provider.createRole(serverId, {
name: 'Muted',
color: 0xFF0000, // Red
mentionable: false
});
console.log(\`Created role: \${newRole.name}\`);Example 6: Create a thread
const thread = await provider.createThread(channelId, {
name: 'Bug: Login fails on Safari',
messageId: originalMessageId,
autoArchiveDuration: 1440 // 1 day
});Example 7: List all roles
const roles = await provider.listRoles(serverId);
roles.forEach(role => {
console.log(\`\${role.name} - members: \${role.memberCount}\`);
});Example 8: Add a reaction
await provider.addReaction(channelId, messageId, {
emoji: '👍' // Unicode emoji
});
// Or custom emoji:
await provider.addReaction(channelId, messageId, {
emoji: '<:myemoji:123456789>'
});| Tool | Description |
|---|---|
list_servers |
List all Discord servers the bot has access to |
get_server_info |
Get detailed info about a specific server |
| Tool | Description |
|---|---|
get_channels |
List all channels in a server |
get_channel |
Get detailed info about a channel |
create_channel |
Create text, voice, category, announcement, forum, or stage channels |
edit_channel |
Edit channel name, topic, NSFW, slowmode, position, category |
delete_channel |
Permanently delete a channel |
create_thread |
Create a thread in a channel (optionally from a message) |
archive_thread |
Archive a thread |
| Tool | Description |
|---|---|
send_message |
Send messages with text, rich embeds, and replies |
read_messages |
Read recent messages with pagination (up to 100) |
search_messages |
Search messages by content, author, or channel |
edit_message |
Edit a bot message |
delete_message |
Delete a single message |
delete_messages_bulk |
Bulk delete 2-100 messages (< 14 days old) |
pin_message |
Pin a message |
unpin_message |
Unpin a message |
| Tool | Description |
|---|---|
add_reaction |
Add emoji reaction (Unicode or custom) |
remove_reaction |
Remove a reaction |
| Tool | Description |
|---|---|
list_members |
List server members with pagination |
get_member |
Get detailed member info (roles, nickname, join date) |
get_user |
Get info about any Discord user by ID |
search_members |
Search members by username or nickname |
| Tool | Description |
|---|---|
list_roles |
List all roles with permissions, colors, and member counts |
create_role |
Create a new role |
add_role |
Add a role to a member |
remove_role |
Remove a role from a member |
| Tool | Description |
|---|---|
timeout_user |
Temporarily mute a user (up to 28 days) |
kick_user |
Kick a user from the server |
ban_user |
Ban a user with optional message deletion |
unban_user |
Unban a user |
| Tool | Description |
|---|---|
get_audit_log |
View server audit log (bans, kicks, changes) |
check_mentions |
Find recent @mentions of the bot or a user |
Symptoms: "Invalid token" error or 401 Unauthorized from Discord API
Solution:
- Verify you copied the token (not the client secret) from Discord Developer Portal
- Ensure the token hasn't been regenerated (regenerating old token invalidates it)
- Check
.envfile has exact format:DISCORD_TOKEN=token-here(no quotes) - Verify bot has at least one permission configured in Developer Portal → OAuth2 → Scopes
Symptoms: Claude says "I don't have access to Discord tools" or tools don't appear
Solution:
- Ensure the MCP server is running:
- For stdio:
npm startshould show "MCP server listening" - For HTTP:
npm run start:httpshould show "HTTP server on port 3100"
- For stdio:
- Restart Claude/Claude Code app after adding the config
- Check config file path:
- Mac:
~/Library/Application\ Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
- Mac:
- Verify
nodecommand is available in your PATH (typenode --versionin terminal)
Symptoms: When using DISCORD_USE_GATEWAY=true, connection hangs or times out
Solution:
- Check your firewall allows WebSocket connections to Discord:
# Test connectivity to Discord gateway curl -I https://gateway.discord.gg - Verify bot has GATEWAY intents enabled in Discord Developer Portal:
- Go to Applications → Your Bot → Bot → Privileged Gateway Intents
- Enable: Message Content Intent, Server Members Intent
- Try without gateway first (
DISCORD_USE_GATEWAY=false) to isolate the issue
Symptoms: Tool executes but fails with "Missing Permissions: SEND_MESSAGES" (or similar)
Solution:
- Check role hierarchy: Bot role must be higher than the role/member it's trying to manage
- Go to Server Settings → Roles
- Ensure @bot role is above target role
- Verify the bot's role has required permissions:
- Go to Server Settings → Roles → @bot role
- Enable the permission in question (e.g., "Send Messages", "Manage Messages")
- If permission is channel-specific, check Channel Settings → Permissions
- Verify bot role isn't being denied the permission
┌─────────────────────────────────────────────────┐
│ MCP Client │
│ (Claude, Cursor, custom) │
└──────────────────┬──────────────────────────────┘
│ stdio or HTTP
┌──────────────────▼──────────────────────────────┐
│ MCP Server │
│ (transport + tool routing) │
└──────────────────┬──────────────────────────────┘
│
┌──────────────────▼──────────────────────────────┐
│ Tool Registry │
│ (80+ tools, Zod validation) │
└──────────────────┬──────────────────────────────┘
│
┌──────────────────▼──────────────────────────────┐
│ DiscordProvider (interface) │
├─────────────────────┬───────────────────────────┤
│ StandaloneProvider │ IntegratedProvider │
│ (own token + REST/ │ (uses host bot's │
│ optional gateway) │ existing connection) │
└─────────────────────┴───────────────────────────┘
│
┌──────────────────▼──────────────────────────────┐
│ Discord API │
└─────────────────────────────────────────────────┘
Provider abstraction: MCP tools never touch discord.js directly. They call the DiscordProvider interface, which has two implementations:
- StandaloneProvider — creates its own connection using a bot token. REST-first with optional gateway. Use when running as a separate process.
- IntegratedProvider — receives an existing discord.js
Clientfrom the host bot. Zero overhead, shared cache and gateway. Use when embedding in an existing bot.
To use mcp-discord as a plugin inside your existing discord.js bot:
import { IntegratedProvider, createMcpServer } from '@goul4rt/mcp-discord';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// Your existing discord.js client
const provider = new IntegratedProvider({ client: myDiscordClient });
await provider.connect();
const server = createMcpServer({ provider });
const transport = new StdioServerTransport();
await server.connect(transport);The IntegratedProvider uses your bot's existing gateway connection — no extra WebSocket, no extra authentication, no extra memory.
| Variable | Required | Default | Description |
|---|---|---|---|
DISCORD_TOKEN |
Yes | — | Discord bot token |
DISCORD_USE_GATEWAY |
No | false |
Connect to Discord WebSocket gateway for real-time features |
MCP_TRANSPORT |
No | stdio |
Transport mode: stdio or http |
MCP_PORT |
No | 3100 |
HTTP server port (only when MCP_TRANSPORT=http) |
MCP_AUTH_TOKEN |
No | — | Bearer token for HTTP transport authentication |
Contributions are welcome! See CONTRIBUTING.md for development setup, code style, and how to add new tools.
Created by @goul4rt. Born from delfus.app, open-sourced for the community.