Build a Discord MCP (Model Context Protocol) server that can:
- Read messages across multiple Discord servers and channels
- Send messages to Discord channels
- List Discord servers and channels
- Provide efficient message reading with proper chronological ordering
- Handle authentication with Discord credentials using web scraping
Enable an LLM to:
- Monitor Discord servers and communities of interest
- Read and summarize recent messages from channels
- Send messages to Discord channels
- Discover available servers and channels
This enables automated community monitoring, content aggregation, and interaction across Discord servers for purposes like community engagement, trend monitoring, and content curation.
This implementation uses Playwright web scraping instead of Discord's API because:
- Discord's API only allows reading from servers where you have bot permissions
- Web scraping enables reading from any Discord server you can access as a user
- No need for bot creation or server permissions
- Use
uvpackage manager for all operations - Use
uv runprefix for all Python commands - Use
uv addfor adding dependencies
- Always run
uv run pyrightfor Python type checking - Always run
uvx ruff format .for formatting - Always run
uvx ruff check --fix --unsafe-fixes .for linting
main.py- Entry point that starts the MCP serversrc/discord_mcp/server.py- FastMCP server with 4 tool definitionssrc/discord_mcp/client.py- Playwright-based Discord client with simplified message extractionsrc/discord_mcp/config.py- Configuration management for Discord credentialssrc/discord_mcp/messages.py- Message reading and time filtering logicsrc/discord_mcp/logger.py- Logging setup and configurationtests/test_integration.py- Integration tests for all MCP tools
get_servers- List all Discord servers you have access toget_channels(server_id)- List all channels in a specific Discord serverread_messages(server_id, channel_id, max_messages, hours_back?)- Read recent messages in chronological order (newest first)send_message(server_id, channel_id, content)- Send messages to specific Discord channels (automatically splits long messages)
- mcp - Official MCP library via FastMCP
- playwright - Browser automation for Discord web scraping
- python-dotenv - Environment variable management
- pytest - Testing framework
The implementation prioritizes reliability over speed through:
- Complete browser reset between every MCP tool call using
_execute_with_fresh_client() - Async lock serialization to prevent race conditions
- Cookie persistence at
~/.discord_mcp_cookies.jsonfor login state
- Chronological ordering: Messages returned newest-first
- Simplified extraction: Streamlined from ~130 lines to ~54 lines
- Robust scrolling: JavaScript-based scroll to bottom for newest messages
- Proper filtering: Time-based and count-based message limiting
- Sequential test execution (
-n 0in pytest.ini) to avoid resource conflicts - Comprehensive integration tests covering all 4 MCP tools
- 100% test reliability across multiple runs
- Cookie persistence eliminates re-login overhead
- JavaScript extraction faster than clicking through UI elements
- Fresh browser state adds ~2-3 seconds per tool call but ensures reliability
- Simplified message logic improved performance while maintaining functionality
- Make changes following functional programming patterns
- Run
uv run pyrightfor type checking - Run
uvx ruff format .anduvx ruff check --fix --unsafe-fixes .for code quality - Run
uv run pytest -v tests/for integration testing - Verify all 4 MCP tools work correctly
Set environment variables:
DISCORD_EMAIL=your_email@example.com
DISCORD_PASSWORD=your_password
DISCORD_HEADLESS=true # For productionMessages are returned in chronological order (newest first):
max_messages: 1returns the most recent messagemax_messages: 20returns the 20 most recent messages- More messages means going further back in time chronologically
This ordering was fixed from previous counterintuitive behavior and now works correctly and consistently.