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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user