Remove and replace vanilla parts
This page shows how to build your own server from the same plugins as the stock one, how to leave a plugin out, and how to pass settings to the plugins.
In Paper you start from a finished server and add plugins. In mcrs you start from a group of plugins and take parts out, so a lobby can run without world generation and a test can run without lighting. The examples come from repository files, and we did not run them outside the repository tests.
The server is a group of plugins
Section titled “The server is a group of plugins”MinecraftServerPlugins is a Bevy PluginGroup, which is a set of plugins that you add to the app as one value. Its four fields are the parts that take settings:
pub struct MinecraftServerPlugins { pub core: ServerCorePlugin, pub worlds: DimensionWorldsPlugin, pub world_gen: WorldGenPlugin, pub network: ServerNetworkPlugin,}crates/mcrs_minecraft_server/src/lib.rs:22-27
build() adds the rest of the vanilla server. The members, in the order build() adds them, are the server core, world generation registries, the vanilla command set, vanilla commands, world commands, dimension worlds, chat, entity physics, mob brains, world generation, lighting and the network.1
impl PluginGroup for MinecraftServerPlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::<Self>() .add(self.core) .add(WorldgenRegistriesPlugin) .add(CommandPlugin::<VanillaCommands>::new()) .add(VanillaCommandsPlugin) .add(WorldCommandsPlugin) .add(self.worlds) .add(VanillaChatPlugin) .add(VanillaEntityPhysicsPlugin) .add(VanillaMobBrainPlugin) .add(self.world_gen) .add(LightingPlugin) .add(self.network) }}crates/mcrs_minecraft_server/src/lib.rs:61-77
You can disable or replace each member without editing the server crate. A member that needs another one panics when the group builds and names the missing plugin. You get the error at start, so you do not have to trace a missing resource at the first tick.
The vanilla gameplay lives in plugins above the engine. Each of them is removable on its own, and they reach every dimension world through add_dimension_plugin.2
Disable a member with disable
Section titled “Disable a member with disable”The binary switches mob AI off like this:
if !settings.mob_ai { server = server.disable::<VanillaMobBrainPlugin>();}disable::<T>() is a method of the Bevy plugin group builder, and T is the plugin you leave out. A repository test uses the same method to start a server without world generation and lighting:
fn a_client_without_an_app_joins_the_embedded_server_offline() { let mut plugins = MinecraftServerPlugins::embedded(); plugins.core.asset_path = Some(ASSETS.to_owned()); plugins.network.announce_on_lan = false; let mut app = App::new(); app.add_plugins( plugins .build() .disable::<WorldGenPlugin>() .disable::<LightingPlugin>(), ); let stop = StopOnDrop(Arc::new(AtomicBool::new(false)));crates/mcrs_minecraft_server/tests/it/client_join.rs:31-42
That test also calls MinecraftServerPlugins::embedded(). It gives a server that runs inside another process. Such a server listens on loopback only, takes a port that the OS assigns, and leaves the task pools to the host app.
impl MinecraftServerPlugins { /// A server running inside another process: loopback only, an OS-assigned /// port, and the task pools left to the host app. pub fn embedded() -> Self { let defaults = Self::default(); Self { core: ServerCorePlugin { owns_task_pools: false, ..defaults.core }, network: ServerNetworkPlugin::embedded(), ..defaults } }}crates/mcrs_minecraft_server/src/lib.rs:45-59
Settings are plugin fields
Section titled “Settings are plugin fields”The binary reads its settings from the environment and turns them into plugin fields in src/main.rs:
let settings = ServerSettings::read( |name| std::env::var(format!("MCRS_{name}")).ok(), |error| bevy_log::warn!("{error}"),);let mut server = MinecraftServerPlugins { core: ServerCorePlugin { compute_threads: settings.compute_threads, ..MinecraftServerPlugins::default().core }, worlds: DimensionWorldsPlugin { world: world_folder(), ops, seed: settings.world_seed, preset: settings.world_preset.clone(), default_game_mode: settings.default_game_mode, ..Default::default() }, world_gen: WorldGenPlugin, network: network(&settings),}.build();Only the binary does this. A library crate does not read an environment variable or a file for its settings, and every setting is a field of the plugin that uses it.3
The environment variables of the binary are the fields of ServerSettings. Each variable has the name of its field with the prefix MCRS_. The full list is in the source file.
In your own program, skip ServerSettings and fill the plugin fields yourself. An embedded server in a test then starts with explicit settings, whatever the shell exported.
You cannot supply your own chunks yet
Section titled “You cannot supply your own chunks yet”Next: Add a command.
Footnotes
Section titled “Footnotes”-
Works#279 Splits the server plugin into a group of removable plugins. ↩
-
Works#282 Moves vanilla gameplay out of the server crate into removable plugins. ↩
-
Works#278 The server is configured only through its plugin. ↩
-
WIP#281 Plans the chunk source as a replaceable plugin point, with empty and flat sources. ↩
Sources and verification
Verified: 26.4-snapshot-7, 51d45c26a, 2026-11-09