Skip to main content
Glama
server.py7.17 kB
""" Main TrueNAS MCP Server implementation Supports both TrueNAS Core and TrueNAS SCALE with automatic variant detection. SCALE-specific features (Apps, Incus, VMs) are only registered when connected to a SCALE system. """ import sys import logging import asyncio from typing import Optional, List, Type from mcp.server.fastmcp import FastMCP from .config import get_settings from .client import get_client, close_client, TrueNASVariant from .tools import ( BaseTool, DebugTools, UserTools, StorageTools, SharingTools, SnapshotTools, AppTools, InstanceTools, LegacyVMTools, ) # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) class TrueNASMCPServer: """ Main TrueNAS MCP Server class Manages the MCP server instance and registers all available tools. Automatically detects TrueNAS variant (Core vs SCALE) and registers appropriate tools. """ # Tools that work on both TrueNAS Core and SCALE UNIVERSAL_TOOLS: List[Type[BaseTool]] = [ UserTools, StorageTools, SharingTools, SnapshotTools, ] # Tools that only work on TrueNAS SCALE SCALE_ONLY_TOOLS: List[Type[BaseTool]] = [ AppTools, # Docker Compose apps (SCALE 24.04+) InstanceTools, # Incus VMs/containers (SCALE 25.04+) LegacyVMTools, # bhyve VMs (SCALE, differs from Core) ] def __init__(self, name: str = "TrueNAS MCP Server"): """ Initialize the TrueNAS MCP Server Args: name: Server name for MCP """ self.name = name self.settings = get_settings() self.mcp = FastMCP(name) self.tools: List[BaseTool] = [] self.variant: TrueNASVariant = TrueNASVariant.UNKNOWN self._setup_logging() # Note: Tools are registered during initialize() after variant detection def _setup_logging(self): """Configure logging based on settings""" log_level = getattr(logging, self.settings.log_level) logging.getLogger().setLevel(log_level) # Set httpx logging to WARNING unless in DEBUG mode if self.settings.log_level != "DEBUG": logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpcore").setLevel(logging.WARNING) def _register_tools(self, variant: TrueNASVariant): """ Register tools with the MCP server based on TrueNAS variant Args: variant: Detected TrueNAS variant (Core, SCALE, or Unknown) """ logger.info(f"Registering MCP tools for TrueNAS {variant.value}...") # Start with universal tools that work on both Core and SCALE tool_classes: List[Type[BaseTool]] = list(self.UNIVERSAL_TOOLS) # Add SCALE-only tools if connected to SCALE if variant == TrueNASVariant.SCALE: tool_classes.extend(self.SCALE_ONLY_TOOLS) logger.info("Including SCALE-specific tools (Apps, Instances, VMs)") elif variant == TrueNASVariant.CORE: logger.info("Skipping SCALE-only tools (Apps, Instances, VMs) - Core detected") else: # Unknown variant - include all tools but warn tool_classes.extend(self.SCALE_ONLY_TOOLS) logger.warning( "TrueNAS variant unknown - registering all tools. " "SCALE-only tools may fail on TrueNAS Core." ) # Add debug tools if enabled if self.settings.enable_debug_tools or self.settings.is_development(): tool_classes.insert(0, DebugTools) # Register each tool class for tool_class in tool_classes: tool_instance = tool_class() self.tools.append(tool_instance) self._register_tool_methods(tool_instance) logger.info(f"Registered {len(self.tools)} tool categories") def _register_tool_methods(self, tool_instance): """Register individual tool methods from a tool instance""" # Get all methods that should be exposed as MCP tools tool_methods = tool_instance.get_tool_definitions() for method_name, method_func, method_description, method_params in tool_methods: # Register with MCP self.mcp.tool(name=method_name, description=method_description)(method_func) logger.debug(f"Registered tool: {method_name}") async def initialize(self): """Initialize the server, detect variant, and register tools""" logger.info(f"Initializing {self.name}...") try: # Initialize the client client = await get_client() # Detect TrueNAS variant (Core vs SCALE) self.variant = await client.detect_variant() logger.info( f"Connected to TrueNAS {self.variant.value.upper()} " f"version {client.version or 'unknown'}" ) # Register tools based on detected variant self._register_tools(self.variant) # Initialize all tools for tool in self.tools: tool.client = client tool.settings = self.settings await tool.initialize() logger.info(f"{self.name} initialized successfully") except Exception as e: logger.error(f"Failed to initialize server: {e}") raise async def cleanup(self): """Cleanup resources""" logger.info("Cleaning up resources...") try: # Close the client await close_client() logger.info("Cleanup completed") except Exception as e: logger.error(f"Error during cleanup: {e}") def run(self): """Run the MCP server""" logger.info(f"Starting {self.name}...") try: # Run initialization asyncio.run(self.initialize()) # Run the MCP server self.mcp.run() except KeyboardInterrupt: logger.info(f"\n{self.name} shutting down...") except Exception as e: logger.error(f"Server error: {e}") raise finally: # Cleanup asyncio.run(self.cleanup()) def create_server(name: Optional[str] = None) -> TrueNASMCPServer: """ Factory function to create a TrueNAS MCP Server instance Args: name: Optional server name Returns: TrueNASMCPServer instance """ return TrueNASMCPServer(name or "TrueNAS MCP Server") def main(): """Main entry point for the TrueNAS MCP server""" try: # Validate configuration settings = get_settings() # Create and run the server server = create_server() server.run() return 0 except Exception as e: logger.error(f"Fatal error: {e}") return 1 if __name__ == "__main__": sys.exit(main())

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vespo92/TrueNasCoreMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server