container-manager-mcp 1.0.2__py3-none-any.whl → 1.0.3__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.
@@ -105,6 +105,10 @@ class ContainerManagerBase(ABC):
105
105
  def remove_image(self, image: str, force: bool = False) -> Dict:
106
106
  pass
107
107
 
108
+ @abstractmethod
109
+ def prune_images(self, force: bool = False, all: bool = False) -> Dict:
110
+ pass
111
+
108
112
  @abstractmethod
109
113
  def list_containers(self, all: bool = False) -> List[Dict]:
110
114
  pass
@@ -130,6 +134,10 @@ class ContainerManagerBase(ABC):
130
134
  def remove_container(self, container_id: str, force: bool = False) -> Dict:
131
135
  pass
132
136
 
137
+ @abstractmethod
138
+ def prune_containers(self, force: bool = False) -> Dict:
139
+ pass
140
+
133
141
  @abstractmethod
134
142
  def get_container_logs(self, container_id: str, tail: str = "all") -> str:
135
143
  pass
@@ -152,6 +160,10 @@ class ContainerManagerBase(ABC):
152
160
  def remove_volume(self, name: str, force: bool = False) -> Dict:
153
161
  pass
154
162
 
163
+ @abstractmethod
164
+ def prune_volumes(self, force: bool = False, all: bool = False) -> Dict:
165
+ pass
166
+
155
167
  @abstractmethod
156
168
  def list_networks(self) -> List[Dict]:
157
169
  pass
@@ -164,6 +176,14 @@ class ContainerManagerBase(ABC):
164
176
  def remove_network(self, network_id: str) -> Dict:
165
177
  pass
166
178
 
179
+ @abstractmethod
180
+ def prune_networks(self) -> Dict:
181
+ pass
182
+
183
+ @abstractmethod
184
+ def prune_system(self, force: bool = False, all: bool = False) -> Dict:
185
+ pass
186
+
167
187
  @abstractmethod
168
188
  def compose_up(
169
189
  self, compose_file: str, detach: bool = True, build: bool = False
@@ -294,6 +314,25 @@ class DockerManager(ContainerManagerBase):
294
314
  self.log_action("pull_image", params, error=e)
295
315
  raise RuntimeError(f"Failed to pull image: {str(e)}")
296
316
 
317
+ def prune_images(self, force: bool = False, all: bool = False) -> Dict:
318
+ params = {"force": force, "all": all}
319
+ try:
320
+ filters = {"dangling": {"true": True}} if not all else {}
321
+ result = self.client.images.prune(filters=filters)
322
+ pruned = {
323
+ "space_reclaimed": self._format_size(result["SpaceReclaimed"]),
324
+ "images_removed": (
325
+ [img["Id"][7:19] for img in result["ImagesRemoved"]]
326
+ if result["ImagesRemoved"]
327
+ else []
328
+ ),
329
+ }
330
+ self.log_action("prune_images", params, pruned)
331
+ return pruned
332
+ except Exception as e:
333
+ self.log_action("prune_images", params, error=e)
334
+ raise RuntimeError(f"Failed to prune images: {str(e)}")
335
+
297
336
  def list_containers(self, all: bool = False) -> List[Dict]:
298
337
  params = {"all": all}
299
338
  try:
@@ -384,6 +423,24 @@ class DockerManager(ContainerManagerBase):
384
423
  self.log_action("run_container", params, error=e)
385
424
  raise RuntimeError(f"Failed to run container: {str(e)}")
386
425
 
426
+ def prune_containers(self, force: bool = False) -> Dict:
427
+ params = {"force": force}
428
+ try:
429
+ result = self.client.containers.prune()
430
+ pruned = {
431
+ "space_reclaimed": self._format_size(result["SpaceReclaimed"]),
432
+ "containers_removed": (
433
+ [c["Id"][7:19] for c in result["ContainersRemoved"]]
434
+ if result["ContainersRemoved"]
435
+ else []
436
+ ),
437
+ }
438
+ self.log_action("prune_containers", params, pruned)
439
+ return pruned
440
+ except Exception as e:
441
+ self.log_action("prune_containers", params, error=e)
442
+ raise RuntimeError(f"Failed to prune containers: {str(e)}")
443
+
387
444
  def list_networks(self) -> List[Dict]:
388
445
  params = {}
389
446
  try:
@@ -429,6 +486,24 @@ class DockerManager(ContainerManagerBase):
429
486
  self.log_action("create_network", params, error=e)
430
487
  raise RuntimeError(f"Failed to create network: {str(e)}")
431
488
 
489
+ def prune_networks(self) -> Dict:
490
+ params = {}
491
+ try:
492
+ result = self.client.networks.prune()
493
+ pruned = {
494
+ "space_reclaimed": self._format_size(result["SpaceReclaimed"]),
495
+ "networks_removed": (
496
+ [n["Id"][7:19] for n in result["NetworksRemoved"]]
497
+ if result["NetworksRemoved"]
498
+ else []
499
+ ),
500
+ }
501
+ self.log_action("prune_networks", params, pruned)
502
+ return pruned
503
+ except Exception as e:
504
+ self.log_action("prune_networks", params, error=e)
505
+ raise RuntimeError(f"Failed to prune networks: {str(e)}")
506
+
432
507
  def get_version(self) -> Dict:
433
508
  params = {}
434
509
  try:
@@ -581,6 +656,41 @@ class DockerManager(ContainerManagerBase):
581
656
  self.log_action("remove_volume", params, error=e)
582
657
  raise RuntimeError(f"Failed to remove volume: {str(e)}")
583
658
 
659
+ def prune_volumes(self, force: bool = False, all: bool = False) -> Dict:
660
+ params = {"force": force, "all": all}
661
+ try:
662
+ if all:
663
+ # Remove all volumes (equivalent to --all, but docker doesn't have --all for prune; we list and remove)
664
+ volumes = self.client.volumes.list(all=True)
665
+ removed = []
666
+ for v in volumes:
667
+ try:
668
+ v.remove(force=force)
669
+ removed.append(v.attrs["Name"])
670
+ except Exception as e:
671
+ logging.info(f"Info: {e}")
672
+ pass
673
+ result = {
674
+ "volumes_removed": removed,
675
+ "space_reclaimed": "N/A (all volumes)",
676
+ }
677
+ else:
678
+ result = self.client.volumes.prune()
679
+ pruned = {
680
+ "space_reclaimed": self._format_size(result["SpaceReclaimed"]),
681
+ "volumes_removed": (
682
+ [v["Name"] for v in result["VolumesRemoved"]]
683
+ if result["VolumesRemoved"]
684
+ else []
685
+ ),
686
+ }
687
+ result = pruned
688
+ self.log_action("prune_volumes", params, result)
689
+ return result
690
+ except Exception as e:
691
+ self.log_action("prune_volumes", params, error=e)
692
+ raise RuntimeError(f"Failed to prune volumes: {str(e)}")
693
+
584
694
  def remove_network(self, network_id: str) -> Dict:
585
695
  params = {"network_id": network_id}
586
696
  try:
@@ -593,6 +703,35 @@ class DockerManager(ContainerManagerBase):
593
703
  self.log_action("remove_network", params, error=e)
594
704
  raise RuntimeError(f"Failed to remove network: {str(e)}")
595
705
 
706
+ def prune_system(self, force: bool = False, all: bool = False) -> Dict:
707
+ params = {"force": force, "all": all}
708
+ try:
709
+ filters = {"until": None} if all else {}
710
+ result = self.client.system.prune(filters=filters, space=True)
711
+ pruned = {
712
+ "space_reclaimed": self._format_size(result["SpaceReclaimed"]),
713
+ "images_removed": (
714
+ [img["Id"][7:19] for img in result["ImagesDeleted"]]
715
+ if "ImagesDeleted" in result
716
+ else []
717
+ ),
718
+ "containers_removed": (
719
+ [c["Id"][7:19] for c in result["ContainersDeleted"]]
720
+ if "ContainersDeleted" in result
721
+ else []
722
+ ),
723
+ "volumes_removed": (
724
+ [v["Name"] for v in result["VolumesDeleted"]]
725
+ if "VolumesDeleted" in result
726
+ else []
727
+ ),
728
+ }
729
+ self.log_action("prune_system", params, pruned)
730
+ return pruned
731
+ except Exception as e:
732
+ self.log_action("prune_system", params, error=e)
733
+ raise RuntimeError(f"Failed to prune system: {str(e)}")
734
+
596
735
  def compose_up(
597
736
  self, compose_file: str, detach: bool = True, build: bool = False
598
737
  ) -> str:
@@ -983,6 +1122,23 @@ class PodmanManager(ContainerManagerBase):
983
1122
  self.log_action("pull_image", params, error=e)
984
1123
  raise RuntimeError(f"Failed to pull image: {str(e)}")
985
1124
 
1125
+ def prune_images(self, force: bool = False, all: bool = False) -> Dict:
1126
+ params = {"force": force, "all": all}
1127
+ try:
1128
+ filters = {"dangling": True} if not all else {}
1129
+ result = self.client.images.prune(filters=filters)
1130
+ pruned = {
1131
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
1132
+ "images_removed": [
1133
+ img["Id"][7:19] for img in result.get("ImagesRemoved", [])
1134
+ ],
1135
+ }
1136
+ self.log_action("prune_images", params, pruned)
1137
+ return pruned
1138
+ except Exception as e:
1139
+ self.log_action("prune_images", params, error=e)
1140
+ raise RuntimeError(f"Failed to prune images: {str(e)}")
1141
+
986
1142
  def list_containers(self, all: bool = False) -> List[Dict]:
987
1143
  params = {"all": all}
988
1144
  try:
@@ -1069,6 +1225,22 @@ class PodmanManager(ContainerManagerBase):
1069
1225
  self.log_action("run_container", params, error=e)
1070
1226
  raise RuntimeError(f"Failed to run container: {str(e)}")
1071
1227
 
1228
+ def prune_containers(self, force: bool = False) -> Dict:
1229
+ params = {"force": force}
1230
+ try:
1231
+ result = self.client.containers.prune()
1232
+ pruned = {
1233
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
1234
+ "containers_removed": [
1235
+ c["Id"][7:19] for c in result.get("ContainersRemoved", [])
1236
+ ],
1237
+ }
1238
+ self.log_action("prune_containers", params, pruned)
1239
+ return pruned
1240
+ except Exception as e:
1241
+ self.log_action("prune_containers", params, error=e)
1242
+ raise RuntimeError(f"Failed to prune containers: {str(e)}")
1243
+
1072
1244
  def list_networks(self) -> List[Dict]:
1073
1245
  params = {}
1074
1246
  try:
@@ -1114,6 +1286,22 @@ class PodmanManager(ContainerManagerBase):
1114
1286
  self.log_action("create_network", params, error=e)
1115
1287
  raise RuntimeError(f"Failed to create network: {str(e)}")
1116
1288
 
1289
+ def prune_networks(self) -> Dict:
1290
+ params = {}
1291
+ try:
1292
+ result = self.client.networks.prune()
1293
+ pruned = {
1294
+ "space_reclaimed": self._format_size(result.get("SpaceReclaimed", 0)),
1295
+ "networks_removed": [
1296
+ n["Id"][7:19] for n in result.get("NetworksRemoved", [])
1297
+ ],
1298
+ }
1299
+ self.log_action("prune_networks", params, pruned)
1300
+ return pruned
1301
+ except Exception as e:
1302
+ self.log_action("prune_networks", params, error=e)
1303
+ raise RuntimeError(f"Failed to prune networks: {str(e)}")
1304
+
1117
1305
  def get_version(self) -> Dict:
1118
1306
  params = {}
1119
1307
  try:
@@ -1267,6 +1455,41 @@ class PodmanManager(ContainerManagerBase):
1267
1455
  self.log_action("remove_volume", params, error=e)
1268
1456
  raise RuntimeError(f"Failed to remove volume: {str(e)}")
1269
1457
 
1458
+ def prune_volumes(self, force: bool = False, all: bool = False) -> Dict:
1459
+ params = {"force": force, "all": all}
1460
+ try:
1461
+ if all:
1462
+ # Remove all volumes
1463
+ volumes = self.client.volumes.list(all=True)
1464
+ removed = []
1465
+ for v in volumes:
1466
+ try:
1467
+ v.remove(force=force)
1468
+ removed.append(v.attrs["Name"])
1469
+ except Exception as e:
1470
+ logging.info(f"Info: {e}")
1471
+ pass
1472
+ result = {
1473
+ "volumes_removed": removed,
1474
+ "space_reclaimed": "N/A (all volumes)",
1475
+ }
1476
+ else:
1477
+ result = self.client.volumes.prune()
1478
+ pruned = {
1479
+ "space_reclaimed": self._format_size(
1480
+ result.get("SpaceReclaimed", 0)
1481
+ ),
1482
+ "volumes_removed": [
1483
+ v["Name"] for v in result.get("VolumesRemoved", [])
1484
+ ],
1485
+ }
1486
+ result = pruned
1487
+ self.log_action("prune_volumes", params, result)
1488
+ return result
1489
+ except Exception as e:
1490
+ self.log_action("prune_volumes", params, error=e)
1491
+ raise RuntimeError(f"Failed to prune volumes: {str(e)}")
1492
+
1270
1493
  def remove_network(self, network_id: str) -> Dict:
1271
1494
  params = {"network_id": network_id}
1272
1495
  try:
@@ -1279,6 +1502,26 @@ class PodmanManager(ContainerManagerBase):
1279
1502
  self.log_action("remove_network", params, error=e)
1280
1503
  raise RuntimeError(f"Failed to remove network: {str(e)}")
1281
1504
 
1505
+ def prune_system(self, force: bool = False, all: bool = False) -> Dict:
1506
+ params = {"force": force, "all": all}
1507
+ try:
1508
+ # Podman system prune uses CLI, as podman-py may not have direct support
1509
+ cmd = ["podman", "system", "prune"]
1510
+ if all:
1511
+ cmd.append("--all")
1512
+ result = subprocess.run(cmd, capture_output=True, text=True)
1513
+ if result.returncode != 0:
1514
+ raise RuntimeError(result.stderr)
1515
+ pruned = {
1516
+ "output": result.stdout.strip(),
1517
+ "space_reclaimed": "Check output",
1518
+ }
1519
+ self.log_action("prune_system", params, pruned)
1520
+ return pruned
1521
+ except Exception as e:
1522
+ self.log_action("prune_system", params, error=e)
1523
+ raise RuntimeError(f"Failed to prune system: {str(e)}")
1524
+
1282
1525
  def compose_up(
1283
1526
  self, compose_file: str, detach: bool = True, build: bool = False
1284
1527
  ) -> str:
@@ -1418,6 +1661,8 @@ Actions:
1418
1661
  --platform <plat> [ Platform, e.g., linux/amd64 ]
1419
1662
  --remove-image <image> [ Remove image ]
1420
1663
  --force [ Force removal (global for remove actions) ]
1664
+ --prune-images [ Prune unused images ]
1665
+ --all [ Prune all unused images ]
1421
1666
  --list-containers [ List containers ]
1422
1667
  --all [ Show all containers ]
1423
1668
  --run-container <image> [ Run container ]
@@ -1431,6 +1676,7 @@ Actions:
1431
1676
  --timeout <sec> [ Timeout, default 10 ]
1432
1677
  --remove-container <id>[ Remove container ]
1433
1678
  --force [ Force ]
1679
+ --prune-containers [ Prune stopped containers ]
1434
1680
  --get-container-logs <id> [ Get logs ]
1435
1681
  --tail <tail> [ Tail lines, default all ]
1436
1682
  --exec-in-container <id> [ Exec command ]
@@ -1440,10 +1686,15 @@ Actions:
1440
1686
  --create-volume <name> [ Create volume ]
1441
1687
  --remove-volume <name> [ Remove volume ]
1442
1688
  --force [ Force ]
1689
+ --prune-volumes [ Prune unused volumes ]
1690
+ --all [ Remove all volumes (dangerous) ]
1443
1691
  --list-networks [ List networks ]
1444
1692
  --create-network <name>[ Create network ]
1445
1693
  --driver <driver> [ Driver, default bridge ]
1446
1694
  --remove-network <id> [ Remove network ]
1695
+ --prune-networks [ Prune unused networks ]
1696
+ --prune-system [ Prune system resources ]
1697
+ --all [ Prune all unused (including volumes, build cache) ]
1447
1698
  --compose-up <file> [ Compose up ]
1448
1699
  --build [ Build images ]
1449
1700
  --detach [ Detach mode, default true ]
@@ -1492,7 +1743,7 @@ def container_manager(argv):
1492
1743
  parser.add_argument(
1493
1744
  "--remove-image", type=str, default=None, help="Image to remove"
1494
1745
  )
1495
- parser.add_argument("--force", action="store_true", help="Force removal")
1746
+ parser.add_argument("--prune-images", action="store_true", help="Prune images")
1496
1747
  parser.add_argument(
1497
1748
  "--list-containers", action="store_true", help="List containers"
1498
1749
  )
@@ -1513,6 +1764,9 @@ def container_manager(argv):
1513
1764
  parser.add_argument(
1514
1765
  "--remove-container", type=str, default=None, help="Container to remove"
1515
1766
  )
1767
+ parser.add_argument(
1768
+ "--prune-containers", action="store_true", help="Prune containers"
1769
+ )
1516
1770
  parser.add_argument(
1517
1771
  "--get-container-logs", type=str, default=None, help="Container logs"
1518
1772
  )
@@ -1529,6 +1783,7 @@ def container_manager(argv):
1529
1783
  parser.add_argument(
1530
1784
  "--remove-volume", type=str, default=None, help="Volume to remove"
1531
1785
  )
1786
+ parser.add_argument("--prune-volumes", action="store_true", help="Prune volumes")
1532
1787
  parser.add_argument("--list-networks", action="store_true", help="List networks")
1533
1788
  parser.add_argument(
1534
1789
  "--create-network", type=str, default=None, help="Network to create"
@@ -1537,6 +1792,8 @@ def container_manager(argv):
1537
1792
  parser.add_argument(
1538
1793
  "--remove-network", type=str, default=None, help="Network to remove"
1539
1794
  )
1795
+ parser.add_argument("--prune-networks", action="store_true", help="Prune networks")
1796
+ parser.add_argument("--prune-system", action="store_true", help="Prune system")
1540
1797
  parser.add_argument("--compose-up", type=str, default=None, help="Compose file up")
1541
1798
  parser.add_argument("--build", action="store_true", help="Build images")
1542
1799
  parser.add_argument(
@@ -1566,6 +1823,7 @@ def container_manager(argv):
1566
1823
  parser.add_argument(
1567
1824
  "--remove-service", type=str, default=None, help="Service to remove"
1568
1825
  )
1826
+ parser.add_argument("--force", action="store_true", help="Force removal")
1569
1827
  parser.add_argument("-h", "--help", action="store_true", help="Show help")
1570
1828
 
1571
1829
  args = parser.parse_args(argv)
@@ -1583,9 +1841,11 @@ def container_manager(argv):
1583
1841
  platform = args.platform
1584
1842
  remove_image = args.remove_image is not None
1585
1843
  remove_image_str = args.remove_image
1844
+ prune_images = args.prune_images
1845
+ prune_images_all = args.all if prune_images else False
1586
1846
  force = args.force
1587
1847
  list_containers = args.list_containers
1588
- all_containers = args.all
1848
+ all_containers = args.all if list_containers else False
1589
1849
  run_container = args.run_container is not None
1590
1850
  run_image = args.run_container
1591
1851
  name = args.name
@@ -1599,6 +1859,7 @@ def container_manager(argv):
1599
1859
  timeout = args.timeout
1600
1860
  remove_container = args.remove_container is not None
1601
1861
  remove_container_id = args.remove_container
1862
+ prune_containers = args.prune_containers
1602
1863
  get_container_logs = args.get_container_logs is not None
1603
1864
  container_logs_id = args.get_container_logs
1604
1865
  tail = args.tail
@@ -1611,12 +1872,17 @@ def container_manager(argv):
1611
1872
  create_volume_name = args.create_volume
1612
1873
  remove_volume = args.remove_volume is not None
1613
1874
  remove_volume_name = args.remove_volume
1875
+ prune_volumes = args.prune_volumes
1876
+ prune_volumes_all = args.all if prune_volumes else False
1614
1877
  list_networks = args.list_networks
1615
1878
  create_network = args.create_network is not None
1616
1879
  create_network_name = args.create_network
1617
1880
  driver = args.driver
1618
1881
  remove_network = args.remove_network is not None
1619
1882
  remove_network_id = args.remove_network
1883
+ prune_networks = args.prune_networks
1884
+ prune_system = args.prune_system
1885
+ prune_system_all = args.all if prune_system else False
1620
1886
  compose_up = args.compose_up is not None
1621
1887
  compose_up_file = args.compose_up
1622
1888
  compose_build = args.build
@@ -1665,6 +1931,9 @@ def container_manager(argv):
1665
1931
  raise ValueError("Image required for remove-image")
1666
1932
  print(json.dumps(manager.remove_image(remove_image_str, force), indent=2))
1667
1933
 
1934
+ if prune_images:
1935
+ print(json.dumps(manager.prune_images(force, prune_images_all), indent=2))
1936
+
1668
1937
  if list_containers:
1669
1938
  print(json.dumps(manager.list_containers(all_containers), indent=2))
1670
1939
 
@@ -1710,6 +1979,9 @@ def container_manager(argv):
1710
1979
  json.dumps(manager.remove_container(remove_container_id, force), indent=2)
1711
1980
  )
1712
1981
 
1982
+ if prune_containers:
1983
+ print(json.dumps(manager.prune_containers(force), indent=2))
1984
+
1713
1985
  if get_container_logs:
1714
1986
  if not container_logs_id:
1715
1987
  raise ValueError("Container ID required for get-container-logs")
@@ -1739,6 +2011,9 @@ def container_manager(argv):
1739
2011
  raise ValueError("Name required for remove-volume")
1740
2012
  print(json.dumps(manager.remove_volume(remove_volume_name, force), indent=2))
1741
2013
 
2014
+ if prune_volumes:
2015
+ print(json.dumps(manager.prune_volumes(force, prune_volumes_all), indent=2))
2016
+
1742
2017
  if list_networks:
1743
2018
  print(json.dumps(manager.list_networks(), indent=2))
1744
2019
 
@@ -1752,6 +2027,12 @@ def container_manager(argv):
1752
2027
  raise ValueError("ID required for remove-network")
1753
2028
  print(json.dumps(manager.remove_network(remove_network_id), indent=2))
1754
2029
 
2030
+ if prune_networks:
2031
+ print(json.dumps(manager.prune_networks(force), indent=2))
2032
+
2033
+ if prune_system:
2034
+ print(json.dumps(manager.prune_system(force, prune_system_all), indent=2))
2035
+
1755
2036
  if compose_up:
1756
2037
  if not compose_up_file:
1757
2038
  raise ValueError("File required for compose-up")
@@ -241,6 +241,44 @@ async def remove_image(
241
241
  raise RuntimeError(f"Failed to remove image: {str(e)}")
242
242
 
243
243
 
244
+ @mcp.tool(
245
+ annotations={
246
+ "title": "Prune Images",
247
+ "readOnlyHint": False,
248
+ "destructiveHint": True,
249
+ "idempotentHint": True,
250
+ "openWorldHint": False,
251
+ },
252
+ tags={"container_management"},
253
+ )
254
+ async def prune_images(
255
+ all: bool = Field(description="Prune all unused images", default=False),
256
+ manager_type: Optional[str] = Field(
257
+ description="Container manager: docker, podman (default: auto-detect)",
258
+ default=environment_container_manager_type,
259
+ ),
260
+ silent: Optional[bool] = Field(
261
+ description="Suppress output", default=environment_silent
262
+ ),
263
+ log_file: Optional[str] = Field(
264
+ description="Path to log file", default=environment_log_file
265
+ ),
266
+ ctx: Context = Field(
267
+ description="MCP context for progress reporting", default=None
268
+ ),
269
+ ) -> Dict:
270
+ logger = logging.getLogger("ContainerManager")
271
+ logger.debug(
272
+ f"Pruning images for {manager_type}, all: {all}, silent: {silent}, log_file: {log_file}"
273
+ )
274
+ try:
275
+ manager = create_manager(manager_type, silent, log_file)
276
+ return manager.prune_images(all=all)
277
+ except Exception as e:
278
+ logger.error(f"Failed to prune images: {str(e)}")
279
+ raise RuntimeError(f"Failed to prune images: {str(e)}")
280
+
281
+
244
282
  @mcp.tool(
245
283
  annotations={
246
284
  "title": "List Containers",
@@ -414,6 +452,43 @@ async def remove_container(
414
452
  raise RuntimeError(f"Failed to remove container: {str(e)}")
415
453
 
416
454
 
455
+ @mcp.tool(
456
+ annotations={
457
+ "title": "Prune Containers",
458
+ "readOnlyHint": False,
459
+ "destructiveHint": True,
460
+ "idempotentHint": True,
461
+ "openWorldHint": False,
462
+ },
463
+ tags={"container_management"},
464
+ )
465
+ async def prune_containers(
466
+ manager_type: Optional[str] = Field(
467
+ description="Container manager: docker, podman (default: auto-detect)",
468
+ default=environment_container_manager_type,
469
+ ),
470
+ silent: Optional[bool] = Field(
471
+ description="Suppress output", default=environment_silent
472
+ ),
473
+ log_file: Optional[str] = Field(
474
+ description="Path to log file", default=environment_log_file
475
+ ),
476
+ ctx: Context = Field(
477
+ description="MCP context for progress reporting", default=None
478
+ ),
479
+ ) -> Dict:
480
+ logger = logging.getLogger("ContainerManager")
481
+ logger.debug(
482
+ f"Pruning containers for {manager_type}, silent: {silent}, log_file: {log_file}"
483
+ )
484
+ try:
485
+ manager = create_manager(manager_type, silent, log_file)
486
+ return manager.prune_containers()
487
+ except Exception as e:
488
+ logger.error(f"Failed to prune containers: {str(e)}")
489
+ raise RuntimeError(f"Failed to prune containers: {str(e)}")
490
+
491
+
417
492
  @mcp.tool(
418
493
  annotations={
419
494
  "title": "Get Container Logs",
@@ -609,6 +684,44 @@ async def remove_volume(
609
684
  raise RuntimeError(f"Failed to remove volume: {str(e)}")
610
685
 
611
686
 
687
+ @mcp.tool(
688
+ annotations={
689
+ "title": "Prune Volumes",
690
+ "readOnlyHint": False,
691
+ "destructiveHint": True,
692
+ "idempotentHint": True,
693
+ "openWorldHint": False,
694
+ },
695
+ tags={"container_management"},
696
+ )
697
+ async def prune_volumes(
698
+ all: bool = Field(description="Remove all volumes (dangerous)", default=False),
699
+ manager_type: Optional[str] = Field(
700
+ description="Container manager: docker, podman (default: auto-detect)",
701
+ default=environment_container_manager_type,
702
+ ),
703
+ silent: Optional[bool] = Field(
704
+ description="Suppress output", default=environment_silent
705
+ ),
706
+ log_file: Optional[str] = Field(
707
+ description="Path to log file", default=environment_log_file
708
+ ),
709
+ ctx: Context = Field(
710
+ description="MCP context for progress reporting", default=None
711
+ ),
712
+ ) -> Dict:
713
+ logger = logging.getLogger("ContainerManager")
714
+ logger.debug(
715
+ f"Pruning volumes for {manager_type}, all: {all}, silent: {silent}, log_file: {log_file}"
716
+ )
717
+ try:
718
+ manager = create_manager(manager_type, silent, log_file)
719
+ return manager.prune_volumes(all=all)
720
+ except Exception as e:
721
+ logger.error(f"Failed to prune volumes: {str(e)}")
722
+ raise RuntimeError(f"Failed to prune volumes: {str(e)}")
723
+
724
+
612
725
  @mcp.tool(
613
726
  annotations={
614
727
  "title": "List Networks",
@@ -723,6 +836,82 @@ async def remove_network(
723
836
  raise RuntimeError(f"Failed to remove network: {str(e)}")
724
837
 
725
838
 
839
+ @mcp.tool(
840
+ annotations={
841
+ "title": "Prune Networks",
842
+ "readOnlyHint": False,
843
+ "destructiveHint": True,
844
+ "idempotentHint": True,
845
+ "openWorldHint": False,
846
+ },
847
+ tags={"container_management"},
848
+ )
849
+ async def prune_networks(
850
+ manager_type: Optional[str] = Field(
851
+ description="Container manager: docker, podman (default: auto-detect)",
852
+ default=environment_container_manager_type,
853
+ ),
854
+ silent: Optional[bool] = Field(
855
+ description="Suppress output", default=environment_silent
856
+ ),
857
+ log_file: Optional[str] = Field(
858
+ description="Path to log file", default=environment_log_file
859
+ ),
860
+ ctx: Context = Field(
861
+ description="MCP context for progress reporting", default=None
862
+ ),
863
+ ) -> Dict:
864
+ logger = logging.getLogger("ContainerManager")
865
+ logger.debug(
866
+ f"Pruning networks for {manager_type}, silent: {silent}, log_file: {log_file}"
867
+ )
868
+ try:
869
+ manager = create_manager(manager_type, silent, log_file)
870
+ return manager.prune_networks()
871
+ except Exception as e:
872
+ logger.error(f"Failed to prune networks: {str(e)}")
873
+ raise RuntimeError(f"Failed to prune networks: {str(e)}")
874
+
875
+
876
+ @mcp.tool(
877
+ annotations={
878
+ "title": "Prune System",
879
+ "readOnlyHint": False,
880
+ "destructiveHint": True,
881
+ "idempotentHint": True,
882
+ "openWorldHint": False,
883
+ },
884
+ tags={"container_management"},
885
+ )
886
+ async def prune_system(
887
+ force: bool = Field(description="Force prune", default=False),
888
+ all: bool = Field(description="Prune all unused resources", default=False),
889
+ manager_type: Optional[str] = Field(
890
+ description="Container manager: docker, podman (default: auto-detect)",
891
+ default=environment_container_manager_type,
892
+ ),
893
+ silent: Optional[bool] = Field(
894
+ description="Suppress output", default=environment_silent
895
+ ),
896
+ log_file: Optional[str] = Field(
897
+ description="Path to log file", default=environment_log_file
898
+ ),
899
+ ctx: Context = Field(
900
+ description="MCP context for progress reporting", default=None
901
+ ),
902
+ ) -> Dict:
903
+ logger = logging.getLogger("ContainerManager")
904
+ logger.debug(
905
+ f"Pruning system for {manager_type}, force: {force}, all: {all}, silent: {silent}, log_file: {log_file}"
906
+ )
907
+ try:
908
+ manager = create_manager(manager_type, silent, log_file)
909
+ return manager.prune_system(force, all)
910
+ except Exception as e:
911
+ logger.error(f"Failed to prune system: {str(e)}")
912
+ raise RuntimeError(f"Failed to prune system: {str(e)}")
913
+
914
+
726
915
  # Swarm-specific tools
727
916
 
728
917
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: container-manager-mcp
3
- Version: 1.0.2
3
+ Version: 1.0.3
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: 1.0.2*
51
+ *Version: 1.0.3*
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=1HN_sKQFGVtf35wk66Uez9MtHOv2B9LJ4tbTbEzCXEA,84152
4
+ container_manager_mcp/container_manager_mcp.py,sha256=M43YmLt_qoOfpnlK2VyyGX2vJ6gMBxepSqBauvMS4AA,46204
5
+ container_manager_mcp-1.0.3.dist-info/licenses/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
6
+ container_manager_mcp-1.0.3.dist-info/METADATA,sha256=RfIVbh9AeL0roB2yyqtXBJR2o8H7MqTpV82epVm0V1I,8238
7
+ container_manager_mcp-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ container_manager_mcp-1.0.3.dist-info/entry_points.txt,sha256=I23pXcCgAShlfYbENzs3kbw3l1lU9Gy7lODPfRqeeiA,156
9
+ container_manager_mcp-1.0.3.dist-info/top_level.txt,sha256=B7QQLOd9mBdu0lsPKqyu4T8-zUtbqKzQJbMbtAzoozU,22
10
+ container_manager_mcp-1.0.3.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=9TPpZw2gB_rz4oLsu45jBeixkG9B-OYppsGM4ctqb5I,72495
4
- container_manager_mcp/container_manager_mcp.py,sha256=PSKbw4NuoGWuZZJdTOstiIu_2mq2wZdNNM1gDQdZMNU,39788
5
- container_manager_mcp-1.0.2.dist-info/licenses/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
6
- container_manager_mcp-1.0.2.dist-info/METADATA,sha256=IyGwMPvMD3xjEeffWzuWO9wTOQTAIoNLQmhaJCTBxQw,8238
7
- container_manager_mcp-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- container_manager_mcp-1.0.2.dist-info/entry_points.txt,sha256=I23pXcCgAShlfYbENzs3kbw3l1lU9Gy7lODPfRqeeiA,156
9
- container_manager_mcp-1.0.2.dist-info/top_level.txt,sha256=B7QQLOd9mBdu0lsPKqyu4T8-zUtbqKzQJbMbtAzoozU,22
10
- container_manager_mcp-1.0.2.dist-info/RECORD,,