Fix compose path: strip /volumeN prefix for FileStation API

Container Manager returns raw filesystem paths (/volume1/docker/...),
but SYNO.FileStation.* APIs expect paths without the volume prefix
(/docker/...). Add _to_filestation_path() to strip /volumeN and apply
it in _find_compose_path before any FileStation call.

Also switch directory probe from getinfo (returns truthy files array
with embedded code:408 for missing paths) to list (empty files array
for non-existent directories), and apply the same prefix stripping to
the error message shown when no compose file is found.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 16:07:47 +02:00
parent c0257f6068
commit b921e3a649
+34 -8
View File
@@ -7,6 +7,8 @@ Supported filenames: docker-compose.yml, docker-compose.yaml, compose.yml, compo
from __future__ import annotations
import logging
import re
import sys
from typing import TYPE_CHECKING, Any
import yaml
@@ -20,6 +22,18 @@ from mcp_synology_container.modules.projects import _find_project
logger = logging.getLogger(__name__)
_VOLUME_PREFIX_RE = re.compile(r"^/volume\d+")
def _to_filestation_path(path: str) -> str:
"""Strip /volumeN prefix so paths work with the FileStation API.
The Docker/Container Manager API returns raw filesystem paths like
/volume1/docker/myapp, but FileStation expects /docker/myapp.
"""
return _VOLUME_PREFIX_RE.sub("", path)
# Recognized compose file names (in priority order)
_COMPOSE_FILENAMES = [
"docker-compose.yml",
@@ -45,11 +59,12 @@ def register_compose(mcp: FastMCP, config: AppConfig, client: DsmClient) -> None
path = await _find_compose_path(client, config, project_name)
if path is None:
project = await _find_project(client, project_name)
searched = (
raw = (
project.get("path", f"{config.compose_base_path}/{project_name}")
if project
else f"{config.compose_base_path}/{project_name}"
)
searched = _to_filestation_path(raw)
return (
f"No compose file found for project '{project_name}'.\n"
f"Looked in {searched}/ for: " + ", ".join(_COMPOSE_FILENAMES)
@@ -326,18 +341,29 @@ async def _find_compose_path(
base,
)
for filename in _COMPOSE_FILENAMES:
path = f"{base}/{filename}"
# FileStation API requires paths without the /volumeN prefix.
fs_base = _to_filestation_path(base)
# List the directory once and match against known filenames.
# getinfo returns {"files": [{"code": 408, ...}]} for missing paths
# (truthy but erroneous), so listing the directory is more reliable.
try:
data = await client.request(
"SYNO.FileStation.List",
"getinfo",
params={"path": path, "additional": "[]"},
"list",
params={"folder_path": fs_base, "additional": "[]"},
)
if data.get("files"):
names_present = {f.get("name", "") for f in data.get("files", [])}
sys.stderr.write(f"[compose] files in {fs_base}: {sorted(names_present)}\n")
sys.stderr.flush()
except Exception as e:
logger.debug("Could not list directory '%s': %s", fs_base, e)
names_present = set()
for filename in _COMPOSE_FILENAMES:
if filename in names_present:
path = f"{fs_base}/{filename}"
logger.debug("Found compose file: %s", path)
return path
except Exception:
continue
return None