Commit Graph

22 Commits

Author SHA1 Message Date
marcus 1d0cf940b4 feat: add dir_size and get_md5 tools
dir_size: SYNO.FileStation.DirSize v2 — async scan of one or more
directories, returns folder/file count and total size as a table.
get_md5: SYNO.FileStation.MD5 v2 — async MD5 checksum of a file.
Both follow the existing _poll_task pattern. 9 new unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:10:51 +02:00
marcus fc706fb809 perf: shorten all tool docstrings to reduce tools/list payload
Reduced tools/list JSON payload from ~45 KB to 5.7 KB by replacing
verbose multi-paragraph docstrings with 1-2 line summaries on all
14 @mcp.tool() functions. Fixes Claude Desktop truncation of tools/list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:58:11 +02:00
marcus 500dc73324 fix: remove -> str return annotations from all mcp.tool() functions
FastMCP generates an outputSchema from the return type annotation, which
inflates the tools/list payload and triggers truncation in Claude Desktop.
Dropping the annotations suppresses outputSchema generation entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:49:04 +02:00
marcus 473c771c20 feat: add compress and extract tools
Implements SYNO.FileStation.Compress (v3) and SYNO.FileStation.Extract (v2)
with async polling identical to copy/move. Includes input validation for
compress (level, mode, format, empty paths) and 11 new unit tests.
Bumps version to 0.2.0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:26:08 +02:00
marcus dbab842738 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>
v0.1.1
2026-04-14 10:49:50 +02:00
marcus 80ac894165 feat: add create_folder, rename, copy, move, delete, upload tools
All path/name params are json.dumps-wrapped per confirmed DSM behaviour.
copy and move use async polling via a shared _poll_task helper
(exponential backoff 200ms→2s, 60s timeout). delete requires
confirmed=True; without it only a preview is returned and no DSM call
is made. upload decodes base64 and enforces a 50 MB cap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.1.0
2026-04-14 10:28:32 +02:00
marcus 544cfb8b06 fix: retain last non-empty result set in search polling loop
DSM sends files=[] on the final finished=True poll even when results
exist in earlier rounds. Overwriting files each iteration discarded
the real results, producing a false "No files found" response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:48:05 +02:00
marcus c47221dc8f feat: add search and download tools
Implements SYNO.FileStation.Search with async polling (exponential
backoff 200ms→2s, 60s timeout) and SYNO.FileStation.Download with
base64 output and a 10 MB size cap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:43:48 +02:00
marcus 1bccf1e5d2 fix: use json.dumps(paths) for getinfo multi-path, delete probe script
DSM accepts multiple paths as a JSON array string, not comma-separated.
Comma-separated is treated as a single literal path; repeated path[]
params return error 400. Confirmed via test_getinfo_multipath.py.

- get_info: path param changed from ",".join(paths) to json.dumps(paths)
- tests: update multi-path assertion to expect JSON array format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:32:11 +02:00
marcus a96a3d460d debug: probe getinfo multi-path formats (comma, path[], JSON array)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:30:53 +02:00
marcus 014af1aefe feat: add get_info tool using SYNO.FileStation.List::getinfo
SYNO.FileStation.Stat is absent from this NAS's API registry.
SYNO.FileStation.List::getinfo returns identical data and is confirmed
working.

- tools/filestation.py: new get_info tool — accepts one or more
  comma-separated paths, calls getinfo with real_path/size/time/perm/
  owner/type additional fields, returns a 9-column table
- tests: 6 new tests covering single file, directory, multi-path,
  empty input, DSM error, and correct API method assertion
- SPEC.md: remove SYNO.FileStation.Stat from API table, rewrite get_info
  tool section to reference getinfo, update list_dir note
- CLAUDE.md: update Implemented Tools list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:28:04 +02:00
marcus 8fc2f731ce fix: restore additional=["size","time"] to list_dir, update SPEC + tests
Root cause of DSM 408 was wrong path format (volume path vs share path),
not the additional parameter. Confirmed via test_additional.py that
json.dumps(["size","time"]) is the correct format; comma-separated string
is silently ignored by DSM.

- list_dir: restore 4-column table (Name, Type, Size, Modified)
- list_dir: use additional=json.dumps(["size","time"]) (confirmed working)
- SPEC.md: document share path requirement, additional format rules,
  note SYNO.FileStation.Stat unavailability, remove comma-sep gotcha
- tests: restore size/mtime mock data and assertions
- delete test_additional.py (throwaway diagnostic script)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:21:52 +02:00
marcus 4c093589c1 chore: add throwaway script to probe additional field formats
test_additional.py tests SYNO.FileStation.List::list with three variants:
no additional, comma-separated string, and JSON array — bypasses client
error mapping to log the raw DSM response for each. Delete after use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:16:38 +02:00
marcus a3e1a557c3 fix: use share paths, drop additional from list_dir, remove debug logging
Root cause: DSM expects share paths (/dev) not volume paths (/volume1/dev).
The 408 errors were triggered by any additional field on the wrong path format.

- list_shares: use share["path"] directly (e.g. /dev), drop real_path from
  additional — only volume_status remains
- list_dir: remove additional parameter entirely; table now shows name + type
  (isdir is returned by default); update docstring to show share path examples
- client.py: remove diagnostic REQUEST and RAW ERROR stderr logging
- tests: update assertions to match share paths and two-column table output

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 09:11:10 +02:00
marcus cb92606217 debug: test additional as comma-separated string (no json.dumps) 2026-04-14 09:00:51 +02:00
marcus 2773ef235e debug: test additional=["size","time"] 2026-04-14 08:59:12 +02:00
marcus ae87edc737 debug: test additional=["time"] only 2026-04-14 08:59:03 +02:00
marcus 9a1f2d5968 debug: test additional=["size"] only 2026-04-14 08:58:55 +02:00
marcus e6c0acd779 debug: log raw DSM error response and API version; remove additional from list_dir
Diagnostic build to identify root cause of 408 on list_dir:
- client.py: log resolved version + NAS maxVersion before every request
- client.py: log full raw body on every error response
- tools/filestation.py: remove additional parameter entirely to test if
  any additional field triggers 408, or if the issue is elsewhere

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:52:41 +02:00
marcus 3a8332da95 fix: remove unsupported additional fields from list_dir
SYNO.FileStation.List::list returns error 408 ("Non-supported additional
field") for real_path, perm, and type. Reduce additional to ["size","time"]
— the only fields reliably supported across DSM versions. isdir is already
present in the default response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:44:24 +02:00
marcus 59301ae760 feat: implement auth infrastructure and first two FileStation tools
- auth.py: AuthManager with OS keyring, env var fallback, 2FA device token flow
- config.py: AppConfig/ConnectionConfig dataclasses, YAML load/save, env overrides
- client.py: FileStationClient with lazy init, session re-auth, upload/download
- cli.py: setup / check / serve subcommands (anyio.run throughout)
- server.py: create_server factory wiring FastMCP to FileStation tools
- tools/filestation.py: list_shares and list_dir with ASCII table output,
  pagination hints, input validation, DSM error mapping
- tests: 30 unit tests, all passing (auth, config, tools)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:23:34 +02:00
marcus 9fc5a3d68c feat: initial project structure 2026-04-14 07:51:51 +02:00