[
  {
    "path": ".gitignore",
    "content": "# Python-generated files\n__pycache__/\n*.py[oc]\nbuild/\ndist/\nwheels/\n*.egg-info\n\n# Virtual environments\n.venv\nconf\nwork\nREADME_PRIVATE.md\ndocker_compose_files"
  },
  {
    "path": ".python-version",
    "content": "3.12\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) [year] [fullname]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 🐳 docker-mcp\n\n[![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![smithery badge](https://smithery.ai/badge/docker-mcp)](https://smithery.ai/protocol/docker-mcp)\n\nA powerful Model Context Protocol (MCP) server for Docker operations, enabling seamless container and compose stack management through Claude AI.\n\n## ✨ Features\n\n- 🚀 Container creation and instantiation\n- 📦 Docker Compose stack deployment\n- 🔍 Container logs retrieval\n- 📊 Container listing and status monitoring\n\n### 🎬 Demos\n#### Deploying a Docker Compose Stack\n\n\nhttps://github.com/user-attachments/assets/b5f6e40a-542b-4a39-ba12-7fdf803ee278\n\n\n\n#### Analyzing Container Logs\n\n\n\nhttps://github.com/user-attachments/assets/da386eea-2fab-4835-82ae-896de955d934\n\n\n\n## 🚀 Quickstart\n\nTo try this in Claude Desktop app, add this to your claude config files:\n```json\n{\n  \"mcpServers\": {\n    \"docker-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"docker-mcp\"\n      ]\n    }\n  }\n}\n```\n\n### Installing via Smithery\n\nTo install Docker MCP for Claude Desktop automatically via [Smithery](https://smithery.ai/protocol/docker-mcp):\n\n```bash\nnpx @smithery/cli install docker-mcp --client claude\n```\n\n### Prerequisites\n\n- UV (package manager)\n- Python 3.12+\n- Docker Desktop or Docker Engine\n- Claude Desktop\n\n### Installation\n\n#### Claude Desktop Configuration\n\nAdd the server configuration to your Claude Desktop config file:\n\n**MacOS**: `~/Library/Application\\ Support/Claude/claude_desktop_config.json`  \n**Windows**: `%APPDATA%/Claude/claude_desktop_config.json`\n\n<details>\n  <summary>💻 Development Configuration</summary>\n\n```json\n{\n  \"mcpServers\": {\n    \"docker-mcp\": {\n      \"command\": \"uv\",\n      \"args\": [\n        \"--directory\",\n        \"<path-to-docker-mcp>\",\n        \"run\",\n        \"docker-mcp\"\n      ]\n    }\n  }\n}\n```\n</details>\n\n<details>\n  <summary>🚀 Production Configuration</summary>\n\n```json\n{\n  \"mcpServers\": {\n    \"docker-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"docker-mcp\"\n      ]\n    }\n  }\n}\n```\n</details>\n\n## 🛠️ Development\n\n### Local Setup\n\n1. Clone the repository:\n```bash\ngit clone https://github.com/QuantGeekDev/docker-mcp.git\ncd docker-mcp\n```\n\n2. Create and activate a virtual environment:\n```bash\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\n```\n\n3. Install dependencies:\n```bash\nuv sync\n```\n\n### 🔍 Debugging\n\nLaunch the MCP Inspector for debugging:\n\n```bash\nnpx @modelcontextprotocol/inspector uv --directory <path-to-docker-mcp> run docker-mcp\n```\n\nThe Inspector will provide a URL to access the debugging interface.\n\n## 📝 Available Tools\n\nThe server provides the following tools:\n\n### create-container\nCreates a standalone Docker container\n```json\n{\n    \"image\": \"image-name\",\n    \"name\": \"container-name\",\n    \"ports\": {\"80\": \"80\"},\n    \"environment\": {\"ENV_VAR\": \"value\"}\n}\n```\n\n### deploy-compose\nDeploys a Docker Compose stack\n```json\n{\n    \"project_name\": \"example-stack\",\n    \"compose_yaml\": \"version: '3.8'\\nservices:\\n  service1:\\n    image: image1:latest\\n    ports:\\n      - '8080:80'\"\n}\n```\n\n### get-logs\nRetrieves logs from a specific container\n```json\n{\n    \"container_name\": \"my-container\"\n}\n```\n\n### list-containers\nLists all Docker containers\n```json\n{}\n```\n\n## 🚧 Current Limitations\n\n- No built-in environment variable support for containers\n- No volume management\n- No network management\n- No container health checks\n- No container restart policies\n- No container resource limits\n\n## 🤝 Contributing\n\n1. Fork the repository from [docker-mcp](https://github.com/QuantGeekDev/docker-mcp)\n2. Create your feature branch\n3. Commit your changes\n4. Push to the branch\n5. Open a Pull Request\n\n## 📜 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## ✨ Authors\n\n- **Alex Andru** - *Initial work | Core contributor* - [@QuantGeekDev](https://github.com/QuantGeekDev)\n- **Ali Sadykov** - *Initial work  | Core contributor* - [@md-archive](https://github.com/md-archive)\n\n---\nMade with ❤️\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"docker-mcp\"\nversion = \"0.1.0\"\ndescription = \"A docker MCP server\"\nreadme = \"README.md\"\nrequires-python = \">=3.12\"\ndependencies = [\n    \"httpx>=0.28.0\",\n    \"mcp>=1.0.0\",\n    \"python-dotenv>=1.0.1\",\n    \"python-on-whales>=0.67.0\",\n    \"pyyaml>=6.0.1\"\n]\n\n[[project.authors]]\nname = \"Alex Andru\"\nemail = \"alex007d@gmail.com\"\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project.scripts]\ndocker-mcp = \"docker_mcp:main\""
  },
  {
    "path": "src/docker_mcp/__init__.py",
    "content": "from . import server\nimport asyncio\n\ndef main():\n    \"\"\"Main entry point for the package.\"\"\"\n    asyncio.run(server.main())\n\n# Optionally expose other important items at package level\n__all__ = ['main', 'server']"
  },
  {
    "path": "src/docker_mcp/docker_executor.py",
    "content": "from typing import Tuple, Protocol, List\nimport asyncio\nimport os\nimport platform\nimport shutil\nfrom abc import ABC, abstractmethod\n\n\nclass CommandExecutor(Protocol):\n    async def execute(self, cmd: str | List[str]) -> Tuple[int, str, str]:\n        pass\n\n\nclass WindowsExecutor:\n    async def execute(self, cmd: str) -> Tuple[int, str, str]:\n        process = await asyncio.create_subprocess_shell(\n            cmd,\n            stdout=asyncio.subprocess.PIPE,\n            stderr=asyncio.subprocess.PIPE,\n            shell=True\n        )\n        stdout, stderr = await process.communicate()\n        return process.returncode, stdout.decode(), stderr.decode()\n\n\nclass UnixExecutor:\n    async def execute(self, cmd: List[str]) -> Tuple[int, str, str]:\n        process = await asyncio.create_subprocess_exec(\n            *cmd,\n            stdout=asyncio.subprocess.PIPE,\n            stderr=asyncio.subprocess.PIPE\n        )\n        stdout, stderr = await process.communicate()\n        return process.returncode, stdout.decode(), stderr.decode()\n\n\nclass DockerExecutorBase(ABC):\n    def __init__(self):\n        self.docker_cmd = self._initialize_docker_cmd()\n        self.executor = WindowsExecutor() if platform.system() == 'Windows' else UnixExecutor()\n\n    @abstractmethod\n    async def run_command(self, command: str, *args) -> Tuple[int, str, str]:\n        pass\n\n    def _initialize_docker_cmd(self) -> str:\n        if platform.system() == 'Windows':\n            docker_dir = r\"C:\\Program Files\\Docker\\Docker\\resources\\bin\"\n            docker_paths = [\n                os.path.join(docker_dir, \"docker-compose.exe\"),\n                os.path.join(docker_dir, \"docker.exe\")\n            ]\n            for path in docker_paths:\n                if os.path.exists(path):\n                    return path\n\n        docker_cmd = shutil.which('docker')\n        if not docker_cmd:\n            raise RuntimeError(\"Docker executable not found\")\n        return docker_cmd\n\n\nclass DockerComposeExecutor(DockerExecutorBase):\n    def __init__(self, compose_file: str, project_name: str):\n        super().__init__()\n        self.compose_file = os.path.abspath(compose_file)\n        self.project_name = project_name\n\n    async def run_command(self, command: str, *args) -> Tuple[int, str, str]:\n        if platform.system() == 'Windows':\n            cmd = self._build_windows_command(command, *args)\n        else:\n            cmd = self._build_unix_command(command, *args)\n        return await self.executor.execute(cmd)\n\n    def _build_windows_command(self, command: str, *args) -> str:\n        compose_file = self.compose_file.replace('\\\\', '/')\n        return (f'cd \"{os.path.dirname(compose_file)}\" && docker compose '\n                f'-f \"{os.path.basename(compose_file)}\" '\n                f'-p {self.project_name} {command} {\" \".join(args)}')\n\n    def _build_unix_command(self, command: str, *args) -> list[str]:\n        return [\n            self.docker_cmd,\n            \"compose\",\n            \"-f\", self.compose_file,\n            \"-p\", self.project_name,\n            command,\n            *args\n        ]\n\n    async def down(self) -> Tuple[int, str, str]:\n        return await self.run_command(\"down\", \"--volumes\")\n\n    async def pull(self) -> Tuple[int, str, str]:\n        return await self.run_command(\"pull\")\n\n    async def up(self) -> Tuple[int, str, str]:\n        return await self.run_command(\"up\", \"-d\")\n\n    async def ps(self) -> Tuple[int, str, str]:\n        return await self.run_command(\"ps\")\n"
  },
  {
    "path": "src/docker_mcp/handlers.py",
    "content": "from typing import List, Dict, Any\nimport asyncio\nimport os\nimport yaml\nimport platform\nfrom python_on_whales import DockerClient\nfrom mcp.types import TextContent, Tool, Prompt, PromptArgument, GetPromptResult, PromptMessage\nfrom .docker_executor import DockerComposeExecutor\ndocker_client = DockerClient()\n\n\nasync def parse_port_mapping(host_key: str, container_port: str | int) -> tuple[str, str] | tuple[str, str, str]:\n    if '/' in str(host_key):\n        host_port, protocol = host_key.split('/')\n        if protocol.lower() == 'udp':\n            return (str(host_port), str(container_port), 'udp')\n        return (str(host_port), str(container_port))\n\n    if isinstance(container_port, str) and '/' in container_port:\n        port, protocol = container_port.split('/')\n        if protocol.lower() == 'udp':\n            return (str(host_key), port, 'udp')\n        return (str(host_key), port)\n\n    return (str(host_key), str(container_port))\n\n\nclass DockerHandlers:\n    TIMEOUT_AMOUNT = 200\n\n    @staticmethod\n    async def handle_create_container(arguments: Dict[str, Any]) -> List[TextContent]:\n        try:\n            image = arguments[\"image\"]\n            container_name = arguments.get(\"name\")\n            ports = arguments.get(\"ports\", {})\n            environment = arguments.get(\"environment\", {})\n\n            if not image:\n                raise ValueError(\"Image name cannot be empty\")\n\n            port_mappings = []\n            for host_key, container_port in ports.items():\n                mapping = await parse_port_mapping(host_key, container_port)\n                port_mappings.append(mapping)\n\n            async def pull_and_run():\n                if not docker_client.image.exists(image):\n                    await asyncio.to_thread(docker_client.image.pull, image)\n\n                container = await asyncio.to_thread(\n                    docker_client.container.run,\n                    image,\n                    name=container_name,\n                    publish=port_mappings,\n                    envs=environment,\n                    detach=True\n                )\n                return container\n\n            container = await asyncio.wait_for(pull_and_run(), timeout=DockerHandlers.TIMEOUT_AMOUNT)\n            return [TextContent(type=\"text\", text=f\"Created container '{container.name}' (ID: {container.id})\")]\n        except asyncio.TimeoutError:\n            return [TextContent(type=\"text\", text=f\"Operation timed out after {DockerHandlers.TIMEOUT_AMOUNT} seconds\")]\n        except Exception as e:\n            return [TextContent(type=\"text\", text=f\"Error creating container: {str(e)} | Arguments: {arguments}\")]\n\n    @staticmethod\n    async def handle_deploy_compose(arguments: Dict[str, Any]) -> List[TextContent]:\n        debug_info = []\n        try:\n            compose_yaml = arguments.get(\"compose_yaml\")\n            project_name = arguments.get(\"project_name\")\n\n            if not compose_yaml or not project_name:\n                raise ValueError(\n                    \"Missing required compose_yaml or project_name\")\n\n            yaml_content = DockerHandlers._process_yaml(\n                compose_yaml, debug_info)\n            compose_path = DockerHandlers._save_compose_file(\n                yaml_content, project_name)\n\n            try:\n                result = await DockerHandlers._deploy_stack(compose_path, project_name, debug_info)\n                return [TextContent(type=\"text\", text=result)]\n            finally:\n                DockerHandlers._cleanup_files(compose_path)\n\n        except Exception as e:\n            debug_output = \"\\n\".join(debug_info)\n            return [TextContent(type=\"text\", text=f\"Error deploying compose stack: {str(e)}\\n\\nDebug Information:\\n{debug_output}\")]\n\n    @staticmethod\n    def _process_yaml(compose_yaml: str, debug_info: List[str]) -> dict:\n        debug_info.append(\"=== Original YAML ===\")\n        debug_info.append(compose_yaml)\n\n        try:\n            yaml_content = yaml.safe_load(compose_yaml)\n            debug_info.append(\"\\n=== Loaded YAML Structure ===\")\n            debug_info.append(str(yaml_content))\n            return yaml_content\n        except yaml.YAMLError as e:\n            raise ValueError(f\"Invalid YAML format: {str(e)}\")\n\n    @staticmethod\n    def _save_compose_file(yaml_content: dict, project_name: str) -> str:\n        compose_dir = os.path.join(os.getcwd(), \"docker_compose_files\")\n        os.makedirs(compose_dir, exist_ok=True)\n\n        compose_yaml = yaml.safe_dump(\n            yaml_content, default_flow_style=False, sort_keys=False)\n        compose_path = os.path.join(\n            compose_dir, f\"{project_name}-docker-compose.yml\")\n\n        with open(compose_path, 'w', encoding='utf-8') as f:\n            f.write(compose_yaml)\n            f.flush()\n            if platform.system() != 'Windows':\n                os.fsync(f.fileno())\n\n        return compose_path\n\n    @staticmethod\n    async def _deploy_stack(compose_path: str, project_name: str, debug_info: List[str]) -> str:\n        compose = DockerComposeExecutor(compose_path, project_name)\n\n        for command in [compose.down, compose.up]:\n            try:\n                code, out, err = await command()\n                debug_info.extend([\n                    f\"\\n=== {command.__name__.capitalize()} Command ===\",\n                    f\"Return Code: {code}\",\n                    f\"Stdout: {out}\",\n                    f\"Stderr: {err}\"\n                ])\n\n                if code != 0 and command == compose.up:\n                    raise Exception(f\"Deploy failed with code {code}: {err}\")\n            except Exception as e:\n                if command != compose.down:\n                    raise e\n                debug_info.append(f\"Warning during {\n                                  command.__name__}: {str(e)}\")\n\n        code, out, err = await compose.ps()\n        service_info = out if code == 0 else \"Unable to list services\"\n\n        return (f\"Successfully deployed compose stack '{project_name}'\\n\"\n                f\"Running services:\\n{service_info}\\n\\n\"\n                f\"Debug Info:\\n{chr(10).join(debug_info)}\")\n\n    @staticmethod\n    def _cleanup_files(compose_path: str) -> None:\n        try:\n            if os.path.exists(compose_path):\n                os.remove(compose_path)\n            compose_dir = os.path.dirname(compose_path)\n            if os.path.exists(compose_dir) and not os.listdir(compose_dir):\n                os.rmdir(compose_dir)\n        except Exception as e:\n            print(f\"Warning during cleanup: {str(e)}\")\n\n    @staticmethod\n    async def handle_get_logs(arguments: Dict[str, Any]) -> List[TextContent]:\n        debug_info = []\n        try:\n            container_name = arguments.get(\"container_name\")\n            if not container_name:\n                raise ValueError(\"Missing required container_name\")\n\n            debug_info.append(f\"Fetching logs for container '{\n                              container_name}'\")\n            logs = await asyncio.to_thread(docker_client.container.logs, container_name, tail=100)\n\n            return [TextContent(type=\"text\", text=f\"Logs for container '{container_name}':\\n{logs}\\n\\nDebug Info:\\n{chr(10).join(debug_info)}\")]\n        except Exception as e:\n            debug_output = \"\\n\".join(debug_info)\n            return [TextContent(type=\"text\", text=f\"Error retrieving logs: {str(e)}\\n\\nDebug Information:\\n{debug_output}\")]\n\n    @staticmethod\n    async def handle_list_containers(arguments: Dict[str, Any]) -> List[TextContent]:\n        debug_info = []\n        try:\n            debug_info.append(\"Listing all Docker containers\")\n            containers = await asyncio.to_thread(docker_client.container.list, all=True)\n            container_list = \"\\n\".join(\n                [f\"{c.id[:12]} - {c.name} - {c.state.status}\" for c in containers])\n\n            return [TextContent(type=\"text\", text=f\"All Docker Containers:\\n{container_list}\\n\\nDebug Info:\\n{chr(10).join(debug_info)}\")]\n        except Exception as e:\n            debug_output = \"\\n\".join(debug_info)\n            return [TextContent(type=\"text\", text=f\"Error listing containers: {str(e)}\\n\\nDebug Information:\\n{debug_output}\")]\n"
  },
  {
    "path": "src/docker_mcp/server.py",
    "content": "import asyncio\nimport signal\nimport sys\nfrom typing import List, Dict, Any\nimport mcp.types as types\nfrom mcp.server import NotificationOptions, Server\nfrom mcp.server.models import InitializationOptions\nimport mcp.server.stdio\nfrom .handlers import DockerHandlers\n\nserver = Server(\"docker-mcp\")\n\n\n@server.list_prompts()\nasync def handle_list_prompts() -> List[types.Prompt]:\n    return [\n        types.Prompt(\n            name=\"deploy-stack\",\n            description=\"Generate and deploy a Docker stack based on requirements\",\n            arguments=[\n                types.PromptArgument(\n                    name=\"requirements\",\n                    description=\"Description of the desired Docker stack\",\n                    required=True\n                ),\n                types.PromptArgument(\n                    name=\"project_name\",\n                    description=\"Name for the Docker Compose project\",\n                    required=True\n                )\n            ]\n        )\n    ]\n\n\n@server.get_prompt()\nasync def handle_get_prompt(name: str, arguments: Dict[str, str] | None) -> types.GetPromptResult:\n    if name != \"deploy-stack\":\n        raise ValueError(f\"Unknown prompt: {name}\")\n\n    if not arguments or \"requirements\" not in arguments or \"project_name\" not in arguments:\n        raise ValueError(\"Missing required arguments\")\n\n    system_message = (\n        \"You are a Docker deployment specialist. Generate appropriate Docker Compose YAML or \"\n        \"container configurations based on user requirements. For simple single-container \"\n        \"deployments, use the create-container tool. For multi-container deployments, generate \"\n        \"a docker-compose.yml and use the deploy-compose tool. To access logs, first use the \"\n        \"list-containers tool to discover running containers, then use the get-logs tool to \"\n        \"retrieve logs for a specific container.\"\n    )\n\n    user_message = f\"\"\"Please help me deploy the following stack:\nRequirements: {arguments['requirements']}\nProject name: {arguments['project_name']}\n\nAnalyze if this needs a single container or multiple containers. Then:\n1. For single container: Use the create-container tool with format:\n{{\n    \"image\": \"image-name\",\n    \"name\": \"container-name\",\n    \"ports\": {{\"80\": \"80\"}},\n    \"environment\": {{\"ENV_VAR\": \"value\"}}\n}}\n\n2. For multiple containers: Use the deploy-compose tool with format:\n{{\n    \"project_name\": \"example-stack\",\n    \"compose_yaml\": \"version: '3.8'\\\\nservices:\\\\n  service1:\\\\n    image: image1:latest\\\\n    ports:\\\\n      - '8080:80'\"\n}}\"\"\"\n\n    return types.GetPromptResult(\n        description=\"Generate and deploy a Docker stack\",\n        messages=[\n            types.PromptMessage(\n                role=\"system\",\n                content=types.TextContent(\n                    type=\"text\",\n                    text=system_message\n                )\n            ),\n            types.PromptMessage(\n                role=\"user\",\n                content=types.TextContent(\n                    type=\"text\",\n                    text=user_message\n                )\n            )\n        ]\n    )\n\n\n@server.list_tools()\nasync def handle_list_tools() -> List[types.Tool]:\n    return [\n        types.Tool(\n            name=\"create-container\",\n            description=\"Create a new standalone Docker container\",\n            inputSchema={\n                \"type\": \"object\",\n                \"properties\": {\n                    \"image\": {\"type\": \"string\"},\n                    \"name\": {\"type\": \"string\"},\n                    \"ports\": {\n                        \"type\": \"object\",\n                        \"additionalProperties\": {\"type\": \"string\"}\n                    },\n                    \"environment\": {\n                        \"type\": \"object\",\n                        \"additionalProperties\": {\"type\": \"string\"}\n                    }\n                },\n                \"required\": [\"image\"]\n            }\n        ),\n        types.Tool(\n            name=\"deploy-compose\",\n            description=\"Deploy a Docker Compose stack\",\n            inputSchema={\n                \"type\": \"object\",\n                \"properties\": {\n                    \"compose_yaml\": {\"type\": \"string\"},\n                    \"project_name\": {\"type\": \"string\"}\n                },\n                \"required\": [\"compose_yaml\", \"project_name\"]\n            }\n        ),\n        types.Tool(\n            name=\"get-logs\",\n            description=\"Retrieve the latest logs for a specified Docker container\",\n            inputSchema={\n                \"type\": \"object\",\n                \"properties\": {\n                    \"container_name\": {\"type\": \"string\"}\n                },\n                \"required\": [\"container_name\"]\n            }\n        ),\n        types.Tool(\n            name=\"list-containers\",\n            description=\"List all Docker containers\",\n            inputSchema={\n                \"type\": \"object\",\n                \"properties\": {}\n            }\n        )\n    ]\n\n\n@server.call_tool()\nasync def handle_call_tool(name: str, arguments: Dict[str, Any] | None) -> List[types.TextContent]:\n    if not arguments and name != \"list-containers\":\n        raise ValueError(\"Missing arguments\")\n\n    try:\n        if name == \"create-container\":\n            return await DockerHandlers.handle_create_container(arguments)\n        elif name == \"deploy-compose\":\n            return await DockerHandlers.handle_deploy_compose(arguments)\n        elif name == \"get-logs\":\n            return await DockerHandlers.handle_get_logs(arguments)\n        elif name == \"list-containers\":\n            return await DockerHandlers.handle_list_containers(arguments)\n        else:\n            raise ValueError(f\"Unknown tool: {name}\")\n    except Exception as e:\n        return [types.TextContent(type=\"text\", text=f\"Error: {str(e)} | Arguments: {arguments}\")]\n\n\nasync def main():\n    signal.signal(signal.SIGINT, handle_shutdown)\n    signal.signal(signal.SIGTERM, handle_shutdown)\n\n    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):\n        await server.run(\n            read_stream,\n            write_stream,\n            InitializationOptions(\n                server_name=\"docker-mcp\",\n                server_version=\"0.1.0\",\n                capabilities=server.get_capabilities(\n                    notification_options=NotificationOptions(),\n                    experimental_capabilities={},\n                ),\n            ),\n        )\n\n\ndef handle_shutdown(signum, frame):\n    print(\"Shutting down gracefully...\")\n    sys.exit(0)\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
  }
]