├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | .env 4 | 5 | # Build output 6 | build/ 7 | dist/ 8 | 9 | # Logs 10 | *.log 11 | 12 | # Environment variables 13 | .env 14 | .env.local 15 | .env.*.local 16 | 17 | # IDE/Editor specific 18 | .vscode/ 19 | .idea/ 20 | *.swp 21 | *.swo 22 | 23 | # OS specific 24 | .DS_Store 25 | Thumbs.db -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord MCP Server 2 | 3 | A Model Context Protocol (MCP) server that enables LLMs to interact with Discord channels, allowing them to send and read messages through Discord's API. Using this server, LLMs like Claude can directly interact with Discord channels while maintaining user control and security. 4 | 5 | ## Features 6 | 7 | - Send messages to Discord channels 8 | - Read recent messages from channels 9 | - Automatic server and channel discovery 10 | - Support for both channel names and IDs 11 | - Proper error handling and validation 12 | 13 | ## Prerequisites 14 | 15 | - Node.js 16.x or higher 16 | - A Discord bot token 17 | - The bot must be invited to your server with proper permissions: 18 | - Read Messages/View Channels 19 | - Send Messages 20 | - Read Message History 21 | 22 | ## Setup 23 | 24 | 1. Clone this repository: 25 | ```bash 26 | git clone https://github.com/yourusername/discordmcp.git 27 | cd discordmcp 28 | ``` 29 | 30 | 2. Install dependencies: 31 | ```bash 32 | npm install 33 | ``` 34 | 35 | 3. Create a `.env` file in the root directory with your Discord bot token: 36 | ``` 37 | DISCORD_TOKEN=your_discord_bot_token_here 38 | ``` 39 | 40 | 4. Build the server: 41 | ```bash 42 | npm run build 43 | ``` 44 | 45 | ## Usage with Claude for Desktop 46 | 47 | 1. Open your Claude for Desktop configuration file: 48 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 49 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 50 | 51 | 2. Add the Discord MCP server configuration: 52 | ```json 53 | { 54 | "mcpServers": { 55 | "discord": { 56 | "command": "node", 57 | "args": ["path/to/discordmcp/build/index.js"], 58 | "env": { 59 | "DISCORD_TOKEN": "your_discord_bot_token_here" 60 | } 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | 3. Restart Claude for Desktop 67 | 68 | ## Available Tools 69 | 70 | ### send-message 71 | Sends a message to a specified Discord channel. 72 | 73 | Parameters: 74 | - `server` (optional): Server name or ID (required if bot is in multiple servers) 75 | - `channel`: Channel name (e.g., "general") or ID 76 | - `message`: Message content to send 77 | 78 | Example: 79 | ```json 80 | { 81 | "channel": "general", 82 | "message": "Hello from MCP!" 83 | } 84 | ``` 85 | 86 | ### read-messages 87 | Reads recent messages from a specified Discord channel. 88 | 89 | Parameters: 90 | - `server` (optional): Server name or ID (required if bot is in multiple servers) 91 | - `channel`: Channel name (e.g., "general") or ID 92 | - `limit` (optional): Number of messages to fetch (default: 50, max: 100) 93 | 94 | Example: 95 | ```json 96 | { 97 | "channel": "general", 98 | "limit": 10 99 | } 100 | ``` 101 | 102 | ## Development 103 | 104 | 1. Install development dependencies: 105 | ```bash 106 | npm install --save-dev typescript @types/node 107 | ``` 108 | 109 | 2. Start the server in development mode: 110 | ```bash 111 | npm run dev 112 | ``` 113 | 114 | ## Testing 115 | 116 | You can test the server using the MCP Inspector: 117 | 118 | ```bash 119 | npx @modelcontextprotocol/inspector node build/index.js 120 | ``` 121 | 122 | ## Examples 123 | 124 | Here are some example interactions you can try with Claude after setting up the Discord MCP server: 125 | 126 | 1. "Can you read the last 5 messages from the general channel?" 127 | 2. "Please send a message to the announcements channel saying 'Meeting starts in 10 minutes'" 128 | 3. "What were the most recent messages in the development channel about the latest release?" 129 | 130 | Claude will use the appropriate tools to interact with Discord while asking for your approval before sending any messages. 131 | 132 | ## Security Considerations 133 | 134 | - The bot requires proper Discord permissions to function 135 | - All message sending operations require explicit user approval 136 | - Environment variables should be properly secured 137 | - Token should never be committed to version control 138 | - Channel access is limited to channels the bot has been given access to 139 | 140 | ## Contributing 141 | 142 | 1. Fork the repository 143 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 144 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 145 | 4. Push to the branch (`git push origin feature/amazing-feature`) 146 | 5. Open a Pull Request 147 | 148 | ## License 149 | 150 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 151 | 152 | ## Support 153 | 154 | If you encounter any issues or have questions: 155 | 1. Check the GitHub Issues section 156 | 2. Consult the MCP documentation at https://modelcontextprotocol.io 157 | 3. Open a new issue with detailed reproduction steps -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-mcp-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "discord-mcp-server", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.2.0", 13 | "discord.js": "^14.14.1", 14 | "dotenv": "^16.4.7", 15 | "zod": "^3.22.4" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20.11.16", 19 | "typescript": "^5.3.3" 20 | } 21 | }, 22 | "node_modules/@discordjs/builders": { 23 | "version": "1.10.0", 24 | "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.0.tgz", 25 | "integrity": "sha512-ikVZsZP+3shmVJ5S1oM+7SveUCK3L9fTyfA8aJ7uD9cNQlTqF+3Irbk2Y22KXTb3C3RNUahRkSInClJMkHrINg==", 26 | "license": "Apache-2.0", 27 | "dependencies": { 28 | "@discordjs/formatters": "^0.6.0", 29 | "@discordjs/util": "^1.1.1", 30 | "@sapphire/shapeshift": "^4.0.0", 31 | "discord-api-types": "^0.37.114", 32 | "fast-deep-equal": "^3.1.3", 33 | "ts-mixer": "^6.0.4", 34 | "tslib": "^2.6.3" 35 | }, 36 | "engines": { 37 | "node": ">=16.11.0" 38 | }, 39 | "funding": { 40 | "url": "https://github.com/discordjs/discord.js?sponsor" 41 | } 42 | }, 43 | "node_modules/@discordjs/collection": { 44 | "version": "1.5.3", 45 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", 46 | "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", 47 | "license": "Apache-2.0", 48 | "engines": { 49 | "node": ">=16.11.0" 50 | } 51 | }, 52 | "node_modules/@discordjs/formatters": { 53 | "version": "0.6.0", 54 | "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz", 55 | "integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==", 56 | "license": "Apache-2.0", 57 | "dependencies": { 58 | "discord-api-types": "^0.37.114" 59 | }, 60 | "engines": { 61 | "node": ">=16.11.0" 62 | }, 63 | "funding": { 64 | "url": "https://github.com/discordjs/discord.js?sponsor" 65 | } 66 | }, 67 | "node_modules/@discordjs/rest": { 68 | "version": "2.4.2", 69 | "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.2.tgz", 70 | "integrity": "sha512-9bOvXYLQd5IBg/kKGuEFq3cstVxAMJ6wMxO2U3wjrgO+lHv8oNCT+BBRpuzVQh7BoXKvk/gpajceGvQUiRoJ8g==", 71 | "license": "Apache-2.0", 72 | "dependencies": { 73 | "@discordjs/collection": "^2.1.1", 74 | "@discordjs/util": "^1.1.1", 75 | "@sapphire/async-queue": "^1.5.3", 76 | "@sapphire/snowflake": "^3.5.3", 77 | "@vladfrangu/async_event_emitter": "^2.4.6", 78 | "discord-api-types": "^0.37.114", 79 | "magic-bytes.js": "^1.10.0", 80 | "tslib": "^2.6.3", 81 | "undici": "6.19.8" 82 | }, 83 | "engines": { 84 | "node": ">=18" 85 | }, 86 | "funding": { 87 | "url": "https://github.com/discordjs/discord.js?sponsor" 88 | } 89 | }, 90 | "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { 91 | "version": "2.1.1", 92 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", 93 | "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", 94 | "license": "Apache-2.0", 95 | "engines": { 96 | "node": ">=18" 97 | }, 98 | "funding": { 99 | "url": "https://github.com/discordjs/discord.js?sponsor" 100 | } 101 | }, 102 | "node_modules/@discordjs/util": { 103 | "version": "1.1.1", 104 | "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", 105 | "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", 106 | "license": "Apache-2.0", 107 | "engines": { 108 | "node": ">=18" 109 | }, 110 | "funding": { 111 | "url": "https://github.com/discordjs/discord.js?sponsor" 112 | } 113 | }, 114 | "node_modules/@discordjs/ws": { 115 | "version": "1.2.0", 116 | "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.0.tgz", 117 | "integrity": "sha512-QH5CAFe3wHDiedbO+EI3OOiyipwWd+Q6BdoFZUw/Wf2fw5Cv2fgU/9UEtJRmJa9RecI+TAhdGPadMaEIur5yJg==", 118 | "license": "Apache-2.0", 119 | "dependencies": { 120 | "@discordjs/collection": "^2.1.0", 121 | "@discordjs/rest": "^2.4.1", 122 | "@discordjs/util": "^1.1.0", 123 | "@sapphire/async-queue": "^1.5.2", 124 | "@types/ws": "^8.5.10", 125 | "@vladfrangu/async_event_emitter": "^2.2.4", 126 | "discord-api-types": "^0.37.114", 127 | "tslib": "^2.6.2", 128 | "ws": "^8.17.0" 129 | }, 130 | "engines": { 131 | "node": ">=16.11.0" 132 | }, 133 | "funding": { 134 | "url": "https://github.com/discordjs/discord.js?sponsor" 135 | } 136 | }, 137 | "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { 138 | "version": "2.1.1", 139 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", 140 | "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", 141 | "license": "Apache-2.0", 142 | "engines": { 143 | "node": ">=18" 144 | }, 145 | "funding": { 146 | "url": "https://github.com/discordjs/discord.js?sponsor" 147 | } 148 | }, 149 | "node_modules/@modelcontextprotocol/sdk": { 150 | "version": "1.3.0", 151 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.3.0.tgz", 152 | "integrity": "sha512-no7j22gAE5dYZ65PppPfbnevO5GiX8b53LA7tb6KfUrmHMFac4ciESZctoivC97aoH4i0xQBtTzli1Q+wpMy5w==", 153 | "license": "MIT", 154 | "dependencies": { 155 | "content-type": "^1.0.5", 156 | "raw-body": "^3.0.0", 157 | "zod": "^3.23.8", 158 | "zod-to-json-schema": "^3.24.1" 159 | }, 160 | "engines": { 161 | "node": ">=18" 162 | } 163 | }, 164 | "node_modules/@sapphire/async-queue": { 165 | "version": "1.5.5", 166 | "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", 167 | "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", 168 | "license": "MIT", 169 | "engines": { 170 | "node": ">=v14.0.0", 171 | "npm": ">=7.0.0" 172 | } 173 | }, 174 | "node_modules/@sapphire/shapeshift": { 175 | "version": "4.0.0", 176 | "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", 177 | "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", 178 | "license": "MIT", 179 | "dependencies": { 180 | "fast-deep-equal": "^3.1.3", 181 | "lodash": "^4.17.21" 182 | }, 183 | "engines": { 184 | "node": ">=v16" 185 | } 186 | }, 187 | "node_modules/@sapphire/snowflake": { 188 | "version": "3.5.3", 189 | "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", 190 | "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", 191 | "license": "MIT", 192 | "engines": { 193 | "node": ">=v14.0.0", 194 | "npm": ">=7.0.0" 195 | } 196 | }, 197 | "node_modules/@types/node": { 198 | "version": "20.17.14", 199 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz", 200 | "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==", 201 | "license": "MIT", 202 | "dependencies": { 203 | "undici-types": "~6.19.2" 204 | } 205 | }, 206 | "node_modules/@types/ws": { 207 | "version": "8.5.13", 208 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", 209 | "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", 210 | "license": "MIT", 211 | "dependencies": { 212 | "@types/node": "*" 213 | } 214 | }, 215 | "node_modules/@vladfrangu/async_event_emitter": { 216 | "version": "2.4.6", 217 | "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", 218 | "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", 219 | "license": "MIT", 220 | "engines": { 221 | "node": ">=v14.0.0", 222 | "npm": ">=7.0.0" 223 | } 224 | }, 225 | "node_modules/bytes": { 226 | "version": "3.1.2", 227 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 228 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 229 | "license": "MIT", 230 | "engines": { 231 | "node": ">= 0.8" 232 | } 233 | }, 234 | "node_modules/content-type": { 235 | "version": "1.0.5", 236 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 237 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 238 | "license": "MIT", 239 | "engines": { 240 | "node": ">= 0.6" 241 | } 242 | }, 243 | "node_modules/depd": { 244 | "version": "2.0.0", 245 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 246 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 247 | "license": "MIT", 248 | "engines": { 249 | "node": ">= 0.8" 250 | } 251 | }, 252 | "node_modules/discord-api-types": { 253 | "version": "0.37.117", 254 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.117.tgz", 255 | "integrity": "sha512-d+Z6RKd7v3q22lsil7yASucqMfVVV0s0XSqu3cw7kyHVXiDO/mAnqMzqma26IYnIm2mk3TlupYJDGrdL908ZKA==", 256 | "license": "MIT" 257 | }, 258 | "node_modules/discord.js": { 259 | "version": "14.17.3", 260 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.17.3.tgz", 261 | "integrity": "sha512-8/j8udc3CU7dz3Eqch64UaSHoJtUT6IXK4da5ixjbav4NAXJicloWswD/iwn1ImZEMoAV3LscsdO0zhBh6H+0Q==", 262 | "license": "Apache-2.0", 263 | "dependencies": { 264 | "@discordjs/builders": "^1.10.0", 265 | "@discordjs/collection": "1.5.3", 266 | "@discordjs/formatters": "^0.6.0", 267 | "@discordjs/rest": "^2.4.2", 268 | "@discordjs/util": "^1.1.1", 269 | "@discordjs/ws": "^1.2.0", 270 | "@sapphire/snowflake": "3.5.3", 271 | "discord-api-types": "^0.37.114", 272 | "fast-deep-equal": "3.1.3", 273 | "lodash.snakecase": "4.1.1", 274 | "tslib": "^2.6.3", 275 | "undici": "6.19.8" 276 | }, 277 | "engines": { 278 | "node": ">=18" 279 | }, 280 | "funding": { 281 | "url": "https://github.com/discordjs/discord.js?sponsor" 282 | } 283 | }, 284 | "node_modules/dotenv": { 285 | "version": "16.4.7", 286 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", 287 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", 288 | "license": "BSD-2-Clause", 289 | "engines": { 290 | "node": ">=12" 291 | }, 292 | "funding": { 293 | "url": "https://dotenvx.com" 294 | } 295 | }, 296 | "node_modules/fast-deep-equal": { 297 | "version": "3.1.3", 298 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 299 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 300 | "license": "MIT" 301 | }, 302 | "node_modules/http-errors": { 303 | "version": "2.0.0", 304 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 305 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 306 | "license": "MIT", 307 | "dependencies": { 308 | "depd": "2.0.0", 309 | "inherits": "2.0.4", 310 | "setprototypeof": "1.2.0", 311 | "statuses": "2.0.1", 312 | "toidentifier": "1.0.1" 313 | }, 314 | "engines": { 315 | "node": ">= 0.8" 316 | } 317 | }, 318 | "node_modules/iconv-lite": { 319 | "version": "0.6.3", 320 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 321 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 322 | "license": "MIT", 323 | "dependencies": { 324 | "safer-buffer": ">= 2.1.2 < 3.0.0" 325 | }, 326 | "engines": { 327 | "node": ">=0.10.0" 328 | } 329 | }, 330 | "node_modules/inherits": { 331 | "version": "2.0.4", 332 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 333 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 334 | "license": "ISC" 335 | }, 336 | "node_modules/lodash": { 337 | "version": "4.17.21", 338 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 339 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 340 | "license": "MIT" 341 | }, 342 | "node_modules/lodash.snakecase": { 343 | "version": "4.1.1", 344 | "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", 345 | "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", 346 | "license": "MIT" 347 | }, 348 | "node_modules/magic-bytes.js": { 349 | "version": "1.10.0", 350 | "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", 351 | "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", 352 | "license": "MIT" 353 | }, 354 | "node_modules/raw-body": { 355 | "version": "3.0.0", 356 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 357 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 358 | "license": "MIT", 359 | "dependencies": { 360 | "bytes": "3.1.2", 361 | "http-errors": "2.0.0", 362 | "iconv-lite": "0.6.3", 363 | "unpipe": "1.0.0" 364 | }, 365 | "engines": { 366 | "node": ">= 0.8" 367 | } 368 | }, 369 | "node_modules/safer-buffer": { 370 | "version": "2.1.2", 371 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 372 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 373 | "license": "MIT" 374 | }, 375 | "node_modules/setprototypeof": { 376 | "version": "1.2.0", 377 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 378 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 379 | "license": "ISC" 380 | }, 381 | "node_modules/statuses": { 382 | "version": "2.0.1", 383 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 384 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 385 | "license": "MIT", 386 | "engines": { 387 | "node": ">= 0.8" 388 | } 389 | }, 390 | "node_modules/toidentifier": { 391 | "version": "1.0.1", 392 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 393 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 394 | "license": "MIT", 395 | "engines": { 396 | "node": ">=0.6" 397 | } 398 | }, 399 | "node_modules/ts-mixer": { 400 | "version": "6.0.4", 401 | "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", 402 | "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", 403 | "license": "MIT" 404 | }, 405 | "node_modules/tslib": { 406 | "version": "2.8.1", 407 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 408 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 409 | "license": "0BSD" 410 | }, 411 | "node_modules/typescript": { 412 | "version": "5.7.3", 413 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", 414 | "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", 415 | "dev": true, 416 | "license": "Apache-2.0", 417 | "bin": { 418 | "tsc": "bin/tsc", 419 | "tsserver": "bin/tsserver" 420 | }, 421 | "engines": { 422 | "node": ">=14.17" 423 | } 424 | }, 425 | "node_modules/undici": { 426 | "version": "6.19.8", 427 | "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", 428 | "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", 429 | "license": "MIT", 430 | "engines": { 431 | "node": ">=18.17" 432 | } 433 | }, 434 | "node_modules/undici-types": { 435 | "version": "6.19.8", 436 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 437 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 438 | "license": "MIT" 439 | }, 440 | "node_modules/unpipe": { 441 | "version": "1.0.0", 442 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 443 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 444 | "license": "MIT", 445 | "engines": { 446 | "node": ">= 0.8" 447 | } 448 | }, 449 | "node_modules/ws": { 450 | "version": "8.18.0", 451 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 452 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 453 | "license": "MIT", 454 | "engines": { 455 | "node": ">=10.0.0" 456 | }, 457 | "peerDependencies": { 458 | "bufferutil": "^4.0.1", 459 | "utf-8-validate": ">=5.0.2" 460 | }, 461 | "peerDependenciesMeta": { 462 | "bufferutil": { 463 | "optional": true 464 | }, 465 | "utf-8-validate": { 466 | "optional": true 467 | } 468 | } 469 | }, 470 | "node_modules/zod": { 471 | "version": "3.24.1", 472 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 473 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 474 | "license": "MIT", 475 | "funding": { 476 | "url": "https://github.com/sponsors/colinhacks" 477 | } 478 | }, 479 | "node_modules/zod-to-json-schema": { 480 | "version": "3.24.1", 481 | "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", 482 | "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", 483 | "license": "ISC", 484 | "peerDependencies": { 485 | "zod": "^3.24.1" 486 | } 487 | } 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-mcp-server", 3 | "version": "1.0.0", 4 | "description": "Discord MCP server for integrating Discord with Claude", 5 | "type": "module", 6 | "main": "build/index.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "start": "node build/index.js", 10 | "dev": "tsc -w", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "discord", 15 | "mcp", 16 | "claude" 17 | ], 18 | "author": "", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@modelcontextprotocol/sdk": "^1.2.0", 22 | "discord.js": "^14.14.1", 23 | "dotenv": "^16.4.7", 24 | "zod": "^3.22.4" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^20.11.16", 28 | "typescript": "^5.3.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import dotenv from 'dotenv'; 4 | import { 5 | CallToolRequestSchema, 6 | ListToolsRequestSchema, 7 | } from "@modelcontextprotocol/sdk/types.js"; 8 | import { Client, GatewayIntentBits, TextChannel } from 'discord.js'; 9 | import { z } from 'zod'; 10 | 11 | // Load environment variables 12 | dotenv.config(); 13 | 14 | // Discord client setup 15 | const client = new Client({ 16 | intents: [ 17 | GatewayIntentBits.Guilds, 18 | GatewayIntentBits.GuildMessages, 19 | GatewayIntentBits.MessageContent, 20 | ], 21 | }); 22 | 23 | // Helper function to find a guild by name or ID 24 | async function findGuild(guildIdentifier?: string) { 25 | if (!guildIdentifier) { 26 | // If no guild specified and bot is only in one guild, use that 27 | if (client.guilds.cache.size === 1) { 28 | return client.guilds.cache.first()!; 29 | } 30 | // List available guilds 31 | const guildList = Array.from(client.guilds.cache.values()) 32 | .map(g => `"${g.name}"`).join(', '); 33 | throw new Error(`Bot is in multiple servers. Please specify server name or ID. Available servers: ${guildList}`); 34 | } 35 | 36 | // Try to fetch by ID first 37 | try { 38 | const guild = await client.guilds.fetch(guildIdentifier); 39 | if (guild) return guild; 40 | } catch { 41 | // If ID fetch fails, search by name 42 | const guilds = client.guilds.cache.filter( 43 | g => g.name.toLowerCase() === guildIdentifier.toLowerCase() 44 | ); 45 | 46 | if (guilds.size === 0) { 47 | const availableGuilds = Array.from(client.guilds.cache.values()) 48 | .map(g => `"${g.name}"`).join(', '); 49 | throw new Error(`Server "${guildIdentifier}" not found. Available servers: ${availableGuilds}`); 50 | } 51 | if (guilds.size > 1) { 52 | const guildList = guilds.map(g => `${g.name} (ID: ${g.id})`).join(', '); 53 | throw new Error(`Multiple servers found with name "${guildIdentifier}": ${guildList}. Please specify the server ID.`); 54 | } 55 | return guilds.first()!; 56 | } 57 | throw new Error(`Server "${guildIdentifier}" not found`); 58 | } 59 | 60 | // Helper function to find a channel by name or ID within a specific guild 61 | async function findChannel(channelIdentifier: string, guildIdentifier?: string): Promise { 62 | const guild = await findGuild(guildIdentifier); 63 | 64 | // First try to fetch by ID 65 | try { 66 | const channel = await client.channels.fetch(channelIdentifier); 67 | if (channel instanceof TextChannel && channel.guild.id === guild.id) { 68 | return channel; 69 | } 70 | } catch { 71 | // If fetching by ID fails, search by name in the specified guild 72 | const channels = guild.channels.cache.filter( 73 | (channel): channel is TextChannel => 74 | channel instanceof TextChannel && 75 | (channel.name.toLowerCase() === channelIdentifier.toLowerCase() || 76 | channel.name.toLowerCase() === channelIdentifier.toLowerCase().replace('#', '')) 77 | ); 78 | 79 | if (channels.size === 0) { 80 | const availableChannels = guild.channels.cache 81 | .filter((c): c is TextChannel => c instanceof TextChannel) 82 | .map(c => `"#${c.name}"`).join(', '); 83 | throw new Error(`Channel "${channelIdentifier}" not found in server "${guild.name}". Available channels: ${availableChannels}`); 84 | } 85 | if (channels.size > 1) { 86 | const channelList = channels.map(c => `#${c.name} (${c.id})`).join(', '); 87 | throw new Error(`Multiple channels found with name "${channelIdentifier}" in server "${guild.name}": ${channelList}. Please specify the channel ID.`); 88 | } 89 | return channels.first()!; 90 | } 91 | throw new Error(`Channel "${channelIdentifier}" is not a text channel or not found in server "${guild.name}"`); 92 | } 93 | 94 | // Updated validation schemas 95 | const SendMessageSchema = z.object({ 96 | server: z.string().optional().describe('Server name or ID (optional if bot is only in one server)'), 97 | channel: z.string().describe('Channel name (e.g., "general") or ID'), 98 | message: z.string(), 99 | }); 100 | 101 | const ReadMessagesSchema = z.object({ 102 | server: z.string().optional().describe('Server name or ID (optional if bot is only in one server)'), 103 | channel: z.string().describe('Channel name (e.g., "general") or ID'), 104 | limit: z.number().min(1).max(100).default(50), 105 | }); 106 | 107 | // Create server instance 108 | const server = new Server( 109 | { 110 | name: "discord", 111 | version: "1.0.0", 112 | }, 113 | { 114 | capabilities: { 115 | tools: {}, 116 | }, 117 | } 118 | ); 119 | 120 | // List available tools 121 | server.setRequestHandler(ListToolsRequestSchema, async () => { 122 | return { 123 | tools: [ 124 | { 125 | name: "send-message", 126 | description: "Send a message to a Discord channel", 127 | inputSchema: { 128 | type: "object", 129 | properties: { 130 | server: { 131 | type: "string", 132 | description: 'Server name or ID (optional if bot is only in one server)', 133 | }, 134 | channel: { 135 | type: "string", 136 | description: 'Channel name (e.g., "general") or ID', 137 | }, 138 | message: { 139 | type: "string", 140 | description: "Message content to send", 141 | }, 142 | }, 143 | required: ["channel", "message"], 144 | }, 145 | }, 146 | { 147 | name: "read-messages", 148 | description: "Read recent messages from a Discord channel", 149 | inputSchema: { 150 | type: "object", 151 | properties: { 152 | server: { 153 | type: "string", 154 | description: 'Server name or ID (optional if bot is only in one server)', 155 | }, 156 | channel: { 157 | type: "string", 158 | description: 'Channel name (e.g., "general") or ID', 159 | }, 160 | limit: { 161 | type: "number", 162 | description: "Number of messages to fetch (max 100)", 163 | default: 50, 164 | }, 165 | }, 166 | required: ["channel"], 167 | }, 168 | }, 169 | ], 170 | }; 171 | }); 172 | 173 | // Handle tool execution 174 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 175 | const { name, arguments: args } = request.params; 176 | 177 | try { 178 | switch (name) { 179 | case "send-message": { 180 | const { channel: channelIdentifier, message } = SendMessageSchema.parse(args); 181 | const channel = await findChannel(channelIdentifier); 182 | 183 | const sent = await channel.send(message); 184 | return { 185 | content: [{ 186 | type: "text", 187 | text: `Message sent successfully to #${channel.name} in ${channel.guild.name}. Message ID: ${sent.id}`, 188 | }], 189 | }; 190 | } 191 | 192 | case "read-messages": { 193 | const { channel: channelIdentifier, limit } = ReadMessagesSchema.parse(args); 194 | const channel = await findChannel(channelIdentifier); 195 | 196 | const messages = await channel.messages.fetch({ limit }); 197 | const formattedMessages = Array.from(messages.values()).map(msg => ({ 198 | channel: `#${channel.name}`, 199 | server: channel.guild.name, 200 | author: msg.author.tag, 201 | content: msg.content, 202 | timestamp: msg.createdAt.toISOString(), 203 | })); 204 | 205 | return { 206 | content: [{ 207 | type: "text", 208 | text: JSON.stringify(formattedMessages, null, 2), 209 | }], 210 | }; 211 | } 212 | 213 | default: 214 | throw new Error(`Unknown tool: ${name}`); 215 | } 216 | } catch (error) { 217 | if (error instanceof z.ZodError) { 218 | throw new Error( 219 | `Invalid arguments: ${error.errors 220 | .map((e) => `${e.path.join(".")}: ${e.message}`) 221 | .join(", ")}` 222 | ); 223 | } 224 | throw error; 225 | } 226 | }); 227 | 228 | // Discord client login and error handling 229 | client.once('ready', () => { 230 | console.error('Discord bot is ready!'); 231 | }); 232 | 233 | // Start the server 234 | async function main() { 235 | // Check for Discord token 236 | const token = process.env.DISCORD_TOKEN; 237 | if (!token) { 238 | throw new Error('DISCORD_TOKEN environment variable is not set'); 239 | } 240 | 241 | try { 242 | // Login to Discord 243 | await client.login(token); 244 | 245 | // Start MCP server 246 | const transport = new StdioServerTransport(); 247 | await server.connect(transport); 248 | console.error("Discord MCP Server running on stdio"); 249 | } catch (error) { 250 | console.error("Fatal error in main():", error); 251 | process.exit(1); 252 | } 253 | } 254 | 255 | main(); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } --------------------------------------------------------------------------------