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.
- container_manager_mcp/container_manager.py +283 -2
- container_manager_mcp/container_manager_mcp.py +189 -0
- {container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/METADATA +2 -2
- container_manager_mcp-1.0.3.dist-info/RECORD +10 -0
- container_manager_mcp-1.0.2.dist-info/RECORD +0 -10
- {container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/WHEEL +0 -0
- {container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/entry_points.txt +0 -0
- {container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/top_level.txt +0 -0
@@ -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("--
|
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.
|
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
|

|
49
49
|

|
50
50
|
|
51
|
-
*Version: 1.0.
|
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,,
|
File without changes
|
{container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/entry_points.txt
RENAMED
File without changes
|
{container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
{container_manager_mcp-1.0.2.dist-info → container_manager_mcp-1.0.3.dist-info}/top_level.txt
RENAMED
File without changes
|