MCME-Connect links MCME's servers into one network. On the Velocity proxy it handles teleports between players on different servers, server switching, restart orchestration, a server watchdog and join/leave messages; on each Paper backend it carries out what the proxy asks (teleports, commands, titles, restarts), syncs player statistics to MySQL and fires an event other plugins use to react to arrivals. The same jar goes on the proxy and on every backend. This page describes 3.1.1, the latest release, from MCME/MCME-Connect.
At a glance
| Repository | MCME/MCME-Connect (public) |
| Release branch | master (development holds work in progress) |
| Version described | 3.1.1 (tag v3.1.1) |
| Platform | One jar, two plugins: Velocity id mcmeconnect (velocity-api 4.2.0) and Paper plugin MCME-Connect (compiled against paper-api 26.2) |
| Build | Maven, JDK 25, emits Java 21 bytecode |
| Depends on | Proxy: mcme-base. Backend: MCME-Base (hard); PremiumVanish, DiscordSRV, PluginUtils, Multiverse-Core (soft) |
BungeeCord and Waterfall support was removed in 3.0.0; the code is Velocity-only.
How the code is organised
Everything lives under com.mcmiddleearth.connect:
| Package | Side | Job |
|---|---|---|
proxy.velocity |
proxy | ConnectVelocityPlugin (entry point, extends mcme-base's AbstractVelocityPlugin), command classes, Velocity event listeners |
proxy.core |
proxy | McmeConnect (state and start-up), McmeConnectConfig, RestartScheduler, ServerInformation |
proxy.core.handler |
proxy | One handler per feature: CommandHandler, TpHandler, TpaHandler, TpahereHandler, TpposHandler, MvtpHandler, ThemeHandler, ConnectionHandler, RestartHandler, VanishHandler, ChatMessageHandler, TitleHandler, DiscordMessageHandler, LegacyPlayerHandler, RestorestatsHandler, PluginMessageHandler |
proxy.core.watchdog |
proxy | ServerWatchdog: pings every server every two minutes and warns staff about servers that seem down |
| (root) | backend | ConnectPlugin (entry point), Channel (channel and sub-channel names), Permission (node names, used on both sides), ServerInfoUpdater |
listener |
backend | ConnectPluginListener (messages from the proxy), PlayerListener |
restart |
backend | /restart, /stop and scheduled restarts |
statistics |
backend | Statistics sync to MySQL (StatisticDBConnector, StatisticSnapshot, StatisticListener) and first-join date sync |
events |
backend | PlayerConnectEvent |
util |
backend | ConnectUtil (the public helpers), Multiverse spawn lookup |
log |
backend | The /log debug logger |
The proxy's commands are thin: each ConnectCommand does nothing in execute. CommandListener catches the command in Velocity's CommandExecuteEvent and passes it to CommandHandler. If the target is on another server, Connect handles it and the command stops there. If not (for example /tp to a player on your own server), the command is forwarded to the backend, where EssentialsX or vanilla answers it.
Building and testing
mvn clean verify
This runs the tests and builds the shaded jar target/MCME-Connect-<version>-<TYPE>-<commit>.jar. TYPE is RELEASE unless you pass -Dbuild.type=DEV; commit is the short hash, so two builds of one version can be told apart.
- JDK 25 is needed because paper-api 26.2 and mcme-base are Java 25 bytecode. The compiler emits Java 21 bytecode because maven-shade-plugin 3.5.3 cannot read Java 25 classes.
- Dependencies resolve from the MCME Maven repository (
releases,snapshots,mirror), with the upstream repositories as fallbacks. mcme-base 2.0.1 and PluginUtils 2.0.2 areprovided; mysql-connector-j and tbnbt are shaded into the jar. velocity-plugin.jsonis generated from the@Pluginannotation. Its version is a literal, so a release must change the version inpom.xmland inConnectVelocityPlugin.VelocityPluginDescriptorTestfails the build when they differ.- Tests are JUnit 5:
PluginMessageHandlerTest,StatisticSnapshotTest,VelocityPluginDescriptorTestandHarnessTest.
CI (.github/workflows/build.yml) rejects version ranges in the pom, then builds and tests every push and pull request and keeps the jar as a workflow artifact. A v* tag must match the pom version and have a ## [X.Y.Z] section in CHANGELOG.md; it then deploys com.mcmiddleearth:MCME-Connect to repo.mcmiddleearth.com and creates the GitHub release "version X.Y.Z for mc 26.2" with the jar, its SHA-256 and the changelog section. A manual run (workflow_dispatch) also deploys to the Maven repository.
Configuration
One config.yml in the plugin folder, from the same file in the jar. Each side reads its own keys. The shipped file holds only proxy keys; the others take the built-in value shown.
Proxy keys
| Key | Shipped / built-in | Meaning |
|---|---|---|
connectDelay |
200 (built-in 2000) |
Milliseconds to wait after a server switch before sending the follow-up message (teleport, spawn, join reason) |
serverWatchdog |
true |
Ping all servers every two minutes and warn players with mcmeconnect.watchdog about servers that seem down |
premiumVanish |
true (built-in false) |
Respect PremiumVanish in join/leave messages and player suggestions |
disableMVTP |
a list of server names | Servers from which /mvtp, /switch, /theme, /survival and cross-server teleports are refused (bypass: mcmeconnect.ignoreDisabledMVTP) |
legacyRedirect.enabled / .from / .to |
false (built-in true), two server names |
Move players who join the network on the from server to the to server |
restorestatsBasePath |
unset | Base folder for /restorestats; unset or empty turns the feature off |
syncGamemode.<server> |
true for several servers |
Not read by any code in 3.1.1 |
themedbuildWorld |
built-in: a server name | The server /theme sends players to |
fixedSwitch |
built-in none | List of "<server> <x> <y> <z> <yaw> <pitch>": /switch <server> lands the player at that spot (in the world named like the server); /switch <server> return skips it |
scheduledRestarts |
built-in none | List of "all HH:MM" or "<DAY> HH:MM" restart times |
database.user / .password / .dbName / .ip / .port |
built-in none | MySQL login used by /restorestats |
Backend keys
| Key | Built-in | Meaning |
|---|---|---|
syncStatistic |
true |
Load and save player statistics in MySQL so they add up across servers |
database.user / .password / .dbName / .ip / .port |
placeholder values, localhost, 3306 |
The statistics database |
discordChannel |
empty | DiscordSRV game-channel name ConnectUtil.sendDiscord uses when none is given |
scheduledRestarts |
none | As on the proxy, for this backend |
Other files: the proxy keeps playerServers.yml (each player's last server, used to pick the server they join), and both sides use a restart.nfo marker file: scheduled restarts and /restart expect a start script that starts the server again while the file exists. First-join date sync reads player data from a playerdata folder inside the plugin folder (see the comment in config.yml) and needs PluginUtils.
Commands and permissions
Most checks run on the proxy, which ignores
plugin.ymldefaults. Grant the nodes in the proxy's permission plugin as well as on the backends.
Proxy commands
| Command | Aliases | What it does | Permission |
|---|---|---|---|
/tp <player> |
Teleport to a player on another server | mcmeconnect.tp + mcmeconnect.world.<their server> |
|
/tp <player> <target> |
Teleport one player to another across servers | mcmeconnect.tp + mcmeconnect.tp.other |
|
/tphere <player> |
Bring a player from another server | mcmeconnect.tphere |
|
/tpa <player>, /tpahere <player> |
Ask to teleport to a player, or ask them to come to you | mcmeconnect.tpa |
|
/tpaccept, /tpdeny, /tpacancel |
/tpyes, /tpno |
Answer or cancel a request | none (target world access is checked) |
/mvtp <server> |
/switch <server> |
/mvtp: switch server and go to the world spawn. /switch: switch server (or go to its fixedSwitch spot) |
mcmeconnect.world.<server> |
/<server> |
One command per registered server; same as /switch <server> |
mcmeconnect.world.<server> |
|
/theme |
Switch to the themed-builds server (themedbuildWorld) |
mcmeconnect.world.<that server> |
|
/survival |
Switch to the survival server named in CommandHandler |
mcmeconnect.survival |
|
/reboot <server…>\|proxy\|all |
Restart those servers, one after another | mcmeconnect.reboot |
|
/stop <server…>\|proxy\|all |
Stop those servers | mcmeconnect.reboot (mcmeconnect.stop shows the command) |
|
/restorestats [joinDateOnly\|allStats] |
Restore your old statistics or first-join date from a backup; off unless restorestatsBasePath is set |
none |
From the proxy console, /reboot restarts the proxy.
Backend commands (declared in plugin.yml)
| Command | Aliases | What it does | Permission |
|---|---|---|---|
/restart |
/reboot |
Restart this server. /restart reloadconfig reloads scheduledRestarts; /restart cancel cancels an announced restart. (/reboot reloadconfig and /reboot cancel reach this command through the proxy) |
mcmeconnect.reboot |
/stop [message] |
/shutdown |
Stop this server | mcmeconnect.stop |
/log |
/logger |
Connect's debug log | logger.admin |
Permission nodes
| Permission | plugin.yml default |
Grants |
|---|---|---|
mcmeconnect.tp |
op | /tp <player> across servers |
mcmeconnect.tp.other |
op (includes mcmeconnect.tp) |
/tp <player> <target> across servers |
mcmeconnect.tphere |
op | /tphere across servers |
mcmeconnect.tpa |
not declared | /tpa, /tpahere |
mcmeconnect.survival |
not declared | /survival |
mcmeconnect.world.<server> |
op (parent node) | Entering that server by any Connect command or teleport |
mcmeconnect.ignoreDisabledMVTP |
op | Using switch and teleport commands on servers in disableMVTP |
mcmeconnect.watchdog |
op | Watchdog warnings about servers that seem down |
mcmeconnect.reboot |
not declared | /reboot, proxy /stop <servers>, backend /restart |
mcmeconnect.restart |
op | Continuing a multi-server /reboot or /stop: the proxy checks it when a backend passes the rest of the list back |
mcmeconnect.stop |
not declared | Backend /stop |
logger.admin |
op | /log |
pv.see, pv.joinvanished |
PremiumVanish's nodes | Read by Connect to decide who sees join/leave messages of vanished players |
mcmeconnect.tablist is still declared in plugin.yml but nothing checks it since the tab list was removed in 3.0.3.
APIs and extension points
Other backend plugins can depend on Connect (Maven: com.mcmiddleearth:MCME-Connect, provided, from the Maven repository) and use:
com.mcmiddleearth.connect.events.PlayerConnectEvent, a Bukkit event fired on the backend once the proxy reports that a player arrived.getReason()returns aConnectReason:JOIN_PROXY,COMMAND,KICK_REDIRECT,LOBBY_FALLBACK,PLUGIN,PLUGIN_MESSAGE,SERVER_DOWN_REDIRECTorUNKNOWN. Use it instead ofPlayerJoinEventwhen you need to know the player really arrived through the proxy.com.mcmiddleearth.connect.util.ConnectUtil, static helpers that send messages to the proxy through a player's connection:teleportPlayer(Player, String server, String world, Location): teleport a player to a spot on any server.sendMessage(Player sender, String server, String recipient, String message, int delay): deliver a chat line. An empty server and recipient ("") means every player on the network. The text arrives as legacy text, so click and hover events are lost.sendTitle(Player sender, String server, String recipient, String title, String subtitle, int intro, int show, int extro, int delay): show a title.sendDiscord(String discordChannel, String message): post to a DiscordSRV channel from this backend;@Namewords become role or user mentions.
How it talks to other plugins and systems
Both halves use one plugin-message channel, mcme:connect. Every message starts with a UTF string naming the sub-channel, followed by that sub-channel's fields (Guava ByteArrayDataOutput). The proxy accepts mcme:connect messages only from backend servers, never from a player's client.
| Sub-channel | Direction | Carries |
|---|---|---|
tppos |
both | Teleport a player to a server, world and position (x;y;z;yaw;pitch) |
message |
backend → proxy | Chat line for a server and recipient, with a delay |
title |
both | Title, subtitle and timings for a server and recipient |
Connect |
backend → proxy | Move a player to a server (handled by the proxy; nothing in this repository sends it) |
restart |
both | Restart or stop the next server in a list; the backend passes the rest back to the proxy |
server_info |
backend → proxy | Periodic server status, read by the proxy's ServerInformation |
tp |
proxy → backend | Teleport an arriving player to another player |
spawn |
proxy → backend | Teleport an arriving player to the world spawn (Multiverse) |
command |
proxy → backend | Run a command as an arriving player |
join |
proxy → backend | Player name and ConnectReason; the backend fires PlayerConnectEvent |
Discord |
proxy → backend | Join/leave line for DiscordSRV |
legacy |
proxy → backend | Save statistics, then move the player (the legacy redirect) |
gamemode |
proxy → backend | Set a player's game mode (handled, but not sent by the 3.1.1 proxy) |
A multi-server /reboot hops: the proxy moves the issuing player to the first server and sends restart with the rest of the list; that backend restarts itself about three seconds later and sends the list back through the player, and the proxy continues only if the player has mcmeconnect.restart.
flowchart LR
subgraph Proxy
CV[MCME-Connect proxy plugin]
MB1[mcme-base]
end
subgraph Backend
CP[MCME-Connect backend plugin]
MB2[MCME-Base]
DS[DiscordSRV]
PV[PremiumVanish]
MV[Multiverse-Core]
PU[PluginUtils]
IN[MCME-Introduction]
AR[MCME-Architect]
end
DB[(MySQL statistics)]
CV ---|mcme:connect| CP
CV --> MB1
CP --> MB2
CP --> DS
CP --> PV
CP --> MV
CP --> PU
CP ---|JDBC| DB
IN -->|PlayerConnectEvent and ConnectUtil| CP
AR -->|PlayerConnectEvent| CP
| Other system | How |
|---|---|
| mcme-base | Required on both sides: the Velocity plugin base class, players, server info, messages and YAML config |
| PluginUtils | Optional on backends; only first-join date sync uses it |
| MySQL | Backend statistics (syncStatistic) and proxy /restorestats, over JDBC with the shaded mysql-connector-j |
| DiscordSRV | Join/leave lines and ConnectUtil.sendDiscord go through DiscordSRV's API; Connect only sends, it never reads Discord |
| PremiumVanish | Vanish state for join/leave messages and suggestions |
| Multiverse-Core | World spawns for /mvtp (Multiverse 5 package names) |
| MCME-Introduction | Listens to PlayerConnectEvent and broadcasts its "just joined" line with ConnectUtil.sendMessage |
| MCME-Architect | Listens to PlayerConnectEvent for its resource-pack handling |
| TheGaffer | Uses no Connect API; it only checks whether Connect is loaded |
| MCME-Warps | No integration. 3.0.1 stopped taking /warp and /to from MCME-Warps, and 3.1.0 removed the old MyWarp bridge |
When a backend kicks a player (other than for idling or an unsupported client), the proxy tries to redirect them to a fallback server; the server names are hard-coded in ConnectionHandler.handleKick.
Further reading
- README and CHANGELOG on GitHub
- Setting up, Maven repository, mcme-base