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
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@@ -20,6 +22,18 @@ from mcp_synology_container.modules.projects import _find_project
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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)
|
# Recognized compose file names (in priority order)
|
||||||
_COMPOSE_FILENAMES = [
|
_COMPOSE_FILENAMES = [
|
||||||
"docker-compose.yml",
|
"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)
|
path = await _find_compose_path(client, config, project_name)
|
||||||
if path is None:
|
if path is None:
|
||||||
project = await _find_project(client, project_name)
|
project = await _find_project(client, project_name)
|
||||||
searched = (
|
raw = (
|
||||||
project.get("path", f"{config.compose_base_path}/{project_name}")
|
project.get("path", f"{config.compose_base_path}/{project_name}")
|
||||||
if project
|
if project
|
||||||
else f"{config.compose_base_path}/{project_name}"
|
else f"{config.compose_base_path}/{project_name}"
|
||||||
)
|
)
|
||||||
|
searched = _to_filestation_path(raw)
|
||||||
return (
|
return (
|
||||||
f"No compose file found for project '{project_name}'.\n"
|
f"No compose file found for project '{project_name}'.\n"
|
||||||
f"Looked in {searched}/ for: " + ", ".join(_COMPOSE_FILENAMES)
|
f"Looked in {searched}/ for: " + ", ".join(_COMPOSE_FILENAMES)
|
||||||
@@ -326,18 +341,29 @@ async def _find_compose_path(
|
|||||||
base,
|
base,
|
||||||
)
|
)
|
||||||
|
|
||||||
for filename in _COMPOSE_FILENAMES:
|
# FileStation API requires paths without the /volumeN prefix.
|
||||||
path = f"{base}/{filename}"
|
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:
|
try:
|
||||||
data = await client.request(
|
data = await client.request(
|
||||||
"SYNO.FileStation.List",
|
"SYNO.FileStation.List",
|
||||||
"getinfo",
|
"list",
|
||||||
params={"path": path, "additional": "[]"},
|
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)
|
logger.debug("Found compose file: %s", path)
|
||||||
return path
|
return path
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user