Skip to main content
Glama

MCP Developer Server

by RA86-dev
main.py25.8 kB
""" MCP Docker Developer Server (MCPDS) """ import docker import tempfile import os import argparse import server_version as sv from models import OutputResponse import sys import json import time import psutil import logging import logging.handlers from pathlib import Path from typing import Dict, Any from fastmcp import FastMCP from pydantic import BaseModel, Field from enum import Enum from cachetools import TTLCache from datetime import datetime from concurrent.futures import ThreadPoolExecutor from models.ContainerStatus import ContainerStatus from models.HealthStatus import HealthStatus from models.ServerMetrics import ServerMetrics from pydantic import BaseModel from enum import Enum from dataclasses import dataclass class SecurityLevel(str, Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" STRICT = "strict" # from SecurityLevel import SecurityLevel @dataclass class ServiceConfig: """Configuration for MCP services""" docker_management: bool = True browser_automation: bool = True monitoring_tools: bool = True development_tools: bool = True workflow_tools: bool = True documentation_tools: bool = True firecrawl_tools: bool = True searxng_tools: bool = True websocket_enabled: bool = False module_Finder: bool = True security_level: SecurityLevel = SecurityLevel.MEDIUM auto_cleanup_enabled: bool = True health_checks_enabled: bool = True distributed_mode: bool = False # Environment variables _DEVDOCS_URL = os.getenv("DEVDOCS_URL", "http://devdocs:9292") _SEARXNG_URL = os.getenv("SEARXNG_URL", "http://searxng:8080") uptime_launched = datetime.now() # Import all our modular tools try: from subtools.docker_tools import DockerTools from subtools.browser_tools import BrowserTools from subtools.monitoring_tools import MonitoringTools from subtools.development_tools import DevelopmentTools from subtools.data_storage import MarkdownTools from subtools.workflow_tools import WorkflowTools from subtools.documentation_tools import DocumentationTools from subtools.firecrawl_tools import FirecrawlTools from subtools.searxng_tools import SearXNGTools from subtools.module_finder import ModuleFinder from subtools.llms_support import LLMSText from subtools.prompts import PromptManager HAS_ALL_SUBTOOLS = True except ImportError as e: print(f"Warning: Some subtools could not be imported: {e}") HAS_ALL_SUBTOOLS = False # Configuration CACHE_TTL = 300 # 5 minutes MAX_CACHE_SIZE = 1000 RATE_LIMIT_REQUESTS = 100 RATE_LIMIT_WINDOW = 60 # seconds MAX_WORKERS = 10 REQUEST_TIMEOUT = 30 CONTAINER_TIMEOUT = 300 class MCPDockerServer: """Modular MCP Docker Server with pluggable tool modules""" def __init__(self, service_config: ServiceConfig = None): self.service_config = service_config or ServiceConfig() self.mcp = FastMCP( "MCPDocker-Enhanced", host="0.0.0.0" ) # Initialize directory structure script_dir = Path(__file__).parent self.docs_dir = script_dir / "documentation" self.logs_dir = script_dir / "logs" self.config_dir = script_dir / "config" self.backup_dir = script_dir / "backups" # Create directories for directory in [ self.docs_dir, self.logs_dir, self.config_dir, self.backup_dir, ]: directory.mkdir(exist_ok=True) # Initialize logging self.setup_enhanced_logging() # Initialize core components self._init_docker_client() self._init_caching_system() # Core storage self.active_containers = {} self.temp_dir = tempfile.mkdtemp(prefix="mcpdocker_enhanced_") # Performance and monitoring self.metrics = ServerMetrics() self.health_status = HealthStatus() self.start_time = time.time() # System capabilities self.gpu_info = self._detect_gpu_support() self.gpu_available = self.gpu_info["has_gpu"] # Thread pool for async operations self.executor = ThreadPoolExecutor( max_workers=min(MAX_WORKERS, (os.cpu_count() or 1) + 4), thread_name_prefix="MCPDocker", ) self.llms_support = LLMSText( ) # Define comprehensive set of allowed development images self.allowed_images = { # Base OS images "ubuntu:latest", "ubuntu:22.04", "ubuntu:20.04", "debian:latest", "debian:bullseye", "debian:bookworm", "alpine:latest", "alpine:3.18", "fedora:latest", "fedora:38", "rockylinux:latest", "rockylinux:9", # Language-specific images "python:3.11", "python:3.10", "python:3.9", "python:latest", "node:18", "node:20", "node:latest", "node:18-alpine", "node:20-alpine", "openjdk:17", "openjdk:11", "openjdk:21", "golang:1.21", "golang:latest", "golang:1.21-alpine", "rust:latest", "rust:1.70", "rust:1.70-slim", "php:8.2", "php:8.1", "php:latest", "ruby:3.2", "ruby:latest", # Database images "postgres:15", "postgres:14", "postgres:latest", "mysql:8.0", "mysql:latest", "redis:7", "redis:latest", "redis:alpine", "mongodb:latest", "mongodb:7", # Web servers "nginx:latest", "nginx:alpine", "httpd:latest", "httpd:alpine", } # Add GPU images if available if self.gpu_available: gpu_images = { "nvidia/cuda:12.0-runtime-ubuntu22.04", "nvidia/cuda:11.8-runtime-ubuntu22.04", "pytorch/pytorch:latest", "tensorflow/tensorflow:latest-gpu", "rocm/rocm-terminal:latest", "rocm/dev-ubuntu-20.04:latest", "rocm/dev-ubuntu-22.04:latest", } self.allowed_images.update(gpu_images) # Initialize all tool modules self._init_tool_modules() # Register all tools self._register_all_tools() def _init_docker_client(self): """Initialize Docker client with enhanced error handling""" try: self.docker_client = docker.from_env() self.docker_client.ping() self.logger.info("Docker client initialized successfully") except Exception as e: self.logger.error(f"Failed to initialize Docker client: {e}") raise def _init_caching_system(self): """Initialize caching system""" self.cache = TTLCache(maxsize=MAX_CACHE_SIZE, ttl=CACHE_TTL) def _init_tool_modules(self): """Initialize all tool modules""" if not HAS_ALL_SUBTOOLS: self.logger.warning( "Not all subtools available - some functionality will be limited" ) # Initialize Docker tools self.docker_tools = ( DockerTools( docker_client=self.docker_client, allowed_images=self.allowed_images, temp_dir=self.temp_dir, logger=self.logger, ) if HAS_ALL_SUBTOOLS else None ) self.module_finder = ModuleFinder() if HAS_ALL_SUBTOOLS else None self.PromptManager = PromptManager() # Initialize browser tools self.browser_tools = ( BrowserTools(temp_dir=self.temp_dir, logger=self.logger) if HAS_ALL_SUBTOOLS else None ) # Initialize monitoring tools self.monitoring_tools = ( MonitoringTools( docker_client=self.docker_client, active_containers=self.active_containers, logger=self.logger, ) if HAS_ALL_SUBTOOLS else None ) # Initialize development tools self.development_tools = ( DevelopmentTools( docker_client=self.docker_client, active_containers=self.active_containers, temp_dir=self.temp_dir, logger=self.logger, ) if HAS_ALL_SUBTOOLS else None ) # Initialize workflow tools self.workflow_tools = ( WorkflowTools(temp_dir=self.temp_dir, logger=self.logger) if HAS_ALL_SUBTOOLS else None ) # Initialize data storage tools self.data_storage_tools = ( MarkdownTools( markdown_path="/markdown" ) if HAS_ALL_SUBTOOLS else None ) # Initialize documentation tools self.documentation_tools = ( DocumentationTools( docs_dir=self.docs_dir, devdocs_url=_DEVDOCS_URL, logger=self.logger ) if HAS_ALL_SUBTOOLS else None ) # Initialize web scraping and search tools if os.getenv("ENABLE_FIRECRAWL", "false") == "true": # self.firecrawl_tools = FirecrawlTools(logger=self.logger) if HAS_ALL_SUBTOOLS else None if os.getenv("ENABLE_LOCAL_FIRECRAWL") == "true" and os.getenv("LOCAL_URL"): self.firecrawl_tools = ( FirecrawlTools( logger=self.logger, local_url=os.getenv("LOCAL_URL"), api_key=None, ) if HAS_ALL_SUBTOOLS else None ) else: self.firecrawl_tools = ( FirecrawlTools( logger=self.logger, local_url="http://localhost:3002", api_key=os.getenv("FIRECRAWL_API_KEY"), ) if HAS_ALL_SUBTOOLS else None ) else: self.firecrawl_tools = None self.searxng_tools = ( SearXNGTools(searxng_url=_SEARXNG_URL, logger=self.logger) if HAS_ALL_SUBTOOLS else None ) def _register_all_tools(self): """Register all tools with the MCP server""" try: if not HAS_ALL_SUBTOOLS: self.logger.error("Cannot register tools - subtools not available") return if self.llms_support: self.llms_support.add_tools(self.mcp) # Register core Docker tools if self.docker_tools and self._get_config("docker_management", True): self.docker_tools.register_tools(self.mcp) self.logger.info("Registered Docker management tools") if self.module_finder and self._get_config("module_finder", True): self.module_finder.add_tools(self.mcp) self.logger.info("Registered Module Finder tools") # Register browser automation tools if self.browser_tools and self._get_config("browser_automation", True): self.browser_tools.register_tools(self.mcp) self.logger.info("Registered browser automation tools") if self.PromptManager: self.PromptManager.add_prompt(self.mcp) self.logger.info("Registered Prompt Manager") # Register monitoring tools if self.monitoring_tools and self._get_config("monitoring_tools", True): self.monitoring_tools.register_tools(self.mcp) self.logger.info("Registered monitoring tools") # Register development tools if self.development_tools and self._get_config("development_tools", True): self.development_tools.register_tools(self.mcp) self.logger.info("Registered development tools") # Register workflow tools if self.workflow_tools and self._get_config("workflow_tools", True): self.workflow_tools.register_tools(self.mcp) self.logger.info("Registered workflow automation tools") # Register data storage tools if self.data_storage_tools: self.data_storage_tools.add_mcp_tools(self.mcp) self.logger.info("Registered data storage tools") # Register documentation tools if self.documentation_tools and self._get_config( "documentation_tools", True ): self.documentation_tools.register_tools(self.mcp) self.documentation_tools.register_resources(self.mcp) self.logger.info("Registered documentation tools") # Register web scraping and search tools if self.firecrawl_tools and self._get_config("firecrawl_tools", True): self.firecrawl_tools.register_tools(self.mcp) self.logger.info("Registered Firecrawl tools") if self.searxng_tools and self._get_config("searxng_tools", True): self.searxng_tools.register_tools(self.mcp) self.logger.info("Registered SearXNG tools") # Register notification tools # Register basic utility tools self._register_utility_tools() # Web interface has been removed to focus on core MCP functionality except Exception as e: self.logger.error(f"Failed to register tools: {e}") raise def _register_utility_tools(self): """Register basic utility tools""" @self.mcp.tool() async def get_server_info() -> str: """Get comprehensive server information and capabilities""" try: info = { "server_name": "MCPDocker-Enhanced-Modular", "version": f"{sv.SERVER_VERSION} - {sv.SERVER_NICKNAME}", "uptime_seconds": time.time() - self.start_time, "capabilities": { "docker_management": bool(self.docker_tools), "browser_automation": bool(self.browser_tools), "monitoring": bool(self.monitoring_tools), "development_tools": bool(self.development_tools), "workflow_automation": bool(self.workflow_tools), "documentation": bool(self.documentation_tools), "web_scraping": bool(self.firecrawl_tools), "web_search": bool(self.searxng_tools), "gpu_support": self.gpu_available, }, "configuration": { "allowed_images_count": len(self.allowed_images), "temp_directory": self.temp_dir, "docs_directory": str(self.docs_dir), "devdocs_url": _DEVDOCS_URL, "searxng_url": _SEARXNG_URL, }, "system": { "cpu_count": os.cpu_count(), "memory_gb": round( psutil.virtual_memory().total / (1024**3), 2 ), "platform": sys.platform, }, "docker": { "connected": True, "version": self.docker_client.version().get( "Version", "Unknown" ), "active_containers": len(self.active_containers), }, } return json.dumps(info, indent=2) except Exception as e: return f"Error getting server info: {str(e)}" @self.mcp.tool() async def health_check() -> str: """Perform a comprehensive health check of all services""" try: health = { "overall_status": "healthy", "timestamp": datetime.now().isoformat(), "services": { "docker": { "status": "healthy", "details": "Connected and responsive", }, "mcp_server": { "status": "healthy", "details": "All tools registered", }, "file_system": { "status": "healthy", "details": "All directories accessible", }, }, "system_resources": { "cpu_percent": psutil.cpu_percent(), "memory_percent": psutil.virtual_memory().percent, "disk_percent": psutil.disk_usage("/").percent, }, } # Check external services if self.documentation_tools: try: import requests response = requests.get(_DEVDOCS_URL, timeout=5) health["services"]["devdocs"] = { "status": ( "healthy" if response.status_code == 200 else "degraded" ), "details": f"HTTP {response.status_code}", } except Exception: health["services"]["devdocs"] = { "status": "unavailable", "details": "Service not reachable", } if self.searxng_tools: try: import requests response = requests.get(_SEARXNG_URL, timeout=5) health["services"]["searxng"] = { "status": ( "healthy" if response.status_code == 200 else "degraded" ), "details": f"HTTP {response.status_code}", } except Exception: health["services"]["searxng"] = { "status": "unavailable", "details": "Service not reachable", } return json.dumps(health, indent=2) except Exception as e: return f"Error performing health check: {str(e)}" def setup_enhanced_logging(self): """Setup enhanced logging with rotation""" log_file = self.logs_dir / "mcpdocker.log" # Create logger self.logger = logging.getLogger("MCPDockerServer") self.logger.setLevel(logging.INFO) # Create handlers file_handler = logging.handlers.RotatingFileHandler( log_file, maxBytes=10 * 1024 * 1024, backupCount=5 # 10MB files, keep 5 ) console_handler = logging.StreamHandler() # Create formatter formatter = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # Add handlers self.logger.addHandler(file_handler) if sys.stdin.isatty(): # Only add console handler if running interactively self.logger.addHandler(console_handler) def _detect_gpu_support(self) -> Dict[str, Any]: """Detect available GPU support""" gpu_info = {"has_gpu": False, "type": None, "details": {}} try: import subprocess # Check NVIDIA result = subprocess.run( ["nvidia-smi"], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: gpu_info["has_gpu"] = True gpu_info["type"] = "nvidia" gpu_info["details"]["nvidia"] = "Available" except (subprocess.TimeoutExpired, FileNotFoundError): pass try: # Check AMD ROCm result = subprocess.run( ["rocm-smi"], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: gpu_info["has_gpu"] = True gpu_info["type"] = "amd_rocm" gpu_info["details"]["amd_rocm"] = "Available" except (subprocess.TimeoutExpired, FileNotFoundError): pass return gpu_info def _get_config(self, key: str, default=None): """Helper method to safely get configuration values""" if hasattr(self.service_config, key): return getattr(self.service_config, key) return default def cleanup(self): """Clean up resources on shutdown""" try: # Stop all active containers for container_id, container_info in self.active_containers.items(): try: container_info["container"].stop() except Exception: pass # Clean up temp directory import shutil if os.path.exists(self.temp_dir): shutil.rmtree(self.temp_dir) # Shutdown thread pool self.executor.shutdown(wait=True) except Exception as e: if hasattr(self, "logger"): self.logger.error(f"Error during cleanup: {e}") if hasattr(self, "logger"): self.logger.info("MCP Docker Server cleanup completed") def run(self, transport_method: str = "stdio"): """Run the MCP server""" # Suppress all logging in MCP mode to avoid protocol conflicts if transport_method == "stdio" and not sys.stdin.isatty(): logging.getLogger().setLevel(logging.CRITICAL) logging.getLogger("uvicorn").setLevel(logging.CRITICAL) logging.getLogger("fastapi").setLevel(logging.CRITICAL) return self.mcp.run(transport=transport_method) def serve_server(): """Main entry point""" parser = argparse.ArgumentParser( description="MCP Docker Server - Enhanced Modular Edition" ) parser.add_argument( "--transport", default="stdio", choices=["stdio", "ws", "sse","streamable-http","http"], help="Transport method for MCP communication", ) parser.add_argument( "--host", default="localhost", help="Host for WebSocket/SSE transport" ) parser.add_argument( "--port", type=int, default=8000, help="Port for WebSocket/SSE transport" ) parser.add_argument("--config", help="Path to configuration file") parser.add_argument( "--disable-docker", action="store_true", help="Disable Docker management" ) parser.add_argument( "--disable-browser", action="store_true", help="Disable browser automation" ) parser.add_argument( "--disable-monitoring", action="store_true", help="Disable monitoring tools" ) parser.add_argument( "--disable-dev", action="store_true", help="Disable development tools" ) parser.add_argument( "--disable-workflow", action="store_true", help="Disable workflow tools" ) parser.add_argument( "--disable-docs", action="store_true", help="Disable documentation tools" ) parser.add_argument( "--disable-firecrawl", action="store_true", help="Disable Firecrawl tools" ) parser.add_argument( "--disable-searxng", action="store_true", help="Disable SearXNG tools" ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose logging" ) args = parser.parse_args() # Create service configuration service_config = ServiceConfig() if args.disable_docker: service_config.docker_management = False if args.disable_browser: service_config.browser_automation = False if args.disable_monitoring: service_config.monitoring_tools = False if args.disable_dev: service_config.development_tools = False if args.disable_workflow: service_config.workflow_tools = False if args.disable_docs: service_config.documentation_tools = False if args.disable_firecrawl: service_config.firecrawl_tools = False if args.disable_searxng: service_config.searxng_tools = False try: # Initialize and run server server = MCPDockerServer(service_config) if args.verbose: server.logger.setLevel(logging.DEBUG) server.logger.info( f"Starting MCP Docker Server with transport: {args.transport}" ) server.logger.info(f"Configuration: {service_config}") return server.run(transport_method=args.transport) except KeyboardInterrupt: server.logger.info("Server shutdown requested") return 0 except Exception as e: print(f"Failed to start server: {e}") return 1 finally: if "server" in locals(): server.cleanup() if __name__ == "__main__": sys.exit(serve_server())

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/RA86-dev/mcpdev-server'

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