diff --git a/src/mcp_synology_container/modules/images.py b/src/mcp_synology_container/modules/images.py index b4f3c5b..254f900 100644 --- a/src/mcp_synology_container/modules/images.py +++ b/src/mcp_synology_container/modules/images.py @@ -166,9 +166,13 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None: confirmed: Must be True to actually delete. Default False shows a preview only. """ - # Parse name:tag - name, _, tag = image_id.partition(":") - if not tag: + # Parse name and tag using the last ":" as separator so that + # registry-prefixed images (e.g. "ghcr.io/foo/bar:v1") are handled + # correctly. rpartition returns ("", "", original) when ":" is absent. + name, sep, tag = image_id.rpartition(":") + if not sep: + # No ":" found — bare name without explicit tag + name = image_id tag = "latest" # Fetch the local image list for size reporting and in-use detection diff --git a/tests/test_modules/test_images.py b/tests/test_modules/test_images.py index 005969a..944f187 100644 --- a/tests/test_modules/test_images.py +++ b/tests/test_modules/test_images.py @@ -291,6 +291,52 @@ async def test_delete_image_by_hash(): assert "redis" in result +@pytest.mark.asyncio +async def test_delete_image_registry_prefixed_name(): + """Registry-prefixed image names (e.g. ghcr.io/foo/bar:v1) must split at last ':'.""" + from mcp_synology_container.modules.images import register_images + + registry_images = { + "images": [ + { + "id": "sha256:dddd", + "repository": "ghcr.io/open-webui/open-webui", + "tags": ["v0.8.10"], + "size": 100 * 1024 * 1024, + "created": 1700000000, + "upgradable": False, + } + ] + } + + client = AsyncMock() + + async def mock_request(api, method, **kwargs): + if api == "SYNO.Docker.Image" and method == "list": + return registry_images + if api == "SYNO.Docker.Container": + return {"containers": []} + if api == "SYNO.Docker.Image" and method == "delete": + return {} + return {} + + client.request.side_effect = mock_request + mcp, tools = make_mock_mcp() + register_images(mcp, make_config(), client) + + result = await tools["delete_image"]( + image_id="ghcr.io/open-webui/open-webui:v0.8.10", confirmed=True + ) + assert "Deleted" in result + assert "open-webui" in result + + # Verify delete was called with correct split params (not "ghcr" as name) + delete_call = next(c for c in client.request.call_args_list if c.args[1] == "delete") + params = delete_call.kwargs.get("params") or {} + assert params.get("name") == "ghcr.io/open-webui/open-webui" + assert params.get("tag") == "v0.8.10" + + @pytest.mark.asyncio async def test_delete_image_api_error(): from mcp_synology_container.dsm_client import SynologyError