036429e9bf
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>
6.5 KiB
6.5 KiB
mcp-synology-container
Project
mcp-synology-container is an MCP server for managing Docker projects on a
Synology DiskStation via Container Manager. It exposes tools for projects,
containers, images, compose files, networks, and system housekeeping.
Tech stack
| Language | Python 3.12+, uv |
| Key deps | MCP SDK, httpx, keyring, click, rich |
| Compose paths | /volume1/docker/<project>/ (default Synology layout) |
Deploy workflow (after every code change)
1. Claude Code commits and pushes
2. uv tool install --reinstall git+<repo-url>
3. Restart Claude Desktop (tray icon → Quit → relaunch)
Push retry: the Gitea remote (gitea.gecheckt.de) occasionally
returns Unauthorized on the first push attempt. If git push fails
with an auth error, wait 1 s and retry once before reporting back.
Only a second consecutive failure is treated as a real auth problem.
Implemented tools (34)
| Category | Tools |
|---|---|
| Projects | list_projects, get_project_status, start_project, stop_project, redeploy_project, create_project, delete_project |
| Containers | list_containers, get_container_status, get_container_logs, exec_in_container, container_stats, delete_container, start_container, stop_container, restart_container |
| Compose | read_compose, update_compose, update_image_tag, update_env_var |
| Images | check_image_updates, list_images, delete_image, inspect_image |
| Registry | search_registry, list_image_tags, pull_image |
| Networks | list_networks, create_network, delete_network |
| System | system_df, system_prune, system_overview |
DSM API quirks
- Hash-prefixed container names — DSM sometimes returns names like
a1b2c3d4e5f6_myservicewhen the compose service name differs fromcontainer_name. All container tools strip this prefix transparently via_strip_hash_prefix/_resolve_container_name. - Async project start —
SYNO.Docker.Project/startreturns immediately while containers are still initialising.redeploy_projectpollsSYNO.Docker.Project/listevery 2 s for up to 30 s after issuing start. SYNO.Docker.Project/build_stream— returns a streamed plaintext build log (content-typetext/html), one short line per step:Container <name> Runningon success,<svc> Errorfollowed byError response from daemon: <cause>on failure. The stream closes when the build is done.DsmClient.trigger_build_streamconsumes the body line-by-line with a 210 s wall-clock budget (under the Claude Desktop ~4 min ceiling) and returns the log as a string; on timeout the partial log is returned with a marker appended so callers know the build is still running server-side.redeploy_projectandcreate_projectgrep the returned log for daemon errors and abort early — these errors are much more actionable than the eventualBUILD_FAILEDpolling status. The log is live-only: it cannot be re-fetched after the build ends, which is why no standaloneget_project_build_logtool exists.- Image delete — requires a form-encoded POST with a JSON
imagesarray (confirmed via browser DevTools); usesDsmClient.post_request(). SYNO.Docker.Image/pullvs.pull_start— the legacypullmethod exists but behaviour varies by DSM version; not exposed as a standalone tool.pull_imageusesSYNO.Docker.Image/pull_start(asynchronous pull entry point) with bothrepositoryandtagJSON-encoded. Note thatpull_startlives onSYNO.Docker.Image, NOT onSYNO.Docker.Registry— the Registry API only exposes the synchronous read-only methods (search,tags,get/set/create/delete,using); callingRegistry/pull_startreturns "Method does not exist". No matchingpull_statusmethod is confirmed on either API, so completion is detected by pollingSYNO.Docker.Image/listuntilrepository:tagappears (2–10 s backoff, 240 s budget). Timeout returns a "still running" hint instead of raising — DSM keeps pulling server-side regardless of the HTTP response.SYNO.Docker.Registry/tags— usesrepo(JSON-encoded) as the parameter name; the n4s4 reference'snamedoes not work on this DSM version. Returns the tag list as the envelope'sdatafield directly, not wrapped in a sub-key.SYNO.Docker.Volume— endpoint does not exist; volume management is not available via the DSM WebAPI.SYNO.Docker.Registry/get— does not behave as documented; registry listing omitted.SYNO.Docker.Container/pauseand/unpause— not implemented in DSM Container Manager on this firmware. The action menu only offers start/stop/force-stop/restart/reset; calls topause/unpausereturn "Method does not exist".pause_containerandunpause_containerwere briefly shipped in 0.4.0 and removed in 0.4.1.
Implementation rules
- Confirmation required before destructive operations:
stop_project,redeploy_project,create_project,delete_project,exec_in_container,update_image_tag,update_env_var,update_compose,delete_container,stop_container,restart_container,pull_image - After compose changes: suggest
redeploy_project - DSM errors → human-readable message, no stack traces
- No secrets in stderr output
- Type hints and docstrings everywhere
- Formatter:
ruff format· Linter:ruff check· Tests:pytest - All text (docstrings, comments, README): English
- CHANGELOG.md: every user-visible change (bug fix, new/changed
tool, behavior change, security fix, dependency bump) gets a
CHANGELOG.mdentry in the same commit — under a## [Unreleased]heading between releases, which becomes## [X.Y.Z] - YYYY-MM-DDon version bump. Pure internal cleanup (renames without external callers, comment-only edits, ruff autofix) needs no entry. Don't ship a release with a stale changelog (this was the C-2 gap that caused 0.2.7 and 0.2.8 to ship undocumented). - Version consistency: the package version lives in
pyproject.tomland must stay in sync withuv.lockand the[X.Y.Z]heading inCHANGELOG.md.src/mcp_synology_container/__init__.pyderives__version__fromimportlib.metadataand is never hand-edited. Every version bump touches all three files in the same commit.
DSM API reference
cmeans/mcp-synology(GitHub) — auth, keyring, CLI structureN4S4/synology-apidocker_api.py(GitHub) —SYNO.Docker.*calls