feat: add check_exist tool

SYNO.FileStation.CheckExist returns error 400 for every parameter format on
this DSM firmware, so the tool falls back to SYNO.FileStation.List::getinfo.
DSM returns an entry per requested path with name=None for non-existent paths,
which provides a reliable exists/not-exists signal.

Accepts a single path or a comma-separated list; returns a table of
Path | Exists (Yes/No) with a count footer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-14 10:49:50 +02:00
parent 80ac894165
commit dbab842738
2 changed files with 179 additions and 0 deletions
@@ -538,6 +538,63 @@ def register_filestation(
return "\n".join(lines)
@mcp.tool()
async def check_exist(path: str) -> str:
"""Check whether one or more files or folders exist on the NAS.
Accepts a single path or a comma-separated list of paths.
Use share paths as returned by list_shares (e.g. "/dev/file.txt").
Note: SYNO.FileStation.CheckExist returns error 400 on this firmware for all
parameter formats. This tool falls back to SYNO.FileStation.List::getinfo, which
returns an entry per path with name=None when the path does not exist.
Args:
path: One or more share-relative paths, comma-separated
(e.g. "/dev/notes.txt" or "/dev/notes.txt,/data/photo.jpg").
Returns:
Formatted table with each path and whether it exists (Yes / No).
"""
from mcp_synology_filestation.client import SynologyError
paths = [p.strip() for p in path.split(",") if p.strip()]
if not paths:
return "Error: no path provided."
try:
data = await client.request(
"SYNO.FileStation.List",
"getinfo",
params={
"path": json.dumps(paths),
"additional": json.dumps([]),
},
)
except SynologyError as e:
return f"Error: {e}"
files: list[dict] = data.get("files", [])
if not files:
return "No information returned for the given path(s)."
# A path that doesn't exist still gets an entry but with name=None
rows = [(f.get("path", ""), "Yes" if f.get("name") is not None else "No") for f in files]
w_path = max(len("Path"), *(len(r[0]) for r in rows))
w_exists = len("Exists") # "Yes" / "No" always shorter
sep = f"+{'-' * (w_path + 2)}+{'-' * (w_exists + 2)}+"
header = f"| {'Path':<{w_path}} | {'Exists':<{w_exists}} |"
lines = [sep, header, sep]
for item_path, exists_str in rows:
lines.append(f"| {item_path:<{w_path}} | {exists_str:<{w_exists}} |")
lines.append(sep)
lines.append(f"\n{len(rows)} path(s) checked.")
return "\n".join(lines)
# ── write tools ───────────────────────────────────────────────────────
@mcp.tool()