Files
mcp-synology-filestation/SPEC.md
T
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

14 KiB
Raw Permalink Blame History

Technical Specification: mcp-synology-filestation

Overview

MCP server exposing Synology FileStation as Claude tools. Communicates with the DSM Web API over HTTPS. All file operations target a single NAS instance configured at setup time.


Architecture

CLI (click)
  └── AppConfig + AuthManager
        └── FileStationClient (httpx, async)
              └── FastMCP server
                    └── tools/filestation.py  (register_filestation)
  • Transport: MCP stdio (Claude Desktop integration)
  • HTTP: httpx async, HTTPS only in production
  • Session: DSM session ID (_sid) injected into every request; transparent re-auth on session expiry (codes 106, 107, 119) with exactly one retry
  • Config: ~/.config/mcp-synology-filestation/config.yaml
  • Credentials: OS keyring (service: mcp-synology-filestation)

Auth Flow

  1. mcp-synology-filestation setup collects NAS host, port, HTTPS flag, username, password.
  2. Credentials stored in OS keyring; connection config written to YAML.
  3. On serve, AuthManager.login() calls SYNO.API.Auth::login and holds the session ID in memory only — never written to disk or logged.
  4. If login requires 2FA: user runs setup interactively; the OTP + device token are handled by the setup wizard; the device token is stored in keyring for subsequent logins.
  5. Credential resolution order: env vars → OS keyring → (forbidden: plaintext config).

Environment variable overrides

Variable Purpose
SYNOLOGY_HOST Override NAS hostname
SYNOLOGY_USERNAME Override DSM username
SYNOLOGY_PASSWORD Override DSM password (not stored)

DSM API Endpoints

All requests use GET /webapi/entry.cgi with query parameters unless noted.

API Version Methods used
SYNO.API.Auth 7 login, logout
SYNO.FileStation.Info 2 get
SYNO.FileStation.List 2 list_share, list, getinfo
SYNO.FileStation.Search 2 start, list, stop, clean
SYNO.FileStation.Download 2 download
SYNO.FileStation.Upload 3 upload (POST multipart)
SYNO.FileStation.CreateFolder 2 create
SYNO.FileStation.Rename 2 rename
SYNO.FileStation.CopyMove 3 start, status, stop
SYNO.FileStation.Delete 2 start, status, stop

DSM returns a taskid from start; the client polls status with exponential backoff (initial 200 ms, max 2 s, timeout 60 s) until finished=true, then calls stop/clean.


Tools

Read-only

list_shares

List all shared folders visible to the authenticated user.

Parameters: none

Returns: Formatted table of share names, paths, and volume usage.

DSM call: SYNO.FileStation.List::list_share

additional=["volume_status"]

Note: DSM returns share paths directly (e.g. /dev, /data). The path field from the response is used as-is — do not prepend the volume prefix.


list_dir

List contents of a directory with optional pagination and sorting.

Parameters:

Name Type Required Default Description
path str yes Share-relative path (e.g. /dev, /data/photos)
offset int no 0 Number of items to skip
limit int no 100 Max items to return (max 500)
sort_by str no name name, size, user, group, mtime, atime, crtime, posix, type
sort_direction str no asc asc or desc

Returns: Table of entries (name, type, size, modified time). Includes total count for pagination context.

DSM call: SYNO.FileStation.List::list

folder_path={path}, offset={offset}, limit={limit},
sort_by={sort_by}, sort_direction={sort_direction},
additional=["size","time"]

Path requirement: folder_path must be a share path as returned by list_share (e.g. /dev, /data). Volume paths (/volume1/dev) are not accepted and cause DSM error 408.

additional format: Must be a JSON array serialised as a string (json.dumps(["size","time"])'["size", "time"]'). A comma-separated string ("size,time") is silently ignored by DSM — the additional field will be absent from every file entry.

SYNO.FileStation.Stat: Not available on this NAS's API registry. Use SYNO.FileStation.List::getinfo for per-path metadata instead.


get_info

Get detailed metadata for one or more files or folders.

Parameters:

Name Type Required Description
path str yes One or more share-relative paths, comma-separated

Returns: Table with type, size, owner, group, permissions (octal), modification time, creation time, and real volume path for each requested item.

DSM call: SYNO.FileStation.List::getinfo

path={comma-joined paths},
additional=["real_path","size","time","perm","owner","type"]

Note: SYNO.FileStation.Stat is not available on all NAS firmware versions and is absent from this NAS's API registry. SYNO.FileStation.List::getinfo returns identical data and is confirmed working.


Search for files matching a pattern within a directory.

Parameters:

Name Type Required Default Description
path str yes Root directory to search from
pattern str yes Filename glob pattern (e.g. *.log)
recursive bool no true Search subdirectories
max_results int no 200 Cap on returned matches

Returns: List of matching paths with type, size, and modification time.

DSM calls: SYNO.FileStation.Search::start → poll ::list::stop + ::clean

start: folder_path={path}, recursive={recursive}, pattern={pattern}
list:  taskid={taskid}, offset=0, limit={max_results}

download

Download a single file and return its content.

Parameters:

Name Type Required Description
path str yes Absolute path to the file on the NAS

Returns: Object with filename, size, content_base64 (base64-encoded file bytes). Files larger than 10 MB return an error suggesting sftp instead.

DSM call: SYNO.FileStation.Download::download (streaming GET)

path={path}, mode=download

Write (require explicit confirmation where noted)

create_folder

Create a new directory (and optionally all intermediate parents).

Parameters:

Name Type Required Default Description
path str yes Parent directory path
name str yes New folder name
create_parents bool no false Create missing parent directories

Returns: Path of the created folder or an error message.

DSM call: SYNO.FileStation.CreateFolder::create

folder_path={path}, name={name}, force_parent={create_parents}

rename

Rename a file or directory.

Parameters:

Name Type Required Description
path str yes Absolute path to the item
new_name str yes New filename (not a full path)

Returns: New absolute path after rename.

DSM call: SYNO.FileStation.Rename::rename

path={path}, name={new_name}

move

Move a file or directory to a new location.

Parameters:

Name Type Required Default Description
src str yes Source absolute path
dst str yes Destination directory path
overwrite bool no false Overwrite if destination exists

Returns: Destination path on success, or a descriptive error.

DSM call: SYNO.FileStation.CopyMove::start (async task)

path={src}, dest_folder_path={dst}, overwrite={overwrite}, remove_src=true

copy

Copy a file or directory to a new location.

Parameters:

Name Type Required Default Description
src str yes Source absolute path
dst str yes Destination directory path
overwrite bool no false Overwrite if destination exists

Returns: Destination path on success, or a descriptive error.

DSM call: SYNO.FileStation.CopyMove::start (async task)

path={src}, dest_folder_path={dst}, overwrite={overwrite}, remove_src=false

delete

Destructive — requires confirmed=True.

Delete a file or directory. Without confirmation, returns a preview of what would be deleted.

Parameters:

Name Type Required Default Description
path str yes Absolute path to delete
confirmed bool yes false Must be true to proceed

Returns:

  • confirmed=false: Preview message listing the path and item type.
  • confirmed=true: Success message or error detail.

DSM call: SYNO.FileStation.Delete::start (async task)

path={path}, recursive=true, accurate_progress=false

upload

Upload a file to the NAS from base64-encoded content.

Parameters:

Name Type Required Default Description
path str yes Destination directory path on the NAS
filename str yes Filename to create
content_base64 str yes Base64-encoded file content
overwrite bool no false Overwrite if a file with this name already exists
create_parents bool no true Create missing parent directories

Returns: Full path of the uploaded file or an error message. Files exceeding 50 MB should not be uploaded via MCP; return a clear error.

DSM call: SYNO.FileStation.Upload::upload (POST multipart/form-data)

path={path}, create_parents={create_parents}, overwrite={overwrite},
file=<binary content decoded from content_base64>

Error Handling Strategy

Principles

  1. DSM error codes are mapped to human-readable messages before surfacing to Claude.
  2. Never expose raw stack traces or session IDs in tool responses.
  3. Auth errors (codes 400403) trigger a clear message with a hint to run setup.
  4. Session expiry errors (106, 107, 119) are retried once transparently; if the retry also fails, the user sees "Session expired — please restart the MCP server."
  5. Network errors (timeouts, connection refused) are reported as "Cannot reach NAS at {host} — check connectivity."
  6. Unknown DSM error codes are reported as "DSM error {code}: {raw_message}".

DSM error code map

Code Meaning User message
100 Unknown error "DSM reported an unknown error."
101 Invalid parameter "Invalid parameter in request."
102 API does not exist "This DSM API is not available on your NAS."
103 Method does not exist "This DSM API method is not supported."
105 Permission denied "Permission denied — check your DSM account privileges."
106 Session timeout (transparent re-auth)
107 Session displaced (transparent re-auth)
119 Invalid session (transparent re-auth)
400 Invalid password "Login failed — check username and password (run setup)."
401 Guest or disabled account "DSM account is disabled."
403 2FA required "Two-factor authentication required — run setup."
404 2FA failed "OTP code incorrect."
408 Device token required "Device token required — run setup again."
1800 File not found "File or folder not found: {path}"
1801 No write permission "No write permission for: {path}"
1802 File exists "A file already exists at this path."
1803 Disk quota exceeded "Disk quota exceeded."
1804 No space left "No space left on the target volume."
1805 Filename too long "Filename too long."
1806 Illegal filename characters "Filename contains illegal characters."
1807 File is read-only "File is read-only."

Configuration Model

# ~/.config/mcp-synology-filestation/config.yaml
connection:
  host: dsm.gecheckt.de
  port: 443
  https: true
  verify_ssl: true
  timeout: 30

auth:
  username: marcus
  # password: never stored here — OS keyring only

AppConfig fields

Field Type Default Description
connection.host str required NAS hostname or IP
connection.port int 443/5000 DSM port
connection.https bool true Use HTTPS
connection.verify_ssl bool true Verify TLS cert
connection.timeout int 30 Request timeout (seconds)
auth.username str required DSM username

base_url is derived as {https_scheme}://{host}:{port}. keyring_service is fixed to "mcp-synology-filestation".


CLI Subcommands

setup

Interactive wizard:

  1. Prompt for NAS host, port, HTTPS, SSL verify.
  2. Prompt for username and password (masked).
  3. Attempt login; detect 2FA requirement.
  4. If 2FA: prompt OTP, request device token, store token in keyring.
  5. Verify FileStation API availability (SYNO.FileStation.List).
  6. Save config to ~/.config/mcp-synology-filestation/config.yaml.
  7. Print Claude Desktop snippet.

check

Validate config, resolve credentials, test login, list available FileStation APIs.

serve

Load config and credentials, create FileStationClient (lazy — no immediate connection), create and run FastMCP("mcp-synology-filestation") over stdio. Uses anyio.run() for Windows compatibility.