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 #3 Closes #5 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
22 KiB
Changelog
All notable changes to this project will be documented in this file.
[0.5.0] - 2026-05-18
Added
Welle B Teil 1 — Registry tools (#3, #5). Three new SYNO.Docker.Registry
tools, reverse-engineered from a live DSM API capture (the n4s4 reference
disagrees on parameter names and methods; the live capture wins):
search_registry(#5) — search the active Docker registry by query string. UsesSYNO.Docker.Registry/search(version 1) with the query JSON-encoded asq, plusoffset,limit, andpage_size. Renders stars, downloads, the official-image flag, and a truncated description per hit, and shows the total match count so the caller knows when to raiselimit. No confirmation gate (read-only).list_image_tags— list available tags for a repository (bonus tool). UsesSYNO.Docker.Registry/tags(version 1) with the repository JSON-encoded asrepo— note the parameter name diverges from the n4s4 reference which usesname. The response shape is unusual: DSM returns the tag list as the envelope'sdatafield directly (not wrapped in a sub-key), so the tool accepts both shapes defensively. Output is capped bylimit(default 50) because popular images likealpineship 200+ tags. No confirmation gate (read-only).pull_image(#3) — pull an image into the local cache viaSYNO.Docker.Registry/pull_start(version 1) with bothrepositoryandtagJSON-encoded. Requiresconfirmed=True. DSM exposes no confirmedpull_statusmethod, so completion is detected by pollingSYNO.Docker.Image/listfor the newrepository:tagpair with a 2–10 s backoff schedule and a 240 s overall budget (kept under the Claude Desktop ~4 min tool-call ceiling). A timeout returns a non-fatal "still running" hint pointing atlist_imagesinstead of raising — DSM keeps pulling server-side regardless. Short-circuits when the image is already present locally so a repeated call is cheap. Closes #3 and #5.
Tool count rises from 31 to 34.
Fixed
inspect_imagerendering polish (follow-up to #4 after the 0.4.2 parameter-contract fix). Three live-NAS observations that the initial implementation got wrong:- Header showed
?:?— DSMSYNO.Docker.Image/getreturnsimage=""andtag=""when the lookup is by name:tag, so the response fields are unreliable. The header now echoes the user- suppliedimage_id(e.g.gitea/gitea:1.26.1), falling back to the sha256idonly ifimage_iditself were empty. - Ports rendered as raw Python dicts like
{'port': '22', 'protocol': 'tcp'}. Theportsarray is actually a list of{"port", "protocol"}objects; each entry is now formatted as22/tcp. - Environment rendered as raw Python dicts like
{'key': 'PATH', 'value': '...'}. Theenvarray is actually a list of{"key", "value"}objects; each entry is now formatted asPATH=/usr/local/....cmd,entrypoint, andvolumes(plain string arrays) were already correct and are unchanged. Referencing #4 (already closed).
- Header showed
[0.4.2] - 2026-05-18
Fixed
inspect_image(#4): the 0.4.0 implementation calledSYNO.Docker.Image/getwithname+tag+idparameters, which DSM rejected with error 114 ("invalid parameter"). Reverse- engineering the live API capture revealed the correct contract: one JSON-encoded parameter namedidentitythat accepts either form:name:tag(e.g."gitea/gitea:1.26.1")sha256:<hash>(e.g."sha256:cd21e54e...") The pre-resolution lookup againstlist_imagesis no longer needed — the user input is JSON-encoded and passed straight through.
inspect_imageresponse parsing: the endpoint does NOT returndetails.Config.*/RootFS.Layers(the Docker-engine inspect shape the previous code assumed). The actual top-level fields areimage,tag,id,digest,size,virtual_size,author,docker_version,cmd,entrypoint,env,ports,volumes. Layer rendering is removed (the endpoint does not surface layers); digest, author, docker version, and volumes are now displayed.
[0.4.1] - 2026-05-18
Removed
pause_containerandunpause_container(added in 0.4.0) — live test against DSM revealed that Container Manager on this firmware does not expose pause/resume at all. The GUI action menu only offers Start / Stop / Force-Stop / Restart / Reset, and direct calls toSYNO.Docker.Container/pauseand/unpausereturn "Method does not exist". Both tools have been removed rather than left as a broken surface. The remaining three lifecycle tools (start_container,stop_container,restart_container) are unaffected. Tool count drops from 33 to 31. Closes #7 (won't fix — DSM-side limitation).
[0.4.0] - 2026-05-18
Added
Container lifecycle (#1, #7) — five new tools fill the gap between
"project" and "exec" operations. Until now the only way to stop or
restart a single container was to stop the whole project. All five
use SYNO.Docker.Container (version=1) with the same JSON-encoded
name parameter pattern as delete_container, and route through
_resolve_container_name so a clean name like jenkins resolves
to DSM's hash-prefixed f93cb8b504f7_jenkins:
start_container— start a stopped container (no confirmation).stop_container— stop a running container (confirmation required).restart_container— stop + start in one call (confirmation required).pause_container— SIGSTOP the container's processes (confirmation required).unpause_container— SIGCONT the container's processes (no confirmation; reversing a pause is non-destructive).
stop is live-verified against DSM; start, restart, pause, and
unpause are implemented by symmetry on the same API surface. If any
of them turns out to need a different parameter shape in production,
it will be reverse-engineered via DevTools capture in a follow-up.
Image inspection (#4) — inspect_image returns the full image
detail blob (layers + sizes, environment variables, exposed ports,
entrypoint/cmd, working directory, labels) via
SYNO.Docker.Image/get. Accepts the same identifier forms as
delete_image — name:tag, registry-prefixed ghcr.io/foo/bar:v1,
and bare hash — and resolves the user input against list_images
first so typos produce a clean "not found" rather than an opaque DSM
error. The response parser is defensive about wrapper shape
(details.<docker fields> vs. flat) so the tool keeps working if DSM
varies the envelope between firmware versions.
System overview (#6) — system_overview aggregates CPU %, RAM
used/limit, network I/O, and block I/O across all running containers
plus running/stopped counts. No new DSM endpoint: composed from
SYNO.Docker.Container/list + SYNO.Docker.Container/stats,
re-using the same Docker-formula CPU calculation as
container_stats. Errors from either upstream call are non-fatal —
the available section is rendered and the failure is surfaced under a
Warnings: block.
[0.3.3] - 2026-05-18
Fixed
delete_container:SYNO.Docker.Container/deleterequires three parameters —name(JSON-encoded),force=false, andpreserve_profile=false. Previously onlynamewas sent (without JSON-encoding), causing DSM to reject the call with error 114.delete_project: DSM does not rejectProject/deleteon a running project — it silently removes the registration and leaves the containers running as orphans. The connector now blocks the call itself when the project status isRUNNING, before issuing any DSM request, and tells the user tostop_projectfirst. The previous implementation relied on a DSM-level rejection that never occurs in practice; the corresponding unit test was a false positive (mocked an error that real DSM never returns) and has been replaced with a test that assertsProject/deleteis never called for aRUNNINGproject.
Added
delete_project— remove a Container Manager project's registration viaSYNO.Docker.Project/deletewith the UUID JSON-encoded as theidparameter (per DSM convention). Mirrors the "Delete project" action in Container Manager: only the registration is removed; the project folder and compose file remain on the NAS. The success message explicitly states the folder was preserved so the user is not surprised. Closes the project lifecycle (create → start/stop/redeploy → delete). Safety:- Project-name validation runs before any I/O.
- A
_find_projectpre-flight returns "not found" with a clear message rather than letting DSM reject an unknown UUID. - The tool deliberately does NOT auto-stop a running project. If
DSM rejects the delete on a
RUNNINGproject, the response tells the user tostop_projectfirst rather than silently halting containers under the guise of a "delete" call. - Requires
confirmed=True; the preview shows name, UUID, status, full path, and share path so the user can verify before deleting.
Added
create_project— register a new Container Manager project from a compose YAML string. Three-step flow:- Create the target folder via
SYNO.FileStation.CreateFolderwithforce_parent=true(idempotent — does not fail if the folder already exists, and creates missing intermediate directories). Without this step,SYNO.Docker.Project/createfails with DSM error code 2100. SYNO.Docker.Project/create(form-encoded POST, JSON-encoded string parameters per DSM convention) returns the new project's UUID.trigger_build_stream+_wait_for_project_running— reuses the existing image-pull / start / poll machinery (including theBUILD_FAILEDearly-exit from welle 2). Defaults:share_pathis derived fromcompose_base_path(e.g./volume1/docker+myapp→/docker/myapp). The compose content is validated as YAML before any side effects. A pre-flightlist_projectscheck rejects duplicate names with a clear message rather than leaving an orphaned folder on the NAS. Requiresconfirmed=True; the preview shows the resolved share path and the service count parsed from the compose content.
- Create the target folder via
Fixed
DsmClient.trigger_build_stream: broaden transport-error handling (M-4).httpx.ReadTimeoutis still treated as a success signal (the request was sent; DSM is processing it server-side), but all otherhttpx.HTTPErrorsubclasses (ConnectError,ConnectTimeout,WriteError,RemoteProtocolError, …) are now converted into aSynologyErrorwith a clear message. Previously these propagated as raw httpx exceptions and left callers with a stack trace.redeploy_project: when build_stream (or the polling step) fails after the project has already been stopped, the response now explicitly tells the user that the project is inSTOPPEDstate and must be recovered withstart_projector anotherredeploy_projectcall. Previously the response suggested "use stop + start separately", which was misleading because stop had already happened._wait_for_project_running: exit the polling loop early onBUILD_FAILED/ERROR(M-5). DSM signals these statuses within seconds of a failed image pull; the old polling loop kept waiting up to 5 minutes forRUNNING.redeploy_projectsurfaces the terminal status with a hint toupdate_image_tagand retry when the cause isBUILD_FAILED.system_prunepreview: count unused user-created networks alongside dangling images and stopped containers (M-6). Built-in networks (bridge,host,none) are excluded because Docker never prunes them. Previously the preview noted "Unused networks: (not counted)", even though the underlyingSYNO.Docker.Utils/prunecall deletes them — users could lose networks they had not been warned about.
Changed
- Minor version bump because
redeploy_projectandsystem_prunereturn different strings (andredeploy_projectreturns earlier onBUILD_FAILED). No tool signatures changed.
[0.2.9] - 2026-05-18
Fixed
__version__is now derived from package metadata viaimportlib.metadata.version(), eliminating the drift betweenpyproject.tomlandsrc/mcp_synology_container/__init__.py(was stuck at0.1.0since the initial release).compose.py: reject project names that contain path separators or other unsafe characters before they reach_find_compose_path. Previously a name like"../../etc"could traverse out ofcompose_base_pathwhen the project was not yet registered with Container Manager. The new_validate_project_namehelper enforces^[a-zA-Z0-9_-]+$and is applied toread_compose,update_compose,update_image_tag, andupdate_env_var. Addresses M-3 from the v0.2.8 review.
Docs
CHANGELOG.mdbackfilled for releases 0.2.7 and 0.2.8 (entries had been missed during those releases).
[0.2.8] - 2026-04-21
Added
tests/test_dsm_client.py— comprehensive offline test suite forDsmClient:_scrub_urland_error_messagepure helpers.request()happy-path, API-not-cached,_sidscrubbing inHTTPStatusError, sensitive-param log masking.- Session re-auth retry: single-retry semantics, auth-manager-absent path, re-auth failure path, thundering-herd (login called once under concurrent 106 responses).
trigger_build_stream: SSE fire-and-forget, JSON error detection,ReadTimeoutswallowing, HTTP-error scrubbing.upload_textanddownload_texthappy-path + error-response branches._ensure_initializeddouble-checked locking and negative-cache cooldown behavior.
Fixed
DsmClient._ensure_initialized: cache failed init outcomes for 60 s so that repeated tool calls during a credential outage (wrong password, IP-blocked 407, DNS failure) do not keep hammering DSM. Each caller receives the cached exception until the cooldown window expires, after which a fresh attempt is made. AddsINIT_ERROR_COOLDOWNmodule constant and_init_error/_init_error_untilstate. Addresses M4 from the 0.2.7 review.
[0.2.7] - 2026-04-21
Fixed
DsmClient: scrub_sidquery-parameter values from URLs embedded inhttpx.HTTPStatusErrormessages so the raw DSM session ID never reaches log output or MCP tool responses (C1).DsmClient: re-auth lock now snapshots_sidbefore entering the lock and skips the redundant login if another task has already refreshed the session, eliminating duplicate logins on concurrent 106/107/119 responses (M3).DsmClient.trigger_build_stream: re-instates immediate JSON-error detection (regression from 0.2.6). Inspects theContent-Typeheader and reads a small capped prefix of the body forapplication/jsonresponses to surface DSM error codes without forcing the caller into a multi-minute polling timeout. SSE responses remain fire-and-forget (C2/M8).compose.update_env_var: parenthesise the apply-branch match condition so(isinstance AND startswith) OR (entry == var_name)no longer evaluates the equality branch for non-string entries — aligns the apply side with the preview-side detection logic (M1).
Changed
- All 23
@mcp.tool()functions: strip-> strreturn annotations and trim docstrings to a single line (≤100 chars). FastMCP generates anoutputSchemaentry for every annotated tool, which roughly doubles thetools/listpayload size; multi-line docstrings withArgs:/Returns:sections add further bulk that Claude Desktop must parse on every connection.
Chore
uv.lockresynced (was stale at0.2.2)..gitignore: exclude.claude/per-user Claude Code settings.- Mechanical
ruff check --fix+ruff formatcleanup (import sorting, unused-import removal). No functional change.
[0.2.6] - 2026-04-21
Fixed
DsmClient.trigger_build_stream: Claude Desktop aborts tool calls after ~4 minutes. The previous implementation read the first SSE chunk before returning, which could block for the entire duration of an image pull. Fixed by making the call truly fire-and-forget: the HTTP request is sent, response headers are received (HTTP status check only), then the connection is closed immediately without reading any SSE events. DSM continues the build server-side regardless. The_jsonimport added in 0.2.5 is removed.
[0.2.5] - 2026-04-21
Changed
redeploy_project: Replaced the delete-before-start image workaround withSYNO.Docker.Project/build_stream— the programmatic equivalent of the DSM "Erstellen" (Build) button, confirmed via browser DevTools capture. New 3-step flow for all project states:- Stop (skipped for STOPPED; error-suppressed for BUILD_FAILED)
build_stream— DSM pulls updated images and starts the project via SSE- Poll for RUNNING (timeout raised from 30 s to 5 min to accommodate image pulls)
build_streamerrors are now fatal (abort the redeploy with a clear message).
Added
DsmClient.trigger_build_stream(project_id)— fires a streaming GET toSYNO.Docker.Project/build_stream, reads the first SSE chunk to confirm DSM accepted the request, then closes the connection. The build continues server-side. Handles immediate JSON error responses; swallowsReadTimeout(stream still open = build running).
[0.2.4] - 2026-04-21
Changed
redeploy_project: Replaced brokenSYNO.Docker.Image/pullwith a delete-before-start workaround. The tool now reads image tags from the project's compose file via FileStation, deletes each cached image before callingstart(so DSM auto-pulls the latest version), then polls forRUNNING. Image deletion is non-fatal — if it fails the project still starts. Unified 4-step flow for all project states (RUNNING, STOPPED, BUILD_FAILED).
Added
update_image_tag: Auto-updates environment variables whose value equals the numeric version prefix of the old tag when the new tag shares the same<digits>-<suffix>pattern. For example, changing2.558-jdk21→2.560-jdk21automatically updatesJENKINS_VERSION=2.558toJENKINS_VERSION=2.560. The preview (unconfirmed call) now lists which env vars will be updated. Only triggers when the variable exists and the pattern matches; no change for plain tags likelatest.
[0.2.3] - 2026-04-21
Changed
CLAUDE.mdrewritten: removed all operator-specific infrastructure details (hostnames, container names, image tags, personal notes). Kept DSM API quirks, implementation rules, and tool inventory.
[0.2.2] - 2026-04-21
Fixed
redeploy_project(BUILD_FAILED): Pull errors are no longer silently suppressed. If the image pull fails (e.g. the tag in compose.yaml does not exist on the registry), redeploy aborts immediately with a clear message pointing toupdate_image_tag.
[0.2.1] - 2026-04-21
Fixed
redeploy_project: After issuingstart, the tool now polls the project status every 2 seconds for up to 30 seconds until the project reachesRUNNING. Previously DSM returned immediately while containers were still starting, causing the project to appear asexitedwhen checked right after redeploy. On timeout a warning is returned instead of an error.delete_image: Now distinguishes between running and stopped container references. A stopped container holding the image produces a clear hint to usedelete_containerorsystem_pruneinstead of a generic "in use" error.redeploy_project(BUILD_FAILED path): Added explicit image pull step before restart (stop → pull → start). Previously the old cached image could be reused.
Added
delete_container— delete a stopped container by name; refuses if container is still running; requiresconfirmed=True.
[0.2.0] - 2026-04-14
Added
Images
list_images— list local images sorted by size; marks images in use and with available updatesdelete_image— delete image byname:tagor hash; refuses if image is used by a container
Container
container_stats— live CPU %, RAM used/limit, network I/O, block I/O
System
system_df— Docker disk usage: image count/size, running/stopped containers, reclaimable spacesystem_prune— remove dangling images, stopped containers, and unused networks
Networks
list_networks— list all Docker networks with driver, subnet, gateway, attached containerscreate_network— create a new bridge (or other driver) networkdelete_network— delete a network; refuses if any container is still attached
Fixed
get_container_statusnow reads the correct DSM response fields (details.Statefor status/running,profile.imagefor image name) and displays IP addresses, port bindings, and mountsredeploy_projectis now status-aware: RUNNING → stop + start; STOPPED → start directly; BUILD_FAILED → force-stop + start; unknown status returns a clear error with workaround hint- Container names with hash prefix (e.g.
f93cb8b504f7_jenkins) are now transparently stripped inlist_containers,container_stats,get_container_status,get_container_logs, andexec_in_container
Changed
DsmClientgained apost_request()method for form-encoded POST operations required by image delete, network create/delete
[0.1.0] - 2026-04-13
Added
- Initial implementation
- Projects:
list_projects,get_project_status,start_project,stop_project,redeploy_project - Containers:
list_containers,get_container_status,get_container_logs,exec_in_container - Compose:
read_compose,update_compose,update_image_tag,update_env_var - Images:
check_image_updates - DSM session management with auto re-authentication on session timeout
- OS keyring integration for secure credential storage
- 2FA / device token support
- Interactive
setupwizard andcheckconnectivity command