Initial commit: Minecraft server monitoring skill

- Basic server status checking (online/offline)
- Player counts, latency, version info
- Example: corejourney.org
- No external dependencies required
This commit is contained in:
2026-02-11 21:05:29 +00:00
commit 87a9013ffb
2 changed files with 301 additions and 0 deletions

85
SKILL.md Normal file
View File

@@ -0,0 +1,85 @@
---
name: minecraft-monitor
description: Monitor Minecraft servers by checking online status, player counts, latency, and version info using the Server List Ping protocol. Use when the user asks to check Minecraft server status, monitor a Minecraft server, verify if a server is online, get player counts, or mentions Minecraft server monitoring. Example servers include corejourney.org.
---
# Minecraft Server Monitoring
Quickly check Minecraft server status without installing any external dependencies.
## Quick Check
Check if a server is online:
```bash
python3 ~/.openclaw/workspace/skills/public/minecraft-monitor/scripts/minecraft-status.py corejourney.org
```
```
🟢 corejourney.org:25565 - ONLINE (45ms)
Version: 1.20.4
Players: 3/20
Online: Steve, Alex, CreeperHunter
MOTD: Welcome to Core Journey!
```
## Usage
```bash
python3 ~/.openclaw/workspace/skills/public/minecraft-monitor/scripts/minecraft-status.py <host[:port]> [timeout]
```
- **host**: Server hostname or IP address (e.g., `corejourney.org`, `192.168.1.10`)
- **port**: Optional, defaults to `25565`
- **timeout**: Optional connection timeout in seconds (default: 5)
### Examples
```bash
# Check default port
python3 ~/.openclaw/workspace/skills/public/minecraft-monitor/scripts/minecraft-status.py corejourney.org
# Check custom port
python3 ~/.openclaw/workspace/skills/public/minecraft-monitor/scripts/minecraft-status.py corejourney.org:25566
# Check IP with longer timeout
python3 ~/.openclaw/workspace/skills/public/minecraft-monitor/scripts/minecraft-status.py 192.168.1.10 10
```
## Output
**Online server:**
- 🟢 Green (good ping) / 🟡 Yellow (moderate) / 🟠 Orange (slow)
- Server address and port
- Response time in milliseconds
- Minecraft version
- Current/maximum player count
- List of online players (up to 5 shown)
- Server MOTD (message of the day)
**Offline server:**
- 🔴 Red indicator
- Error message (timeout, connection refused, etc.)
## What's Being Monitored
- ✅ Online/offline status
- ✅ Player count (current/max)
- ✅ Response time/latency
- ✅ Server version
- ✅ Online player list (if available)
- ✅ Server MOTD
## Notes
- Uses Minecraft Server List Ping (SLP) protocol - works with all modern Minecraft servers
- No server-side plugins or RCON access required
- Exit code 0 if online, 1 if offline (useful for scripts/automation)
- SRV records are not automatically resolved - use the actual server address
## Integration Ideas
- Add to a cron job for periodic health checks
- Wrap in a monitoring script that alerts if server goes offline
- Use in automation pipelines that depend on server availability
- Create a dashboard showing server status history

216
scripts/minecraft-status.py Executable file
View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""
Minecraft Server Status Checker
Queries a Minecraft server for basic status information:
- Online status
- Player count (current/max)
- Server motd/message
- Ping response time
- Version information
Uses Minecraft Server List Ping protocol (SLP).
"""
import socket
import struct
import json
import sys
import time
def encode_varint(num):
"""Encode an integer as a Minecraft varint."""
if num < 0:
num = (1 << 32) + num
out = b""
while num > 0x7F:
out += bytes([(num & 0x7F) | 0x80])
num >>= 7
out += bytes([num])
return out
def decode_varint(sock):
"""Decode a Minecraft varint from socket."""
num = 0
shift = 0
while True:
byte = sock.recv(1)
if not byte:
raise IOError("Connection closed")
byte = byte[0]
num |= (byte & 0x7F) << shift
if not (byte & 0x80):
break
shift += 7
return num
def send_handshake(sock, host, port):
"""Send handshake packet."""
data = b""
# Packet ID (handshake = 0x00)
data += b"\x00"
# Protocol version (use latest: 765 for 1.19.3+)
data += encode_varint(765)
# Server address
host_bytes = host.encode('utf-8') if isinstance(host, str) else host
data += encode_varint(len(host_bytes))
data += host_bytes
# Server port
data += struct.pack(">H", port)
# Next state (status = 1)
data += b"\x01"
# Length prefix
length = encode_varint(len(data))
sock.send(length + data)
def send_status_request(sock):
"""Send status request packet."""
data = b"\x01\x00" # Request (empty)
length = encode_varint(len(data))
sock.send(length + data)
def read_status_response(sock):
"""Read server status response."""
# Packet length
length = decode_varint(sock)
# Packet ID should be 0x00
packet_id = decode_varint(sock)
# Response length
response_len = decode_varint(sock)
# Response data (JSON)
response = b""
while len(response) < response_len:
response += sock.recv(response_len - len(response))
return json.loads(response.decode('utf-8'))
def minecraft_status(host, port=25565, timeout=5):
"""Get Minecraft server status."""
try:
start_time = time.time()
# Connect to server
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((host, port))
# Handshake
send_handshake(sock, host, port)
send_status_request(sock)
# Read response
data = read_status_response(sock)
sock.close()
ping_ms = round((time.time() - start_time) * 1000, 1)
# Parse response
result = {
'online': True,
'host': host,
'port': port,
'ping': ping_ms,
}
# Version info
if 'version' in data:
result['version'] = data['version'].get('name', 'Unknown')
result['protocol'] = data['version'].get('protocol', 'Unknown')
# Player count
if 'players' in data:
players = data['players']
result['players_online'] = players.get('online', 0)
result['players_max'] = players.get('max', 0)
if 'sample' in players and players['sample']:
result['player_list'] = [p['name'] for p in players['sample']]
# MOTD (message of the day)
if 'description' in data:
motd = data['description']
if isinstance(motd, dict):
motd = motd.get('text', '')
result['motd'] = motd
return result
except socket.timeout:
return {
'online': False,
'host': host,
'port': port,
'error': 'Connection timed out'
}
except ConnectionRefusedError:
return {
'online': False,
'host': host,
'port': port,
'error': 'Connection refused'
}
except Exception as e:
return {
'online': False,
'host': host,
'port': port,
'error': str(e)
}
def format_status(status):
"""Format status for human-readable output."""
if not status['online']:
print(f"🔴 {status['host']}:{status['port']} - OFFLINE")
print(f" Error: {status.get('error', 'Unknown')}")
return
emoji = "🟢" if status['ping'] < 100 else "🟡" if status['ping'] < 200 else "🟠"
print(f"{emoji} {status['host']}:{status['port']} - ONLINE ({status['ping']}ms)")
print(f" Version: {status.get('version', 'Unknown')}")
print(f" Players: {status.get('players_online', 0)}/{status.get('players_max', 0)}")
if 'player_list' in status and status['player_list']:
print(f" Online: {', '.join(status['player_list'][:5])}" +
(f" ... +{len(status['player_list'])-5} more" if len(status['player_list']) > 5 else ""))
if 'motd' in status and status['motd']:
motd = status['motd'].strip()
if motd:
print(f" MOTD: {motd[:80]}{'...' if len(motd) > 80 else ''}")
def main():
if len(sys.argv) < 2:
print("Usage: python minecraft-status.py <host[:port]> [timeout]")
print("")
print("Examples:")
print(" python minecraft-status.py corejourney.org")
print(" python minecraft-status.py corejourney.org:25565")
print(" python minecraft-status.py 192.168.1.10:25566 10")
sys.exit(1)
# Parse host:port
hostport = sys.argv[1]
if ':' in hostport:
host, port_str = hostport.rsplit(':', 1)
try:
port = int(port_str)
except ValueError:
print(f"Invalid port: {port_str}")
sys.exit(1)
else:
host = hostport
port = 25565
# Parse timeout
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 5
status = minecraft_status(host, port, timeout)
format_status(status)
# Return exit code 0 if online, 1 if offline
sys.exit(0 if status['online'] else 1)
if __name__ == '__main__':
main()