A server-side Fabric 1.21.1 mod that bridges Minecraft chat with Discord at near-native fidelity.
Each player appears in Discord as their own user (via webhook with their skin head as avatar). Discord pings reach players in game with sound and highlight. Replies, edits, attachments, embeds, custom emoji, reactions, and threads all carry across cleanly. Server events (joins, leaves, deaths, advancements, lifecycle) post as colored embeds.
- Webhook per player. Each MC chatter shows up in Discord as themselves: their MC name as the username, their skin head as the avatar.
- Two way mentions.
@PlayerNamein Discord pings the linked player in game with a sound and highlighted name.@PlayerNametyped in Minecraft becomes a real<@discordId>mention if that player has linked their Discord account.
- Replies render as a quoted preview, click to jump to the original message in Discord.
- Edits repost as
(edited)with italic gray styling. Deletes leave a small gravestone marker. Both can be disabled. - Attachments become typed clickable hover text that opens in your browser:
- Image ->
[image]cyan - Video ->
[video.mp4]cyan - Audio ->
[audio.mp3]cyan - File ->
[filename.ext]cyan
- Image ->
- Custom emoji render as
:name:in light purple. - Embeds render as title plus a truncated description, one quoted line per piece.
- Reactions are supported, off by default.
- Account linking via a 6 character one time code. Run
/linkin Minecraft, paste the code into the bot's/link <code>slash command in Discord. - Configurable channel mapping. Each scope (GLOBAL, STAFF, DEATHS, ADVANCEMENTS, LIFECYCLE) maps to its own Discord channel. Any number of
[[channels]]entries supported. - Server events relayed as colored embeds with player heads:
- Joins (green) and leaves (gray)
- Deaths (red) with the full vanilla death message
- Advancements (blue / purple)
- Server start, stop, crash (lifecycle)
- Hexagonal architecture. All bridge logic is pure JVM and unit tested. Only adapters touch JDA / Fabric APIs. ~85 tests covering domain, translation, orchestration, persistence, config, and JDA wiring.
- Download the latest
dmcl-x.y.z.jarfrom the Releases page. - Drop it into your server's
mods/folder alongside Fabric API. - Start the server once. It writes
config/dmcl.tomlwith example values. - Stop the server, edit
config/dmcl.toml(channel IDs, guild ID, mention rules), and provide your bot token via theDMCL_DISCORD_TOKENenvironment variable. - Start the server again. Watch for
DMCL readyin the log.
- Create a bot at https://discord.com/developers/applications.
- Enable the Message Content intent under Bot settings.
- Invite the bot with the
applications.commandsandbotscopes. Permissions needed:- View Channels
- Send Messages
- Manage Webhooks
- Add Reactions
- Read Message History
The mod reads the token from one of three places, in this order:
- The OS environment variable
DMCL_DISCORD_TOKEN. - A
KEY=VALUEline inconfig/dmcl.env. - A TOML key in
config/dmcl.secrets.toml.
Pick whichever fits your hosting. The example config/dmcl.toml references the env var by default.
The example config/dmcl.toml written on first start documents every option. Key sections:
[discord]token, guild ID, presence[storage]SQLite location, per world or per game directory[avatars]skin head provider and image size[behavior]toggle edits, deletes, reactions, ping sound, mention color[[channels]]repeat per scope (GLOBAL, STAFF, DEATHS, ADVANCEMENTS, LIFECYCLE), each with channel ID and direction flags[mentions]allow_everyone, allow_here, allow_role list
| Command | Who | What |
|---|---|---|
/link |
any player | Generates a 6 character one time link code |
/unlink |
any player | Removes your Discord link |
/dmcl status |
op level 2 | JDA connection state and channel health summary |
| Command | What |
|---|---|
/link <code> |
Completes a pending link from Minecraft |
/unlink |
Removes your Discord link |
/players |
Ephemeral list of online MC players |
Requires JDK 21.
./gradlew buildThe jar lands in build/libs/ and is auto copied to /mnt/c/dev/mc-server/mods/ for the dev cycle (adjust the installMod task in build.gradle.kts for your own mods folder).
Run tests:
./gradlew testHexagonal: a pure JVM core/ package (domain, ports, translation, orchestrator) plus three adapters (JDA, Fabric, SQLite). All translation is unit tested with no MC or JDA imports. Adapter classes wire the IO and stay small.
src/main/java/com/westwardmc/dmcl/
├── core/ pure JVM, no MC/JDA imports
│ ├── domain/ records: BridgeMessage, Author, ...
│ ├── port/ interfaces: DiscordPort, MinecraftPort, LinkRepo, ...
│ ├── translate/ McToDiscord, DiscordToMc, mention resolver, ...
│ └── orchestrator/ BridgeOrchestrator + EventBus
├── adapter/
│ ├── jda/ JDA listener, webhook cache, slash commands
│ ├── fabric/ server thread bridges, MC text converter
│ ├── sqlite/ linked accounts persistence
│ └── config/ TOML + secret resolver
├── mixin/ advancement event hook
└── DmclMod.java composition root
Full design notes are in docs/superpowers/specs/.
Releases are published to Modrinth.
To publish a new version yourself:
MODRINTH_TOKEN=mr_xxx MODRINTH_PROJECT_ID=dmcl ./gradlew modrinthThe Minotaur plugin reads modVersion from gradle.properties, uploads the remapped jar, and syncs the README into the Modrinth project body.
| Minecraft | Loader | Status |
|---|---|---|
| 1.21.1 | Fabric | tested |
| 1.21.2, 1.21.3 | Fabric | should work (same Text API) |
| 1.21.4+ | Fabric | not supported (Text API changed to sealed records, needs port) |
| 1.20.x | Fabric | not supported (different fabric-api event names) |
The fabric.mod.json declares "minecraft": ">=1.21.1 <1.21.4", so installs on incompatible versions are rejected by Fabric Loader at boot rather than crashing later.
MIT. See LICENSE.