ai-skills-api/mcp/gameservers.py
Lukas Parsons 7f7699ff94 Initial commit: Skills API with MCP servers
- FastAPI backend with SQLite (ai.db)
- Tables: skills, snippets, conventions, cache, memory
- MCP servers: homelab, gameservers, skills
- Docker Compose setup
- Seed data with 8 skills, 2 conventions, 2 snippets
- Token savings patterns via context bundles and caching
2026-03-22 21:18:23 -04:00

236 lines
6.9 KiB
Python

from mcp.server.fastmcp import FastMCP
import os
import json
from pathlib import Path
from typing import Optional
import subprocess
mcp = FastMCP("gameservers")
GAME_SERVERS_DIR = Path(os.getenv("GAME_SERVERS_DIR", "/opt/game-servers"))
@mcp.tool()
def list_servers() -> list[dict]:
"""List all game servers"""
if not GAME_SERVERS_DIR.exists():
return []
servers = []
for server_dir in GAME_SERVERS_DIR.iterdir():
if server_dir.is_dir():
config_file = server_dir / "config.json"
servers.append({
"name": server_dir.name,
"path": str(server_dir),
"has_config": config_file.exists(),
"config": json.loads(config_file.read_text()) if config_file.exists() else None
})
return servers
@mcp.tool()
def get_server_config(server_name: str) -> dict:
"""Get config for a specific game server"""
config_file = GAME_SERVERS_DIR / server_name / "config.json"
if not config_file.exists():
return {"error": f"No config found for {server_name}"}
return json.loads(config_file.read_text())
@mcp.tool()
def update_server_config(server_name: str, config: dict) -> dict:
"""Update game server config"""
server_dir = GAME_SERVERS_DIR / server_name
if not server_dir.exists():
return {"error": f"Server {server_name} not found"}
config_file = server_dir / "config.json"
config_file.write_text(json.dumps(config, indent=2))
return {"success": True, "message": f"Updated config for {server_name}"}
@mcp.tool()
def server_status(server_name: str) -> dict:
"""Get status of a game server"""
server_dir = GAME_SERVERS_DIR / server_name
if not server_dir.exists():
return {"error": f"Server {server_name} not found"}
status_file = server_dir / "status.json"
if not status_file.exists():
return {"status": "unknown", "message": "No status file found"}
return json.loads(status_file.read_text())
@mcp.tool()
def start_server(server_name: str) -> dict:
"""Start a game server"""
server_dir = GAME_SERVERS_DIR / server_name
if not server_dir.exists():
return {"error": f"Server {server_name} not found"}
start_script = server_dir / "start.sh"
if not start_script.exists():
return {"error": f"No start script found for {server_name}"}
try:
subprocess.run(
["bash", str(start_script)],
cwd=server_dir,
capture_output=True,
text=True,
timeout=30
)
return {"success": True, "message": f"Started {server_name}"}
except subprocess.TimeoutExpired:
return {"error": "Start command timed out"}
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def stop_server(server_name: str) -> dict:
"""Stop a game server"""
server_dir = GAME_SERVERS_DIR / server_name
if not server_dir.exists():
return {"error": f"Server {server_name} not found"}
stop_script = server_dir / "stop.sh"
if not stop_script.exists():
return {"error": f"No stop script found for {server_name}"}
try:
subprocess.run(
["bash", str(stop_script)],
cwd=server_dir,
capture_output=True,
text=True,
timeout=30
)
return {"success": True, "message": f"Stopped {server_name}"}
except subprocess.TimeoutExpired:
return {"error": "Stop command timed out"}
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def get_server_logs(server_name: str, lines: int = 100) -> str:
"""Get logs from a game server"""
server_dir = GAME_SERVERS_DIR / server_name
if not server_dir.exists():
return f"Server {server_name} not found"
log_file = server_dir / "logs" / "latest.log"
if not log_file.exists():
return f"No log file found for {server_name}"
try:
result = subprocess.run(
["tail", "-n", str(lines), str(log_file)],
capture_output=True,
text=True
)
return result.stdout
except Exception as e:
return f"Error reading logs: {e}"
@mcp.tool()
def create_server(
name: str,
game_type: str,
port: int,
max_players: int = 10,
config: Optional[dict] = None
) -> dict:
"""Create a new game server directory structure"""
server_dir = GAME_SERVERS_DIR / name
if server_dir.exists():
return {"error": f"Server {name} already exists"}
server_dir.mkdir(parents=True, exist_ok=True)
(server_dir / "logs").mkdir(exist_ok=True)
default_config = {
"name": name,
"game_type": game_type,
"port": port,
"max_players": max_players,
"auto_restart": True,
"restart_cron": "0 4 * * *"
}
if config:
default_config.update(config)
config_file = server_dir / "config.json"
config_file.write_text(json.dumps(default_config, indent=2))
start_script = server_dir / "start.sh"
start_script.write_text(f"#!/bin/bash\n# TODO: Add start command for {game_type}\necho 'Starting {name}'\n")
start_script.chmod(0o755)
stop_script = server_dir / "stop.sh"
stop_script.write_text(f"#!/bin/bash\n# TODO: Add stop command for {game_type}\necho 'Stopping {name}'\n")
stop_script.chmod(0o755)
return {
"success": True,
"message": f"Created server {name}",
"path": str(server_dir)
}
@mcp.tool()
def delete_server(server_name: str, keep_logs: bool = False) -> dict:
"""Delete a game server"""
server_dir = GAME_SERVERS_DIR / server_name
if not server_dir.exists():
return {"error": f"Server {server_name} not found"}
try:
if keep_logs:
import shutil
logs_dir = server_dir / "logs"
if logs_dir.exists():
shutil.rmtree(logs_dir)
else:
import shutil
shutil.rmtree(server_dir)
return {"success": True, "message": f"Deleted {server_name}"}
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def get_templates() -> list[dict]:
"""Get available game server templates"""
templates = {
"valheim": {
"game_type": "valheim",
"default_port": 2456,
"description": "Valheim dedicated server"
},
"minecraft": {
"game_type": "minecraft",
"default_port": 25565,
"description": "Minecraft Java Edition server"
},
"terraria": {
"game_type": "terraria",
"default_port": 7777,
"description": "Terraria server"
},
"factorio": {
"game_type": "factorio",
"default_port": 34197,
"description": "Factorio dedicated server"
}
}
return [{"name": k, **v} for k, v in templates.items()]
if __name__ == "__main__":
mcp.run()