Files
mcp-synology-filestation/CLAUDE.md
T

181 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
1. Commit and push via Claude Code.
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` |
## 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.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
- 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`, `move` with `overwrite=True`, `copy` with `overwrite=True`, and `upload` with
`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`.
## 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 (400403): return message + "Run `mcp-synology-filestation setup` to
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`, `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 ~68 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
└── tools/
├── __init__.py
└── filestation.py # register_filestation(mcp, config, client)
```
## 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 |
| `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 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`|