container-manager-mcp 0.0.2__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/__init__.py +24 -0
- container_manager_mcp/container_manager.py +1381 -0
- container_manager_mcp/container_manager_mcp.py +1134 -0
- container_manager_mcp-0.0.2.dist-info/METADATA +248 -0
- container_manager_mcp-0.0.2.dist-info/RECORD +9 -0
- container_manager_mcp-0.0.2.dist-info/WHEEL +5 -0
- container_manager_mcp-0.0.2.dist-info/entry_points.txt +3 -0
- container_manager_mcp-0.0.2.dist-info/licenses/LICENSE +20 -0
- container_manager_mcp-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1134 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import getopt
|
5
|
+
import os
|
6
|
+
import sys
|
7
|
+
import logging
|
8
|
+
from typing import Optional, List, Dict
|
9
|
+
|
10
|
+
from fastmcp import FastMCP, Context
|
11
|
+
from pydantic import Field
|
12
|
+
from container_manager import create_manager
|
13
|
+
|
14
|
+
|
15
|
+
def setup_logging(
|
16
|
+
is_mcp_server: bool = False, log_file: str = "container_manager_mcp.log"
|
17
|
+
):
|
18
|
+
logging.basicConfig(
|
19
|
+
filename=log_file,
|
20
|
+
level=logging.INFO,
|
21
|
+
format="%(asctime)s - %(levelname)s - %(message)s",
|
22
|
+
)
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
logger.info(f"MCP server logging initialized to {log_file}")
|
25
|
+
|
26
|
+
|
27
|
+
mcp = FastMCP(name="ContainerManagerServer")
|
28
|
+
|
29
|
+
|
30
|
+
def to_boolean(string):
|
31
|
+
normalized = str(string).strip().lower()
|
32
|
+
true_values = {"t", "true", "y", "yes", "1"}
|
33
|
+
false_values = {"f", "false", "n", "no", "0"}
|
34
|
+
if normalized in true_values:
|
35
|
+
return True
|
36
|
+
elif normalized in false_values:
|
37
|
+
return False
|
38
|
+
else:
|
39
|
+
raise ValueError(f"Cannot convert '{string}' to boolean")
|
40
|
+
|
41
|
+
|
42
|
+
environment_silent = os.environ.get("SILENT", False)
|
43
|
+
environment_log_file = os.environ.get("LOG_FILE", None)
|
44
|
+
|
45
|
+
if environment_silent:
|
46
|
+
environment_silent = to_boolean(environment_silent)
|
47
|
+
|
48
|
+
# Common tools
|
49
|
+
|
50
|
+
|
51
|
+
@mcp.tool(
|
52
|
+
annotations={
|
53
|
+
"title": "Get Version",
|
54
|
+
"readOnlyHint": True,
|
55
|
+
"destructiveHint": False,
|
56
|
+
"idempotentHint": True,
|
57
|
+
"openWorldHint": False,
|
58
|
+
},
|
59
|
+
tags={"container_management"},
|
60
|
+
)
|
61
|
+
async def get_version(
|
62
|
+
manager_type: str = Field(
|
63
|
+
description="Container manager: docker, podman", default="docker"
|
64
|
+
),
|
65
|
+
silent: Optional[bool] = Field(
|
66
|
+
description="Suppress output", default=environment_silent
|
67
|
+
),
|
68
|
+
log_file: Optional[str] = Field(
|
69
|
+
description="Path to log file", default=environment_log_file
|
70
|
+
),
|
71
|
+
ctx: Context = Field(
|
72
|
+
description="MCP context for progress reporting", default=None
|
73
|
+
),
|
74
|
+
) -> Dict:
|
75
|
+
logger = logging.getLogger("ContainerManager")
|
76
|
+
logger.debug(
|
77
|
+
f"Getting version for {manager_type}, silent: {silent}, log_file: {log_file}"
|
78
|
+
)
|
79
|
+
try:
|
80
|
+
manager = create_manager(manager_type, silent, log_file)
|
81
|
+
return manager.get_version()
|
82
|
+
except Exception as e:
|
83
|
+
logger.error(f"Failed to get version: {str(e)}")
|
84
|
+
raise RuntimeError(f"Failed to get version: {str(e)}")
|
85
|
+
|
86
|
+
|
87
|
+
@mcp.tool(
|
88
|
+
annotations={
|
89
|
+
"title": "Get Info",
|
90
|
+
"readOnlyHint": True,
|
91
|
+
"destructiveHint": False,
|
92
|
+
"idempotentHint": True,
|
93
|
+
"openWorldHint": False,
|
94
|
+
},
|
95
|
+
tags={"container_management"},
|
96
|
+
)
|
97
|
+
async def get_info(
|
98
|
+
manager_type: str = Field(
|
99
|
+
description="Container manager: docker, podman", default="docker"
|
100
|
+
),
|
101
|
+
silent: Optional[bool] = Field(
|
102
|
+
description="Suppress output", default=environment_silent
|
103
|
+
),
|
104
|
+
log_file: Optional[str] = Field(
|
105
|
+
description="Path to log file", default=environment_log_file
|
106
|
+
),
|
107
|
+
ctx: Context = Field(
|
108
|
+
description="MCP context for progress reporting", default=None
|
109
|
+
),
|
110
|
+
) -> Dict:
|
111
|
+
logger = logging.getLogger("ContainerManager")
|
112
|
+
logger.debug(
|
113
|
+
f"Getting info for {manager_type}, silent: {silent}, log_file: {log_file}"
|
114
|
+
)
|
115
|
+
try:
|
116
|
+
manager = create_manager(manager_type, silent, log_file)
|
117
|
+
return manager.get_info()
|
118
|
+
except Exception as e:
|
119
|
+
logger.error(f"Failed to get info: {str(e)}")
|
120
|
+
raise RuntimeError(f"Failed to get info: {str(e)}")
|
121
|
+
|
122
|
+
|
123
|
+
@mcp.tool(
|
124
|
+
annotations={
|
125
|
+
"title": "List Images",
|
126
|
+
"readOnlyHint": True,
|
127
|
+
"destructiveHint": False,
|
128
|
+
"idempotentHint": True,
|
129
|
+
"openWorldHint": False,
|
130
|
+
},
|
131
|
+
tags={"container_management"},
|
132
|
+
)
|
133
|
+
async def list_images(
|
134
|
+
manager_type: str = Field(
|
135
|
+
description="Container manager: docker, podman", default="docker"
|
136
|
+
),
|
137
|
+
silent: Optional[bool] = Field(
|
138
|
+
description="Suppress output", default=environment_silent
|
139
|
+
),
|
140
|
+
log_file: Optional[str] = Field(
|
141
|
+
description="Path to log file", default=environment_log_file
|
142
|
+
),
|
143
|
+
ctx: Context = Field(
|
144
|
+
description="MCP context for progress reporting", default=None
|
145
|
+
),
|
146
|
+
) -> List[Dict]:
|
147
|
+
logger = logging.getLogger("ContainerManager")
|
148
|
+
logger.debug(
|
149
|
+
f"Listing images for {manager_type}, silent: {silent}, log_file: {log_file}"
|
150
|
+
)
|
151
|
+
try:
|
152
|
+
manager = create_manager(manager_type, silent, log_file)
|
153
|
+
return manager.list_images()
|
154
|
+
except Exception as e:
|
155
|
+
logger.error(f"Failed to list images: {str(e)}")
|
156
|
+
raise RuntimeError(f"Failed to list images: {str(e)}")
|
157
|
+
|
158
|
+
|
159
|
+
@mcp.tool(
|
160
|
+
annotations={
|
161
|
+
"title": "Pull Image",
|
162
|
+
"readOnlyHint": False,
|
163
|
+
"destructiveHint": False,
|
164
|
+
"idempotentHint": True,
|
165
|
+
"openWorldHint": False,
|
166
|
+
},
|
167
|
+
tags={"container_management"},
|
168
|
+
)
|
169
|
+
async def pull_image(
|
170
|
+
image: str = Field(description="Image name to pull"),
|
171
|
+
tag: str = Field(description="Image tag", default="latest"),
|
172
|
+
platform: Optional[str] = Field(
|
173
|
+
description="Platform (e.g., linux/amd64)", default=None
|
174
|
+
),
|
175
|
+
manager_type: str = Field(
|
176
|
+
description="Container manager: docker, podman", default="docker"
|
177
|
+
),
|
178
|
+
silent: Optional[bool] = Field(
|
179
|
+
description="Suppress output", default=environment_silent
|
180
|
+
),
|
181
|
+
log_file: Optional[str] = Field(
|
182
|
+
description="Path to log file", default=environment_log_file
|
183
|
+
),
|
184
|
+
ctx: Context = Field(
|
185
|
+
description="MCP context for progress reporting", default=None
|
186
|
+
),
|
187
|
+
) -> Dict:
|
188
|
+
logger = logging.getLogger("ContainerManager")
|
189
|
+
logger.debug(
|
190
|
+
f"Pulling image {image}:{tag} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
191
|
+
)
|
192
|
+
try:
|
193
|
+
manager = create_manager(manager_type, silent, log_file)
|
194
|
+
return manager.pull_image(image, tag, platform)
|
195
|
+
except Exception as e:
|
196
|
+
logger.error(f"Failed to pull image: {str(e)}")
|
197
|
+
raise RuntimeError(f"Failed to pull image: {str(e)}")
|
198
|
+
|
199
|
+
|
200
|
+
@mcp.tool(
|
201
|
+
annotations={
|
202
|
+
"title": "Remove Image",
|
203
|
+
"readOnlyHint": False,
|
204
|
+
"destructiveHint": True,
|
205
|
+
"idempotentHint": True,
|
206
|
+
"openWorldHint": False,
|
207
|
+
},
|
208
|
+
tags={"container_management"},
|
209
|
+
)
|
210
|
+
async def remove_image(
|
211
|
+
image: str = Field(description="Image name or ID to remove"),
|
212
|
+
force: bool = Field(description="Force removal", default=False),
|
213
|
+
manager_type: str = Field(
|
214
|
+
description="Container manager: docker, podman", default="docker"
|
215
|
+
),
|
216
|
+
silent: Optional[bool] = Field(
|
217
|
+
description="Suppress output", default=environment_silent
|
218
|
+
),
|
219
|
+
log_file: Optional[str] = Field(
|
220
|
+
description="Path to log file", default=environment_log_file
|
221
|
+
),
|
222
|
+
ctx: Context = Field(
|
223
|
+
description="MCP context for progress reporting", default=None
|
224
|
+
),
|
225
|
+
) -> Dict:
|
226
|
+
logger = logging.getLogger("ContainerManager")
|
227
|
+
logger.debug(
|
228
|
+
f"Removing image {image} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
229
|
+
)
|
230
|
+
try:
|
231
|
+
manager = create_manager(manager_type, silent, log_file)
|
232
|
+
return manager.remove_image(image, force)
|
233
|
+
except Exception as e:
|
234
|
+
logger.error(f"Failed to remove image: {str(e)}")
|
235
|
+
raise RuntimeError(f"Failed to remove image: {str(e)}")
|
236
|
+
|
237
|
+
|
238
|
+
@mcp.tool(
|
239
|
+
annotations={
|
240
|
+
"title": "List Containers",
|
241
|
+
"readOnlyHint": True,
|
242
|
+
"destructiveHint": False,
|
243
|
+
"idempotentHint": True,
|
244
|
+
"openWorldHint": False,
|
245
|
+
},
|
246
|
+
tags={"container_management"},
|
247
|
+
)
|
248
|
+
async def list_containers(
|
249
|
+
all: bool = Field(
|
250
|
+
description="Show all containers (default running only)", default=False
|
251
|
+
),
|
252
|
+
manager_type: str = Field(
|
253
|
+
description="Container manager: docker, podman", default="docker"
|
254
|
+
),
|
255
|
+
silent: Optional[bool] = Field(
|
256
|
+
description="Suppress output", default=environment_silent
|
257
|
+
),
|
258
|
+
log_file: Optional[str] = Field(
|
259
|
+
description="Path to log file", default=environment_log_file
|
260
|
+
),
|
261
|
+
ctx: Context = Field(
|
262
|
+
description="MCP context for progress reporting", default=None
|
263
|
+
),
|
264
|
+
) -> List[Dict]:
|
265
|
+
logger = logging.getLogger("ContainerManager")
|
266
|
+
logger.debug(
|
267
|
+
f"Listing containers for {manager_type}, all: {all}, silent: {silent}, log_file: {log_file}"
|
268
|
+
)
|
269
|
+
try:
|
270
|
+
manager = create_manager(manager_type, silent, log_file)
|
271
|
+
return manager.list_containers(all)
|
272
|
+
except Exception as e:
|
273
|
+
logger.error(f"Failed to list containers: {str(e)}")
|
274
|
+
raise RuntimeError(f"Failed to list containers: {str(e)}")
|
275
|
+
|
276
|
+
|
277
|
+
@mcp.tool(
|
278
|
+
annotations={
|
279
|
+
"title": "Run Container",
|
280
|
+
"readOnlyHint": False,
|
281
|
+
"destructiveHint": True,
|
282
|
+
"idempotentHint": False,
|
283
|
+
"openWorldHint": False,
|
284
|
+
},
|
285
|
+
tags={"container_management"},
|
286
|
+
)
|
287
|
+
async def run_container(
|
288
|
+
image: str = Field(description="Image to run"),
|
289
|
+
name: Optional[str] = Field(description="Container name", default=None),
|
290
|
+
command: Optional[str] = Field(
|
291
|
+
description="Command to run in container", default=None
|
292
|
+
),
|
293
|
+
detach: bool = Field(description="Run in detached mode", default=False),
|
294
|
+
ports: Optional[Dict[str, str]] = Field(
|
295
|
+
description="Port mappings {container_port: host_port}", default=None
|
296
|
+
),
|
297
|
+
volumes: Optional[Dict[str, Dict]] = Field(
|
298
|
+
description="Volume mappings {/host/path: {bind: /container/path, mode: rw}}",
|
299
|
+
default=None,
|
300
|
+
),
|
301
|
+
environment: Optional[Dict[str, str]] = Field(
|
302
|
+
description="Environment variables", default=None
|
303
|
+
),
|
304
|
+
manager_type: str = Field(
|
305
|
+
description="Container manager: docker, podman", default="docker"
|
306
|
+
),
|
307
|
+
silent: Optional[bool] = Field(
|
308
|
+
description="Suppress output", default=environment_silent
|
309
|
+
),
|
310
|
+
log_file: Optional[str] = Field(
|
311
|
+
description="Path to log file", default=environment_log_file
|
312
|
+
),
|
313
|
+
ctx: Context = Field(
|
314
|
+
description="MCP context for progress reporting", default=None
|
315
|
+
),
|
316
|
+
) -> Dict:
|
317
|
+
logger = logging.getLogger("ContainerManager")
|
318
|
+
logger.debug(
|
319
|
+
f"Running container from {image} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
320
|
+
)
|
321
|
+
try:
|
322
|
+
manager = create_manager(manager_type, silent, log_file)
|
323
|
+
return manager.run_container(
|
324
|
+
image, name, command, detach, ports, volumes, environment
|
325
|
+
)
|
326
|
+
except Exception as e:
|
327
|
+
logger.error(f"Failed to run container: {str(e)}")
|
328
|
+
raise RuntimeError(f"Failed to run container: {str(e)}")
|
329
|
+
|
330
|
+
|
331
|
+
@mcp.tool(
|
332
|
+
annotations={
|
333
|
+
"title": "Stop Container",
|
334
|
+
"readOnlyHint": False,
|
335
|
+
"destructiveHint": True,
|
336
|
+
"idempotentHint": True,
|
337
|
+
"openWorldHint": False,
|
338
|
+
},
|
339
|
+
tags={"container_management"},
|
340
|
+
)
|
341
|
+
async def stop_container(
|
342
|
+
container_id: str = Field(description="Container ID or name"),
|
343
|
+
timeout: int = Field(description="Timeout in seconds", default=10),
|
344
|
+
manager_type: str = Field(
|
345
|
+
description="Container manager: docker, podman", default="docker"
|
346
|
+
),
|
347
|
+
silent: Optional[bool] = Field(
|
348
|
+
description="Suppress output", default=environment_silent
|
349
|
+
),
|
350
|
+
log_file: Optional[str] = Field(
|
351
|
+
description="Path to log file", default=environment_log_file
|
352
|
+
),
|
353
|
+
ctx: Context = Field(
|
354
|
+
description="MCP context for progress reporting", default=None
|
355
|
+
),
|
356
|
+
) -> Dict:
|
357
|
+
logger = logging.getLogger("ContainerManager")
|
358
|
+
logger.debug(
|
359
|
+
f"Stopping container {container_id} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
360
|
+
)
|
361
|
+
try:
|
362
|
+
manager = create_manager(manager_type, silent, log_file)
|
363
|
+
return manager.stop_container(container_id, timeout)
|
364
|
+
except Exception as e:
|
365
|
+
logger.error(f"Failed to stop container: {str(e)}")
|
366
|
+
raise RuntimeError(f"Failed to stop container: {str(e)}")
|
367
|
+
|
368
|
+
|
369
|
+
@mcp.tool(
|
370
|
+
annotations={
|
371
|
+
"title": "Remove Container",
|
372
|
+
"readOnlyHint": False,
|
373
|
+
"destructiveHint": True,
|
374
|
+
"idempotentHint": True,
|
375
|
+
"openWorldHint": False,
|
376
|
+
},
|
377
|
+
tags={"container_management"},
|
378
|
+
)
|
379
|
+
async def remove_container(
|
380
|
+
container_id: str = Field(description="Container ID or name"),
|
381
|
+
force: bool = Field(description="Force removal", default=False),
|
382
|
+
manager_type: str = Field(
|
383
|
+
description="Container manager: docker, podman", default="docker"
|
384
|
+
),
|
385
|
+
silent: Optional[bool] = Field(
|
386
|
+
description="Suppress output", default=environment_silent
|
387
|
+
),
|
388
|
+
log_file: Optional[str] = Field(
|
389
|
+
description="Path to log file", default=environment_log_file
|
390
|
+
),
|
391
|
+
ctx: Context = Field(
|
392
|
+
description="MCP context for progress reporting", default=None
|
393
|
+
),
|
394
|
+
) -> Dict:
|
395
|
+
logger = logging.getLogger("ContainerManager")
|
396
|
+
logger.debug(
|
397
|
+
f"Removing container {container_id} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
398
|
+
)
|
399
|
+
try:
|
400
|
+
manager = create_manager(manager_type, silent, log_file)
|
401
|
+
return manager.remove_container(container_id, force)
|
402
|
+
except Exception as e:
|
403
|
+
logger.error(f"Failed to remove container: {str(e)}")
|
404
|
+
raise RuntimeError(f"Failed to remove container: {str(e)}")
|
405
|
+
|
406
|
+
|
407
|
+
@mcp.tool(
|
408
|
+
annotations={
|
409
|
+
"title": "Get Container Logs",
|
410
|
+
"readOnlyHint": True,
|
411
|
+
"destructiveHint": False,
|
412
|
+
"idempotentHint": True,
|
413
|
+
"openWorldHint": False,
|
414
|
+
},
|
415
|
+
tags={"container_management"},
|
416
|
+
)
|
417
|
+
async def get_container_logs(
|
418
|
+
container_id: str = Field(description="Container ID or name"),
|
419
|
+
tail: str = Field(
|
420
|
+
description="Number of lines to show from the end (or 'all')", default="all"
|
421
|
+
),
|
422
|
+
manager_type: str = Field(
|
423
|
+
description="Container manager: docker, podman", default="docker"
|
424
|
+
),
|
425
|
+
silent: Optional[bool] = Field(
|
426
|
+
description="Suppress output", default=environment_silent
|
427
|
+
),
|
428
|
+
log_file: Optional[str] = Field(
|
429
|
+
description="Path to log file", default=environment_log_file
|
430
|
+
),
|
431
|
+
ctx: Context = Field(
|
432
|
+
description="MCP context for progress reporting", default=None
|
433
|
+
),
|
434
|
+
) -> str:
|
435
|
+
logger = logging.getLogger("ContainerManager")
|
436
|
+
logger.debug(
|
437
|
+
f"Getting logs for container {container_id} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
438
|
+
)
|
439
|
+
try:
|
440
|
+
manager = create_manager(manager_type, silent, log_file)
|
441
|
+
return manager.get_container_logs(container_id, tail)
|
442
|
+
except Exception as e:
|
443
|
+
logger.error(f"Failed to get container logs: {str(e)}")
|
444
|
+
raise RuntimeError(f"Failed to get container logs: {str(e)}")
|
445
|
+
|
446
|
+
|
447
|
+
@mcp.tool(
|
448
|
+
annotations={
|
449
|
+
"title": "Exec in Container",
|
450
|
+
"readOnlyHint": False,
|
451
|
+
"destructiveHint": True,
|
452
|
+
"idempotentHint": False,
|
453
|
+
"openWorldHint": False,
|
454
|
+
},
|
455
|
+
tags={"container_management"},
|
456
|
+
)
|
457
|
+
async def exec_in_container(
|
458
|
+
container_id: str = Field(description="Container ID or name"),
|
459
|
+
command: List[str] = Field(description="Command to execute"),
|
460
|
+
detach: bool = Field(description="Detach execution", default=False),
|
461
|
+
manager_type: str = Field(
|
462
|
+
description="Container manager: docker, podman", default="docker"
|
463
|
+
),
|
464
|
+
silent: Optional[bool] = Field(
|
465
|
+
description="Suppress output", default=environment_silent
|
466
|
+
),
|
467
|
+
log_file: Optional[str] = Field(
|
468
|
+
description="Path to log file", default=environment_log_file
|
469
|
+
),
|
470
|
+
ctx: Context = Field(
|
471
|
+
description="MCP context for progress reporting", default=None
|
472
|
+
),
|
473
|
+
) -> Dict:
|
474
|
+
logger = logging.getLogger("ContainerManager")
|
475
|
+
logger.debug(
|
476
|
+
f"Executing {command} in container {container_id} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
477
|
+
)
|
478
|
+
try:
|
479
|
+
manager = create_manager(manager_type, silent, log_file)
|
480
|
+
return manager.exec_in_container(container_id, command, detach)
|
481
|
+
except Exception as e:
|
482
|
+
logger.error(f"Failed to exec in container: {str(e)}")
|
483
|
+
raise RuntimeError(f"Failed to exec in container: {str(e)}")
|
484
|
+
|
485
|
+
|
486
|
+
@mcp.tool(
|
487
|
+
annotations={
|
488
|
+
"title": "List Volumes",
|
489
|
+
"readOnlyHint": True,
|
490
|
+
"destructiveHint": False,
|
491
|
+
"idempotentHint": True,
|
492
|
+
"openWorldHint": False,
|
493
|
+
},
|
494
|
+
tags={"container_management"},
|
495
|
+
)
|
496
|
+
async def list_volumes(
|
497
|
+
manager_type: str = Field(
|
498
|
+
description="Container manager: docker, podman", default="docker"
|
499
|
+
),
|
500
|
+
silent: Optional[bool] = Field(
|
501
|
+
description="Suppress output", default=environment_silent
|
502
|
+
),
|
503
|
+
log_file: Optional[str] = Field(
|
504
|
+
description="Path to log file", default=environment_log_file
|
505
|
+
),
|
506
|
+
ctx: Context = Field(
|
507
|
+
description="MCP context for progress reporting", default=None
|
508
|
+
),
|
509
|
+
) -> Dict:
|
510
|
+
logger = logging.getLogger("ContainerManager")
|
511
|
+
logger.debug(
|
512
|
+
f"Listing volumes for {manager_type}, silent: {silent}, log_file: {log_file}"
|
513
|
+
)
|
514
|
+
try:
|
515
|
+
manager = create_manager(manager_type, silent, log_file)
|
516
|
+
return manager.list_volumes()
|
517
|
+
except Exception as e:
|
518
|
+
logger.error(f"Failed to list volumes: {str(e)}")
|
519
|
+
raise RuntimeError(f"Failed to list volumes: {str(e)}")
|
520
|
+
|
521
|
+
|
522
|
+
@mcp.tool(
|
523
|
+
annotations={
|
524
|
+
"title": "Create Volume",
|
525
|
+
"readOnlyHint": False,
|
526
|
+
"destructiveHint": False,
|
527
|
+
"idempotentHint": True,
|
528
|
+
"openWorldHint": False,
|
529
|
+
},
|
530
|
+
tags={"container_management"},
|
531
|
+
)
|
532
|
+
async def create_volume(
|
533
|
+
name: str = Field(description="Volume name"),
|
534
|
+
manager_type: str = Field(
|
535
|
+
description="Container manager: docker, podman", default="docker"
|
536
|
+
),
|
537
|
+
silent: Optional[bool] = Field(
|
538
|
+
description="Suppress output", default=environment_silent
|
539
|
+
),
|
540
|
+
log_file: Optional[str] = Field(
|
541
|
+
description="Path to log file", default=environment_log_file
|
542
|
+
),
|
543
|
+
ctx: Context = Field(
|
544
|
+
description="MCP context for progress reporting", default=None
|
545
|
+
),
|
546
|
+
) -> Dict:
|
547
|
+
logger = logging.getLogger("ContainerManager")
|
548
|
+
logger.debug(
|
549
|
+
f"Creating volume {name} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
550
|
+
)
|
551
|
+
try:
|
552
|
+
manager = create_manager(manager_type, silent, log_file)
|
553
|
+
return manager.create_volume(name)
|
554
|
+
except Exception as e:
|
555
|
+
logger.error(f"Failed to create volume: {str(e)}")
|
556
|
+
raise RuntimeError(f"Failed to create volume: {str(e)}")
|
557
|
+
|
558
|
+
|
559
|
+
@mcp.tool(
|
560
|
+
annotations={
|
561
|
+
"title": "Remove Volume",
|
562
|
+
"readOnlyHint": False,
|
563
|
+
"destructiveHint": True,
|
564
|
+
"idempotentHint": True,
|
565
|
+
"openWorldHint": False,
|
566
|
+
},
|
567
|
+
tags={"container_management"},
|
568
|
+
)
|
569
|
+
async def remove_volume(
|
570
|
+
name: str = Field(description="Volume name"),
|
571
|
+
force: bool = Field(description="Force removal", default=False),
|
572
|
+
manager_type: str = Field(
|
573
|
+
description="Container manager: docker, podman", default="docker"
|
574
|
+
),
|
575
|
+
silent: Optional[bool] = Field(
|
576
|
+
description="Suppress output", default=environment_silent
|
577
|
+
),
|
578
|
+
log_file: Optional[str] = Field(
|
579
|
+
description="Path to log file", default=environment_log_file
|
580
|
+
),
|
581
|
+
ctx: Context = Field(
|
582
|
+
description="MCP context for progress reporting", default=None
|
583
|
+
),
|
584
|
+
) -> Dict:
|
585
|
+
logger = logging.getLogger("ContainerManager")
|
586
|
+
logger.debug(
|
587
|
+
f"Removing volume {name} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
588
|
+
)
|
589
|
+
try:
|
590
|
+
manager = create_manager(manager_type, silent, log_file)
|
591
|
+
return manager.remove_volume(name, force)
|
592
|
+
except Exception as e:
|
593
|
+
logger.error(f"Failed to remove volume: {str(e)}")
|
594
|
+
raise RuntimeError(f"Failed to remove volume: {str(e)}")
|
595
|
+
|
596
|
+
|
597
|
+
@mcp.tool(
|
598
|
+
annotations={
|
599
|
+
"title": "List Networks",
|
600
|
+
"readOnlyHint": True,
|
601
|
+
"destructiveHint": False,
|
602
|
+
"idempotentHint": True,
|
603
|
+
"openWorldHint": False,
|
604
|
+
},
|
605
|
+
tags={"container_management"},
|
606
|
+
)
|
607
|
+
async def list_networks(
|
608
|
+
manager_type: str = Field(
|
609
|
+
description="Container manager: docker, podman", default="docker"
|
610
|
+
),
|
611
|
+
silent: Optional[bool] = Field(
|
612
|
+
description="Suppress output", default=environment_silent
|
613
|
+
),
|
614
|
+
log_file: Optional[str] = Field(
|
615
|
+
description="Path to log file", default=environment_log_file
|
616
|
+
),
|
617
|
+
ctx: Context = Field(
|
618
|
+
description="MCP context for progress reporting", default=None
|
619
|
+
),
|
620
|
+
) -> List[Dict]:
|
621
|
+
logger = logging.getLogger("ContainerManager")
|
622
|
+
logger.debug(
|
623
|
+
f"Listing networks for {manager_type}, silent: {silent}, log_file: {log_file}"
|
624
|
+
)
|
625
|
+
try:
|
626
|
+
manager = create_manager(manager_type, silent, log_file)
|
627
|
+
return manager.list_networks()
|
628
|
+
except Exception as e:
|
629
|
+
logger.error(f"Failed to list networks: {str(e)}")
|
630
|
+
raise RuntimeError(f"Failed to list networks: {str(e)}")
|
631
|
+
|
632
|
+
|
633
|
+
@mcp.tool(
|
634
|
+
annotations={
|
635
|
+
"title": "Create Network",
|
636
|
+
"readOnlyHint": False,
|
637
|
+
"destructiveHint": False,
|
638
|
+
"idempotentHint": True,
|
639
|
+
"openWorldHint": False,
|
640
|
+
},
|
641
|
+
tags={"container_management"},
|
642
|
+
)
|
643
|
+
async def create_network(
|
644
|
+
name: str = Field(description="Network name"),
|
645
|
+
driver: str = Field(description="Network driver (e.g., bridge)", default="bridge"),
|
646
|
+
manager_type: str = Field(
|
647
|
+
description="Container manager: docker, podman", default="docker"
|
648
|
+
),
|
649
|
+
silent: Optional[bool] = Field(
|
650
|
+
description="Suppress output", default=environment_silent
|
651
|
+
),
|
652
|
+
log_file: Optional[str] = Field(
|
653
|
+
description="Path to log file", default=environment_log_file
|
654
|
+
),
|
655
|
+
ctx: Context = Field(
|
656
|
+
description="MCP context for progress reporting", default=None
|
657
|
+
),
|
658
|
+
) -> Dict:
|
659
|
+
logger = logging.getLogger("ContainerManager")
|
660
|
+
logger.debug(
|
661
|
+
f"Creating network {name} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
662
|
+
)
|
663
|
+
try:
|
664
|
+
manager = create_manager(manager_type, silent, log_file)
|
665
|
+
return manager.create_network(name, driver)
|
666
|
+
except Exception as e:
|
667
|
+
logger.error(f"Failed to create network: {str(e)}")
|
668
|
+
raise RuntimeError(f"Failed to create network: {str(e)}")
|
669
|
+
|
670
|
+
|
671
|
+
@mcp.tool(
|
672
|
+
annotations={
|
673
|
+
"title": "Remove Network",
|
674
|
+
"readOnlyHint": False,
|
675
|
+
"destructiveHint": True,
|
676
|
+
"idempotentHint": True,
|
677
|
+
"openWorldHint": False,
|
678
|
+
},
|
679
|
+
tags={"container_management"},
|
680
|
+
)
|
681
|
+
async def remove_network(
|
682
|
+
network_id: str = Field(description="Network ID or name"),
|
683
|
+
manager_type: str = Field(
|
684
|
+
description="Container manager: docker, podman", default="docker"
|
685
|
+
),
|
686
|
+
silent: Optional[bool] = Field(
|
687
|
+
description="Suppress output", default=environment_silent
|
688
|
+
),
|
689
|
+
log_file: Optional[str] = Field(
|
690
|
+
description="Path to log file", default=environment_log_file
|
691
|
+
),
|
692
|
+
ctx: Context = Field(
|
693
|
+
description="MCP context for progress reporting", default=None
|
694
|
+
),
|
695
|
+
) -> Dict:
|
696
|
+
logger = logging.getLogger("ContainerManager")
|
697
|
+
logger.debug(
|
698
|
+
f"Removing network {network_id} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
699
|
+
)
|
700
|
+
try:
|
701
|
+
manager = create_manager(manager_type, silent, log_file)
|
702
|
+
return manager.remove_network(network_id)
|
703
|
+
except Exception as e:
|
704
|
+
logger.error(f"Failed to remove network: {str(e)}")
|
705
|
+
raise RuntimeError(f"Failed to remove network: {str(e)}")
|
706
|
+
|
707
|
+
|
708
|
+
# Swarm-specific tools
|
709
|
+
|
710
|
+
|
711
|
+
@mcp.tool(
|
712
|
+
annotations={
|
713
|
+
"title": "Init Swarm",
|
714
|
+
"readOnlyHint": False,
|
715
|
+
"destructiveHint": True,
|
716
|
+
"idempotentHint": False,
|
717
|
+
"openWorldHint": False,
|
718
|
+
},
|
719
|
+
tags={"container_management", "swarm"},
|
720
|
+
)
|
721
|
+
async def init_swarm(
|
722
|
+
advertise_addr: Optional[str] = Field(
|
723
|
+
description="Advertise address", default=None
|
724
|
+
),
|
725
|
+
manager_type: str = Field(description="Must be docker for swarm", default="docker"),
|
726
|
+
silent: Optional[bool] = Field(
|
727
|
+
description="Suppress output", default=environment_silent
|
728
|
+
),
|
729
|
+
log_file: Optional[str] = Field(
|
730
|
+
description="Path to log file", default=environment_log_file
|
731
|
+
),
|
732
|
+
ctx: Context = Field(
|
733
|
+
description="MCP context for progress reporting", default=None
|
734
|
+
),
|
735
|
+
) -> Dict:
|
736
|
+
if manager_type != "docker":
|
737
|
+
raise ValueError("Swarm operations are only supported on Docker")
|
738
|
+
logger = logging.getLogger("ContainerManager")
|
739
|
+
logger.debug(
|
740
|
+
f"Initializing swarm for {manager_type}, silent: {silent}, log_file: {log_file}"
|
741
|
+
)
|
742
|
+
try:
|
743
|
+
manager = create_manager(manager_type, silent, log_file)
|
744
|
+
return manager.init_swarm(advertise_addr)
|
745
|
+
except Exception as e:
|
746
|
+
logger.error(f"Failed to init swarm: {str(e)}")
|
747
|
+
raise RuntimeError(f"Failed to init swarm: {str(e)}")
|
748
|
+
|
749
|
+
|
750
|
+
@mcp.tool(
|
751
|
+
annotations={
|
752
|
+
"title": "Leave Swarm",
|
753
|
+
"readOnlyHint": False,
|
754
|
+
"destructiveHint": True,
|
755
|
+
"idempotentHint": True,
|
756
|
+
"openWorldHint": False,
|
757
|
+
},
|
758
|
+
tags={"container_management", "swarm"},
|
759
|
+
)
|
760
|
+
async def leave_swarm(
|
761
|
+
force: bool = Field(description="Force leave", default=False),
|
762
|
+
manager_type: str = Field(description="Must be docker for swarm", default="docker"),
|
763
|
+
silent: Optional[bool] = Field(
|
764
|
+
description="Suppress output", default=environment_silent
|
765
|
+
),
|
766
|
+
log_file: Optional[str] = Field(
|
767
|
+
description="Path to log file", default=environment_log_file
|
768
|
+
),
|
769
|
+
ctx: Context = Field(
|
770
|
+
description="MCP context for progress reporting", default=None
|
771
|
+
),
|
772
|
+
) -> Dict:
|
773
|
+
if manager_type != "docker":
|
774
|
+
raise ValueError("Swarm operations are only supported on Docker")
|
775
|
+
logger = logging.getLogger("ContainerManager")
|
776
|
+
logger.debug(
|
777
|
+
f"Leaving swarm for {manager_type}, silent: {silent}, log_file: {log_file}"
|
778
|
+
)
|
779
|
+
try:
|
780
|
+
manager = create_manager(manager_type, silent, log_file)
|
781
|
+
return manager.leave_swarm(force)
|
782
|
+
except Exception as e:
|
783
|
+
logger.error(f"Failed to leave swarm: {str(e)}")
|
784
|
+
raise RuntimeError(f"Failed to leave swarm: {str(e)}")
|
785
|
+
|
786
|
+
|
787
|
+
@mcp.tool(
|
788
|
+
annotations={
|
789
|
+
"title": "List Nodes",
|
790
|
+
"readOnlyHint": True,
|
791
|
+
"destructiveHint": False,
|
792
|
+
"idempotentHint": True,
|
793
|
+
"openWorldHint": False,
|
794
|
+
},
|
795
|
+
tags={"container_management", "swarm"},
|
796
|
+
)
|
797
|
+
async def list_nodes(
|
798
|
+
manager_type: str = Field(description="Must be docker for swarm", default="docker"),
|
799
|
+
silent: Optional[bool] = Field(
|
800
|
+
description="Suppress output", default=environment_silent
|
801
|
+
),
|
802
|
+
log_file: Optional[str] = Field(
|
803
|
+
description="Path to log file", default=environment_log_file
|
804
|
+
),
|
805
|
+
ctx: Context = Field(
|
806
|
+
description="MCP context for progress reporting", default=None
|
807
|
+
),
|
808
|
+
) -> List[Dict]:
|
809
|
+
if manager_type != "docker":
|
810
|
+
raise ValueError("Swarm operations are only supported on Docker")
|
811
|
+
logger = logging.getLogger("ContainerManager")
|
812
|
+
logger.debug(
|
813
|
+
f"Listing nodes for {manager_type}, silent: {silent}, log_file: {log_file}"
|
814
|
+
)
|
815
|
+
try:
|
816
|
+
manager = create_manager(manager_type, silent, log_file)
|
817
|
+
return manager.list_nodes()
|
818
|
+
except Exception as e:
|
819
|
+
logger.error(f"Failed to list nodes: {str(e)}")
|
820
|
+
raise RuntimeError(f"Failed to list nodes: {str(e)}")
|
821
|
+
|
822
|
+
|
823
|
+
@mcp.tool(
|
824
|
+
annotations={
|
825
|
+
"title": "List Services",
|
826
|
+
"readOnlyHint": True,
|
827
|
+
"destructiveHint": False,
|
828
|
+
"idempotentHint": True,
|
829
|
+
"openWorldHint": False,
|
830
|
+
},
|
831
|
+
tags={"container_management", "swarm"},
|
832
|
+
)
|
833
|
+
async def list_services(
|
834
|
+
manager_type: str = Field(description="Must be docker for swarm", default="docker"),
|
835
|
+
silent: Optional[bool] = Field(
|
836
|
+
description="Suppress output", default=environment_silent
|
837
|
+
),
|
838
|
+
log_file: Optional[str] = Field(
|
839
|
+
description="Path to log file", default=environment_log_file
|
840
|
+
),
|
841
|
+
ctx: Context = Field(
|
842
|
+
description="MCP context for progress reporting", default=None
|
843
|
+
),
|
844
|
+
) -> List[Dict]:
|
845
|
+
if manager_type != "docker":
|
846
|
+
raise ValueError("Swarm operations are only supported on Docker")
|
847
|
+
logger = logging.getLogger("ContainerManager")
|
848
|
+
logger.debug(
|
849
|
+
f"Listing services for {manager_type}, silent: {silent}, log_file: {log_file}"
|
850
|
+
)
|
851
|
+
try:
|
852
|
+
manager = create_manager(manager_type, silent, log_file)
|
853
|
+
return manager.list_services()
|
854
|
+
except Exception as e:
|
855
|
+
logger.error(f"Failed to list services: {str(e)}")
|
856
|
+
raise RuntimeError(f"Failed to list services: {str(e)}")
|
857
|
+
|
858
|
+
|
859
|
+
@mcp.tool(
|
860
|
+
annotations={
|
861
|
+
"title": "Create Service",
|
862
|
+
"readOnlyHint": False,
|
863
|
+
"destructiveHint": True,
|
864
|
+
"idempotentHint": False,
|
865
|
+
"openWorldHint": False,
|
866
|
+
},
|
867
|
+
tags={"container_management", "swarm"},
|
868
|
+
)
|
869
|
+
async def create_service(
|
870
|
+
name: str = Field(description="Service name"),
|
871
|
+
image: str = Field(description="Image for the service"),
|
872
|
+
replicas: int = Field(description="Number of replicas", default=1),
|
873
|
+
ports: Optional[Dict[str, str]] = Field(
|
874
|
+
description="Port mappings {target: published}", default=None
|
875
|
+
),
|
876
|
+
mounts: Optional[List[str]] = Field(
|
877
|
+
description="Mounts [source:target:mode]", default=None
|
878
|
+
),
|
879
|
+
manager_type: str = Field(description="Must be docker for swarm", default="docker"),
|
880
|
+
silent: Optional[bool] = Field(
|
881
|
+
description="Suppress output", default=environment_silent
|
882
|
+
),
|
883
|
+
log_file: Optional[str] = Field(
|
884
|
+
description="Path to log file", default=environment_log_file
|
885
|
+
),
|
886
|
+
ctx: Context = Field(
|
887
|
+
description="MCP context for progress reporting", default=None
|
888
|
+
),
|
889
|
+
) -> Dict:
|
890
|
+
if manager_type != "docker":
|
891
|
+
raise ValueError("Swarm operations are only supported on Docker")
|
892
|
+
logger = logging.getLogger("ContainerManager")
|
893
|
+
logger.debug(
|
894
|
+
f"Creating service {name} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
895
|
+
)
|
896
|
+
try:
|
897
|
+
manager = create_manager(manager_type, silent, log_file)
|
898
|
+
return manager.create_service(name, image, replicas, ports, mounts)
|
899
|
+
except Exception as e:
|
900
|
+
logger.error(f"Failed to create service: {str(e)}")
|
901
|
+
raise RuntimeError(f"Failed to create service: {str(e)}")
|
902
|
+
|
903
|
+
|
904
|
+
@mcp.tool(
|
905
|
+
annotations={
|
906
|
+
"title": "Remove Service",
|
907
|
+
"readOnlyHint": False,
|
908
|
+
"destructiveHint": True,
|
909
|
+
"idempotentHint": True,
|
910
|
+
"openWorldHint": False,
|
911
|
+
},
|
912
|
+
tags={"container_management", "swarm"},
|
913
|
+
)
|
914
|
+
async def remove_service(
|
915
|
+
service_id: str = Field(description="Service ID or name"),
|
916
|
+
manager_type: str = Field(description="Must be docker for swarm", default="docker"),
|
917
|
+
silent: Optional[bool] = Field(
|
918
|
+
description="Suppress output", default=environment_silent
|
919
|
+
),
|
920
|
+
log_file: Optional[str] = Field(
|
921
|
+
description="Path to log file", default=environment_log_file
|
922
|
+
),
|
923
|
+
ctx: Context = Field(
|
924
|
+
description="MCP context for progress reporting", default=None
|
925
|
+
),
|
926
|
+
) -> Dict:
|
927
|
+
if manager_type != "docker":
|
928
|
+
raise ValueError("Swarm operations are only supported on Docker")
|
929
|
+
logger = logging.getLogger("ContainerManager")
|
930
|
+
logger.debug(
|
931
|
+
f"Removing service {service_id} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
932
|
+
)
|
933
|
+
try:
|
934
|
+
manager = create_manager(manager_type, silent, log_file)
|
935
|
+
return manager.remove_service(service_id)
|
936
|
+
except Exception as e:
|
937
|
+
logger.error(f"Failed to remove service: {str(e)}")
|
938
|
+
raise RuntimeError(f"Failed to remove service: {str(e)}")
|
939
|
+
|
940
|
+
|
941
|
+
@mcp.tool(
|
942
|
+
annotations={
|
943
|
+
"title": "Compose Up",
|
944
|
+
"readOnlyHint": False,
|
945
|
+
"destructiveHint": True,
|
946
|
+
"idempotentHint": False,
|
947
|
+
"openWorldHint": False,
|
948
|
+
},
|
949
|
+
tags={"container_management", "compose"},
|
950
|
+
)
|
951
|
+
async def compose_up(
|
952
|
+
compose_file: str = Field(description="Path to compose file"),
|
953
|
+
detach: bool = Field(description="Detach mode", default=True),
|
954
|
+
build: bool = Field(description="Build images", default=False),
|
955
|
+
manager_type: str = Field(
|
956
|
+
description="Container manager: docker, podman", default="docker"
|
957
|
+
),
|
958
|
+
silent: Optional[bool] = Field(
|
959
|
+
description="Suppress output", default=environment_silent
|
960
|
+
),
|
961
|
+
log_file: Optional[str] = Field(
|
962
|
+
description="Path to log file", default=environment_log_file
|
963
|
+
),
|
964
|
+
ctx: Context = Field(
|
965
|
+
description="MCP context for progress reporting", default=None
|
966
|
+
),
|
967
|
+
) -> str:
|
968
|
+
logger = logging.getLogger("ContainerManager")
|
969
|
+
logger.debug(
|
970
|
+
f"Compose up {compose_file} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
971
|
+
)
|
972
|
+
try:
|
973
|
+
manager = create_manager(manager_type, silent, log_file)
|
974
|
+
return manager.compose_up(compose_file, detach, build)
|
975
|
+
except Exception as e:
|
976
|
+
logger.error(f"Failed to compose up: {str(e)}")
|
977
|
+
raise RuntimeError(f"Failed to compose up: {str(e)}")
|
978
|
+
|
979
|
+
|
980
|
+
@mcp.tool(
|
981
|
+
annotations={
|
982
|
+
"title": "Compose Down",
|
983
|
+
"readOnlyHint": False,
|
984
|
+
"destructiveHint": True,
|
985
|
+
"idempotentHint": True,
|
986
|
+
"openWorldHint": False,
|
987
|
+
},
|
988
|
+
tags={"container_management", "compose"},
|
989
|
+
)
|
990
|
+
async def compose_down(
|
991
|
+
compose_file: str = Field(description="Path to compose file"),
|
992
|
+
manager_type: str = Field(
|
993
|
+
description="Container manager: docker, podman", default="docker"
|
994
|
+
),
|
995
|
+
silent: Optional[bool] = Field(
|
996
|
+
description="Suppress output", default=environment_silent
|
997
|
+
),
|
998
|
+
log_file: Optional[str] = Field(
|
999
|
+
description="Path to log file", default=environment_log_file
|
1000
|
+
),
|
1001
|
+
ctx: Context = Field(
|
1002
|
+
description="MCP context for progress reporting", default=None
|
1003
|
+
),
|
1004
|
+
) -> str:
|
1005
|
+
logger = logging.getLogger("ContainerManager")
|
1006
|
+
logger.debug(
|
1007
|
+
f"Compose down {compose_file} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
1008
|
+
)
|
1009
|
+
try:
|
1010
|
+
manager = create_manager(manager_type, silent, log_file)
|
1011
|
+
return manager.compose_down(compose_file)
|
1012
|
+
except Exception as e:
|
1013
|
+
logger.error(f"Failed to compose down: {str(e)}")
|
1014
|
+
raise RuntimeError(f"Failed to compose down: {str(e)}")
|
1015
|
+
|
1016
|
+
|
1017
|
+
@mcp.tool(
|
1018
|
+
annotations={
|
1019
|
+
"title": "Compose Ps",
|
1020
|
+
"readOnlyHint": True,
|
1021
|
+
"destructiveHint": False,
|
1022
|
+
"idempotentHint": True,
|
1023
|
+
"openWorldHint": False,
|
1024
|
+
},
|
1025
|
+
tags={"container_management", "compose"},
|
1026
|
+
)
|
1027
|
+
async def compose_ps(
|
1028
|
+
compose_file: str = Field(description="Path to compose file"),
|
1029
|
+
manager_type: str = Field(
|
1030
|
+
description="Container manager: docker, podman", default="docker"
|
1031
|
+
),
|
1032
|
+
silent: Optional[bool] = Field(
|
1033
|
+
description="Suppress output", default=environment_silent
|
1034
|
+
),
|
1035
|
+
log_file: Optional[str] = Field(
|
1036
|
+
description="Path to log file", default=environment_log_file
|
1037
|
+
),
|
1038
|
+
ctx: Context = Field(
|
1039
|
+
description="MCP context for progress reporting", default=None
|
1040
|
+
),
|
1041
|
+
) -> str:
|
1042
|
+
logger = logging.getLogger("ContainerManager")
|
1043
|
+
logger.debug(
|
1044
|
+
f"Compose ps {compose_file} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
1045
|
+
)
|
1046
|
+
try:
|
1047
|
+
manager = create_manager(manager_type, silent, log_file)
|
1048
|
+
return manager.compose_ps(compose_file)
|
1049
|
+
except Exception as e:
|
1050
|
+
logger.error(f"Failed to compose ps: {str(e)}")
|
1051
|
+
raise RuntimeError(f"Failed to compose ps: {str(e)}")
|
1052
|
+
|
1053
|
+
|
1054
|
+
@mcp.tool(
|
1055
|
+
annotations={
|
1056
|
+
"title": "Compose Logs",
|
1057
|
+
"readOnlyHint": True,
|
1058
|
+
"destructiveHint": False,
|
1059
|
+
"idempotentHint": True,
|
1060
|
+
"openWorldHint": False,
|
1061
|
+
},
|
1062
|
+
tags={"container_management", "compose"},
|
1063
|
+
)
|
1064
|
+
async def compose_logs(
|
1065
|
+
compose_file: str = Field(description="Path to compose file"),
|
1066
|
+
service: Optional[str] = Field(description="Specific service", default=None),
|
1067
|
+
manager_type: str = Field(
|
1068
|
+
description="Container manager: docker, podman", default="docker"
|
1069
|
+
),
|
1070
|
+
silent: Optional[bool] = Field(
|
1071
|
+
description="Suppress output", default=environment_silent
|
1072
|
+
),
|
1073
|
+
log_file: Optional[str] = Field(
|
1074
|
+
description="Path to log file", default=environment_log_file
|
1075
|
+
),
|
1076
|
+
ctx: Context = Field(
|
1077
|
+
description="MCP context for progress reporting", default=None
|
1078
|
+
),
|
1079
|
+
) -> str:
|
1080
|
+
logger = logging.getLogger("ContainerManager")
|
1081
|
+
logger.debug(
|
1082
|
+
f"Compose logs {compose_file} for {manager_type}, silent: {silent}, log_file: {log_file}"
|
1083
|
+
)
|
1084
|
+
try:
|
1085
|
+
manager = create_manager(manager_type, silent, log_file)
|
1086
|
+
return manager.compose_logs(compose_file, service)
|
1087
|
+
except Exception as e:
|
1088
|
+
logger.error(f"Failed to compose logs: {str(e)}")
|
1089
|
+
raise RuntimeError(f"Failed to compose logs: {str(e)}")
|
1090
|
+
|
1091
|
+
|
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()
|
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)
|
1122
|
+
setup_logging(is_mcp_server=True, log_file="container_manager_mcp.log")
|
1123
|
+
if transport == "stdio":
|
1124
|
+
mcp.run(transport="stdio")
|
1125
|
+
elif transport == "http":
|
1126
|
+
mcp.run(transport="http", host=host, port=port)
|
1127
|
+
else:
|
1128
|
+
logger = logging.getLogger("ContainerManager")
|
1129
|
+
logger.error("Transport not supported")
|
1130
|
+
sys.exit(1)
|
1131
|
+
|
1132
|
+
|
1133
|
+
if __name__ == "__main__":
|
1134
|
+
container_manager_mcp(sys.argv[1:])
|