fix: v0.9.1 — clearer messages for exec/redeploy/check_image_updates

Three live-verified MCP weaknesses (update run 2026-06-22):

- exec_in_container: SYNO.Docker.Container/exec does not exist on this DSM
  firmware (code 103, like pause/unpause). Catch it and return a guiding
  "not supported on this firmware" message instead of the raw DSM error;
  the tool stays available for firmwares that do implement exec.
- redeploy_project: the failure hint suggested stop_project + start_project,
  but start_project does not recreate on a new image (it restarts the old
  one). Both failure hints now point back at redeploy_project and warn about
  parallel pull_image lock contention (DSM 2101/2202).
- check_image_updates: docstring and output now state it only reflects DSM
  digest drift on already-pulled tags and does NOT discover newer version
  tags (use list_image_tags + release notes for that).

Tests updated accordingly; CHANGELOG + version bumped to 0.9.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 16:32:22 +02:00
parent 46b36f6b08
commit 04ffaa457b
9 changed files with 77 additions and 7 deletions
+28
View File
@@ -188,6 +188,34 @@ async def test_exec_in_container_confirmed():
assert "Exit code: 0" in result
@pytest.mark.asyncio
async def test_exec_in_container_unsupported_firmware():
"""DSM code 103 ('Method does not exist') → clear 'not supported' guidance,
not the raw DSM error."""
from mcp_synology_container.dsm_client import SynologyError
from mcp_synology_container.modules.containers import register_containers
client = AsyncMock()
async def mock_request(api, method, **kwargs):
if api == "SYNO.Docker.Container" and method == "list":
return {"containers": []}
if api == "SYNO.Docker.Container" and method == "exec":
raise SynologyError("Method does not exist", code=103)
return {}
client.request.side_effect = mock_request
mcp, tools = make_mock_mcp()
register_containers(mcp, make_config(), client)
result = await tools["exec_in_container"]("myapp_web", "ls /app", confirmed=True)
assert "not supported on this DSM firmware" in result
assert "filestation" in result.lower()
# The raw DSM phrasing must not leak as the only message.
assert not result.startswith("Error executing command")
# ──────────────────────────────────────────────────────────────────────────────
# container_stats
# ──────────────────────────────────────────────────────────────────────────────
+2
View File
@@ -720,6 +720,7 @@ async def test_check_image_updates_all():
assert "nginx:1.24" in result
assert "UPDATE AVAILABLE" in result
assert "postgres:15" in result
assert "does NOT detect newer version tags" in result
@pytest.mark.asyncio
@@ -744,6 +745,7 @@ async def test_check_image_updates_all_up_to_date():
result = await tools["check_image_updates"]()
assert "All images are up to date" in result
assert "does NOT detect newer version tags" in result
@pytest.mark.asyncio
+3 -2
View File
@@ -353,7 +353,7 @@ async def test_redeploy_large_image_returns_background_hint():
@pytest.mark.asyncio
async def test_redeploy_unknown_status_returns_error():
"""Unknown status must return a clear error with a workaround hint."""
"""Unknown status must return a clear error pointing back at redeploy_project."""
client = AsyncMock()
async def mock_request(api, method, **kwargs):
@@ -368,7 +368,8 @@ async def test_redeploy_unknown_status_returns_error():
result = await tools["redeploy_project"]("myapp", confirmed=True)
assert "UPDATING" in result
assert "Workaround" in result or "stop_project" in result
assert "redeploy_project" in result
assert "does NOT recreate" in result
# ──────────────────────────────────────────────────────────────────────