From ff79d438b05530fe9b95702af3b9a383770c92a3 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Tue, 14 Apr 2026 16:51:29 +0200 Subject: [PATCH] docs: update CLAUDE.md for v0.3 --- CLAUDE.md | 147 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 37 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c1fd43b..28c81d7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,32 +14,57 @@ Sibling project to `mcp-synology-container`. - **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, `httpx`, `keyring`, `click`, `rich` +- **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 1. Commit and push via Claude Code. -2. `uv tool install --reinstall git+https://gitea.gecheckt.de/marcus/mcp-synology-filestation.git` -3. Restart Claude Desktop. +2. **Close Claude Desktop first** (Windows holds file locks — reinstall fails otherwise). +3. Install: + ``` + uv tool install --reinstall dist\mcp_synology_filestation-X.Y.Z-py3-none-any.whl + ``` + Or directly from Gitea: + ``` + uv tool install --reinstall git+https://gitea.gecheckt.de/marcus/mcp-synology-filestation.git + ``` +4. Restart Claude Desktop. +5. Verify: `uv tool list` shows 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` | +| 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) on all public modules, classes, and functions. +- **Docstrings** (English, 1 line max for `@mcp.tool()` functions) on all public modules, + classes, and functions. - **Async-first:** use `httpx.AsyncClient` throughout; never `requests`. - **Formatter:** `ruff format` (line length 100). - **Linter:** `ruff check` — fix all warnings before committing. +- **No `-> str` on `@mcp.tool()` functions.** FastMCP generates `outputSchema` from return + annotations, which bloats the `tools/list` payload 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/list` payload. ## Security Rules @@ -53,8 +78,8 @@ Sibling project to `mcp-synology-container`. `overwrite=True` are considered destructive. - The `delete` tool MUST require `confirmed=True` to 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 default `overwrite` to `False`. +- For overwrite scenarios in `move`/`copy`/`upload`, include a warning in the tool + description and default `overwrite` to `False`. ## Error Handling Rules @@ -72,36 +97,84 @@ Sibling project to `mcp-synology-container`. - 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`, `name` etc. + must be passed as `json.dumps("/path")` or `json.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. +- **`additional` field:** must be `json.dumps(["size","time"])` — comma-separated string + does not work. +- **`List::list` additional:** only `["size","time"]` confirmed working on this firmware. + `real_path`, `perm`, `type` cause error 408. +- **`SYNO.FileStation.Stat`:** not in API registry — use `List::getinfo` instead. +- **`SYNO.FileStation.CheckExist`:** returns error 400 — use `List::getinfo` instead + (entries with `name=null` do not exist). +- **DirSize / MD5 one-shot:** `finished=true` is returned exactly once, then the task is + deleted. Implemented via `_start_and_poll_oneshot()` — never poll again after + `finished=true`. +- **DirSize cold start:** after inactivity, DSM's background service needs ~6–8 s to + initialise. During this window every `status` poll returns 599. + Fix: restart the task (new `start` call) — up to 6 restarts within a 60 s budget. +- **DirSize `status` version:** must use `version=1`. Version 2 always returns 599. +- **`Sharing::delete` id parameter:** must be `json.dumps(link_id)`. +- **`CheckPermission::write`:** `path` and `filename` are plain strings (no `json.dumps`). + Returns `{"blSkip": false}` on success. +- **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 +├── __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) + └── filestation.py # register_filestation(mcp, config, client) ``` -## Implemented Tools +## Implemented Tools (v0.2.10 — 20 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 | -| `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 default) | -| `move` | Move a file or folder (async polling, overwrite=False default) | -| `delete` | Delete a file or folder — requires confirmed=True | -| `upload` | Upload base64-encoded content to a path (max 50 MB) | +| 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 | -See [SPEC.md](SPEC.md) for the full planned tool set. +See [SPEC.md](SPEC.md) for full tool specifications and DSM call details. + +## Roadmap + +### v0.3 — next +| Tool | API | +|--------------------|----------------------------------| +| `get_thumbnail` | `SYNO.FileStation.Thumb` | +| `list_favorites` | `SYNO.FileStation.Favorite` | +| `add_favorite` | `SYNO.FileStation.Favorite` | +| `delete_favorite` | `SYNO.FileStation.Favorite` | +| `list_snapshots` | `SYNO.FileStation.Snapshot` | +| `background_tasks` | `SYNO.FileStation.BackgroundTask`|