container-manager-mcp 0.0.12__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,11 +6,12 @@ import logging
6
6
  import os
7
7
  from abc import ABC, abstractmethod
8
8
  from typing import List, Dict, Optional, Any
9
- import getopt
9
+ import argparse
10
10
  import json
11
11
  import subprocess
12
12
  from datetime import datetime
13
13
  import dateutil.parser
14
+ import platform
14
15
 
15
16
  try:
16
17
  import docker
@@ -815,15 +816,107 @@ class DockerManager(ContainerManagerBase):
815
816
 
816
817
 
817
818
  class PodmanManager(ContainerManagerBase):
818
- def __init__(self, silent: bool = False, log_file: str = None):
819
+ def __init__(self, silent: bool = False, log_file: Optional[str] = None):
819
820
  super().__init__(silent, log_file)
821
+
820
822
  if PodmanClient is None:
821
823
  raise ImportError("Please install podman-py: pip install podman")
824
+
825
+ base_url = self._autodetect_podman_url()
826
+ if base_url is None:
827
+ self.logger.error(
828
+ "No valid Podman socket found after trying all known locations"
829
+ )
830
+ raise RuntimeError("Failed to connect to Podman: No valid socket found")
831
+
822
832
  try:
823
- self.client = PodmanClient()
833
+ self.client = PodmanClient(base_url=base_url)
834
+ self.logger.info(f"Connected to Podman with base_url: {base_url}")
824
835
  except PodmanError as e:
825
- self.logger.error(f"Failed to connect to Podman daemon: {str(e)}")
826
- raise RuntimeError(f"Failed to connect to Podman: {str(e)}")
836
+ self.logger.error(
837
+ f"Failed to connect to Podman daemon with {base_url}: {str(e)}"
838
+ )
839
+ raise RuntimeError(f"Failed to connect to Podman with {base_url}: {str(e)}")
840
+
841
+ def _is_wsl(self) -> bool:
842
+ """Check if running inside WSL2."""
843
+ try:
844
+ with open("/proc/version", "r") as f:
845
+ return "WSL" in f.read()
846
+ except FileNotFoundError:
847
+ return "WSL_DISTRO_NAME" in os.environ
848
+
849
+ def _is_podman_machine_running(self) -> bool:
850
+ """Check if Podman machine is running (for Windows/WSL2)."""
851
+ try:
852
+ result = subprocess.run(
853
+ ["podman", "machine", "list", "--format", "{{.Running}}"],
854
+ capture_output=True,
855
+ text=True,
856
+ check=False,
857
+ )
858
+ return "true" in result.stdout.lower()
859
+ except (subprocess.SubprocessError, FileNotFoundError):
860
+ return False
861
+
862
+ def _try_connect(self, base_url: str) -> Optional[PodmanClient]:
863
+ """Attempt to connect to Podman with the given base_url."""
864
+ try:
865
+ client = PodmanClient(base_url=base_url)
866
+ # Test connection
867
+ client.version()
868
+ return client
869
+ except PodmanError as e:
870
+ self.logger.debug(f"Connection failed for {base_url}: {str(e)}")
871
+ return None
872
+
873
+ def _autodetect_podman_url(self) -> Optional[str]:
874
+ """Autodetect the appropriate Podman socket URL based on platform."""
875
+ # Check for environment variable override
876
+ base_url = os.environ.get("PODMAN_BASE_URL")
877
+ if base_url:
878
+ self.logger.info(f"Using PODMAN_BASE_URL from environment: {base_url}")
879
+ return base_url
880
+
881
+ system = platform.system()
882
+ is_wsl = self._is_wsl()
883
+
884
+ # Define socket candidates based on platform
885
+ socket_candidates = []
886
+ if system == "Windows" and not is_wsl:
887
+ # Windows with Podman machine
888
+ if self._is_podman_machine_running():
889
+ socket_candidates.append("npipe:////./pipe/docker_engine")
890
+ # Fallback to WSL2 distro sockets if running in a mixed setup
891
+ socket_candidates.extend(
892
+ [
893
+ "unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-user.sock", # Rootless
894
+ "unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-root.sock", # Rootful
895
+ ]
896
+ )
897
+ elif system == "Linux" or is_wsl:
898
+ # Linux or WSL2 distro: prioritize rootless, then rootful
899
+ uid = os.getuid()
900
+ socket_candidates.extend(
901
+ [
902
+ f"unix:///run/user/{uid}/podman/podman.sock", # Rootless
903
+ "unix:///run/podman/podman.sock", # Rootful
904
+ ]
905
+ )
906
+
907
+ # Try each socket candidate
908
+ for url in socket_candidates:
909
+ # For Unix sockets, check if the file exists (on Linux/WSL2)
910
+ if url.startswith("unix://") and (system == "Linux" or is_wsl):
911
+ socket_path = url.replace("unix://", "")
912
+ if not os.path.exists(socket_path):
913
+ self.logger.debug(f"Socket {socket_path} does not exist")
914
+ continue
915
+ client = self._try_connect(url)
916
+ if client:
917
+ return url
918
+
919
+ return None
827
920
 
828
921
  def list_images(self) -> List[Dict]:
829
922
  params = {}
@@ -1272,13 +1365,32 @@ class PodmanManager(ContainerManagerBase):
1272
1365
  raise NotImplementedError("Swarm not supported in Podman")
1273
1366
 
1274
1367
 
1275
- # The rest of the file (create_manager, usage, container_manager) remains unchanged
1276
-
1277
-
1278
1368
  def create_manager(
1279
- manager_type: str, silent: bool = False, log_file: str = None
1369
+ manager_type: Optional[str] = None, silent: bool = False, log_file: str = None
1280
1370
  ) -> ContainerManagerBase:
1281
- if manager_type.lower() == "docker" or manager_type.lower() == "swarm":
1371
+ if manager_type is None:
1372
+ manager_type = os.environ.get("CONTAINER_MANAGER_TYPE")
1373
+ if manager_type is None:
1374
+ # Autodetect
1375
+ if PodmanClient is not None:
1376
+ try:
1377
+ test_client = PodmanClient()
1378
+ test_client.close()
1379
+ manager_type = "podman"
1380
+ except Exception:
1381
+ pass
1382
+ if manager_type is None and docker is not None:
1383
+ try:
1384
+ test_client = docker.from_env()
1385
+ test_client.close()
1386
+ manager_type = "docker"
1387
+ except Exception:
1388
+ pass
1389
+ if manager_type is None:
1390
+ raise ValueError(
1391
+ "No supported container manager detected. Set CONTAINER_MANAGER_TYPE or install Docker/Podman."
1392
+ )
1393
+ if manager_type.lower() in ["docker", "swarm"]:
1282
1394
  return DockerManager(silent=silent, log_file=log_file)
1283
1395
  elif manager_type.lower() == "podman":
1284
1396
  return PodmanManager(silent=silent, log_file=log_file)
@@ -1294,7 +1406,7 @@ Container Manager: A tool to manage containers with Docker, Podman, and Docker S
1294
1406
  Usage:
1295
1407
  -h | --help [ See usage for script ]
1296
1408
  -s | --silent [ Suppress output ]
1297
- -m | --manager <type> [ docker, podman, swarm; default: docker ]
1409
+ -m | --manager <type> [ docker, podman, swarm; default: auto-detect ]
1298
1410
  --log-file <path> [ Log to specified file (default: container_manager.log in script dir) ]
1299
1411
 
1300
1412
  Actions:
@@ -1359,262 +1471,178 @@ container_manager.py --manager docker --pull-image nginx --tag latest --list-con
1359
1471
 
1360
1472
 
1361
1473
  def container_manager(argv):
1362
- get_version = False
1363
- get_info = False
1364
- list_images = False
1365
- pull_image = False
1366
- pull_image_str = None
1367
- tag = "latest"
1368
- platform = None
1369
- remove_image = False
1370
- remove_image_str = None
1371
- force = False
1372
- list_containers = False
1373
- all_containers = False
1374
- run_container = False
1375
- run_image = None
1376
- name = None
1377
- command = None
1378
- detach = False
1379
- ports_str = None
1380
- volumes_str = None
1381
- environment_str = None
1382
- stop_container = False
1383
- stop_container_id = None
1384
- timeout = 10
1385
- remove_container = False
1386
- remove_container_id = None
1387
- get_container_logs = False
1388
- container_logs_id = None
1389
- tail = "all"
1390
- exec_in_container = False
1391
- exec_container_id = None
1392
- exec_command = None
1393
- exec_detach = False
1394
- list_volumes = False
1395
- create_volume = False
1396
- create_volume_name = None
1397
- remove_volume = False
1398
- remove_volume_name = None
1399
- list_networks = False
1400
- create_network = False
1401
- create_network_name = None
1402
- driver = "bridge"
1403
- remove_network = False
1404
- remove_network_id = None
1405
- compose_up = False
1406
- compose_up_file = None
1407
- compose_build = False
1408
- compose_detach = True
1409
- compose_down = False
1410
- compose_down_file = None
1411
- compose_ps = False
1412
- compose_ps_file = None
1413
- compose_logs = False
1414
- compose_logs_file = None
1415
- compose_service = None
1416
- init_swarm = False
1417
- advertise_addr = None
1418
- leave_swarm = False
1419
- list_nodes = False
1420
- list_services = False
1421
- create_service = False
1422
- create_service_name = None
1423
- service_image = None
1424
- replicas = 1
1425
- mounts_str = None
1426
- remove_service = False
1427
- remove_service_id = None
1428
- manager_type = "docker"
1429
- silent = False
1430
- log_file = None
1431
-
1432
- try:
1433
- opts, _ = getopt.getopt(
1434
- argv,
1435
- "hsm:",
1436
- [
1437
- "help",
1438
- "silent",
1439
- "manager=",
1440
- "log-file=",
1441
- "get-version",
1442
- "get-info",
1443
- "list-images",
1444
- "pull-image=",
1445
- "tag=",
1446
- "platform=",
1447
- "remove-image=",
1448
- "force",
1449
- "list-containers",
1450
- "all",
1451
- "run-container=",
1452
- "name=",
1453
- "command=",
1454
- "detach",
1455
- "ports=",
1456
- "volumes=",
1457
- "environment=",
1458
- "stop-container=",
1459
- "timeout=",
1460
- "remove-container=",
1461
- "get-container-logs=",
1462
- "tail=",
1463
- "exec-in-container=",
1464
- "exec-command=",
1465
- "exec-detach",
1466
- "list-volumes",
1467
- "create-volume=",
1468
- "remove-volume=",
1469
- "list-networks",
1470
- "create-network=",
1471
- "driver=",
1472
- "remove-network=",
1473
- "compose-up=",
1474
- "build",
1475
- "compose-down=",
1476
- "compose-ps=",
1477
- "compose-logs=",
1478
- "service=",
1479
- "init-swarm",
1480
- "advertise-addr=",
1481
- "leave-swarm",
1482
- "list-nodes",
1483
- "list-services",
1484
- "create-service=",
1485
- "image=",
1486
- "replicas=",
1487
- "mounts=",
1488
- "remove-service=",
1489
- ],
1490
- )
1491
- except getopt.GetoptError:
1492
- usage()
1493
- sys.exit(2)
1474
+ parser = argparse.ArgumentParser(
1475
+ description="Container Manager: A tool to manage containers with Docker, Podman, and Docker Swarm!"
1476
+ )
1477
+ parser.add_argument("-s", "--silent", action="store_true", help="Suppress output")
1478
+ parser.add_argument(
1479
+ "-m",
1480
+ "--manager",
1481
+ type=str,
1482
+ default=None,
1483
+ help="Container manager type: docker, podman, swarm (default: auto-detect)",
1484
+ )
1485
+ parser.add_argument("--log-file", type=str, default=None, help="Path to log file")
1486
+ parser.add_argument("--get-version", action="store_true", help="Get version info")
1487
+ parser.add_argument("--get-info", action="store_true", help="Get system info")
1488
+ parser.add_argument("--list-images", action="store_true", help="List images")
1489
+ parser.add_argument("--pull-image", type=str, default=None, help="Image to pull")
1490
+ parser.add_argument("--tag", type=str, default="latest", help="Image tag")
1491
+ parser.add_argument("--platform", type=str, default=None, help="Platform")
1492
+ parser.add_argument(
1493
+ "--remove-image", type=str, default=None, help="Image to remove"
1494
+ )
1495
+ parser.add_argument("--force", action="store_true", help="Force removal")
1496
+ parser.add_argument(
1497
+ "--list-containers", action="store_true", help="List containers"
1498
+ )
1499
+ parser.add_argument("--all", action="store_true", help="Show all containers")
1500
+ parser.add_argument("--run-container", type=str, default=None, help="Image to run")
1501
+ parser.add_argument("--name", type=str, default=None, help="Container name")
1502
+ parser.add_argument("--command", type=str, default=None, help="Command to run")
1503
+ parser.add_argument("--detach", action="store_true", help="Detach mode")
1504
+ parser.add_argument("--ports", type=str, default=None, help="Port mappings")
1505
+ parser.add_argument("--volumes", type=str, default=None, help="Volume mappings")
1506
+ parser.add_argument(
1507
+ "--environment", type=str, default=None, help="Environment vars"
1508
+ )
1509
+ parser.add_argument(
1510
+ "--stop-container", type=str, default=None, help="Container to stop"
1511
+ )
1512
+ parser.add_argument("--timeout", type=int, default=10, help="Timeout in seconds")
1513
+ parser.add_argument(
1514
+ "--remove-container", type=str, default=None, help="Container to remove"
1515
+ )
1516
+ parser.add_argument(
1517
+ "--get-container-logs", type=str, default=None, help="Container logs"
1518
+ )
1519
+ parser.add_argument("--tail", type=str, default="all", help="Tail lines")
1520
+ parser.add_argument(
1521
+ "--exec-in-container", type=str, default=None, help="Container to exec"
1522
+ )
1523
+ parser.add_argument("--exec-command", type=str, default=None, help="Exec command")
1524
+ parser.add_argument("--exec-detach", action="store_true", help="Detach exec")
1525
+ parser.add_argument("--list-volumes", action="store_true", help="List volumes")
1526
+ parser.add_argument(
1527
+ "--create-volume", type=str, default=None, help="Volume to create"
1528
+ )
1529
+ parser.add_argument(
1530
+ "--remove-volume", type=str, default=None, help="Volume to remove"
1531
+ )
1532
+ parser.add_argument("--list-networks", action="store_true", help="List networks")
1533
+ parser.add_argument(
1534
+ "--create-network", type=str, default=None, help="Network to create"
1535
+ )
1536
+ parser.add_argument("--driver", type=str, default="bridge", help="Network driver")
1537
+ parser.add_argument(
1538
+ "--remove-network", type=str, default=None, help="Network to remove"
1539
+ )
1540
+ parser.add_argument("--compose-up", type=str, default=None, help="Compose file up")
1541
+ parser.add_argument("--build", action="store_true", help="Build images")
1542
+ parser.add_argument(
1543
+ "--compose-detach", action="store_true", default=True, help="Detach compose"
1544
+ )
1545
+ parser.add_argument(
1546
+ "--compose-down", type=str, default=None, help="Compose file down"
1547
+ )
1548
+ parser.add_argument("--compose-ps", type=str, default=None, help="Compose ps")
1549
+ parser.add_argument("--compose-logs", type=str, default=None, help="Compose logs")
1550
+ parser.add_argument("--service", type=str, default=None, help="Specific service")
1551
+ parser.add_argument("--init-swarm", action="store_true", help="Init swarm")
1552
+ parser.add_argument(
1553
+ "--advertise-addr", type=str, default=None, help="Advertise address"
1554
+ )
1555
+ parser.add_argument("--leave-swarm", action="store_true", help="Leave swarm")
1556
+ parser.add_argument("--list-nodes", action="store_true", help="List swarm nodes")
1557
+ parser.add_argument(
1558
+ "--list-services", action="store_true", help="List swarm services"
1559
+ )
1560
+ parser.add_argument(
1561
+ "--create-service", type=str, default=None, help="Service to create"
1562
+ )
1563
+ parser.add_argument("--image", type=str, default=None, help="Service image")
1564
+ parser.add_argument("--replicas", type=int, default=1, help="Replicas")
1565
+ parser.add_argument("--mounts", type=str, default=None, help="Mounts")
1566
+ parser.add_argument(
1567
+ "--remove-service", type=str, default=None, help="Service to remove"
1568
+ )
1569
+ parser.add_argument("-h", "--help", action="store_true", help="Show help")
1570
+
1571
+ args = parser.parse_args(argv)
1494
1572
 
1495
- for opt, arg in opts:
1496
- if opt in ("-h", "--help"):
1497
- usage()
1498
- sys.exit()
1499
- elif opt in ("-s", "--silent"):
1500
- silent = True
1501
- elif opt in ("-m", "--manager"):
1502
- manager_type = arg
1503
- elif opt == "--log-file":
1504
- log_file = arg
1505
- elif opt == "--get-version":
1506
- get_version = True
1507
- elif opt == "--get-info":
1508
- get_info = True
1509
- elif opt == "--list-images":
1510
- list_images = True
1511
- elif opt == "--pull-image":
1512
- pull_image = True
1513
- pull_image_str = arg
1514
- elif opt == "--tag":
1515
- tag = arg
1516
- elif opt == "--platform":
1517
- platform = arg
1518
- elif opt == "--remove-image":
1519
- remove_image = True
1520
- remove_image_str = arg
1521
- elif opt == "--force":
1522
- force = True
1523
- elif opt == "--list-containers":
1524
- list_containers = True
1525
- elif opt == "--all":
1526
- all_containers = True
1527
- elif opt == "--run-container":
1528
- run_container = True
1529
- run_image = arg
1530
- elif opt == "--name":
1531
- name = arg
1532
- elif opt == "--command":
1533
- command = arg
1534
- elif opt == "--detach":
1535
- detach = True
1536
- elif opt == "--ports":
1537
- ports_str = arg
1538
- elif opt == "--volumes":
1539
- volumes_str = arg
1540
- elif opt == "--environment":
1541
- environment_str = arg
1542
- elif opt == "--stop-container":
1543
- stop_container = True
1544
- stop_container_id = arg
1545
- elif opt == "--timeout":
1546
- timeout = int(arg)
1547
- elif opt == "--remove-container":
1548
- remove_container = True
1549
- remove_container_id = arg
1550
- elif opt == "--get-container-logs":
1551
- get_container_logs = True
1552
- container_logs_id = arg
1553
- elif opt == "--tail":
1554
- tail = arg
1555
- elif opt == "--exec-in-container":
1556
- exec_in_container = True
1557
- exec_container_id = arg
1558
- elif opt == "--exec-command":
1559
- exec_command = arg
1560
- elif opt == "--exec-detach":
1561
- exec_detach = True
1562
- elif opt == "--list-volumes":
1563
- list_volumes = True
1564
- elif opt == "--create-volume":
1565
- create_volume = True
1566
- create_volume_name = arg
1567
- elif opt == "--remove-volume":
1568
- remove_volume = True
1569
- remove_volume_name = arg
1570
- elif opt == "--list-networks":
1571
- list_networks = True
1572
- elif opt == "--create-network":
1573
- create_network = True
1574
- create_network_name = arg
1575
- elif opt == "--driver":
1576
- driver = arg
1577
- elif opt == "--remove-network":
1578
- remove_network = True
1579
- remove_network_id = arg
1580
- elif opt == "--compose-up":
1581
- compose_up = True
1582
- compose_up_file = arg
1583
- elif opt == "--build":
1584
- compose_build = True
1585
- elif opt == "--compose-down":
1586
- compose_down = True
1587
- compose_down_file = arg
1588
- elif opt == "--compose-ps":
1589
- compose_ps = True
1590
- compose_ps_file = arg
1591
- elif opt == "--compose-logs":
1592
- compose_logs = True
1593
- compose_logs_file = arg
1594
- elif opt == "--service":
1595
- compose_service = arg
1596
- elif opt == "--init-swarm":
1597
- init_swarm = True
1598
- elif opt == "--advertise-addr":
1599
- advertise_addr = arg
1600
- elif opt == "--leave-swarm":
1601
- leave_swarm = True
1602
- elif opt == "--list-nodes":
1603
- list_nodes = True
1604
- elif opt == "--list-services":
1605
- list_services = True
1606
- elif opt == "--create-service":
1607
- create_service = True
1608
- create_service_name = arg
1609
- elif opt == "--image":
1610
- service_image = arg
1611
- elif opt == "--replicas":
1612
- replicas = int(arg)
1613
- elif opt == "--mounts":
1614
- mounts_str = arg
1615
- elif opt == "--remove-service":
1616
- remove_service = True
1617
- remove_service_id = arg
1573
+ if args.help:
1574
+ usage()
1575
+ sys.exit(0)
1576
+
1577
+ get_version = args.get_version
1578
+ get_info = args.get_info
1579
+ list_images = args.list_images
1580
+ pull_image = args.pull_image is not None
1581
+ pull_image_str = args.pull_image
1582
+ tag = args.tag
1583
+ platform = args.platform
1584
+ remove_image = args.remove_image is not None
1585
+ remove_image_str = args.remove_image
1586
+ force = args.force
1587
+ list_containers = args.list_containers
1588
+ all_containers = args.all
1589
+ run_container = args.run_container is not None
1590
+ run_image = args.run_container
1591
+ name = args.name
1592
+ command = args.command
1593
+ detach = args.detach
1594
+ ports_str = args.ports
1595
+ volumes_str = args.volumes
1596
+ environment_str = args.environment
1597
+ stop_container = args.stop_container is not None
1598
+ stop_container_id = args.stop_container
1599
+ timeout = args.timeout
1600
+ remove_container = args.remove_container is not None
1601
+ remove_container_id = args.remove_container
1602
+ get_container_logs = args.get_container_logs is not None
1603
+ container_logs_id = args.get_container_logs
1604
+ tail = args.tail
1605
+ exec_in_container = args.exec_in_container is not None
1606
+ exec_container_id = args.exec_in_container
1607
+ exec_command = args.exec_command
1608
+ exec_detach = args.exec_detach
1609
+ list_volumes = args.list_volumes
1610
+ create_volume = args.create_volume is not None
1611
+ create_volume_name = args.create_volume
1612
+ remove_volume = args.remove_volume is not None
1613
+ remove_volume_name = args.remove_volume
1614
+ list_networks = args.list_networks
1615
+ create_network = args.create_network is not None
1616
+ create_network_name = args.create_network
1617
+ driver = args.driver
1618
+ remove_network = args.remove_network is not None
1619
+ remove_network_id = args.remove_network
1620
+ compose_up = args.compose_up is not None
1621
+ compose_up_file = args.compose_up
1622
+ compose_build = args.build
1623
+ compose_detach = args.compose_detach
1624
+ compose_down = args.compose_down is not None
1625
+ compose_down_file = args.compose_down
1626
+ compose_ps = args.compose_ps is not None
1627
+ compose_ps_file = args.compose_ps
1628
+ compose_logs = args.compose_logs is not None
1629
+ compose_logs_file = args.compose_logs
1630
+ compose_service = args.service
1631
+ init_swarm = args.init_swarm
1632
+ advertise_addr = args.advertise_addr
1633
+ leave_swarm = args.leave_swarm
1634
+ list_nodes = args.list_nodes
1635
+ list_services = args.list_services
1636
+ create_service = args.create_service is not None
1637
+ create_service_name = args.create_service
1638
+ service_image = args.image
1639
+ replicas = args.replicas
1640
+ mounts_str = args.mounts
1641
+ remove_service = args.remove_service is not None
1642
+ remove_service_id = args.remove_service
1643
+ manager_type = args.manager
1644
+ silent = args.silent
1645
+ log_file = args.log_file
1618
1646
 
1619
1647
  manager = create_manager(manager_type, silent, log_file)
1620
1648
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  # coding: utf-8
3
3
 
4
- import getopt
4
+ import argparse
5
5
  import os
6
6
  import sys
7
7
  import logging
@@ -41,6 +41,7 @@ def to_boolean(string):
41
41
 
42
42
  environment_silent = os.environ.get("SILENT", False)
43
43
  environment_log_file = os.environ.get("LOG_FILE", None)
44
+ environment_container_manager_type = os.environ.get("CONTAINER_MANAGER_TYPE", None)
44
45
 
45
46
  if environment_silent:
46
47
  environment_silent = to_boolean(environment_silent)
@@ -59,8 +60,9 @@ if environment_silent:
59
60
  tags={"container_management"},
60
61
  )
61
62
  async def get_version(
62
- manager_type: str = Field(
63
- description="Container manager: docker, podman", default="docker"
63
+ manager_type: Optional[str] = Field(
64
+ description="Container manager: docker, podman (default: auto-detect)",
65
+ default=environment_container_manager_type,
64
66
  ),
65
67
  silent: Optional[bool] = Field(
66
68
  description="Suppress output", default=environment_silent
@@ -95,8 +97,9 @@ async def get_version(
95
97
  tags={"container_management"},
96
98
  )
97
99
  async def get_info(
98
- manager_type: str = Field(
99
- description="Container manager: docker, podman", default="docker"
100
+ manager_type: Optional[str] = Field(
101
+ description="Container manager: docker, podman (default: auto-detect)",
102
+ default=environment_container_manager_type,
100
103
  ),
101
104
  silent: Optional[bool] = Field(
102
105
  description="Suppress output", default=environment_silent
@@ -131,8 +134,9 @@ async def get_info(
131
134
  tags={"container_management"},
132
135
  )
133
136
  async def list_images(
134
- manager_type: str = Field(
135
- description="Container manager: docker, podman", default="docker"
137
+ manager_type: Optional[str] = Field(
138
+ description="Container manager: docker, podman (default: auto-detect)",
139
+ default=environment_container_manager_type,
136
140
  ),
137
141
  silent: Optional[bool] = Field(
138
142
  description="Suppress output", default=environment_silent
@@ -172,8 +176,9 @@ async def pull_image(
172
176
  platform: Optional[str] = Field(
173
177
  description="Platform (e.g., linux/amd64)", default=None
174
178
  ),
175
- manager_type: str = Field(
176
- description="Container manager: docker, podman", default="docker"
179
+ manager_type: Optional[str] = Field(
180
+ description="Container manager: docker, podman (default: auto-detect)",
181
+ default=environment_container_manager_type,
177
182
  ),
178
183
  silent: Optional[bool] = Field(
179
184
  description="Suppress output", default=environment_silent
@@ -210,8 +215,9 @@ async def pull_image(
210
215
  async def remove_image(
211
216
  image: str = Field(description="Image name or ID to remove"),
212
217
  force: bool = Field(description="Force removal", default=False),
213
- manager_type: str = Field(
214
- description="Container manager: docker, podman", default="docker"
218
+ manager_type: Optional[str] = Field(
219
+ description="Container manager: docker, podman (default: auto-detect)",
220
+ default=environment_container_manager_type,
215
221
  ),
216
222
  silent: Optional[bool] = Field(
217
223
  description="Suppress output", default=environment_silent
@@ -249,8 +255,9 @@ async def list_containers(
249
255
  all: bool = Field(
250
256
  description="Show all containers (default running only)", default=False
251
257
  ),
252
- manager_type: str = Field(
253
- description="Container manager: docker, podman", default="docker"
258
+ manager_type: Optional[str] = Field(
259
+ description="Container manager: docker, podman (default: auto-detect)",
260
+ default=environment_container_manager_type,
254
261
  ),
255
262
  silent: Optional[bool] = Field(
256
263
  description="Suppress output", default=environment_silent
@@ -301,8 +308,9 @@ async def run_container(
301
308
  environment: Optional[Dict[str, str]] = Field(
302
309
  description="Environment variables", default=None
303
310
  ),
304
- manager_type: str = Field(
305
- description="Container manager: docker, podman", default="docker"
311
+ manager_type: Optional[str] = Field(
312
+ description="Container manager: docker, podman (default: auto-detect)",
313
+ default=environment_container_manager_type,
306
314
  ),
307
315
  silent: Optional[bool] = Field(
308
316
  description="Suppress output", default=environment_silent
@@ -341,8 +349,9 @@ async def run_container(
341
349
  async def stop_container(
342
350
  container_id: str = Field(description="Container ID or name"),
343
351
  timeout: int = Field(description="Timeout in seconds", default=10),
344
- manager_type: str = Field(
345
- description="Container manager: docker, podman", default="docker"
352
+ manager_type: Optional[str] = Field(
353
+ description="Container manager: docker, podman (default: auto-detect)",
354
+ default=environment_container_manager_type,
346
355
  ),
347
356
  silent: Optional[bool] = Field(
348
357
  description="Suppress output", default=environment_silent
@@ -379,8 +388,9 @@ async def stop_container(
379
388
  async def remove_container(
380
389
  container_id: str = Field(description="Container ID or name"),
381
390
  force: bool = Field(description="Force removal", default=False),
382
- manager_type: str = Field(
383
- description="Container manager: docker, podman", default="docker"
391
+ manager_type: Optional[str] = Field(
392
+ description="Container manager: docker, podman (default: auto-detect)",
393
+ default=environment_container_manager_type,
384
394
  ),
385
395
  silent: Optional[bool] = Field(
386
396
  description="Suppress output", default=environment_silent
@@ -419,8 +429,9 @@ async def get_container_logs(
419
429
  tail: str = Field(
420
430
  description="Number of lines to show from the end (or 'all')", default="all"
421
431
  ),
422
- manager_type: str = Field(
423
- description="Container manager: docker, podman", default="docker"
432
+ manager_type: Optional[str] = Field(
433
+ description="Container manager: docker, podman (default: auto-detect)",
434
+ default=environment_container_manager_type,
424
435
  ),
425
436
  silent: Optional[bool] = Field(
426
437
  description="Suppress output", default=environment_silent
@@ -458,8 +469,9 @@ async def exec_in_container(
458
469
  container_id: str = Field(description="Container ID or name"),
459
470
  command: List[str] = Field(description="Command to execute"),
460
471
  detach: bool = Field(description="Detach execution", default=False),
461
- manager_type: str = Field(
462
- description="Container manager: docker, podman", default="docker"
472
+ manager_type: Optional[str] = Field(
473
+ description="Container manager: docker, podman (default: auto-detect)",
474
+ default=environment_container_manager_type,
463
475
  ),
464
476
  silent: Optional[bool] = Field(
465
477
  description="Suppress output", default=environment_silent
@@ -494,8 +506,9 @@ async def exec_in_container(
494
506
  tags={"container_management"},
495
507
  )
496
508
  async def list_volumes(
497
- manager_type: str = Field(
498
- description="Container manager: docker, podman", default="docker"
509
+ manager_type: Optional[str] = Field(
510
+ description="Container manager: docker, podman (default: auto-detect)",
511
+ default=environment_container_manager_type,
499
512
  ),
500
513
  silent: Optional[bool] = Field(
501
514
  description="Suppress output", default=environment_silent
@@ -531,8 +544,9 @@ async def list_volumes(
531
544
  )
532
545
  async def create_volume(
533
546
  name: str = Field(description="Volume name"),
534
- manager_type: str = Field(
535
- description="Container manager: docker, podman", default="docker"
547
+ manager_type: Optional[str] = Field(
548
+ description="Container manager: docker, podman (default: auto-detect)",
549
+ default=environment_container_manager_type,
536
550
  ),
537
551
  silent: Optional[bool] = Field(
538
552
  description="Suppress output", default=environment_silent
@@ -569,8 +583,9 @@ async def create_volume(
569
583
  async def remove_volume(
570
584
  name: str = Field(description="Volume name"),
571
585
  force: bool = Field(description="Force removal", default=False),
572
- manager_type: str = Field(
573
- description="Container manager: docker, podman", default="docker"
586
+ manager_type: Optional[str] = Field(
587
+ description="Container manager: docker, podman (default: auto-detect)",
588
+ default=environment_container_manager_type,
574
589
  ),
575
590
  silent: Optional[bool] = Field(
576
591
  description="Suppress output", default=environment_silent
@@ -605,8 +620,9 @@ async def remove_volume(
605
620
  tags={"container_management"},
606
621
  )
607
622
  async def list_networks(
608
- manager_type: str = Field(
609
- description="Container manager: docker, podman", default="docker"
623
+ manager_type: Optional[str] = Field(
624
+ description="Container manager: docker, podman (default: auto-detect)",
625
+ default=environment_container_manager_type,
610
626
  ),
611
627
  silent: Optional[bool] = Field(
612
628
  description="Suppress output", default=environment_silent
@@ -643,8 +659,9 @@ async def list_networks(
643
659
  async def create_network(
644
660
  name: str = Field(description="Network name"),
645
661
  driver: str = Field(description="Network driver (e.g., bridge)", default="bridge"),
646
- manager_type: str = Field(
647
- description="Container manager: docker, podman", default="docker"
662
+ manager_type: Optional[str] = Field(
663
+ description="Container manager: docker, podman (default: auto-detect)",
664
+ default=environment_container_manager_type,
648
665
  ),
649
666
  silent: Optional[bool] = Field(
650
667
  description="Suppress output", default=environment_silent
@@ -680,8 +697,9 @@ async def create_network(
680
697
  )
681
698
  async def remove_network(
682
699
  network_id: str = Field(description="Network ID or name"),
683
- manager_type: str = Field(
684
- description="Container manager: docker, podman", default="docker"
700
+ manager_type: Optional[str] = Field(
701
+ description="Container manager: docker, podman (default: auto-detect)",
702
+ default=environment_container_manager_type,
685
703
  ),
686
704
  silent: Optional[bool] = Field(
687
705
  description="Suppress output", default=environment_silent
@@ -722,7 +740,10 @@ async def init_swarm(
722
740
  advertise_addr: Optional[str] = Field(
723
741
  description="Advertise address", default=None
724
742
  ),
725
- manager_type: str = Field(description="Must be docker for swarm", default="docker"),
743
+ manager_type: Optional[str] = Field(
744
+ description="Container manager: must be docker for swarm (default: auto-detect)",
745
+ default=environment_container_manager_type,
746
+ ),
726
747
  silent: Optional[bool] = Field(
727
748
  description="Suppress output", default=environment_silent
728
749
  ),
@@ -733,7 +754,7 @@ async def init_swarm(
733
754
  description="MCP context for progress reporting", default=None
734
755
  ),
735
756
  ) -> Dict:
736
- if manager_type != "docker":
757
+ if manager_type and manager_type != "docker":
737
758
  raise ValueError("Swarm operations are only supported on Docker")
738
759
  logger = logging.getLogger("ContainerManager")
739
760
  logger.debug(
@@ -759,7 +780,10 @@ async def init_swarm(
759
780
  )
760
781
  async def leave_swarm(
761
782
  force: bool = Field(description="Force leave", default=False),
762
- manager_type: str = Field(description="Must be docker for swarm", default="docker"),
783
+ manager_type: Optional[str] = Field(
784
+ description="Container manager: must be docker for swarm (default: auto-detect)",
785
+ default=environment_container_manager_type,
786
+ ),
763
787
  silent: Optional[bool] = Field(
764
788
  description="Suppress output", default=environment_silent
765
789
  ),
@@ -770,7 +794,7 @@ async def leave_swarm(
770
794
  description="MCP context for progress reporting", default=None
771
795
  ),
772
796
  ) -> Dict:
773
- if manager_type != "docker":
797
+ if manager_type and manager_type != "docker":
774
798
  raise ValueError("Swarm operations are only supported on Docker")
775
799
  logger = logging.getLogger("ContainerManager")
776
800
  logger.debug(
@@ -795,7 +819,10 @@ async def leave_swarm(
795
819
  tags={"container_management", "swarm"},
796
820
  )
797
821
  async def list_nodes(
798
- manager_type: str = Field(description="Must be docker for swarm", default="docker"),
822
+ manager_type: Optional[str] = Field(
823
+ description="Container manager: must be docker for swarm (default: auto-detect)",
824
+ default=environment_container_manager_type,
825
+ ),
799
826
  silent: Optional[bool] = Field(
800
827
  description="Suppress output", default=environment_silent
801
828
  ),
@@ -806,7 +833,7 @@ async def list_nodes(
806
833
  description="MCP context for progress reporting", default=None
807
834
  ),
808
835
  ) -> List[Dict]:
809
- if manager_type != "docker":
836
+ if manager_type and manager_type != "docker":
810
837
  raise ValueError("Swarm operations are only supported on Docker")
811
838
  logger = logging.getLogger("ContainerManager")
812
839
  logger.debug(
@@ -831,7 +858,10 @@ async def list_nodes(
831
858
  tags={"container_management", "swarm"},
832
859
  )
833
860
  async def list_services(
834
- manager_type: str = Field(description="Must be docker for swarm", default="docker"),
861
+ manager_type: Optional[str] = Field(
862
+ description="Container manager: must be docker for swarm (default: auto-detect)",
863
+ default=environment_container_manager_type,
864
+ ),
835
865
  silent: Optional[bool] = Field(
836
866
  description="Suppress output", default=environment_silent
837
867
  ),
@@ -842,7 +872,7 @@ async def list_services(
842
872
  description="MCP context for progress reporting", default=None
843
873
  ),
844
874
  ) -> List[Dict]:
845
- if manager_type != "docker":
875
+ if manager_type and manager_type != "docker":
846
876
  raise ValueError("Swarm operations are only supported on Docker")
847
877
  logger = logging.getLogger("ContainerManager")
848
878
  logger.debug(
@@ -876,7 +906,10 @@ async def create_service(
876
906
  mounts: Optional[List[str]] = Field(
877
907
  description="Mounts [source:target:mode]", default=None
878
908
  ),
879
- manager_type: str = Field(description="Must be docker for swarm", default="docker"),
909
+ manager_type: Optional[str] = Field(
910
+ description="Container manager: must be docker for swarm (default: auto-detect)",
911
+ default=environment_container_manager_type,
912
+ ),
880
913
  silent: Optional[bool] = Field(
881
914
  description="Suppress output", default=environment_silent
882
915
  ),
@@ -887,7 +920,7 @@ async def create_service(
887
920
  description="MCP context for progress reporting", default=None
888
921
  ),
889
922
  ) -> Dict:
890
- if manager_type != "docker":
923
+ if manager_type and manager_type != "docker":
891
924
  raise ValueError("Swarm operations are only supported on Docker")
892
925
  logger = logging.getLogger("ContainerManager")
893
926
  logger.debug(
@@ -913,7 +946,10 @@ async def create_service(
913
946
  )
914
947
  async def remove_service(
915
948
  service_id: str = Field(description="Service ID or name"),
916
- manager_type: str = Field(description="Must be docker for swarm", default="docker"),
949
+ manager_type: Optional[str] = Field(
950
+ description="Container manager: must be docker for swarm (default: auto-detect)",
951
+ default=environment_container_manager_type,
952
+ ),
917
953
  silent: Optional[bool] = Field(
918
954
  description="Suppress output", default=environment_silent
919
955
  ),
@@ -924,7 +960,7 @@ async def remove_service(
924
960
  description="MCP context for progress reporting", default=None
925
961
  ),
926
962
  ) -> Dict:
927
- if manager_type != "docker":
963
+ if manager_type and manager_type != "docker":
928
964
  raise ValueError("Swarm operations are only supported on Docker")
929
965
  logger = logging.getLogger("ContainerManager")
930
966
  logger.debug(
@@ -952,8 +988,9 @@ async def compose_up(
952
988
  compose_file: str = Field(description="Path to compose file"),
953
989
  detach: bool = Field(description="Detach mode", default=True),
954
990
  build: bool = Field(description="Build images", default=False),
955
- manager_type: str = Field(
956
- description="Container manager: docker, podman", default="docker"
991
+ manager_type: Optional[str] = Field(
992
+ description="Container manager: docker, podman (default: auto-detect)",
993
+ default=environment_container_manager_type,
957
994
  ),
958
995
  silent: Optional[bool] = Field(
959
996
  description="Suppress output", default=environment_silent
@@ -989,8 +1026,9 @@ async def compose_up(
989
1026
  )
990
1027
  async def compose_down(
991
1028
  compose_file: str = Field(description="Path to compose file"),
992
- manager_type: str = Field(
993
- description="Container manager: docker, podman", default="docker"
1029
+ manager_type: Optional[str] = Field(
1030
+ description="Container manager: docker, podman (default: auto-detect)",
1031
+ default=environment_container_manager_type,
994
1032
  ),
995
1033
  silent: Optional[bool] = Field(
996
1034
  description="Suppress output", default=environment_silent
@@ -1026,8 +1064,9 @@ async def compose_down(
1026
1064
  )
1027
1065
  async def compose_ps(
1028
1066
  compose_file: str = Field(description="Path to compose file"),
1029
- manager_type: str = Field(
1030
- description="Container manager: docker, podman", default="docker"
1067
+ manager_type: Optional[str] = Field(
1068
+ description="Container manager: docker, podman (default: auto-detect)",
1069
+ default=environment_container_manager_type,
1031
1070
  ),
1032
1071
  silent: Optional[bool] = Field(
1033
1072
  description="Suppress output", default=environment_silent
@@ -1064,8 +1103,9 @@ async def compose_ps(
1064
1103
  async def compose_logs(
1065
1104
  compose_file: str = Field(description="Path to compose file"),
1066
1105
  service: Optional[str] = Field(description="Specific service", default=None),
1067
- manager_type: str = Field(
1068
- description="Container manager: docker, podman", default="docker"
1106
+ manager_type: Optional[str] = Field(
1107
+ description="Container manager: docker, podman (default: auto-detect)",
1108
+ default=environment_container_manager_type,
1069
1109
  ),
1070
1110
  silent: Optional[bool] = Field(
1071
1111
  description="Suppress output", default=environment_silent
@@ -1089,36 +1129,22 @@ async def compose_logs(
1089
1129
  raise RuntimeError(f"Failed to compose logs: {str(e)}")
1090
1130
 
1091
1131
 
1092
- def container_manager_mcp(argv):
1093
- transport = "stdio"
1094
- host = "0.0.0.0"
1095
- port = 8000
1096
- try:
1097
- opts, args = getopt.getopt(
1098
- argv,
1099
- "ht:h:p:",
1100
- ["help", "transport=", "host=", "port="],
1101
- )
1102
- except getopt.GetoptError:
1103
- logger = logging.getLogger("ContainerManager")
1104
- logger.error("Incorrect arguments")
1105
- sys.exit(2)
1106
- for opt, arg in opts:
1107
- if opt in ("-h", "--help"):
1108
- sys.exit(2)
1109
- elif opt in ("-t", "--transport"):
1110
- transport = arg
1111
- elif opt in ("-h", "--host"):
1112
- host = arg
1113
- elif opt in ("-p", "--port"):
1114
- try:
1115
- port = int(arg)
1116
- if not (0 <= port <= 65535):
1117
- print(f"Error: Port {arg} is out of valid range (0-65535).")
1118
- sys.exit(1)
1119
- except ValueError:
1120
- print(f"Error: Port {arg} is not a valid integer.")
1121
- sys.exit(1)
1132
+ def container_manager_mcp():
1133
+ parser = argparse.ArgumentParser(description="Container Manager MCP Server")
1134
+ parser.add_argument(
1135
+ "-t", "--transport", type=str, default="stdio", help="Transport (stdio/http)"
1136
+ )
1137
+ parser.add_argument("-h", "--host", type=str, default="0.0.0.0", help="Host")
1138
+ parser.add_argument("-p", "--port", type=int, default=8000, help="Port")
1139
+ args = parser.parse_args()
1140
+
1141
+ transport = args.transport
1142
+ host = args.host
1143
+ port = args.port
1144
+ if not (0 <= port <= 65535):
1145
+ print(f"Error: Port {port} is out of valid range (0-65535).")
1146
+ sys.exit(1)
1147
+
1122
1148
  setup_logging(is_mcp_server=True, log_file="container_manager_mcp.log")
1123
1149
  if transport == "stdio":
1124
1150
  mcp.run(transport="stdio")
@@ -1131,8 +1157,8 @@ def container_manager_mcp(argv):
1131
1157
 
1132
1158
 
1133
1159
  def main():
1134
- container_manager_mcp(sys.argv[1:])
1160
+ container_manager_mcp()
1135
1161
 
1136
1162
 
1137
1163
  if __name__ == "__main__":
1138
- container_manager_mcp(sys.argv[1:])
1164
+ container_manager_mcp()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: container-manager-mcp
3
- Version: 0.0.12
3
+ Version: 1.0.1
4
4
  Summary: Container Manager manage Docker, Docker Swarm, and Podman containers as an MCP Server
5
5
  Author-email: Audel Rouhi <knucklessg1@gmail.com>
6
6
  License: MIT
@@ -48,7 +48,7 @@ Dynamic: license-file
48
48
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/container-manager-mcp)
49
49
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/container-manager-mcp)
50
50
 
51
- *Version: 0.0.12*
51
+ *Version: 1.0.1*
52
52
 
53
53
  Container Manager MCP Server provides a robust interface to manage Docker and Podman containers, networks, volumes, and Docker Swarm services through a FastMCP server, enabling programmatic and remote container management.
54
54
 
@@ -0,0 +1,10 @@
1
+ container_manager_mcp/__init__.py,sha256=N3bhKd_oh5YmBBl9N1omfZgaXhJyP0vOzH4VKxs68_g,506
2
+ container_manager_mcp/__main__.py,sha256=zic5tX336HG8LfdzQQ0sDVx-tMSOsgOZCtaxHWgJ4Go,134
3
+ container_manager_mcp/container_manager.py,sha256=9TPpZw2gB_rz4oLsu45jBeixkG9B-OYppsGM4ctqb5I,72495
4
+ container_manager_mcp/container_manager_mcp.py,sha256=sUxkhCdsMtH4HZKdzJMAbxfJgawgXiWD8RoAsygJOZE,39788
5
+ container_manager_mcp-1.0.1.dist-info/licenses/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
6
+ container_manager_mcp-1.0.1.dist-info/METADATA,sha256=Apw7KYcfeJO2Yz3Es5OKPp2Q0Rq_15b9_mnNRtO2Zjk,8238
7
+ container_manager_mcp-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ container_manager_mcp-1.0.1.dist-info/entry_points.txt,sha256=I23pXcCgAShlfYbENzs3kbw3l1lU9Gy7lODPfRqeeiA,156
9
+ container_manager_mcp-1.0.1.dist-info/top_level.txt,sha256=B7QQLOd9mBdu0lsPKqyu4T8-zUtbqKzQJbMbtAzoozU,22
10
+ container_manager_mcp-1.0.1.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- container_manager_mcp/__init__.py,sha256=N3bhKd_oh5YmBBl9N1omfZgaXhJyP0vOzH4VKxs68_g,506
2
- container_manager_mcp/__main__.py,sha256=zic5tX336HG8LfdzQQ0sDVx-tMSOsgOZCtaxHWgJ4Go,134
3
- container_manager_mcp/container_manager.py,sha256=hIdXyI7L2kFrJc_7bMOaQpKAPECxCVjaQoohqjmY-Iw,67859
4
- container_manager_mcp/container_manager_mcp.py,sha256=cIAN8YGflQ9nZEHodkYdQKo2GECAuTH7WB-oMhnAd_0,37959
5
- container_manager_mcp-0.0.12.dist-info/licenses/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
6
- container_manager_mcp-0.0.12.dist-info/METADATA,sha256=ItKjQvWeRj9e8J26zMjGilRfuFPywXf6afihA_knBdA,8240
7
- container_manager_mcp-0.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- container_manager_mcp-0.0.12.dist-info/entry_points.txt,sha256=I23pXcCgAShlfYbENzs3kbw3l1lU9Gy7lODPfRqeeiA,156
9
- container_manager_mcp-0.0.12.dist-info/top_level.txt,sha256=B7QQLOd9mBdu0lsPKqyu4T8-zUtbqKzQJbMbtAzoozU,22
10
- container_manager_mcp-0.0.12.dist-info/RECORD,,