fix: address 54 MEDIUM/LOW priority PR review issues

Comprehensive fixes across Python code, shell scripts, and documentation
addressing all remaining MEDIUM and LOW priority review comments.

Python Code Fixes (27 fixes):
- tools/info.py: Simplified dispatch with lookup tables, defensive guards,
  CPU fallback formatting, !s conversion flags, module-level sync assertion
- tools/docker.py: Case-insensitive container ID regex, keyword-only confirm,
  module-level ALL_ACTIONS constant
- tools/virtualization.py: Normalized single-VM dict responses, unified
  list/details queries
- core/client.py: Fixed HTTP client singleton race condition, compound key
  substring matching for sensitive data redaction
- subscriptions/: Extracted SSL context creation to shared helper in utils.py,
  replaced deprecated ssl._create_unverified_context API
- tools/array.py: Renamed parity_history to parity_status, hoisted ALL_ACTIONS
- tools/storage.py: Fixed dict(None) risks, temperature 0 falsiness bug
- tools/notifications.py, keys.py, rclone.py: Fixed dict(None) TypeError risks
- tests/: Fixed generator type annotations, added coverage for compound keys

Shell Script Fixes (13 fixes):
- dashboard.sh: Dynamic server discovery, conditional debug output, null-safe
  jq, notification count guard order, removed unused variables
- unraid-query.sh: Proper JSON escaping via jq, --ignore-errors and --insecure
  CLI flags, TLS verification now on by default
- validate-marketplace.sh: Removed unused YELLOW variable, defensive jq,
  simplified repository URL output

Documentation Fixes (24+ fixes):
- Version consistency: Updated all references to v0.2.0 across pyproject.toml,
  plugin.json, marketplace.json, MARKETPLACE.md, __init__.py, README files
- Tool count updates: Changed all "26 tools" references to "10 tools, 90 actions"
- Markdown lint: Fixed MD022, MD031, MD047 issues across multiple files
- Research docs: Fixed auth headers, removed web artifacts, corrected stale info
- Skills docs: Fixed query examples, endpoint counts, env var references

All 227 tests pass, ruff and ty checks clean.
This commit is contained in:
Jacob Magar
2026-02-15 17:09:31 -05:00
parent 6bbe46879e
commit 37e9424a5c
58 changed files with 1333 additions and 1175 deletions

View File

@@ -5,8 +5,8 @@ that cap at 10MB and start over (no rotation) for consistent use across all modu
"""
import logging
import os
from datetime import datetime
from pathlib import Path
import pytz
from rich.align import Align
@@ -16,6 +16,7 @@ from rich.panel import Panel
from rich.rule import Rule
from rich.text import Text
try:
from fastmcp.utilities.logging import get_logger as get_fastmcp_logger
FASTMCP_AVAILABLE = True
@@ -24,6 +25,7 @@ except ImportError:
from .settings import LOG_FILE_PATH, LOG_LEVEL_STR
# Global Rich console for consistent formatting
console = Console(stderr=True, force_terminal=True)
@@ -31,7 +33,7 @@ console = Console(stderr=True, force_terminal=True)
class OverwriteFileHandler(logging.FileHandler):
"""Custom file handler that overwrites the log file when it reaches max size."""
def __init__(self, filename, max_bytes=10*1024*1024, mode='a', encoding=None, delay=False):
def __init__(self, filename, max_bytes=10*1024*1024, mode="a", encoding=None, delay=False):
"""Initialize the handler.
Args:
@@ -47,18 +49,19 @@ class OverwriteFileHandler(logging.FileHandler):
def emit(self, record):
"""Emit a record, checking file size and overwriting if needed."""
# Check file size before writing
if self.stream and hasattr(self.stream, 'name'):
if self.stream and hasattr(self.stream, "name"):
try:
if os.path.exists(self.baseFilename):
file_size = os.path.getsize(self.baseFilename)
base_path = Path(self.baseFilename)
if base_path.exists():
file_size = base_path.stat().st_size
if file_size >= self.max_bytes:
# Close current stream
if self.stream:
self.stream.close()
# Remove the old file and start fresh
if os.path.exists(self.baseFilename):
os.remove(self.baseFilename)
if base_path.exists():
base_path.unlink()
# Reopen with truncate mode
self.stream = self._open()
@@ -75,9 +78,10 @@ class OverwriteFileHandler(logging.FileHandler):
)
super().emit(reset_record)
except OSError:
# If there's an issue checking file size, just continue normally
pass
except OSError as e:
import sys
print(f"WARNING: Log file size check failed: {e}. Continuing without rotation.",
file=sys.stderr)
# Emit the original record
super().emit(record)
@@ -119,11 +123,11 @@ def setup_logger(name: str = "UnraidMCPServer") -> logging.Logger:
file_handler = OverwriteFileHandler(
LOG_FILE_PATH,
max_bytes=10*1024*1024,
encoding='utf-8'
encoding="utf-8"
)
file_handler.setLevel(numeric_log_level)
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
"%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s"
)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
@@ -163,11 +167,11 @@ def configure_fastmcp_logger_with_rich() -> logging.Logger | None:
file_handler = OverwriteFileHandler(
LOG_FILE_PATH,
max_bytes=10*1024*1024,
encoding='utf-8'
encoding="utf-8"
)
file_handler.setLevel(numeric_log_level)
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
"%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s"
)
file_handler.setFormatter(file_formatter)
fastmcp_logger.addHandler(file_handler)
@@ -196,7 +200,7 @@ def configure_fastmcp_logger_with_rich() -> logging.Logger | None:
root_file_handler = OverwriteFileHandler(
LOG_FILE_PATH,
max_bytes=10*1024*1024,
encoding='utf-8'
encoding="utf-8"
)
root_file_handler.setLevel(numeric_log_level)
root_file_handler.setFormatter(file_formatter)
@@ -225,12 +229,12 @@ def log_configuration_status(logger: logging.Logger) -> None:
config = get_config_summary()
# Log configuration status
if config['api_url_configured']:
if config["api_url_configured"]:
logger.info(f"UNRAID_API_URL loaded: {config['api_url_preview']}")
else:
logger.warning("UNRAID_API_URL not found in environment or .env file.")
if config['api_key_configured']:
if config["api_key_configured"]:
logger.info("UNRAID_API_KEY loaded: ****") # Don't log the key itself
else:
logger.warning("UNRAID_API_KEY not found in environment or .env file.")
@@ -240,14 +244,14 @@ def log_configuration_status(logger: logging.Logger) -> None:
logger.info(f"UNRAID_MCP_TRANSPORT set to: {config['transport']}")
logger.info(f"UNRAID_MCP_LOG_LEVEL set to: {config['log_level']}")
if not config['config_valid']:
if not config["config_valid"]:
logger.error(f"Missing required configuration: {config['missing_config']}")
# Development logging helpers for Rich formatting
def get_est_timestamp() -> str:
"""Get current timestamp in EST timezone with YY/MM/DD format."""
est = pytz.timezone('US/Eastern')
est = pytz.timezone("US/Eastern")
now = datetime.now(est)
return now.strftime("%y/%m/%d %H:%M:%S")
@@ -271,7 +275,7 @@ def log_with_level_and_indent(message: str, level: str = "info", indent: int = 0
"error": {"color": "#BF616A", "icon": "", "style": "bold"}, # Nordic red
"warning": {"color": "#EBCB8B", "icon": "⚠️", "style": ""}, # Nordic yellow
"success": {"color": "#A3BE8C", "icon": "", "style": "bold"}, # Nordic green
"info": {"color": "#5E81AC", "icon": "", "style": "bold"}, # Nordic blue (bold)
"info": {"color": "#5E81AC", "icon": "\u2139\ufe0f", "style": "bold"}, # Nordic blue (bold)
"status": {"color": "#81A1C1", "icon": "🔍", "style": ""}, # Light Nordic blue
"debug": {"color": "#4C566A", "icon": "🐛", "style": ""}, # Nordic dark gray
}
@@ -328,11 +332,7 @@ def log_status(message: str, indent: int = 0) -> None:
if FASTMCP_AVAILABLE:
# Use FastMCP logger with Rich formatting
_fastmcp_logger = configure_fastmcp_logger_with_rich()
if _fastmcp_logger is not None:
logger = _fastmcp_logger
else:
# Fallback to our custom logger if FastMCP configuration fails
logger = setup_logger()
logger = _fastmcp_logger if _fastmcp_logger is not None else setup_logger()
else:
# Fallback to our custom logger if FastMCP is not available
logger = setup_logger()