fix: v0.4.3 — inspect_image rendering polish (follow-up to #4)

Three live-NAS observations that the 0.4.2 implementation got wrong:

1. Header showed "?:?" — DSM Image/get returns image="" and tag=""
   for name:tag lookups; the response fields are unreliable. The
   header now echoes the user-supplied image_id, falling back to the
   sha256 id only when image_id itself is empty.

2. Ports printed as raw Python dicts ({'port':'22','protocol':'tcp'}).
   The ports array is a list of {port, protocol} objects — each entry
   now renders as "22/tcp".

3. Environment printed as raw Python dicts ({'key':'PATH','value':...}).
   The env array is a list of {key, value} objects — each entry now
   renders as "PATH=/usr/local/...".

cmd, entrypoint, and volumes (plain string arrays) were already
correct and are untouched. Tests updated to match the live shape;
two new tests guard the header fallback. References #4 (already
closed by 0.4.2 — no need to reopen for a rendering polish).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 13:13:42 +02:00
parent 4030b8d5ee
commit 82e8167f67
5 changed files with 106 additions and 23 deletions
+22 -8
View File
@@ -290,10 +290,12 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
if not isinstance(data, dict) or not data:
return f"Image '{image_id}' not found."
repo = data.get("image") or "?"
tag = data.get("tag") or "?"
display_name = f"{repo}:{tag}"
img_hash = data.get("id") or ""
# The DSM Image/get response leaves `image` and `tag` empty when the
# lookup is by name:tag (live-confirmed against the NAS), so the
# response fields are unreliable for the header. Prefer the user-
# supplied image_id, then fall back to the id hash.
display_name = image_id or img_hash or "?"
digest = data.get("digest") or ""
size_val = data.get("size") or 0
virtual_size_val = data.get("virtual_size") or 0
@@ -301,9 +303,9 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
docker_version = data.get("docker_version") or ""
cmd = data.get("cmd") or []
entrypoint = data.get("entrypoint") or []
env_list: list[str] = data.get("env") or []
ports: list[str] = data.get("ports") or []
volumes: list[str] = data.get("volumes") or []
env_list: list[Any] = data.get("env") or []
ports: list[Any] = data.get("ports") or []
volumes: list[Any] = data.get("volumes") or []
lines = [f"Image: {display_name}"]
if img_hash:
@@ -337,7 +339,13 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
lines.append("")
lines.append(f"Exposed ports ({len(ports)}):")
for port in ports:
lines.append(f" {port}")
# Live shape: {"port": "22", "protocol": "tcp"}.
if isinstance(port, dict):
port_num = port.get("port", "?")
proto = port.get("protocol", "tcp")
lines.append(f" {port_num}/{proto}")
else:
lines.append(f" {port}")
if volumes:
lines.append("")
@@ -349,7 +357,13 @@ def register_images(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None:
lines.append("")
lines.append(f"Environment ({len(env_list)}):")
for var in env_list:
lines.append(f" {var}")
# Live shape: {"key": "PATH", "value": "/usr/local/sbin"}.
if isinstance(var, dict):
key = var.get("key", "?")
value = var.get("value", "")
lines.append(f" {key}={value}")
else:
lines.append(f" {var}")
return "\n".join(lines)