MCP server

Discord MCP

A small, stateless Model Context Protocol server for Discord. It speaks to the Discord REST API on every call, so there is no gateway socket and nothing to babysit - just connect MCP clients and ask.

POST/mcpGET/mcpStreamable HTTP transport, JSON-RPC 2.0

Tools

01

send_message

Post a message to a Discord channel.

channel_id
Discord channel id (snowflake)
content
Plain-text body, up to 2000 chars (optional)
embeds
Rich embed objects, up to 10 (optional)
reply_to
Message id to reply to (optional)
tts
Read the message via text-to-speech (optional)

Returns - The new message object with id, channel_id, timestamp, and author block.

02

read_messages

Read recent messages from a channel.

channel_id
Discord channel id (snowflake)
limit
How many to fetch, 1-100 (default 50, optional)
before
Only return messages older than this id (optional)
after
Only return messages newer than this id (optional)

Returns - An array of messages with author, content, timestamp, embeds, attachments.

03

list_channels

List every channel the bot can see in a guild.

guild_id
Discord guild id (snowflake)

Returns - Channels with id, name, type, parent_id, topic, position.

04

list_guilds

List every guild the bot is a member of.

Returns - Guilds with id, name, icon, owner flag, member counts.

05

search_members

Find members in a guild by username prefix.

guild_id
Discord guild id (snowflake)
query
Username prefix, 1-32 chars
limit
Max matches (default 25, max 1000, optional)

Returns - Members with id, username, global_name, nick, avatar, roles.

06

search_channels

Search a guild's channels by name (case-insensitive substring).

guild_id
Discord guild id (snowflake)
query
Substring to look for in channel names

Returns - Matching channels with id, name, type.

07

get_guild_info

Detail card for one guild plus a channel-type breakdown.

guild_id
Discord guild id (snowflake)

Returns - Guild metadata and channel counts (text, voice, category, forum).

Configure

The server reads these environment variables at startup. Both must be set on the deployment that hosts the MCP endpoint.

Environment variables
# Required: a Discord bot token with the right guild intents.
DISCORD_TOKEN=your-bot-token-here

# Optional: shared-secret auth for the MCP endpoint.
# If set, every request must send the same value in:
#   Authorization: Bearer <key>   or   x-mcp-api-key: <key>
MCP_API_KEY=your-mcp-api-key

Connect a client

Point any MCP client at this URL. The handshake is a normal Streamable HTTP POST with JSON-RPC 2.0 bodies.

Claude Desktop - claude_desktop_config.json
{
  "mcpServers": {
    "discord": {
      "url": "https://<your-host>/mcp",
      "headers": {
        "Authorization": "Bearer <MCP_API_KEY>"
      }
    }
  }
}
curl - initialize, then list tools
# 1. initialize
curl -X POST https://<your-host>/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-03-26",
                 "capabilities":{},
                 "clientInfo":{"name":"curl","version":"0"}}}'

# 2. list tools
curl -X POST https://<your-host>/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
curl - call a tool
curl -X POST https://<your-host>/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{
    "jsonrpc":"2.0",
    "id":3,
    "method":"tools/call",
    "params":{
      "name":"list_guilds",
      "arguments":{}
    }
  }'

How it works

Every call is stateless. The Nitro route constructs a fresh discord.js REST client from DISCORD_TOKEN, routes one JSON-RPC envelope through the MCP transport, then closes both. There is no gateway WebSocket and no per-instance cache, so deploys, cold starts, and route changes are non-events.

Discord rate limits (429s) are honored automatically by the REST client. Permission failures surface as 403 forbidden with a message that names the missing permission, and token problems surface as 401 unauthorizedwith a hint to check DISCORD_TOKEN. Every tool result is wrapped in a stable shape: { ok, ... } on success, { code, message, status } on failure.

Auth - if MCP_API_KEY is set, every request must include the matching value in Authorization: Bearer <key> or the x-mcp-api-key header. When unset, the endpoint is open.

Health - GET /mcp returns the tool list and the current auth mode.