diff --git a/.coveragerc b/.coveragerc
index 6e2ea5ba89bf30083947281aae2b1308beeed8cb..369aaa3b4e00490a61d380af2c4f741c5c992f17 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -425,6 +425,10 @@ omit =
     homeassistant/components/mikrotik/device_tracker.py
     homeassistant/components/mill/climate.py
     homeassistant/components/mill/const.py
+    homeassistant/components/minecraft_server/__init__.py
+    homeassistant/components/minecraft_server/binary_sensor.py
+    homeassistant/components/minecraft_server/const.py
+    homeassistant/components/minecraft_server/sensor.py
     homeassistant/components/minio/*
     homeassistant/components/mitemp_bt/sensor.py
     homeassistant/components/mjpeg/camera.py
diff --git a/CODEOWNERS b/CODEOWNERS
index 4078a45f990014b9fe742530eda321bb784340fa..c3f018ef83a38a33d0c15b5cad692057f1097f64 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -212,6 +212,7 @@ homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
 homeassistant/components/mikrotik/* @engrbm87
 homeassistant/components/mill/* @danielhiversen
 homeassistant/components/min_max/* @fabaff
+homeassistant/components/minecraft_server/* @elmurato
 homeassistant/components/minio/* @tkislan
 homeassistant/components/mobile_app/* @robbiet480
 homeassistant/components/modbus/* @adamchengtkc
diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..789e4d8f1b83f927008c850fb795bf177cfb8c49
--- /dev/null
+++ b/homeassistant/components/minecraft_server/__init__.py
@@ -0,0 +1,273 @@
+"""The Minecraft Server integration."""
+
+import asyncio
+from datetime import datetime, timedelta
+import logging
+from typing import Any, Dict
+
+from mcstatus.server import MinecraftServer as MCStatus
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import (
+    async_dispatcher_connect,
+    async_dispatcher_send,
+)
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.event import async_track_time_interval
+from homeassistant.helpers.typing import ConfigType, HomeAssistantType
+
+from .const import DOMAIN, MANUFACTURER, SCAN_INTERVAL, SIGNAL_NAME_PREFIX
+
+PLATFORMS = ["binary_sensor", "sensor"]
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
+    """Set up the Minecraft Server component."""
+    return True
+
+
+async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool:
+    """Set up Minecraft Server from a config entry."""
+    domain_data = hass.data.setdefault(DOMAIN, {})
+
+    # Create and store server instance.
+    unique_id = config_entry.unique_id
+    _LOGGER.debug(
+        "Creating server instance for '%s' (host='%s', port=%s)",
+        config_entry.data[CONF_NAME],
+        config_entry.data[CONF_HOST],
+        config_entry.data[CONF_PORT],
+    )
+    server = MinecraftServer(hass, unique_id, config_entry.data)
+    domain_data[unique_id] = server
+    await server.async_update()
+    server.start_periodic_update()
+
+    # Set up platform(s).
+    for platform in PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(config_entry, platform)
+        )
+
+    return True
+
+
+async def async_unload_entry(
+    hass: HomeAssistantType, config_entry: ConfigEntry
+) -> bool:
+    """Unload Minecraft Server config entry."""
+    unique_id = config_entry.unique_id
+    server = hass.data[DOMAIN][unique_id]
+
+    # Unload platforms.
+    await asyncio.gather(
+        *[
+            hass.config_entries.async_forward_entry_unload(config_entry, platform)
+            for platform in PLATFORMS
+        ]
+    )
+
+    # Clean up.
+    server.stop_periodic_update()
+    hass.data[DOMAIN].pop(unique_id)
+
+    return True
+
+
+class MinecraftServer:
+    """Representation of a Minecraft server."""
+
+    # Private constants
+    _MAX_RETRIES_PING = 3
+    _MAX_RETRIES_STATUS = 3
+
+    def __init__(
+        self, hass: HomeAssistantType, unique_id: str, config_data: ConfigType
+    ) -> None:
+        """Initialize server instance."""
+        self._hass = hass
+
+        # Server data
+        self.unique_id = unique_id
+        self.name = config_data[CONF_NAME]
+        self.host = config_data[CONF_HOST]
+        self.port = config_data[CONF_PORT]
+        self.online = False
+        self._last_status_request_failed = False
+
+        # 3rd party library instance
+        self._mc_status = MCStatus(self.host, self.port)
+
+        # Data provided by 3rd party library
+        self.description = None
+        self.version = None
+        self.protocol_version = None
+        self.latency_time = None
+        self.players_online = None
+        self.players_max = None
+        self.players_list = None
+
+        # Dispatcher signal name
+        self.signal_name = f"{SIGNAL_NAME_PREFIX}_{self.unique_id}"
+
+        # Callback for stopping periodic update.
+        self._stop_periodic_update = None
+
+    def start_periodic_update(self) -> None:
+        """Start periodic execution of update method."""
+        self._stop_periodic_update = async_track_time_interval(
+            self._hass, self.async_update, timedelta(seconds=SCAN_INTERVAL)
+        )
+
+    def stop_periodic_update(self) -> None:
+        """Stop periodic execution of update method."""
+        self._stop_periodic_update()
+
+    async def async_check_connection(self) -> None:
+        """Check server connection using a 'ping' request and store result."""
+        try:
+            await self._hass.async_add_executor_job(
+                self._mc_status.ping, self._MAX_RETRIES_PING
+            )
+            self.online = True
+        except OSError as error:
+            _LOGGER.debug(
+                "Error occurred while trying to ping the server - OSError: %s", error
+            )
+            self.online = False
+
+    async def async_update(self, now: datetime = None) -> None:
+        """Get server data from 3rd party library and update properties."""
+        # Check connection status.
+        server_online_old = self.online
+        await self.async_check_connection()
+        server_online = self.online
+
+        # Inform user once about connection state changes if necessary.
+        if server_online_old and not server_online:
+            _LOGGER.warning("Connection to server lost")
+        elif not server_online_old and server_online:
+            _LOGGER.info("Connection to server (re-)established")
+
+        # Update the server properties if server is online.
+        if server_online:
+            await self._async_status_request()
+
+        # Notify sensors about new data.
+        async_dispatcher_send(self._hass, self.signal_name)
+
+    async def _async_status_request(self) -> None:
+        """Request server status and update properties."""
+        try:
+            status_response = await self._hass.async_add_executor_job(
+                self._mc_status.status, self._MAX_RETRIES_STATUS
+            )
+
+            # Got answer to request, update properties.
+            self.description = status_response.description["text"]
+            self.version = status_response.version.name
+            self.protocol_version = status_response.version.protocol
+            self.players_online = status_response.players.online
+            self.players_max = status_response.players.max
+            self.latency_time = status_response.latency
+            self.players_list = []
+            if status_response.players.sample is not None:
+                for player in status_response.players.sample:
+                    self.players_list.append(player.name)
+
+            # Inform user once about successful update if necessary.
+            if self._last_status_request_failed:
+                _LOGGER.info("Updating the server properties succeeded again")
+            self._last_status_request_failed = False
+        except OSError as error:
+            # No answer to request, set all properties to unknown.
+            self.description = None
+            self.version = None
+            self.protocol_version = None
+            self.players_online = None
+            self.players_max = None
+            self.latency_time = None
+            self.players_list = None
+
+            # Inform user once about failed update if necessary.
+            if not self._last_status_request_failed:
+                _LOGGER.warning(
+                    "Updating the server properties failed - OSError: %s", error,
+                )
+            self._last_status_request_failed = True
+
+
+class MinecraftServerEntity(Entity):
+    """Representation of a Minecraft Server base entity."""
+
+    def __init__(
+        self, server: MinecraftServer, type_name: str, icon: str, device_class: str
+    ) -> None:
+        """Initialize base entity."""
+        self._server = server
+        self._name = f"{server.name} {type_name}"
+        self._icon = icon
+        self._unique_id = f"{self._server.unique_id}-{type_name}"
+        self._device_info = {
+            "identifiers": {(DOMAIN, self._server.unique_id)},
+            "name": self._server.name,
+            "manufacturer": MANUFACTURER,
+            "model": f"Minecraft Server ({self._server.version})",
+            "sw_version": self._server.protocol_version,
+        }
+        self._device_class = device_class
+        self._device_state_attributes = None
+        self._disconnect_dispatcher = None
+
+    @property
+    def name(self) -> str:
+        """Return name."""
+        return self._name
+
+    @property
+    def unique_id(self) -> str:
+        """Return unique ID."""
+        return self._unique_id
+
+    @property
+    def device_info(self) -> Dict[str, Any]:
+        """Return device information."""
+        return self._device_info
+
+    @property
+    def device_class(self) -> str:
+        """Return device class."""
+        return self._device_class
+
+    @property
+    def icon(self) -> str:
+        """Return icon."""
+        return self._icon
+
+    @property
+    def should_poll(self) -> bool:
+        """Disable polling."""
+        return False
+
+    async def async_update(self) -> None:
+        """Fetch data from the server."""
+        raise NotImplementedError()
+
+    async def async_added_to_hass(self) -> None:
+        """Connect dispatcher to signal from server."""
+        self._disconnect_dispatcher = async_dispatcher_connect(
+            self.hass, self._server.signal_name, self._update_callback
+        )
+
+    async def async_will_remove_from_hass(self) -> None:
+        """Disconnect dispatcher before removal."""
+        self._disconnect_dispatcher()
+
+    @callback
+    def _update_callback(self) -> None:
+        """Triggers update of properties after receiving signal from server."""
+        self.async_schedule_update_ha_state(force_refresh=True)
diff --git a/homeassistant/components/minecraft_server/binary_sensor.py b/homeassistant/components/minecraft_server/binary_sensor.py
new file mode 100644
index 0000000000000000000000000000000000000000..cde2a4149007d84031da0f33df51c9a9da28b673
--- /dev/null
+++ b/homeassistant/components/minecraft_server/binary_sensor.py
@@ -0,0 +1,47 @@
+"""The Minecraft Server binary sensor platform."""
+
+from homeassistant.components.binary_sensor import (
+    DEVICE_CLASS_CONNECTIVITY,
+    BinarySensorDevice,
+)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.helpers.typing import HomeAssistantType
+
+from . import MinecraftServer, MinecraftServerEntity
+from .const import DOMAIN, ICON_STATUS, NAME_STATUS
+
+
+async def async_setup_entry(
+    hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
+) -> None:
+    """Set up the Minecraft Server binary sensor platform."""
+    server = hass.data[DOMAIN][config_entry.unique_id]
+
+    # Create entities list.
+    entities = [MinecraftServerStatusBinarySensor(server)]
+
+    # Add binary sensor entities.
+    async_add_entities(entities, True)
+
+
+class MinecraftServerStatusBinarySensor(MinecraftServerEntity, BinarySensorDevice):
+    """Representation of a Minecraft Server status binary sensor."""
+
+    def __init__(self, server: MinecraftServer) -> None:
+        """Initialize status binary sensor."""
+        super().__init__(
+            server=server,
+            type_name=NAME_STATUS,
+            icon=ICON_STATUS,
+            device_class=DEVICE_CLASS_CONNECTIVITY,
+        )
+        self._is_on = False
+
+    @property
+    def is_on(self) -> bool:
+        """Return binary state."""
+        return self._is_on
+
+    async def async_update(self) -> None:
+        """Update status."""
+        self._is_on = self._server.online
diff --git a/homeassistant/components/minecraft_server/config_flow.py b/homeassistant/components/minecraft_server/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c6049a2c1b5caf9fcfd6f855681cd56f62f664e
--- /dev/null
+++ b/homeassistant/components/minecraft_server/config_flow.py
@@ -0,0 +1,116 @@
+"""Config flow for Minecraft Server integration."""
+from functools import partial
+import ipaddress
+
+import getmac
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.config_entries import ConfigFlow
+from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
+
+from . import MinecraftServer
+from .const import (  # pylint: disable=unused-import
+    DEFAULT_HOST,
+    DEFAULT_NAME,
+    DEFAULT_PORT,
+    DOMAIN,
+)
+
+
+class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
+    """Handle a config flow for Minecraft Server."""
+
+    VERSION = 1
+    CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
+
+    async def async_step_user(self, user_input=None):
+        """Handle the initial step."""
+        errors = {}
+
+        if user_input is not None:
+            # User inputs.
+            host = user_input[CONF_HOST]
+            port = user_input[CONF_PORT]
+
+            unique_id = ""
+
+            # Check if 'host' is a valid IP address and if so, get the MAC address.
+            ip_address = None
+            mac_address = None
+            try:
+                ip_address = ipaddress.ip_address(host)
+            except ValueError:
+                # Host is not a valid IP address.
+                pass
+            else:
+                # Host is a valid IP address.
+                if ip_address.version == 4:
+                    # Address type is IPv4.
+                    params = {"ip": host}
+                else:
+                    # Address type is IPv6.
+                    params = {"ip6": host}
+                mac_address = await self.hass.async_add_executor_job(
+                    partial(getmac.get_mac_address, **params)
+                )
+
+            # Validate IP address via valid MAC address.
+            if ip_address is not None and mac_address is None:
+                errors["base"] = "invalid_ip"
+            # Validate port configuration (limit to user and dynamic port range).
+            elif (port < 1024) or (port > 65535):
+                errors["base"] = "invalid_port"
+            # Validate host and port via ping request to server.
+            else:
+                # Build unique_id.
+                if ip_address is not None:
+                    # Since IP addresses can change and therefore are not allowed in a
+                    # unique_id, fall back to the MAC address.
+                    unique_id = f"{mac_address}-{port}"
+                else:
+                    # Use host name in unique_id (host names should not change).
+                    unique_id = f"{host}-{port}"
+
+                # Abort in case the host was already configured before.
+                await self.async_set_unique_id(unique_id)
+                self._abort_if_unique_id_configured()
+
+                # Create server instance with configuration data and try pinging the server.
+                server = MinecraftServer(self.hass, unique_id, user_input)
+                await server.async_check_connection()
+                if not server.online:
+                    # Host or port invalid or server not reachable.
+                    errors["base"] = "cannot_connect"
+                else:
+                    # Configuration data are available and no error was detected, create configuration entry.
+                    return self.async_create_entry(
+                        title=f"{host}:{port}", data=user_input
+                    )
+
+        # Show configuration form (default form in case of no user_input,
+        # form filled with user_input and eventually with errors otherwise).
+        return self._show_config_form(user_input, errors)
+
+    def _show_config_form(self, user_input=None, errors=None):
+        """Show the setup form to the user."""
+        if user_input is None:
+            user_input = {}
+
+        return self.async_show_form(
+            step_id="user",
+            data_schema=vol.Schema(
+                {
+                    vol.Required(
+                        CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)
+                    ): str,
+                    vol.Required(
+                        CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST)
+                    ): vol.All(str, vol.Lower),
+                    vol.Optional(
+                        CONF_PORT, default=user_input.get(CONF_PORT, DEFAULT_PORT)
+                    ): int,
+                }
+            ),
+            errors=errors,
+        )
diff --git a/homeassistant/components/minecraft_server/const.py b/homeassistant/components/minecraft_server/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3ab66154813a16ab04ef20cdd9d482c3c69221c
--- /dev/null
+++ b/homeassistant/components/minecraft_server/const.py
@@ -0,0 +1,37 @@
+"""Constants for the Minecraft Server integration."""
+
+ATTR_PLAYERS_LIST = "players_list"
+
+DEFAULT_HOST = "localhost"
+DEFAULT_NAME = "Minecraft Server"
+DEFAULT_PORT = 25565
+
+DOMAIN = "minecraft_server"
+
+ICON_LATENCY_TIME = "mdi:signal"
+ICON_PLAYERS_MAX = "mdi:account-multiple"
+ICON_PLAYERS_ONLINE = "mdi:account-multiple"
+ICON_PROTOCOL_VERSION = "mdi:numeric"
+ICON_STATUS = "mdi:lan"
+ICON_VERSION = "mdi:numeric"
+
+KEY_SERVERS = "servers"
+
+MANUFACTURER = "Mojang AB"
+
+NAME_LATENCY_TIME = "Latency Time"
+NAME_PLAYERS_MAX = "Players Max"
+NAME_PLAYERS_ONLINE = "Players Online"
+NAME_PROTOCOL_VERSION = "Protocol Version"
+NAME_STATUS = "Status"
+NAME_VERSION = "Version"
+
+SCAN_INTERVAL = 60
+
+SIGNAL_NAME_PREFIX = f"signal_{DOMAIN}"
+
+UNIT_LATENCY_TIME = "ms"
+UNIT_PLAYERS_MAX = "players"
+UNIT_PLAYERS_ONLINE = "players"
+UNIT_PROTOCOL_VERSION = None
+UNIT_VERSION = None
diff --git a/homeassistant/components/minecraft_server/manifest.json b/homeassistant/components/minecraft_server/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..1dda76dee772ef18688c49484e5d2c0d9714090d
--- /dev/null
+++ b/homeassistant/components/minecraft_server/manifest.json
@@ -0,0 +1,10 @@
+{
+  "domain": "minecraft_server",
+  "name": "Minecraft Server",
+  "config_flow": true,
+  "documentation": "https://www.home-assistant.io/integrations/minecraft_server",
+  "requirements": ["getmac==0.8.1", "mcstatus==2.3.0"],
+  "dependencies": [],
+  "codeowners": ["@elmurato"],
+  "quality_scale": "silver"
+}
\ No newline at end of file
diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b37a7d979b7dc2d372d16c79457e71926e0be74
--- /dev/null
+++ b/homeassistant/components/minecraft_server/sensor.py
@@ -0,0 +1,177 @@
+"""The Minecraft Server sensor platform."""
+
+import logging
+from typing import Any, Dict
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.helpers.typing import HomeAssistantType
+
+from . import MinecraftServer, MinecraftServerEntity
+from .const import (
+    ATTR_PLAYERS_LIST,
+    DOMAIN,
+    ICON_LATENCY_TIME,
+    ICON_PLAYERS_MAX,
+    ICON_PLAYERS_ONLINE,
+    ICON_PROTOCOL_VERSION,
+    ICON_VERSION,
+    NAME_LATENCY_TIME,
+    NAME_PLAYERS_MAX,
+    NAME_PLAYERS_ONLINE,
+    NAME_PROTOCOL_VERSION,
+    NAME_VERSION,
+    UNIT_LATENCY_TIME,
+    UNIT_PLAYERS_MAX,
+    UNIT_PLAYERS_ONLINE,
+    UNIT_PROTOCOL_VERSION,
+    UNIT_VERSION,
+)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(
+    hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
+) -> None:
+    """Set up the Minecraft Server sensor platform."""
+    server = hass.data[DOMAIN][config_entry.unique_id]
+
+    # Create entities list.
+    entities = [
+        MinecraftServerVersionSensor(server),
+        MinecraftServerProtocolVersionSensor(server),
+        MinecraftServerLatencyTimeSensor(server),
+        MinecraftServerPlayersOnlineSensor(server),
+        MinecraftServerPlayersMaxSensor(server),
+    ]
+
+    # Add sensor entities.
+    async_add_entities(entities, True)
+
+
+class MinecraftServerSensorEntity(MinecraftServerEntity):
+    """Representation of a Minecraft Server sensor base entity."""
+
+    def __init__(
+        self,
+        server: MinecraftServer,
+        type_name: str,
+        icon: str = None,
+        unit: str = None,
+        device_class: str = None,
+    ) -> None:
+        """Initialize sensor base entity."""
+        super().__init__(server, type_name, icon, device_class)
+        self._state = None
+        self._unit = unit
+
+    @property
+    def available(self) -> bool:
+        """Return sensor availability."""
+        return self._server.online
+
+    @property
+    def state(self) -> Any:
+        """Return sensor state."""
+        return self._state
+
+    @property
+    def unit_of_measurement(self) -> str:
+        """Return sensor measurement unit."""
+        return self._unit
+
+
+class MinecraftServerVersionSensor(MinecraftServerSensorEntity):
+    """Representation of a Minecraft Server version sensor."""
+
+    def __init__(self, server: MinecraftServer) -> None:
+        """Initialize version sensor."""
+        super().__init__(
+            server=server, type_name=NAME_VERSION, icon=ICON_VERSION, unit=UNIT_VERSION
+        )
+
+    async def async_update(self) -> None:
+        """Update version."""
+        self._state = self._server.version
+
+
+class MinecraftServerProtocolVersionSensor(MinecraftServerSensorEntity):
+    """Representation of a Minecraft Server protocol version sensor."""
+
+    def __init__(self, server: MinecraftServer) -> None:
+        """Initialize protocol version sensor."""
+        super().__init__(
+            server=server,
+            type_name=NAME_PROTOCOL_VERSION,
+            icon=ICON_PROTOCOL_VERSION,
+            unit=UNIT_PROTOCOL_VERSION,
+        )
+
+    async def async_update(self) -> None:
+        """Update protocol version."""
+        self._state = self._server.protocol_version
+
+
+class MinecraftServerLatencyTimeSensor(MinecraftServerSensorEntity):
+    """Representation of a Minecraft Server latency time sensor."""
+
+    def __init__(self, server: MinecraftServer) -> None:
+        """Initialize latency time sensor."""
+        super().__init__(
+            server=server,
+            type_name=NAME_LATENCY_TIME,
+            icon=ICON_LATENCY_TIME,
+            unit=UNIT_LATENCY_TIME,
+        )
+
+    async def async_update(self) -> None:
+        """Update latency time."""
+        self._state = self._server.latency_time
+
+
+class MinecraftServerPlayersOnlineSensor(MinecraftServerSensorEntity):
+    """Representation of a Minecraft Server online players sensor."""
+
+    def __init__(self, server: MinecraftServer) -> None:
+        """Initialize online players sensor."""
+        super().__init__(
+            server=server,
+            type_name=NAME_PLAYERS_ONLINE,
+            icon=ICON_PLAYERS_ONLINE,
+            unit=UNIT_PLAYERS_ONLINE,
+        )
+
+    async def async_update(self) -> None:
+        """Update online players state and device state attributes."""
+        self._state = self._server.players_online
+
+        device_state_attributes = None
+        players_list = self._server.players_list
+
+        if players_list is not None:
+            if len(players_list) != 0:
+                device_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list}
+
+        self._device_state_attributes = device_state_attributes
+
+    @property
+    def device_state_attributes(self) -> Dict[str, Any]:
+        """Return players list in device state attributes."""
+        return self._device_state_attributes
+
+
+class MinecraftServerPlayersMaxSensor(MinecraftServerSensorEntity):
+    """Representation of a Minecraft Server maximum number of players sensor."""
+
+    def __init__(self, server: MinecraftServer) -> None:
+        """Initialize maximum number of players sensor."""
+        super().__init__(
+            server=server,
+            type_name=NAME_PLAYERS_MAX,
+            icon=ICON_PLAYERS_MAX,
+            unit=UNIT_PLAYERS_MAX,
+        )
+
+    async def async_update(self) -> None:
+        """Update maximum number of players."""
+        self._state = self._server.players_max
diff --git a/homeassistant/components/minecraft_server/strings.json b/homeassistant/components/minecraft_server/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..7743d940be671400dd74c1830d0d6804f7114821
--- /dev/null
+++ b/homeassistant/components/minecraft_server/strings.json
@@ -0,0 +1,24 @@
+{
+    "config": {
+        "title": "Minecraft Server",
+        "step": {
+            "user": {
+                "title": "Link your Minecraft Server",
+                "description": "Set up your Minecraft Server instance to allow monitoring.",
+                "data": {
+                    "name": "Name",
+                    "host": "Host",
+                    "port": "Port"
+                }
+            }
+        },
+        "error": {
+            "invalid_port": "Port must be in range from 1024 to 65535. Please correct it and try again.",
+            "cannot_connect": "Failed to connect to server. Please check the host and port and try again. Also ensure that you are running at least Minecraft version 1.7 on your server.",
+            "invalid_ip": "IP address is invalid (MAC address could not be determined). Please correct it and try again."
+          },
+        "abort": {
+            "already_configured": "Host is already configured."
+        }
+    }
+}
\ No newline at end of file
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index 83f7d4cfcfa7f016c331a7ce1b0e500d1d07b1fe..4872b08e9fc86b4adeff93f00d92f41b435c72f0 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -57,6 +57,7 @@ FLOWS = [
     "met",
     "meteo_france",
     "mikrotik",
+    "minecraft_server",
     "mobile_app",
     "mqtt",
     "neato",
diff --git a/requirements_all.txt b/requirements_all.txt
index d83f0884def3e4fc8df4bea90932dd48b8ded28d..a0c76d3ab22e8f64d6c0fe1cd07f2292069f3c95 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -585,6 +585,7 @@ georss_qld_bushfire_alert_client==0.3
 # homeassistant.components.braviatv
 # homeassistant.components.huawei_lte
 # homeassistant.components.kef
+# homeassistant.components.minecraft_server
 # homeassistant.components.nmap_tracker
 getmac==0.8.1
 
@@ -837,6 +838,9 @@ maxcube-api==0.1.0
 # homeassistant.components.mythicbeastsdns
 mbddns==0.1.2
 
+# homeassistant.components.minecraft_server
+mcstatus==2.3.0
+
 # homeassistant.components.message_bird
 messagebird==1.2.0
 
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index c8d873b2fd64e6660a9c904fffa50cba3da1104f..33942e1b244e0a061eac791e7125730777fad61d 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -210,6 +210,7 @@ georss_qld_bushfire_alert_client==0.3
 # homeassistant.components.braviatv
 # homeassistant.components.huawei_lte
 # homeassistant.components.kef
+# homeassistant.components.minecraft_server
 # homeassistant.components.nmap_tracker
 getmac==0.8.1
 
@@ -301,6 +302,9 @@ luftdaten==0.6.3
 # homeassistant.components.mythicbeastsdns
 mbddns==0.1.2
 
+# homeassistant.components.minecraft_server
+mcstatus==2.3.0
+
 # homeassistant.components.meteo_france
 meteofrance==0.3.7
 
diff --git a/tests/components/minecraft_server/__init__.py b/tests/components/minecraft_server/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..36a1bb3f69d2ed2151924daf62036d7356508678
--- /dev/null
+++ b/tests/components/minecraft_server/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Minecraft Server integration."""
diff --git a/tests/components/minecraft_server/test_config_flow.py b/tests/components/minecraft_server/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..30626fbdcb02805a7149a054d7b79f8b1f689a05
--- /dev/null
+++ b/tests/components/minecraft_server/test_config_flow.py
@@ -0,0 +1,194 @@
+"""Test the Minecraft Server config flow."""
+
+from asynctest import patch
+from mcstatus.pinger import PingResponse
+
+from homeassistant.components.minecraft_server.const import (
+    DEFAULT_NAME,
+    DEFAULT_PORT,
+    DOMAIN,
+)
+from homeassistant.config_entries import SOURCE_USER
+from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
+from homeassistant.data_entry_flow import (
+    RESULT_TYPE_ABORT,
+    RESULT_TYPE_CREATE_ENTRY,
+    RESULT_TYPE_FORM,
+)
+from homeassistant.helpers.typing import HomeAssistantType
+
+from tests.common import MockConfigEntry
+
+STATUS_RESPONSE_RAW = {
+    "description": {"text": "Dummy Description"},
+    "version": {"name": "Dummy Version", "protocol": 123},
+    "players": {
+        "online": 3,
+        "max": 10,
+        "sample": [
+            {"name": "Player 1", "id": "1"},
+            {"name": "Player 2", "id": "2"},
+            {"name": "Player 3", "id": "3"},
+        ],
+    },
+}
+
+USER_INPUT = {
+    CONF_NAME: DEFAULT_NAME,
+    CONF_HOST: "mc.dummyserver.com",
+    CONF_PORT: DEFAULT_PORT,
+}
+
+USER_INPUT_IPV4 = {
+    CONF_NAME: DEFAULT_NAME,
+    CONF_HOST: "1.1.1.1",
+    CONF_PORT: DEFAULT_PORT,
+}
+
+USER_INPUT_IPV6 = {
+    CONF_NAME: DEFAULT_NAME,
+    CONF_HOST: "::ffff:0101:0101",
+    CONF_PORT: DEFAULT_PORT,
+}
+
+USER_INPUT_PORT_TOO_SMALL = {
+    CONF_NAME: DEFAULT_NAME,
+    CONF_HOST: "mc.dummyserver.com",
+    CONF_PORT: 1023,
+}
+
+USER_INPUT_PORT_TOO_LARGE = {
+    CONF_NAME: DEFAULT_NAME,
+    CONF_HOST: "mc.dummyserver.com",
+    CONF_PORT: 65536,
+}
+
+
+async def test_show_config_form(hass: HomeAssistantType) -> None:
+    """Test if initial configuration form is shown."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["step_id"] == "user"
+
+
+async def test_invalid_ip(hass: HomeAssistantType) -> None:
+    """Test error in case of an invalid IP address."""
+    with patch("getmac.get_mac_address", return_value=None):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4
+        )
+
+        assert result["type"] == RESULT_TYPE_FORM
+        assert result["errors"] == {"base": "invalid_ip"}
+
+
+async def test_same_host(hass: HomeAssistantType) -> None:
+    """Test abort in case of same host name."""
+    unique_id = f"{USER_INPUT[CONF_HOST]}-{USER_INPUT[CONF_PORT]}"
+    mock_config_entry = MockConfigEntry(
+        domain=DOMAIN, unique_id=unique_id, data=USER_INPUT
+    )
+    mock_config_entry.add_to_hass(hass)
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
+    )
+
+    assert result["type"] == RESULT_TYPE_ABORT
+    assert result["reason"] == "already_configured"
+
+
+async def test_port_too_small(hass: HomeAssistantType) -> None:
+    """Test error in case of a too small port."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_SMALL
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["errors"] == {"base": "invalid_port"}
+
+
+async def test_port_too_large(hass: HomeAssistantType) -> None:
+    """Test error in case of a too large port."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_LARGE
+    )
+
+    assert result["type"] == RESULT_TYPE_FORM
+    assert result["errors"] == {"base": "invalid_port"}
+
+
+async def test_connection_failed(hass: HomeAssistantType) -> None:
+    """Test error in case of a failed connection."""
+    with patch("mcstatus.server.MinecraftServer.ping", side_effect=OSError):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
+        )
+
+        assert result["type"] == RESULT_TYPE_FORM
+        assert result["errors"] == {"base": "cannot_connect"}
+
+
+async def test_connection_succeeded_with_host(hass: HomeAssistantType) -> None:
+    """Test config entry in case of a successful connection with a host name."""
+    with patch("mcstatus.server.MinecraftServer.ping", return_value=50):
+        with patch(
+            "mcstatus.server.MinecraftServer.status",
+            return_value=PingResponse(STATUS_RESPONSE_RAW),
+        ):
+            result = await hass.config_entries.flow.async_init(
+                DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
+            )
+
+            assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+            assert result["title"] == f"{USER_INPUT[CONF_HOST]}:{USER_INPUT[CONF_PORT]}"
+            assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME]
+            assert result["data"][CONF_HOST] == USER_INPUT[CONF_HOST]
+            assert result["data"][CONF_PORT] == USER_INPUT[CONF_PORT]
+
+
+async def test_connection_succeeded_with_ip4(hass: HomeAssistantType) -> None:
+    """Test config entry in case of a successful connection with an IPv4 address."""
+    with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"):
+        with patch("mcstatus.server.MinecraftServer.ping", return_value=50):
+            with patch(
+                "mcstatus.server.MinecraftServer.status",
+                return_value=PingResponse(STATUS_RESPONSE_RAW),
+            ):
+                result = await hass.config_entries.flow.async_init(
+                    DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4
+                )
+
+                assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+                assert (
+                    result["title"]
+                    == f"{USER_INPUT_IPV4[CONF_HOST]}:{USER_INPUT_IPV4[CONF_PORT]}"
+                )
+                assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME]
+                assert result["data"][CONF_HOST] == USER_INPUT_IPV4[CONF_HOST]
+                assert result["data"][CONF_PORT] == USER_INPUT_IPV4[CONF_PORT]
+
+
+async def test_connection_succeeded_with_ip6(hass: HomeAssistantType) -> None:
+    """Test config entry in case of a successful connection with an IPv6 address."""
+    with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"):
+        with patch("mcstatus.server.MinecraftServer.ping", return_value=50):
+            with patch(
+                "mcstatus.server.MinecraftServer.status",
+                return_value=PingResponse(STATUS_RESPONSE_RAW),
+            ):
+                result = await hass.config_entries.flow.async_init(
+                    DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6
+                )
+
+                assert result["type"] == RESULT_TYPE_CREATE_ENTRY
+                assert (
+                    result["title"]
+                    == f"{USER_INPUT_IPV6[CONF_HOST]}:{USER_INPUT_IPV6[CONF_PORT]}"
+                )
+                assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME]
+                assert result["data"][CONF_HOST] == USER_INPUT_IPV6[CONF_HOST]
+                assert result["data"][CONF_PORT] == USER_INPUT_IPV6[CONF_PORT]