4c6de3bfc7
- background_tasks: SYNO.FileStation.BackgroundTask::list (v3) — paginated table of active/recent copy/move/delete/extract/compress tasks - list_snapshots: SYNO.FileStation.Snapshot::list (v2) — Btrfs snapshots per share; maps error 400 to a clear Btrfs-required message - 20 new tests (107 total) - SPEC.md and CLAUDE.md updated (26 tools) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.8 KiB
9.8 KiB
CLAUDE.md — mcp-synology-filestation
Development context for this project.
Project
mcp-synology-filestation — MCP server exposing Synology FileStation as Claude tools.
Sibling project to mcp-synology-container.
Infrastructure
- NAS:
https://dsm.gecheckt.de - Config file:
~/.config/mcp-synology-filestation/config.yaml - Credentials: OS keyring, service name
mcp-synology-filestation - Gitea:
https://gitea.gecheckt.de/marcus/mcp-synology-filestation - Local code:
D:\Dev\Projects\mcp-synology-filestation - Runtime: Python 3.12+,
uv, MCP SDK (mcp>=1.0.0),httpx,keyring,click,rich - Test share on NAS:
/test-mcp— only for MCP tests, no production data - DSM test user:
Testuser/Lasdas1234(read-only, for browser tests)
Deploy Workflow
- Commit and push via Claude Code.
- Close Claude Desktop first (Windows holds file locks — reinstall fails otherwise).
- Install:
Or directly from Gitea:
uv tool install --reinstall dist\mcp_synology_filestation-X.Y.Z-py3-none-any.whluv tool install --reinstall git+https://gitea.gecheckt.de/marcus/mcp-synology-filestation.git - Restart Claude Desktop.
- Verify:
uv tool listshows the expected version.
Versioning
Bump the patch version on every commit in both files:
pyproject.toml→version = "X.Y.Z"src/mcp_synology_filestation/__init__.py→__version__ = "X.Y.Z"
Current series: 0.3.x
Toolchain
| Task | Command |
|---|---|
| Format | ruff format src/ tests/ |
| Lint | ruff check src/ tests/ |
| Tests | pytest |
| Install dev | uv sync --dev |
| Run server | uv run mcp-synology-filestation serve |
| Setup | uv run mcp-synology-filestation setup |
Code Standards
- Type hints on all public functions and methods.
- Docstrings (English, 1 line max for
@mcp.tool()functions) on all public modules, classes, and functions. - Async-first: use
httpx.AsyncClientthroughout; neverrequests. - Formatter:
ruff format(line length 100). - Linter:
ruff check— fix all warnings before committing. - No
-> stron@mcp.tool()functions. FastMCP generatesoutputSchemafrom return annotations, which bloats thetools/listpayload and causes Claude Desktop to truncate the tool list. Omit return type annotations on all tool functions. - Short docstrings on tools. Keep tool docstrings to 1 line. Long docstrings also bloat
the
tools/listpayload.
Security Rules
- Never log or write credentials, session IDs, or passwords to disk or stderr.
- Never store passwords in the config YAML — OS keyring only.
- Mask sensitive query parameters (
_sid,passwd) before logging request URLs.
Destructive Operation Rules
delete,movewithoverwrite=True,copywithoverwrite=True, anduploadwithoverwrite=Trueare considered destructive.- The
deletetool MUST requireconfirmed=Trueto proceed. Without it, return a preview message that describes exactly what would be deleted — never silently proceed. - For overwrite scenarios in
move/copy/upload, include a warning in the tool description and defaultoverwritetoFalse.
Error Handling Rules
- DSM error codes must be mapped to human-readable messages. See SPEC.md for the full map.
- Never surface raw Python stack traces in tool return values.
- Session expiry (codes 106, 107, 119): transparent single retry with re-login.
- Network errors: return "Cannot reach NAS at {host} — check connectivity."
- Auth failures (400–403): return message + "Run
mcp-synology-filestation setupto reconfigure credentials."
Tool Return Format
- All tools return
str. - Use
rich.table.Table(rendered to string) for tabular data. - Include item counts and pagination hints where relevant.
- Error messages are prefixed with
Error:for easy recognition by Claude.
DSM Quirks (hard-won knowledge)
- JSON-encode path parameters:
path,dest_file_path,dest_folder_path,nameetc. must be passed asjson.dumps("/path")orjson.dumps(["/path1", "/path2"]). Plain strings or Python lists are silently ignored by DSM. - Share paths only: always use
/docker,/homes/marcus— never/volume1/docker. Volume paths cause DSM error 408. additionalfield: must bejson.dumps(["size","time"])— comma-separated string does not work.List::listadditional: only["size","time"]confirmed working on this firmware.real_path,perm,typecause error 408.SYNO.FileStation.Stat: not in API registry — useList::getinfoinstead.SYNO.FileStation.CheckExist: returns error 400 — useList::getinfoinstead (entries withname=nulldo not exist).- DirSize / MD5 one-shot:
finished=trueis returned exactly once, then the task is deleted. Implemented via_start_and_poll_oneshot()— never poll again afterfinished=true. - DirSize cold start: after inactivity, DSM's background service needs ~6–8 s to
initialise. During this window every
statuspoll returns 599. Fix: restart the task (newstartcall) — up to 6 restarts within a 60 s budget. - DirSize
statusversion: must useversion=1. Version 2 always returns 599. Sharing::deleteid parameter: must bejson.dumps(link_id).CheckPermission::write:pathandfilenameare plain strings (nojson.dumps). Returns{"blSkip": false}on success.Search::startfolder_path: must be a JSON array:json.dumps([path]). A plain string orjson.dumps(path)is silently ignored by DSM — it starts an empty search and immediately returnsfinished=true, files=[].Extract::startparameter name: the source archive key isfile_path(notpath). Both path parameters needjson.dumps():file_path=json.dumps(...)anddest_folder_path=json.dumps(...).- Error 599: means "background service not ready / task not found" for DirSize/MD5.
Handled by
_start_and_poll_oneshot().
Module Structure
src/mcp_synology_filestation/
├── __init__.py # __version__
├── __main__.py # entry point
├── server.py # create_server(config, client) → FastMCP
├── client.py # FileStationClient (async httpx wrapper)
├── auth.py # AuthManager: keyring, env vars, 2FA, login/logout
├── config.py # AppConfig, ConnectionConfig, load_config, save_config
├── cli.py # click: setup / check / serve
└── tools/
├── __init__.py
└── filestation.py # register_filestation(mcp, config, client)
Implemented Tools (v0.3.4 — 26 tools)
| Tool | Description |
|---|---|
list_shares |
List all shared folders with volume usage |
list_dir |
List directory contents with pagination and sorting |
get_info |
Get detailed metadata for one or more paths |
check_exist |
Check if one or more paths exist (Yes/No table) |
search |
Search for files by glob pattern with async polling |
download |
Download a file as base64 (max 10 MB) |
create_folder |
Create a new folder (optionally with parent dirs) |
rename |
Rename a file or folder |
copy |
Copy a file or folder (async polling, overwrite=False) |
move |
Move a file or folder (async polling, overwrite=False) |
delete |
Delete a file or folder — requires confirmed=True |
upload |
Upload base64-encoded content to a path (max 50 MB) |
compress |
Compress paths into a ZIP or 7z archive |
extract |
Extract a ZIP or 7z archive to a destination folder |
dir_size |
Total size, file count, folder count for directories |
get_md5 |
Compute MD5 checksum of a file |
check_permission |
Check write permission for a filename in a directory |
create_sharing_link |
Create a public sharing link (optional password + expiry) |
list_sharing_links |
List all sharing links (paginated table) |
delete_sharing_link |
Delete a sharing link by ID |
get_thumbnail |
Fetch a thumbnail for an image/video file as base64 |
list_favorites |
List all FileStation user favorites |
add_favorite |
Pin a path as a FileStation favorite |
delete_favorite |
Remove a path from FileStation favorites |
background_tasks |
List FileStation background tasks (copy, move, delete, etc.) |
list_snapshots |
List Btrfs snapshots for a share (requires Btrfs volume) |
See SPEC.md for full tool specifications and DSM call details.
DSM Quirks (continued)
SYNO.FileStation.Snapshot::list: Returns error 400 when the share is not on a Btrfs-formatted volume. Confirmed: this NAS has no Btrfs volumes —list_snapshotsmaps error 400 to a clear "requires Btrfs-formatted volume" message.SYNO.FileStation.BackgroundTask: Only thelistmethod is available (v1–v3). No stop/cancel/clear methods exist on this firmware.
Roadmap
v0.4 — candidates
No further tools currently planned.