fix: v0.2.7 — remove -> str annotations and trim docstrings to reduce tools/list payload
FastMCP generates outputSchema for every tool with a return annotation, roughly doubling the tools/list payload size. Multi-line docstrings with Args/Returns sections add further bulk that Claude Desktop must parse. - Strip -> str from all 23 @mcp.tool() functions - Trim every tool docstring to a single descriptive line (≤100 chars) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcp-synology-container"
|
name = "mcp-synology-container"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
description = "MCP server for Synology Container Manager"
|
description = "MCP server for Synology Container Manager"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -66,15 +66,8 @@ def register_compose(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None
|
|||||||
"""Register all compose file management tools with the MCP server."""
|
"""Register all compose file management tools with the MCP server."""
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def read_compose(project_name: str) -> str:
|
async def read_compose(project_name: str):
|
||||||
"""Read the compose file of a project.
|
"""Read the compose file (YAML) for a project."""
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the Container Manager project.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The compose file content as YAML text.
|
|
||||||
"""
|
|
||||||
path = await _find_compose_path(client, config, project_name)
|
path = await _find_compose_path(client, config, project_name)
|
||||||
if path is None:
|
if path is None:
|
||||||
project = await _find_project(client, project_name)
|
project = await _find_project(client, project_name)
|
||||||
@@ -102,17 +95,8 @@ def register_compose(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None
|
|||||||
service_name: str,
|
service_name: str,
|
||||||
new_tag: str,
|
new_tag: str,
|
||||||
confirmed: bool = False,
|
confirmed: bool = False,
|
||||||
) -> str:
|
):
|
||||||
"""Update the image tag of a service in the compose file.
|
"""Update a service's image tag in the compose file. Requires confirmed=True."""
|
||||||
|
|
||||||
After confirming, suggests running redeploy_project.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the Container Manager project.
|
|
||||||
service_name: Name of the service within the compose file.
|
|
||||||
new_tag: New image tag (e.g. "latest", "1.2.3").
|
|
||||||
confirmed: Must be True to proceed. Set to True to confirm the change.
|
|
||||||
"""
|
|
||||||
path = await _find_compose_path(client, config, project_name)
|
path = await _find_compose_path(client, config, project_name)
|
||||||
if path is None:
|
if path is None:
|
||||||
return f"No compose file found for project '{project_name}'."
|
return f"No compose file found for project '{project_name}'."
|
||||||
@@ -230,18 +214,8 @@ def register_compose(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None
|
|||||||
var_name: str,
|
var_name: str,
|
||||||
var_value: str,
|
var_value: str,
|
||||||
confirmed: bool = False,
|
confirmed: bool = False,
|
||||||
) -> str:
|
):
|
||||||
"""Add or update an environment variable in a service's compose definition.
|
"""Add or update an env var in a service's compose definition. Requires confirmed=True."""
|
||||||
|
|
||||||
After confirming, suggests running redeploy_project.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the Container Manager project.
|
|
||||||
service_name: Name of the service within the compose file.
|
|
||||||
var_name: Environment variable name.
|
|
||||||
var_value: New value for the variable.
|
|
||||||
confirmed: Must be True to proceed. Set to True to confirm the change.
|
|
||||||
"""
|
|
||||||
path = await _find_compose_path(client, config, project_name)
|
path = await _find_compose_path(client, config, project_name)
|
||||||
if path is None:
|
if path is None:
|
||||||
return f"No compose file found for project '{project_name}'."
|
return f"No compose file found for project '{project_name}'."
|
||||||
@@ -333,17 +307,8 @@ def register_compose(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None
|
|||||||
project_name: str,
|
project_name: str,
|
||||||
new_content: str,
|
new_content: str,
|
||||||
confirmed: bool = False,
|
confirmed: bool = False,
|
||||||
) -> str:
|
):
|
||||||
"""Replace the entire compose file with new content.
|
"""Replace the entire compose file with new YAML content. Requires confirmed=True."""
|
||||||
|
|
||||||
Validates that the content is valid YAML before writing.
|
|
||||||
After confirming, suggests running redeploy_project.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the Container Manager project.
|
|
||||||
new_content: Complete new content for the compose file (must be valid YAML).
|
|
||||||
confirmed: Must be True to proceed. Set to True to confirm the overwrite.
|
|
||||||
"""
|
|
||||||
# Validate YAML before anything else
|
# Validate YAML before anything else
|
||||||
try:
|
try:
|
||||||
parsed = yaml.safe_load(new_content)
|
parsed = yaml.safe_load(new_content)
|
||||||
|
|||||||
@@ -64,13 +64,8 @@ def register_containers(mcp: FastMCP, config: AppConfig, client: DsmClient) -> N
|
|||||||
"""Register all container management tools with the MCP server."""
|
"""Register all container management tools with the MCP server."""
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def list_containers(project_name: str | None = None) -> str:
|
async def list_containers(project_name: str | None = None):
|
||||||
"""List containers, optionally filtered by project name.
|
"""List all containers, optionally filtered by project name."""
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Optional project name to filter containers.
|
|
||||||
If omitted, lists all containers.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
data = await client.request(
|
data = await client.request(
|
||||||
"SYNO.Docker.Container",
|
"SYNO.Docker.Container",
|
||||||
@@ -107,12 +102,8 @@ def register_containers(mcp: FastMCP, config: AppConfig, client: DsmClient) -> N
|
|||||||
return "\n".join(lines).rstrip()
|
return "\n".join(lines).rstrip()
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_container_status(container_name: str) -> str:
|
async def get_container_status(container_name: str):
|
||||||
"""Get detailed status, uptime, and resource usage of a container.
|
"""Get detailed status, uptime, ports, and mounts for a container."""
|
||||||
|
|
||||||
Args:
|
|
||||||
container_name: Name of the container to inspect.
|
|
||||||
"""
|
|
||||||
# SYNO.Docker.Container/get accepts the clean name (no hash prefix).
|
# SYNO.Docker.Container/get accepts the clean name (no hash prefix).
|
||||||
clean_name = _strip_hash_prefix(container_name)
|
clean_name = _strip_hash_prefix(container_name)
|
||||||
try:
|
try:
|
||||||
@@ -134,14 +125,8 @@ def register_containers(mcp: FastMCP, config: AppConfig, client: DsmClient) -> N
|
|||||||
container_name: str,
|
container_name: str,
|
||||||
tail: int = 100,
|
tail: int = 100,
|
||||||
keyword: str | None = None,
|
keyword: str | None = None,
|
||||||
) -> str:
|
):
|
||||||
"""Get log output from a container.
|
"""Get recent log output from a container, with optional keyword filter."""
|
||||||
|
|
||||||
Args:
|
|
||||||
container_name: Name of the container.
|
|
||||||
tail: Number of recent log lines to return (default 100).
|
|
||||||
keyword: Optional keyword to filter log lines.
|
|
||||||
"""
|
|
||||||
resolved_name = await _resolve_container_name(client, container_name)
|
resolved_name = await _resolve_container_name(client, container_name)
|
||||||
params: dict[str, Any] = {
|
params: dict[str, Any] = {
|
||||||
"name": resolved_name,
|
"name": resolved_name,
|
||||||
@@ -181,15 +166,8 @@ def register_containers(mcp: FastMCP, config: AppConfig, client: DsmClient) -> N
|
|||||||
return header + "\n".join(lines)
|
return header + "\n".join(lines)
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def container_stats(container_name: str) -> str:
|
async def container_stats(container_name: str):
|
||||||
"""Get live resource usage statistics for a container.
|
"""Get live CPU, memory, network, and block I/O stats for a container."""
|
||||||
|
|
||||||
Reports CPU %, RAM used/limit, Network I/O (rx/tx), and Block I/O
|
|
||||||
(read/write) for the named container.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
container_name: Name of the container (e.g. "jenkins").
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
data = await client.request("SYNO.Docker.Container", "stats")
|
data = await client.request("SYNO.Docker.Container", "stats")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -270,17 +248,8 @@ def register_containers(mcp: FastMCP, config: AppConfig, client: DsmClient) -> N
|
|||||||
container_name: str,
|
container_name: str,
|
||||||
command: str,
|
command: str,
|
||||||
confirmed: bool = False,
|
confirmed: bool = False,
|
||||||
) -> str:
|
):
|
||||||
"""Execute a command in a running container.
|
"""Execute a shell command inside a running container. Requires confirmed=True."""
|
||||||
|
|
||||||
This executes a shell command inside the container. Use with caution.
|
|
||||||
Requires confirmation before executing.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
container_name: Name of the container.
|
|
||||||
command: Shell command to execute.
|
|
||||||
confirmed: Must be True to proceed. Set to True to confirm execution.
|
|
||||||
"""
|
|
||||||
if not confirmed:
|
if not confirmed:
|
||||||
return (
|
return (
|
||||||
f"About to run in container '{container_name}':\n"
|
f"About to run in container '{container_name}':\n"
|
||||||
@@ -313,16 +282,8 @@ def register_containers(mcp: FastMCP, config: AppConfig, client: DsmClient) -> N
|
|||||||
return "\n".join(result_lines)
|
return "\n".join(result_lines)
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def delete_container(container_name: str, confirmed: bool = False) -> str:
|
async def delete_container(container_name: str, confirmed: bool = False):
|
||||||
"""Delete a container.
|
"""Delete a stopped container. Requires confirmed=True."""
|
||||||
|
|
||||||
Container must be stopped before deletion. Without confirmed=True,
|
|
||||||
returns a preview only.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
container_name: Name of the container.
|
|
||||||
confirmed: Must be True to proceed. Set to True to confirm deletion.
|
|
||||||
"""
|
|
||||||
if not confirmed:
|
if not confirmed:
|
||||||
return (
|
return (
|
||||||
f"Preview: would delete container '{container_name}'.\n"
|
f"Preview: would delete container '{container_name}'.\n"
|
||||||
|
|||||||
@@ -102,12 +102,8 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
|
|||||||
"""Register all image management tools with the MCP server."""
|
"""Register all image management tools with the MCP server."""
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def list_images() -> str:
|
async def list_images():
|
||||||
"""List all local Docker images sorted by size (largest first).
|
"""List local Docker images sorted by size, showing tag, date, and in-use status."""
|
||||||
|
|
||||||
Shows name:tag, size, creation date, and whether the image is
|
|
||||||
currently used by at least one container.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
img_data = await client.request(
|
img_data = await client.request(
|
||||||
"SYNO.Docker.Image",
|
"SYNO.Docker.Image",
|
||||||
@@ -156,18 +152,8 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def delete_image(image_id: str, confirmed: bool = False) -> str:
|
async def delete_image(image_id: str, confirmed: bool = False):
|
||||||
"""Delete a local Docker image by name:tag or image ID hash.
|
"""Delete a local image by name:tag or hash. Requires confirmed=True; refuses if in use."""
|
||||||
|
|
||||||
Without confirmed=True, returns a preview of what would be deleted.
|
|
||||||
Refuses deletion if the image is used by any container.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image_id: Image reference as "name:tag" (e.g. "nginx:1.24")
|
|
||||||
or a full/short image hash (e.g. "sha256:14300de7…").
|
|
||||||
confirmed: Must be True to actually delete. Default False shows
|
|
||||||
a preview only.
|
|
||||||
"""
|
|
||||||
# Parse name and tag using the last ":" as separator so that
|
# Parse name and tag using the last ":" as separator so that
|
||||||
# registry-prefixed images (e.g. "ghcr.io/foo/bar:v1") are handled
|
# registry-prefixed images (e.g. "ghcr.io/foo/bar:v1") are handled
|
||||||
# correctly. rpartition returns ("", "", original) when ":" is absent.
|
# correctly. rpartition returns ("", "", original) when ":" is absent.
|
||||||
@@ -284,16 +270,8 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
|
|||||||
return f"Deleted {display_name} — {size_str} freed."
|
return f"Deleted {display_name} — {size_str} freed."
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def check_image_updates(project_name: str | None = None) -> str:
|
async def check_image_updates(project_name: str | None = None):
|
||||||
"""Check for available image updates for a project or all images.
|
"""Check which local images have updates available (upgradable flag from NAS registry)."""
|
||||||
|
|
||||||
Queries the local image list and reports which images have the
|
|
||||||
'upgradable' flag set by the NAS registry check.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Optional project name to filter images.
|
|
||||||
If omitted, checks all locally available images.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
data = await client.request(
|
data = await client.request(
|
||||||
"SYNO.Docker.Image",
|
"SYNO.Docker.Image",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def register_networks(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
"""Register all network management tools with the MCP server."""
|
"""Register all network management tools with the MCP server."""
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def list_networks() -> str:
|
async def list_networks():
|
||||||
"""List all Docker networks with driver, subnet, gateway, and attached containers."""
|
"""List all Docker networks with driver, subnet, gateway, and attached containers."""
|
||||||
try:
|
try:
|
||||||
data = await client.request("SYNO.Docker.Network", "list")
|
data = await client.request("SYNO.Docker.Network", "list")
|
||||||
@@ -60,18 +60,8 @@ def register_networks(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
ip_range: str | None = None,
|
ip_range: str | None = None,
|
||||||
enable_ipv6: bool = False,
|
enable_ipv6: bool = False,
|
||||||
confirmed: bool = False,
|
confirmed: bool = False,
|
||||||
) -> str:
|
):
|
||||||
"""Create a new Docker network.
|
"""Create a Docker network with optional subnet/gateway/IPv6. Requires confirmed=True."""
|
||||||
|
|
||||||
Args:
|
|
||||||
name: Name for the new network.
|
|
||||||
driver: Network driver. Defaults to "bridge".
|
|
||||||
subnet: Subnet in CIDR notation (e.g. "172.28.0.0/16").
|
|
||||||
gateway: Gateway IP address.
|
|
||||||
ip_range: Allocatable IP range in CIDR notation.
|
|
||||||
enable_ipv6: Enable IPv6 support. Defaults to False.
|
|
||||||
confirmed: Must be True to actually create. Default False shows a preview.
|
|
||||||
"""
|
|
||||||
details = [f" Name: {name}", f" Driver: {driver}"]
|
details = [f" Name: {name}", f" Driver: {driver}"]
|
||||||
if subnet:
|
if subnet:
|
||||||
details.append(f" Subnet: {subnet}")
|
details.append(f" Subnet: {subnet}")
|
||||||
@@ -113,15 +103,8 @@ def register_networks(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
return f"Network '{name}' created{id_str}."
|
return f"Network '{name}' created{id_str}."
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def delete_network(name: str, confirmed: bool = False) -> str:
|
async def delete_network(name: str, confirmed: bool = False):
|
||||||
"""Delete a Docker network by name.
|
"""Delete a Docker network. Requires confirmed=True; refuses if containers are attached."""
|
||||||
|
|
||||||
Refuses deletion if any container is still attached to the network.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: Name of the network to delete.
|
|
||||||
confirmed: Must be True to actually delete. Default False shows a preview.
|
|
||||||
"""
|
|
||||||
# Fetch network list to validate existence and check for attached containers
|
# Fetch network list to validate existence and check for attached containers
|
||||||
try:
|
try:
|
||||||
data = await client.request("SYNO.Docker.Network", "list")
|
data = await client.request("SYNO.Docker.Network", "list")
|
||||||
|
|||||||
@@ -24,12 +24,8 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
"""Register all project management tools with the MCP server."""
|
"""Register all project management tools with the MCP server."""
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def list_projects() -> str:
|
async def list_projects():
|
||||||
"""List all Container Manager projects with their current status.
|
"""List all Container Manager projects with name, status, path, and container count."""
|
||||||
|
|
||||||
Returns a formatted table of projects including name, status, path,
|
|
||||||
and container count.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
data = await client.request("SYNO.Docker.Project", "list")
|
data = await client.request("SYNO.Docker.Project", "list")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -54,12 +50,8 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
return "\n".join(lines).rstrip()
|
return "\n".join(lines).rstrip()
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_project_status(project_name: str) -> str:
|
async def get_project_status(project_name: str):
|
||||||
"""Get detailed status of a specific project.
|
"""Get detailed status and container list for a specific project."""
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the project to inspect.
|
|
||||||
"""
|
|
||||||
project = await _find_project(client, project_name)
|
project = await _find_project(client, project_name)
|
||||||
if project is None:
|
if project is None:
|
||||||
return f"Project '{project_name}' not found."
|
return f"Project '{project_name}' not found."
|
||||||
@@ -67,12 +59,8 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
return _format_project_detail(project)
|
return _format_project_detail(project)
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def start_project(project_name: str) -> str:
|
async def start_project(project_name: str):
|
||||||
"""Start a Container Manager project.
|
"""Start a Container Manager project."""
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the project to start.
|
|
||||||
"""
|
|
||||||
project = await _find_project(client, project_name)
|
project = await _find_project(client, project_name)
|
||||||
if project is None:
|
if project is None:
|
||||||
return f"Project '{project_name}' not found."
|
return f"Project '{project_name}' not found."
|
||||||
@@ -89,16 +77,8 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
return f"Error starting project '{project_name}': {e}"
|
return f"Error starting project '{project_name}': {e}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def stop_project(project_name: str, confirmed: bool = False) -> str:
|
async def stop_project(project_name: str, confirmed: bool = False):
|
||||||
"""Stop a running Container Manager project.
|
"""Stop all containers in a project. Requires confirmed=True."""
|
||||||
|
|
||||||
This operation stops all containers in the project.
|
|
||||||
Requires confirmation before executing.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the project to stop.
|
|
||||||
confirmed: Must be True to proceed. Set to True to confirm the stop operation.
|
|
||||||
"""
|
|
||||||
if not confirmed:
|
if not confirmed:
|
||||||
return (
|
return (
|
||||||
f"Stopping project '{project_name}' will halt all its containers.\n"
|
f"Stopping project '{project_name}' will halt all its containers.\n"
|
||||||
@@ -121,26 +101,8 @@ def register_projects(mcp: FastMCP, config: AppConfig, client: DsmClient) -> Non
|
|||||||
return f"Error stopping project '{project_name}': {e}"
|
return f"Error stopping project '{project_name}': {e}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def redeploy_project(project_name: str, confirmed: bool = False) -> str:
|
async def redeploy_project(project_name: str, confirmed: bool = False):
|
||||||
"""Redeploy a project, pulling the latest images via SYNO.Docker.Project/build_stream.
|
"""Pull latest images and restart a project via build_stream. Requires confirmed=True."""
|
||||||
|
|
||||||
This is the programmatic equivalent of the DSM "Erstellen" (Build) button.
|
|
||||||
Unified 3-step flow for all project states:
|
|
||||||
|
|
||||||
Step 1 — Stop (skipped for STOPPED; error-suppressed for BUILD_FAILED)
|
|
||||||
Step 2 — Trigger build_stream: DSM pulls updated images and starts the
|
|
||||||
project. This is a Server-Sent Events endpoint; the call
|
|
||||||
returns once DSM confirms it accepted the request.
|
|
||||||
Step 3 — Poll SYNO.Docker.Project/list every 2 s for up to 5 min
|
|
||||||
until the project reaches RUNNING. A warning is emitted on
|
|
||||||
timeout instead of returning an error.
|
|
||||||
|
|
||||||
Requires confirmation before executing.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_name: Name of the project to redeploy.
|
|
||||||
confirmed: Must be True to proceed. Set to True to confirm the redeploy.
|
|
||||||
"""
|
|
||||||
if not confirmed:
|
if not confirmed:
|
||||||
return (
|
return (
|
||||||
f"Redeploying project '{project_name}' will stop and restart all its "
|
f"Redeploying project '{project_name}' will stop and restart all its "
|
||||||
|
|||||||
@@ -20,13 +20,8 @@ def register_system(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
|
|||||||
"""Register all system-level tools with the MCP server."""
|
"""Register all system-level tools with the MCP server."""
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def system_df() -> str:
|
async def system_df():
|
||||||
"""Show Docker disk usage: images, containers, and volumes.
|
"""Show Docker disk usage: image count/size and container running/stopped counts."""
|
||||||
|
|
||||||
Assembles disk-usage data from the image and container lists.
|
|
||||||
Images that are not referenced by any container are marked as
|
|
||||||
reclaimable.
|
|
||||||
"""
|
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
|
|
||||||
# ── Images ───────────────────────────────────────────────────────────
|
# ── Images ───────────────────────────────────────────────────────────
|
||||||
@@ -94,16 +89,8 @@ def register_system(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def system_prune(confirmed: bool = False) -> str:
|
async def system_prune(confirmed: bool = False):
|
||||||
"""Remove unused Docker resources: dangling images, stopped containers, unused networks.
|
"""Remove unused Docker resources (images, stopped containers). Requires confirmed=True."""
|
||||||
|
|
||||||
Without confirmed=True, shows a preview of what would be removed.
|
|
||||||
With confirmed=True, runs the prune and reports reclaimed space.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
confirmed: Must be True to actually prune. Default False shows
|
|
||||||
a preview only.
|
|
||||||
"""
|
|
||||||
# ── Gather preview data ───────────────────────────────────────────────
|
# ── Gather preview data ───────────────────────────────────────────────
|
||||||
dangling_images: list[dict[str, Any]] = []
|
dangling_images: list[dict[str, Any]] = []
|
||||||
stopped_containers: list[dict[str, Any]] = []
|
stopped_containers: list[dict[str, Any]] = []
|
||||||
|
|||||||
Reference in New Issue
Block a user