New tool inspect_container surfaces the full configuration of a single
container as the foundation for a future GUI-container → Compose
migration workflow. Output covers image, status, restart policy,
network mode + per-network IPs, port bindings, volume mounts, env
vars, labels, entrypoint/command, links, and capabilities.
Mount paths come from details.Mounts[].Source (full /volume1/...
path), NOT from profile.volume_bindings[].host_volume_file — the
latter is share-relative (e.g. /docker/foo for /volume1/docker/foo)
and not directly Compose-usable. Verified live against the NAS;
quirk documented in CLAUDE.md.
DSM API: SYNO.Docker.Container/get with name JSON-encoded (action
inspect does not exist and returns code 103). Hash-prefixed names
are resolved transparently, matching the convention of the other
container tools.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DSM emits a readable plaintext build log over the build_stream HTTP
body (one short status line per step) and closes the connection when
the build is done. The 0.2.5 implementation sent the request and
dropped the body unread, leaving users with nothing more than a
BUILD_FAILED polling status and no actionable diagnostic.
DsmClient.trigger_build_stream now consumes the body line-by-line and
returns the collected log as a string. Wall-clock budget of 210 s
(under the Claude Desktop ~4 min ceiling); on timeout the partial log
is returned with a "[build_stream: timeout — stream still open
server-side]" marker so callers know the build continues server-side.
Per-chunk ReadTimeout is treated the same way. JSON error envelope,
transport-error mapping (M-4), and SID-scrubbed HTTP-error formatting
are unchanged.
redeploy_project and create_project now parse the returned log via
_parse_build_stream_log (any line containing "Error response from
daemon:" or ending in " Error" counts as a failure). On a failed log
the tools abort immediately, surface the daemon line(s) in the result
(e.g. "Error response from daemon: manifest for nginx:9.9.9 not
found: manifest unknown"), and skip the polling step. The BUILD_FAILED
polling guard (M-5) stays as a second safety net for late failures
where the stream was clean but the container exited after start.
No new MCP tool: the build log is a live stream and cannot be
re-fetched after the build ends, so it is surfaced during
redeploy_project / create_project rather than exposed as a standalone
get_project_build_log call.
Minor version bump because redeploy_project and create_project return
materially different strings on a failed build and exit earlier in
the failure path. Signatures unchanged.
Tests: streamed-log collection, daemon-error log, header ReadTimeout
marker, per-chunk ReadTimeout partial log, wall-clock budget
truncation, _parse_build_stream_log unit tests, redeploy/create end-
to-end behavior with a failing log.
Closes#2
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 0.5.0 prompt mis-attributed the API: pull_start lives on
SYNO.Docker.Image, not SYNO.Docker.Registry (live DSM capture).
search and tags ARE correctly on Registry; only pull_start belongs
to Image. Registry/pull_start returns "Method does not exist".
Parameters are unchanged (repository + tag both JSON-encoded), and
the Image/list polling for completion detection is untouched.
Tests updated to assert SYNO.Docker.Image/pull_start. CLAUDE.md
DSM-quirks section consolidates the Image vs. Registry split so
this trap is documented for future surface additions. References
#3 (already closed).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three new SYNO.Docker.Registry tools, reverse-engineered from a live
DSM API capture (n4s4 reference disagrees on param names and methods).
- search_registry (#5): SYNO.Docker.Registry/search v1 with JSON-encoded
q, plus offset/limit/page_size. Renders stars, downloads, official
flag, truncated description, and total match count. Read-only.
- list_image_tags: SYNO.Docker.Registry/tags v1 with JSON-encoded repo
(not name — DSM live capture diverges from n4s4). Response shape is
unusual: tag list comes back as the envelope's data field directly.
Output capped by limit (default 50); accepts both list and dict
response shapes defensively. Read-only.
- pull_image (#3): SYNO.Docker.Registry/pull_start v1 with both
repository and tag JSON-encoded. Async pull — no pull_status method
confirmed on this DSM, so completion is detected by polling
SYNO.Docker.Image/list (2–10 s backoff, 240 s budget under the
Claude Desktop ~4 min tool-call ceiling). Timeout returns a
non-fatal "still running" hint. Short-circuits when the image is
already present locally. Confirmation gate required.
Tool count: 31 → 34. CLAUDE.md confirmation list updated. New DSM
quirks documented for pull_start (no pull_status) and tags (repo
param name, top-level data array).
Closes#3Closes#5
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Live test on this DSM firmware: SYNO.Docker.Container has no pause/
unpause method ("Method does not exist"). The Container Manager GUI
action menu only exposes Start / Stop / Force-Stop / Restart / Reset —
pause/resume simply isn't a feature here.
The two tools were briefly shipped in 0.4.0 (implemented by symmetry
with the verified stop call) and have now been removed rather than
left as a broken surface. The remaining lifecycle tools
(start_container, stop_container, restart_container) are unaffected.
Tool count: 33 → 31. Closes#7 (won't fix — DSM limitation).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes#1, #4, #6, #7.
Container lifecycle (#1, #7):
- start_container, stop_container, restart_container, pause_container,
unpause_container — all via SYNO.Docker.Container with JSON-encoded
name parameter, routed through _resolve_container_name for hash-
prefix resolution. stop is live-verified; the other four are
implemented by symmetry on the same API surface.
inspect_image (#4):
- Returns full image detail (layers, env, ports, entrypoint/cmd,
labels) via SYNO.Docker.Image/get. Accepts name:tag, registry-
prefixed names, and bare hashes. Defensive response parsing
handles both wrapped (details.*) and flat envelopes.
system_overview (#6):
- Aggregates CPU %, RAM, network and block I/O across all running
containers plus running/stopped counts. No new DSM endpoint —
composed from list + stats, reusing the container_stats CPU
formula. Per-source errors are non-fatal.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the project lifecycle (create → start/stop/redeploy → delete).
The tool calls SYNO.Docker.Project/delete with the UUID JSON-encoded
as the `id` parameter (per DSM convention) and removes only the
Container Manager registration — the project folder and compose
file remain on the NAS. This mirrors DSM's own "Delete project"
behaviour, not a bug; the success message states the folder was
preserved so the user is not surprised.
Safety:
- Project-name validation runs before any I/O.
- A `_find_project` pre-flight returns "not found" with a clear
message rather than letting DSM reject an unknown UUID.
- No automatic stop. If the project is RUNNING and DSM rejects
the delete, the response tells the user to `stop_project` first
rather than silently halting containers under the guise of a
"delete" call.
- Requires confirmed=True; preview shows name, UUID, status, full
path, and share path so the user can verify before deleting.
Tests cover preview-only, not-found, invalid-name, happy path
(verifies the UUID is JSON-encoded in the delete call), and the
running-project rejection path that surfaces the stop_project hint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds `create_project` for registering a new Container Manager project
from a compose YAML string. Three-step flow that mirrors the DSM
"Create Project" wizard:
1. SYNO.FileStation.CreateFolder with force_parent=true (idempotent
— does not fail if the folder already exists, and creates missing
intermediate directories). Without this step, Docker.Project/create
fails with DSM error 2100.
2. SYNO.Docker.Project/create (form-encoded POST; JSON-encoded string
parameters per DSM convention) returns the new project UUID.
3. trigger_build_stream + _wait_for_project_running, reusing the
existing image-pull / start / poll machinery (including the
BUILD_FAILED early-exit from welle 2).
Safety:
- Project-name validation (Welle-1 regex) runs before any I/O.
- Compose content is YAML-parsed and must contain a top-level
`services` key before any side effects.
- A pre-flight list_projects check rejects duplicate names with a
clear message rather than leaving an orphaned folder on the NAS.
- share_path defaults to compose_base_path + project_name (e.g.
/volume1/docker + myapp → /docker/myapp); a caller-supplied value
overrides it.
- Requires confirmed=True; the preview shows the resolved share path
and the service count parsed from the compose content.
- DSM error 2100 surfaces as "target folder issue" with the attempted
path. A build_stream failure after a successful Project/create tells
the user the project is registered-but-not-started and points at
redeploy_project for recovery.
Tests cover preview-only, already-exists, happy path (with parameter
JSON-encoding assertions), explicit share_path, malformed YAML,
missing services key, invalid project name, error 2100, and
build_stream failure after registration.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add an explicit rule that every user-visible change updates
CHANGELOG.md in the same commit, under an `## [Unreleased]` heading
between releases. References the C-2 gap (0.2.7 and 0.2.8 shipped
without changelog entries) so the motivation is durable.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Document that git push to gitea.gecheckt.de occasionally returns
Unauthorized on the first attempt and must be retried once after a
1 s wait before reporting an auth failure.
- Codify the version-consistency invariant: pyproject.toml, uv.lock,
and the latest CHANGELOG heading must move together;
src/mcp_synology_container/__init__.py reads __version__ from
importlib.metadata and is never hand-edited.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove hostnames, concrete container names, image tags, personal notes,
and the completed task backlog. Replace with generic DSM quirks reference,
implementation rules, and tool inventory — suitable for a public connector.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 1: Container name hash-prefix (e.g. f93cb8b504f7_jenkins)
- _strip_hash_prefix(): strips 12-char hex prefix and leading slash
- _resolve_container_name(): looks up actual DSM name from container list
- Applied in list_containers (display), container_stats (matching),
get_container_status/get_container_logs/exec_in_container (lookup)
Bug 2: redeploy_project DSM 2101/1202 on wrong project state
- Fetch project status before acting
- RUNNING → stop then start
- STOPPED → start directly (nothing to stop)
- BUILD_FAILED → suppress stop error, then start
- Other → return error with workaround hint
36 tests all passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DSM methods for SYNO.Docker.Image/pull and SYNO.Docker.Registry/get
did not behave as expected in production testing against the NAS.
Tools deregistered, modules deleted, tests removed, CLAUDE.md updated.
Tool count: 19 → 17.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pull_image: SYNO.Docker.Image/pull with repository+tag split via
rpartition; polls image list every 3 s until image appears, 120 s timeout
- list_registries: SYNO.Docker.Registry/get; shows name, URL, active marker
- Gruppe 5 (Volumes) removed from roadmap — SYNO.Docker.Volume does not exist
- CLAUDE.md: tool count 17 → 19, Volumes section removed
- 28 tests all passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>