diff --git a/src/mcp_synology_container/modules/compose.py b/src/mcp_synology_container/modules/compose.py index 20f6896..e001a7a 100644 --- a/src/mcp_synology_container/modules/compose.py +++ b/src/mcp_synology_container/modules/compose.py @@ -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, ) + # 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", + "list", + params={"folder_path": fs_base, "additional": "[]"}, + ) + 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: - path = f"{base}/{filename}" - try: - data = await client.request( - "SYNO.FileStation.List", - "getinfo", - params={"path": path, "additional": "[]"}, - ) - if data.get("files"): - logger.debug("Found compose file: %s", path) - return path - except Exception: - continue + if filename in names_present: + path = f"{fs_base}/{filename}" + logger.debug("Found compose file: %s", path) + return path return None