Files
mcp-synology-container/CLAUDE.md
T
marcus 036429e9bf feat: v0.6.0 — read build_stream log instead of dropping it (#2)
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>
2026-05-18 13:58:55 +02:00

6.5 KiB
Raw Blame History

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_myservice when the compose service name differs from container_name. All container tools strip this prefix transparently via _strip_hash_prefix / _resolve_container_name.
  • Async project startSYNO.Docker.Project/start returns immediately while containers are still initialising. redeploy_project polls SYNO.Docker.Project/list every 2 s for up to 30 s after issuing start.
  • SYNO.Docker.Project/build_stream — returns a streamed plaintext build log (content-type text/html), one short line per step: Container <name> Running on success, <svc> Error followed by Error response from daemon: <cause> on failure. The stream closes when the build is done. DsmClient.trigger_build_stream consumes 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_project and create_project grep the returned log for daemon errors and abort early — these errors are much more actionable than the eventual BUILD_FAILED polling status. The log is live-only: it cannot be re-fetched after the build ends, which is why no standalone get_project_build_log tool exists.
  • Image delete — requires a form-encoded POST with a JSON images array (confirmed via browser DevTools); uses DsmClient.post_request().
  • SYNO.Docker.Image/pull vs. pull_start — the legacy pull method exists but behaviour varies by DSM version; not exposed as a standalone tool. pull_image uses SYNO.Docker.Image/pull_start (asynchronous pull entry point) with both repository and tag JSON-encoded. Note that pull_start lives on SYNO.Docker.Image, NOT on SYNO.Docker.Registry — the Registry API only exposes the synchronous read-only methods (search, tags, get/set/create/delete, using); calling Registry/pull_start returns "Method does not exist". No matching pull_status method is confirmed on either API, so completion is detected by polling SYNO.Docker.Image/list until repository:tag appears (210 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 — uses repo (JSON-encoded) as the parameter name; the n4s4 reference's name does not work on this DSM version. Returns the tag list as the envelope's data field 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/pause and /unpause — not implemented in DSM Container Manager on this firmware. The action menu only offers start/stop/force-stop/restart/reset; calls to pause/unpause return "Method does not exist". pause_container and unpause_container were 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.md entry in the same commit — under a ## [Unreleased] heading between releases, which becomes ## [X.Y.Z] - YYYY-MM-DD on 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.toml and must stay in sync with uv.lock and the [X.Y.Z] heading in CHANGELOG.md. src/mcp_synology_container/__init__.py derives __version__ from importlib.metadata and 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 structure
  • N4S4/synology-api docker_api.py (GitHub) — SYNO.Docker.* calls