Skip to main content
Glama
README.md
# syncthing-mcp

MCP connector for a self-hosted [Syncthing](https://github.com/syncthing/syncthing) v2.1.7 GUI REST API. It speaks official `/rest` JSON over stdio and over stateless Streamable HTTP on localhost.

Syncthing is MPL-2.0 software (`LICENSE` in the v2.1.7 source and binary tree). This connector is MIT. It does not copy Syncthing source, man pages, or licence-restricted specs. It is not affiliated with syncthing/syncthing.

`spec/api-inventory.json` is a compact extraction of every GUI REST route registered in v2.1.7 `lib/api/api.go` plus expanded methods and `{id}` paths from `lib/api/confighandler.go`. It records the release version, MPL-2.0, `lib/api/api.go` sha256, `lib/api/confighandler.go` sha256, `man/syncthing-rest-api.7` sha256, and `routeTreeSha256`. `scripts/gen-operations.mjs` maps that inventory onto tool actions or exclusions and writes `src/operations.ts` and `syncthing.routes.json`. A route that is neither mapped nor excluded fails generation. Do not edit `src/operations.ts` by hand. Default generation and `npm run ci` read only the committed inventory.

`SYNCTHING_URL` and `SYNCTHING_API_KEY` are required. There is no default origin and no read of `~/.config/syncthing`. The connector does not start a Syncthing process. Each request sends `X-API-Key` as documented in `man/syncthing-rest-api.7` at v2.1.7. Official `/rest/noauth/health` is the unauthenticated health exception.

## Setup

```bash
npm install
npm run ci
npm start
npm run start:http
```

`npm start` speaks stdio. `npm run start:http` serves Streamable HTTP at `http://127.0.0.1:3000/mcp` and `GET /health`. HTTP mode has no authentication of its own. Keep it on localhost.

```json
{
  "mcpServers": {
    "syncthing": {
      "command": "node",
      "args": ["dist/index.js"],
      "env": {
        "SYNCTHING_URL": "http://127.0.0.1:8385",
        "SYNCTHING_API_KEY": "...",
        "SYNCTHING_ALLOW_WRITES": "false"
      }
    }
  }
}
```

The key is the GUI API key from the self-hosted instance. It is not a session cookie. The connector never prints it.

HTTP mode has no authentication of its own. Keep it on localhost. Do not put secrets in a Docker image.

```bash
docker build -t syncthing-mcp .
docker run -p 127.0.0.1:3000:3000 \
  -e SYNCTHING_URL=http://127.0.0.1:8385 \
  -e SYNCTHING_API_KEY=... \
  syncthing-mcp
```

Regenerate the registry from the committed inventory.

```bash
npm run gen:operations
node scripts/gen-operations.mjs --check
```

Refresh the inventory from an official v2.1.7 tree. This is the only command that reads route source. Keep that tree outside this MIT connector (for example `/workspace/tools/syncthing/upstream`).

```bash
node scripts/gen-operations.mjs --source-root /path/to/syncthing --write-inventory
node scripts/gen-operations.mjs --source-root /path/to/syncthing --check
```

`SYNCTHING_SOURCE_ROOT` is accepted by a write run. `--check` ignores that variable unless you also pass `--source-root`.

`scripts/live-check.mjs` talks to a real GUI over stdio. Set `SYNCTHING_URL`. Set `SYNCTHING_API_KEY` or `SYNCTHING_API_KEY_FILE` to a chmod 600 file outside this repo. The script unsets `DATABASE_URL`. It prints pass or fail lines only, and it redacts the key. It does not run inside `npm run ci`. A missing key is a failure.

Isolated local runtime scripts live under `/workspace/tools/syncthing` (`start.sh`, `stop.sh`). They generate their own config under `/tmp/syncthing-mcp-runtime`, never `~/.config/syncthing`.

## Config

| Variable                  | Meaning                                                                                                           |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `SYNCTHING_URL`           | Required. Origin only, `http` or `https`, no userinfo, query, hash, or path. Example `http://127.0.0.1:8385`.     |
| `SYNCTHING_API_KEY`       | Required unless `SYNCTHING_API_KEY_FILE` is set. Sent as `X-API-Key`. Redacted in logs, errors, and tool results. |
| `SYNCTHING_API_KEY_FILE`  | Optional. chmod 600 file whose trimmed contents are the API key. Used when `SYNCTHING_API_KEY` is omitted.        |
| `SYNCTHING_ALLOW_WRITES`  | Must be the literal `true` before a confirmed write is sent.                                                      |
| `SYNCTHING_PER_MINUTE`    | Local request budget. Default 30. Cache hits do not spend it. Live-check sets 600.                                |
| `SYNCTHING_TIMEOUT_MS`    | Upstream timeout. Default 20000.                                                                                  |
| `SYNCTHING_CACHE_TTL_SEC` | Read cache TTL. Default 60. Writes clear the cache.                                                               |
| `SYNCTHING_CONTACT`       | Contact token in the User-Agent. Must not contain the API key.                                                    |
| `MCP_TRANSPORT`           | `stdio` or `http`. `--http` overrides.                                                                            |
| `PORT`                    | HTTP port. Default 3000.                                                                                          |

Invalid values are reported by field name and issue code. The rejected value is not included.

Calls go to `/rest/...` paths from the operations registry. `Accept` is `application/json`. JSON writes send `Content-Type: application/json`. Redirects are refused so the key is not forwarded. There are no automatic retries, including HTTP 429. The API key is never placed in the URL, query, or User-Agent.

## Safety

Reads run without a write flag. A write is sent only when `confirm` is the boolean `true` and `SYNCTHING_ALLOW_WRITES=true`. That gate covers every mutation in the registry: folder and device add/patch/remove, options patch, scan, pause, resume, error clear, restart, and shutdown. Omit `confirm`, or pass boolean `false`, and the tool returns a dry-run preview of method, path, query, and body. It sends nothing. `blocked` is false on that preview, whether or not writes are enabled. `confirm: true` with writes disabled returns the same preview with `blocked` true and the reason `SYNCTHING_ALLOW_WRITES is not true. The write was not sent.` The string `"true"` is rejected.

Omitted fields are dropped. `paused: false`, `delay: 0`, and an empty `sub` are sent when you set them. Top-level `document: null` is rejected because `document` must be a JSON object. Nested `null` values inside that object are sent as JSON `null`. A field that is not valid for the selected action is rejected before any request is sent. Unknown fields are rejected.

HTTP 401 is unauthorized. HTTP 403 is forbidden. HTTP 400 and 422 are invalid input. HTTP 404 is not found. HTTP 409 is conflict. HTTP 429 includes Retry-After when the header is present. Other failures are server, network, or timeout. Response bodies are capped at 1,000,000 bytes.

Every non-empty string or number under a sensitive key on that request is a secret, at any nesting depth and inside arrays or objects below that key, including 1-character values and values longer than 64 characters. Sensitive keys include `apiKey`/`apikey`, `password`, `passwordHash`, `bindPassword`, `encryptionPassword`, `token`, `secret`, `session`, `cookie`, `authorization`, `credential`, `passphrase`, `privateKey`, and similar names. Two real Syncthing settings that match the pattern but hold no credential, `gui.sessionCookieDurationS` and `gui.sessionCookiePath`, are returned unchanged. The configured API key (minimum 8 characters) and any URL userinfo found in the request are included too. Secrets are collected once per request and are not kept for the next call.

Sensitive-key values become `[redacted]` in objects. Substring masking of free text (raw, percent-encoded, and JSON-escaped forms) runs only for secrets of length 8 or more, the same floor as the API key. Percent-encoded forms match upper-case and lower-case hex. URL userinfo (`scheme://user:pass@`) in any returned or logged string becomes `scheme://[redacted]@`, so device addresses in `GET /rest/config` and its cached copy carry no credentials. If the request carries a shorter credential, upstream free-text error bodies and log detail are replaced with `upstream detail omitted because this request carried a short credential` instead of substring replacement, which would mangle this connector's own words and still leak. Object keys and this connector's preview envelope (`method`, `path`, `summary`, `executed`, and the other write-gate fields) are not rewritten. That rule covers MCP success results, MCP error text, write previews, HTTP and network errors, every logger line (including lines emitted inside the HTTP client), and config GET output.

`GET /rest/config` returns `{ config, warnings }`. `gui.apiKey`, `gui.password`, and `passwordHash` become `[redacted]` placeholders. The warning is `gui.apiKey, gui.password, and passwordHash are redacted placeholders. Do not PUT this document back. Use PATCH on folders, devices, or options.` Full `PUT /rest/config` and GUI put/patch are excluded so a redacted document cannot be written back through this connector.

POST add takes one JSON object (`document`). Official collection PUT routes that expect JSON arrays are excluded as whole-list replace. PATCH merge is the registered edit so `false`, `0`, and empty strings can be sent without wiping sibling keys.

## Tools

Five tools. Each takes `action` plus the fields for that action. Fields for a different action are rejected.

| Tool                | Actions                                                                                                      | Routes                                                                                                                                                                                                                                                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `syncthing_system`  | health, ping, status, version, connections, discovery, errors, error_clear, pause, resume, restart, shutdown | GET `/rest/noauth/health`, GET `/rest/system/ping`, GET `/rest/system/status`, GET `/rest/system/version`, GET `/rest/system/connections`, GET `/rest/system/discovery`, GET `/rest/system/error`, POST `/rest/system/error/clear`, POST `/rest/system/pause`, POST `/rest/system/resume`, POST `/rest/system/restart`, POST `/rest/system/shutdown` |
| `syncthing_config`  | get, restart_required, options, patch_options                                                                | GET `/rest/config`, GET `/rest/config/restart-required`, GET `/rest/config/options`, PATCH `/rest/config/options`                                                                                                                                                                                                                                    |
| `syncthing_folders` | list, get, add, patch, remove, pending                                                                       | GET `/rest/config/folders`, GET `/rest/config/folders/{id}`, POST `/rest/config/folders`, PATCH `/rest/config/folders/{id}`, DELETE `/rest/config/folders/{id}`, GET `/rest/cluster/pending/folders`                                                                                                                                                 |
| `syncthing_devices` | list, get, add, patch, remove, pending                                                                       | GET `/rest/config/devices`, GET `/rest/config/devices/{id}`, POST `/rest/config/devices`, PATCH `/rest/config/devices/{id}`, DELETE `/rest/config/devices/{id}`, GET `/rest/cluster/pending/devices`                                                                                                                                                 |
| `syncthing_db`      | status, need, file, errors, completion, scan, folder_stats, device_stats                                     | GET `/rest/db/status`, GET `/rest/db/need`, GET `/rest/db/file`, GET `/rest/folder/errors`, GET `/rest/db/completion`, POST `/rest/db/scan`, GET `/rest/stats/folder`, GET `/rest/stats/device`                                                                                                                                                      |

`health` calls GET `/rest/noauth/health` and returns the JSON body. v2.1.7 responds `{"status":"OK"}`. The connector's own Streamable HTTP `GET /health` is separate and returns the plain text `ok`. `add` and `patch` need `document` as a JSON object. `get`, `patch`, and `remove` need `id`. db `status`, `need`, `file`, `errors`, and `scan` take `folder`. `file` also needs `file`. `completion` takes `folder` and/or `device`. `pause` may take `device`. `scan` may take `sub` and `delay`, including `delay: 0` and `sub: ""`.

36 of 97 expanded v2.1.7 REST routes are mapped. 61 are excluded. The registry has 36 actions and 13 writes. See `syncthing.routes.json` for every mapping and exclusion reason.

## Coverage and exclusions

Useful JSON management is in scope: system health/version/status/connections/discovery, folder and device CRUD plus pending reads, folder status/need/file/errors/statistics, scan, pause/resume, restart/shutdown, config get and options patch.

Excluded with a stored reason, grouped:

- Auth and session: `/rest/noauth/auth/password`, `/rest/noauth/auth/logout`.
- GUI listener and secrets: GET/PUT/PATCH `/rest/config/gui`. Changing listen address or API key can shut the connector out.
- Full replace: PUT `/rest/config`, PUT collection `/rest/config/folders` and `/rest/config/devices` (JSON arrays), PUT per-id folder/device, PUT/POST deprecated `/rest/system/config`.
- LDAP bind credentials: GET/PUT/PATCH `/rest/config/ldap`.
- Defaults and ignores: `/rest/config/defaults/*`, GET/POST `/rest/db/ignores`.
- Debug, profile, logs, support dumps: `/rest/debug/*`, `/rest/system/log`, `/rest/system/log.txt`, `/rest/system/loglevels`.
- Host filesystem: `/rest/system/browse`, `/rest/db/browse`, `/rest/system/paths`.
- Self-healing and file restore: POST `/rest/db/override`, POST `/rest/db/revert`, POST `/rest/folder/versions`.
- Upgrade, reset, events: POST/GET `/rest/system/upgrade`, POST `/rest/system/reset`, GET `/rest/events`, GET `/rest/events/disk`.
- Deprecated aliases: `/rest/config/insync`, `/rest/system/config/insync`, `/rest/folder/pullerrors`, POST `/rest/system/ping`.
- Helpers outside lean management: `/rest/svc/*`, `/rest/db/prio`, `/rest/db/localchanged`, `/rest/db/remoteneed`, pending DELETE reject, POST `/rest/system/error`.

There is no arbitrary HTTP method/path passthrough.

## Source provenance

Official latest stable release checked via the GitHub releases API: **v2.1.7**, released 21 Oct 2026. Linux amd64 binary:

`https://github.com/syncthing/syncthing/releases/download/v2.1.7/syncthing-linux-amd64-v2.1.7.tar.gz`

Checksum manifest `sha256sum.txt.asc` alongside. Verified tarball sha256 `2edc416e7ed136b7c858ed5f3f02a3b8316589605e54c380228ceea7acbf0142`.

Pinned inventory hashes:

| File                       | sha256                                                             |
| -------------------------- | ------------------------------------------------------------------ |
| `lib/api/api.go`           | `734cade4aeb44688d95be996fbb1e6e394a0eb802af606cd3d52c733f64469f7` |
| `lib/api/confighandler.go` | `0b3fabaff28c8ffe0246dcba20eb860076e9360f7ada62fcd4f6f32c08ac733b` |
| `man/syncthing-rest-api.7` | `1e4c1dc81e9f40d4bcb64bcebf5239655c3d4fcddcca8b9cfa5e1b524e1efe08` |
| route tree                 | `2f0d516aac510c86b05c327e02deccbca177d40170eada0515ac16137f8c63fb` |

The man page confirms REST on the GUI port, the `X-API-Key` header, and `/rest/noauth` as the health exception. Public docs URLs returned 404 to the fetch used during this build; inventory comes from the release-pinned source and man page instead.

## Other MCP servers

Read 8 Nov 2026. Folder/device CRUD, scan, pause, resume, restart, and config already exist in the strongest rivals. Those API operations are not unique here.

[nicholaswilde/syncthing-mcp-rs](https://github.com/nicholaswilde/syncthing-mcp-rs) (`main` README, about 48 tools) is a full management server: patches, dry-run semantic previews on `patch_instance_config` and `replicate_config`, GUI credential masking, tests, multi-instance, and HTTP auth. It is not a read-only toy.

- [`src/tools/devices.rs`](https://raw.githubusercontent.com/nicholaswilde/syncthing-mcp-rs/main/src/tools/devices.rs) `manage_devices`: `add` (72–83), `remove` (85–95), `pause` (97–109), `resume` (111–123), and `approve` (53–70) call `client.add_device` / `remove_device` / `patch_device` immediately. There is no `confirm` argument and no write-environment gate on those actions.
- README `maintain_system` force-rescans, restarts, or shuts down. Source `maintain_system` also has a `"shutdown"` arm that calls `client.shutdown().await?` and an upgrade arm.
- README advertises automatic API retry. [`src/api/client.rs`](https://raw.githubusercontent.com/nicholaswilde/syncthing-mcp-rs/main/src/api/client.rs) uses `tokio_retry` `send_with_retry`, `retry_max_attempts.unwrap_or(3)`, and `.danger_accept_invalid_certs(config.no_verify_ssl.unwrap_or(true))`.
- README also lists `delete_conflict`, `resolve_conflict`, `monitor_self_healing`, `browse_folder`, and `perform_upgrade`. This connector excludes conflict-delete helpers, automatic self-healing mutations, host browsing, and upgrades.

[Zdendys79/mcp-server-syncthing](https://github.com/Zdendys79/mcp-server-syncthing) (`master` README plus [`src/index.ts`](https://raw.githubusercontent.com/Zdendys79/mcp-server-syncthing/master/src/index.ts)): eight tools (`status`, folders, folder status, folder errors, file info, scan, devices, connections). `SYNCTHING_API_URL` defaults to `http://localhost:8384`. The `syncthing_scan_folder` handler (lines 312–320) builds `/rest/db/scan?...` and `await syncthingApi(endpoint, "POST")` with no confirm or env gate. Uniform code-level write gating is a verified advantage against this server, not only a client UI approval.

[epichub/syncthing-mcp](https://github.com/epichub/syncthing-mcp) README (8 Nov 2026) advertises **35 tools** and clones [zaphodsdad/syncthing-mcp](https://github.com/zaphodsdad/syncthing-mcp). It already has folder/device CRUD, pause/resume, restart, full config, `scan_folder`, `override_folder`, and `revert_folder`. Those operations are not missing there. This connector maps override/revert as excluded self-healing mutations.

[gladcow/syncthing_mcp_srv](https://github.com/gladcow/syncthing_mcp_srv) (`master` README): one `sync` tool plus `syncthing://devices` and `syncthing://folders` resources. Default `SYNCTHING_HOME` is `~/.config/syncthing`. "If Syncthing is not already running, the server starts it automatically on the first request." This connector does not read the user config directory and does not auto-start Syncthing.

Honest differences that still hold after those reads:

- Every mutation, including scan, needs boolean `confirm: true` **and** `SYNCTHING_ALLOW_WRITES=true`.
- Omitted or false `confirm` always returns a method/path/query/body preview with zero HTTP writes.
- No automatic retries (especially 429). TLS verification is not disabled.
- Origin and API key are required and explicit. No `~/.config/syncthing` and no auto-start.
- No conflict-delete helpers, override/revert, upgrade, or host filesystem browsing.
- Source-pinned v2.1.7 inventory with an offline generator `--check`.

nicholaswilde already has dry-run on some config patches, GUI masking, and tests. It does not apply a uniform confirm-plus-env gate on device add/remove/pause or on `maintain_system` restart/shutdown. If a later rival adds that same two-key gate on every write, this would no longer be a unique protection.

## Limitations

- Collection PUT routes that expect JSON arrays are excluded. Add one folder or device with POST `document` as an object. Edit with PATCH.
- `GET /rest/config` redacts GUI secrets. Do not treat the returned document as a PUT body.
- Live bad API keys from v2.1.7 returned HTTP **403** `CSRF Error`, not 401. Missing folder ids returned HTTP **404** `No folder with given ID`. Unit tests still simulate 401, 422, and 429.
- REST `POST /rest/system/restart` forks a new process. A pidfile written at serve start can become stale; `stop.sh` also stops the verified binary still listening on the GUI port.
- The connector never starts or stops the user's Syncthing. Isolated live scripts under `/workspace/tools/syncthing` are throwaway only.
- A short credential (under 8 characters) that is spelled the same as an object key or a word in this connector's own preview envelope still appears there as that key or word. Values that contain it are redacted, and upstream free text is replaced. Use credentials of 8 or more characters.
- Events, logs, debug profiles, LDAP, GUI settings, ignores, restore, and upgrades stay out of the tool list.

## Live check

See `LIVE-CHECK.md` for the checks that actually ran, including first-start notes. `npm run live-check` is not part of `npm run ci`.

`npm run ci` typechecks, lints, tests, and builds. `node scripts/gen-operations.mjs --check` reports 36 mapped routes, 61 excluded, 36 tool actions, and 97 spec routes.

Run `unset DATABASE_URL`, set `SYNCTHING_URL`, and set `SYNCTHING_API_KEY` or `SYNCTHING_API_KEY_FILE`. Then run `npm run live-check`. The script never prints the API key.

## License

MIT. Syncthing itself is MPL-2.0. This connector only talks to the published GUI REST API.

TDQS

A3.8/5.0

Scored across 5 tools

Disambiguation4/5

The five tools are cleanly partitioned by resource domain (system, config, folders, devices, db), so an agent can usually pick the right one from the noun alone. There is some residual overlap: syncthing_system exposes status/errors while syncthing_db exposes folder status/errors/completion, and syncthing_folders.get vs syncthing_db.folder_stats could be confused without reading the action lists.

Naming Consistency4/5

All tools share the syncthing_ prefix plus a single domain noun, which is predictable and readable. The names are noun-only rather than verb_noun, and syncthing_db breaks the 'config resource' pattern slightly, so the verb is hidden inside the action parameter.

Tool Count5/5

Five tools for a Syncthing admin surface is well-scoped: each domain (system, config, folders, devices, db) earns its place and nothing looks redundant. The heavy action multiplexing is handled inside each tool rather than by inflating the tool count.

Completeness4/5

Coverage is strong: read/write lifecycle for folders and devices, config read/patch, pending offers/requests, and runtime status, need, completion, and scan. Minor gaps exist for event/log streaming and version upgrade operations, but agents can work around them.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues