proton-drive-mcp 1.0.39 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +76 -11
- package/dist/cli.js +60 -13
- package/dist/index.js +144 -233
- package/dist/services/drive.js +136 -34
- package/dist/utils/claudeConfig.js +126 -0
- package/dist/utils/doctor.js +122 -0
- package/dist/utils/errors.js +3 -1
- package/dist/utils/localguard.js +2 -1
- package/dist/utils/subprocess.js +88 -3
- package/dist/utils/syncfs.js +19 -3
- package/dist/utils/validation.js +4 -2
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0 — 2026-09-28
|
|
4
|
+
|
|
5
|
+
Setup, packaging and reliability round. Five features were built in parallel on separate branches, merged, then verified live (real account, 3 concurrent servers), on Node 22/24 and on the new Ubuntu + macOS CI matrix, plus an independent code review. Review and CI findings were fixed before release.
|
|
6
|
+
|
|
7
|
+
**Breaking:** Node 20 is no longer supported (`engines` is `>=22`; Node 20 is past end-of-life). It may still start, but it is not tested and `doctor` fails on it.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`proton-drive-cli doctor`**: checks Node, the `proton-drive` CLI, login state, sync folder and the Claude Desktop config; `--json` output; exits 1 on failure. It only reports on our own config entry, never other servers' entries.
|
|
11
|
+
- **`proton-drive-cli setup-claude-desktop`**: prints (default) or writes (`--write`) the Claude Desktop entry with absolute paths. Touches only `mcpServers["proton-drive-mcp"]`, keeps a timestamped `.bak`, writes atomically, skips the write if nothing changed, and refuses to run from a temporary `npx` cache or on invalid JSON.
|
|
12
|
+
- **MCPB bundle** (`npm run package:mcpb`): one-click Claude Desktop extension attached to each GitHub release (macOS and Linux only; installing it into Claude Desktop is untested).
|
|
13
|
+
- **`PROTON_DRIVE_TOOL_TIER=core`**: exposes 16 everyday tools (~13 KB of tool definitions instead of ~29 KB). Hidden tools are refused at call time. Tool descriptions were also shortened; every safety note was kept.
|
|
14
|
+
- **Retry of read-only calls** on rate limiting, timeouts and network errors (max 3 attempts, bounded by the call timeout). Mutating and transfer commands are never retried.
|
|
15
|
+
- **CI**: Ubuntu and macOS × Node 22/24, non-blocking Windows probe; publish workflow is ready for npm Trusted Publishing (the `NPM_TOKEN` fallback stays until one OIDC publish has succeeded).
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- An empty `PROTON_DRIVE_BIN` (some clients pass `""` for an optional setting) is now treated as unset.
|
|
19
|
+
- The README Claude Desktop snippets use the same server key as `setup-claude-desktop` (`proton-drive-mcp`), so `doctor` finds it.
|
|
20
|
+
- Local-path guard: an unreadable ancestor directory (such as `/var/root` for a non-root user) no longer aborts the denylist check with a raw `EACCES`.
|
|
21
|
+
|
|
22
|
+
### Known limitations
|
|
23
|
+
- Windows is not supported (tests never passed there; only probed in CI).
|
|
24
|
+
- `drive_share_leave`, `drive_invitation_accept` and `drive_invitation_reject` have never been tested live (no second Proton account).
|
|
25
|
+
- The MCPB bundle and the npm OIDC publish path have not been exercised against the real Claude Desktop / npm yet.
|
|
26
|
+
|
|
27
|
+
## 1.1.0 — 2026-09-28
|
|
28
|
+
|
|
29
|
+
A "test it like Mail Bridge" round: 6 agents live-tested every tool group against a real account for the first time at scale — full-byte round trips up to 200 MB, the real Proton Drive sync folder, real photo uploads/albums, three MCP servers sharing one CLI session concurrently, official MCP clients (SDK, Inspector, Node 20/22/24, a packed-and-installed tarball), and adversarial edge cases. 3 high-severity and 7 medium-severity bugs were found, fixed by 5 agents working in parallel on separate branches, then the merged result was re-verified live by another 5 agents (one an independent code review) before anything shipped. Every fix below was reproduced from the original bug, not assumed from a commit message.
|
|
30
|
+
|
|
31
|
+
**Behavior changes:** `drive_list`/`drive_list_trash`/`photos_list_timeline`/`photos_list_album_photos` now return items in a stable sorted order; `drive_list`'s `size` is the real file size (the old value is `storageSize`); `drive_restore`/`drive_delete` accept an optional `uid`; `drive_read_file`/`drive_write_file` interpret a `/my-files/...` path as documented.
|
|
32
|
+
|
|
33
|
+
### Fixed — high severity
|
|
34
|
+
- **`drive_list` pagination lost and duplicated items.** Each page re-ran the CLI, whose item order isn't stable, so a page slice could return the same item twice or skip one entirely while `total` still looked correct. Confirmed live: walking a 1,000-file folder returned 998 of 1,000 unique items. `drive_list`, `drive_list_trash`, `photos_list_timeline` and `photos_list_album_photos` now sort deterministically before paginating (name, then trash time, then capture time — each with a uid tie-break) and their descriptions say each page is a fresh read.
|
|
35
|
+
- **`drive_restore`/`drive_delete` could act on the wrong item when a trash name repeated.** `drive_list_trash` documents using `uid` to tell duplicates apart, but neither tool accepted one. Confirmed live: restoring/deleting `/trash/x.txt` with two items named `x.txt` picked an arbitrary one — the delete case is irreversible. Both tools now accept `uid`, refuse an ambiguous path (listing every matching uid and trash time instead of guessing), and the same check now also covers `/photos-trash`. The underlying CLI can only address a trashed item by name, so a genuinely ambiguous duplicate still can't be targeted — resolve it in the Proton Drive web or desktop app.
|
|
36
|
+
- **`drive_read_file`/`drive_write_file` didn't map the documented `/my-files/...` path.** Following the example in the tool description created a local folder literally named `my-files`, which then synced to Drive as `/my-files/my-files/...`. `/my-files/<rest>` now maps to the sync root correctly; every other Drive root (`/photos`, `/trash`, ...) is rejected with a clear message, since only `/my-files` is synced locally.
|
|
37
|
+
|
|
38
|
+
### Fixed — medium severity
|
|
39
|
+
- **`photos_delete_album` deleted non-empty albums** despite its description promising a refusal without `force` — the check never existed. Now enforced, and fails closed (refuses) if the album's photo count can't be looked up at all, rather than assuming empty.
|
|
40
|
+
- **`photos_download` advertised `/albums/<album>/<photo>` paths that the CLI can't resolve.** Now rejected up front with a message pointing at the `/photos/<name>` form.
|
|
41
|
+
- **`drive_list`'s `size` was the encrypted storage size summed across every revision**, not the file's real size (a 1-byte file showed 79; it doubled after a new revision). Real size is now `size`; the old value is `storageSize`.
|
|
42
|
+
- **`drive_move` misreported a missing source as "Destination already exists"**, because the destination was checked before the source. Now reports "Source not found" correctly, and hints at the fix when the "existing destination" is actually the target folder itself (a path/parent mix-up seen in live testing).
|
|
43
|
+
- **`drive_share_status` on a Drive root (`/my-files`, etc.) failed with a raw CLI decryption error**, which also broke this project's own live smoke test. Roots now get a clear "cannot be shared" error without calling the CLI.
|
|
44
|
+
- **A folder name ending in a backslash made its children unaddressable** (`tail\` + child `\/` is read as an escaped slash by the CLI, which has no way to escape a literal backslash). No fix exists upstream; documented as a known limitation.
|
|
45
|
+
- **The Claude Desktop config example didn't set `PROTON_DRIVE_BIN`**, and Claude Desktop launches servers with a minimal `PATH` that usually can't find `proton-drive`. The example now sets it, and the "CLI not found" error mentions the variable.
|
|
46
|
+
|
|
47
|
+
### Also fixed
|
|
48
|
+
- Non-UTF-8 text read through the sync folder was silently corrupted (replacement characters, no warning) — now a clear error.
|
|
49
|
+
- The invite-message limit was 2000 characters; the CLI enforces 500 — now matched.
|
|
50
|
+
- `drive_mkdir` accepted a spaces-only name while `drive_rename` rejected one — now consistent.
|
|
51
|
+
- `drive_share_remove_all` said "Removed all access" while an active public link stayed live — now says so, and how to remove it.
|
|
52
|
+
- Raw upstream error codes (`InvalidRequirementsAPIError` 2000, `APICodeError` 2511/2500) are now mapped to a readable sentence, with the code kept in parentheses; bundled CLI source lines and bare `Error details: {}` are stripped from error text.
|
|
53
|
+
- Read-only CLI calls now retry (up to twice) on the CLI's own `database is locked` error, seen live when multiple MCP clients share one CLI session concurrently; writes are never retried.
|
|
54
|
+
- `photos_list_album_photos` without `loadDetails` has no `captureTime`, so its description now says the order is by `nodeUid`, not capture time.
|
|
55
|
+
|
|
56
|
+
### Verified with no changes needed
|
|
57
|
+
Data integrity (sha256 round-trips up to 200 MB, 500+ file trees, every upload/download conflict strategy, `drive_copy`), the real sync folder end to end, 3 concurrent MCP server processes sharing one CLI session, a 20+ minute long-running session (no memory or process leaks), server-kill-mid-upload recovery, the official MCP SDK client and MCP Inspector, Node 20/22/24, a packed-and-installed npm tarball launched via its real `.bin` symlink, and dozens of adversarial edge cases (special Drive roots, Unicode name tricks, 40-level nesting, etc.).
|
|
58
|
+
|
|
59
|
+
### Known, not fixed (upstream CLI limitations, now documented in README)
|
|
60
|
+
Big folders can't be copied; a `"` in a name becomes `_` on local download; a public link's expiration caps at ~90 days; sharing inherited from a parent folder isn't reported by `drive_share_status`; upload/download counts include folders.
|
|
61
|
+
|
|
62
|
+
### Testing status
|
|
63
|
+
`drive_share_leave`, `drive_invitation_accept` and `drive_invitation_reject` have never been tested against a real account — they need a second Proton account, which the maintainer doesn't have. Only unit-tested against a fake CLI. Documented in README.
|
|
64
|
+
|
|
3
65
|
## 1.0.39 — 2026-09-26
|
|
4
66
|
|
|
5
67
|
Test-suite hardening only — no `src/` changes, no behavior change.
|
package/README.md
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
[](https://www.npmjs.com/package/proton-drive-mcp)
|
|
13
13
|
[](https://github.com/googlarz/proton-drive-mcp/actions/workflows/ci.yml)
|
|
14
14
|
[](LICENSE)
|
|
15
|
-
[](https://nodejs.org)
|
|
16
16
|
[](https://www.typescriptlang.org)
|
|
17
17
|
[](https://modelcontextprotocol.io)
|
|
18
18
|
[](https://github.com/googlarz/proton-drive-mcp)
|
|
19
19
|
[](https://github.com/googlarz/proton-drive-mcp/commits/main)
|
|
20
|
-
[](https://github.com/googlarz/proton-drive-mcp)
|
|
21
21
|
[](https://glama.ai/mcp/servers/googlarz/proton-drive-mcp)
|
|
22
22
|
|
|
23
23
|
</div>
|
|
@@ -60,7 +60,7 @@ proton-drive auth login
|
|
|
60
60
|
|
|
61
61
|
This opens a browser for Proton's standard sign-in flow. Credentials are stored in your OS keychain — not on disk, not in config files.
|
|
62
62
|
|
|
63
|
-
**3. Node.js
|
|
63
|
+
**3. Node.js 22 or later** — `node --version` to check.
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
@@ -91,24 +91,31 @@ Add to your `claude_desktop_config.json`:
|
|
|
91
91
|
```json
|
|
92
92
|
{
|
|
93
93
|
"mcpServers": {
|
|
94
|
-
"proton-drive": {
|
|
94
|
+
"proton-drive-mcp": {
|
|
95
95
|
"command": "npx",
|
|
96
|
-
"args": ["-y", "proton-drive-mcp"]
|
|
96
|
+
"args": ["-y", "proton-drive-mcp"],
|
|
97
|
+
"env": { "PROTON_DRIVE_BIN": "/absolute/path/to/proton-drive" }
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
```
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
Claude Desktop starts servers with a minimal `PATH`, so a `proton-drive` in `~/.local/bin` is usually not found. Set `PROTON_DRIVE_BIN` to the output of `which proton-drive` (the default install location is `~/.local/bin/proton-drive`, written out in full, e.g. `/Users/you/.local/bin/proton-drive`). If `npx` itself is not found, use its absolute path as `command` (`which npx`).
|
|
104
|
+
|
|
105
|
+
Restart Claude Desktop. Check **`+` → Connectors → proton-drive-mcp** to confirm the server is connected.
|
|
103
106
|
|
|
104
107
|
> **Tip:** Make sure `proton-drive auth login` has been run at least once before starting Claude Desktop.
|
|
105
108
|
|
|
109
|
+
### One-click install (MCPB bundle)
|
|
110
|
+
|
|
111
|
+
Instead of editing JSON, download `proton-drive-mcp-<version>.mcpb` from the [latest GitHub release](https://github.com/googlarz/proton-drive-mcp/releases/latest) and open it (or drag it into **Settings → Extensions** in Claude Desktop). In the extension settings, set **proton-drive CLI path** to the output of `which proton-drive` (usually `~/.local/bin/proton-drive`); optionally set the **Proton Drive sync folder** to enable `drive_read_file` / `drive_write_file`. You still need the official `proton-drive` CLI installed and `proton-drive auth login` done once.
|
|
112
|
+
|
|
106
113
|
### If installed globally
|
|
107
114
|
|
|
108
115
|
```json
|
|
109
116
|
{
|
|
110
117
|
"mcpServers": {
|
|
111
|
-
"proton-drive": {
|
|
118
|
+
"proton-drive-mcp": {
|
|
112
119
|
"command": "proton-drive-mcp"
|
|
113
120
|
}
|
|
114
121
|
}
|
|
@@ -117,6 +124,29 @@ Restart Claude Desktop. Check **`+` → Connectors → proton-drive** to confirm
|
|
|
117
124
|
|
|
118
125
|
---
|
|
119
126
|
|
|
127
|
+
## Setup and diagnostics
|
|
128
|
+
|
|
129
|
+
Two commands of the companion CLI help when Claude Desktop reports "proton-drive CLI not found" (it starts servers with a minimal `PATH`):
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Read-only checks: Node >= 22, proton-drive CLI + version, auth, PROTON_DRIVE_SYNC_PATH, your Claude Desktop entry
|
|
133
|
+
proton-drive-cli doctor [--json] [--config <path>]
|
|
134
|
+
|
|
135
|
+
# Dry run: prints the entry it would add and the target file, changes nothing
|
|
136
|
+
proton-drive-cli setup-claude-desktop [--config <path>] [--sync-path <dir>]
|
|
137
|
+
|
|
138
|
+
# Apply it (timestamped backup first; only mcpServers["proton-drive-mcp"] is touched)
|
|
139
|
+
proton-drive-cli setup-claude-desktop --sync-path "$HOME/Proton Drive" --write
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`doctor` exits 1 if any check fails (warnings do not fail). It only ever reports on the `proton-drive-mcp` entry of the config, never on other servers. `setup-claude-desktop` writes the absolute path of the running `node`, of this package's `dist/index.js`, and of the resolved `proton-drive` binary (`PROTON_DRIVE_BIN`). It refuses to run if the CLI cannot be found, the config file is not valid JSON, or the package is running from a temporary `npx` cache (install it first with `npm install -g proton-drive-mcp`). The entry pins the current `node` binary, so re-run it after switching Node versions (e.g. with nvm). Re-running with unchanged settings leaves the file alone; otherwise the file is replaced atomically and a timestamped `.bak-…` copy is kept.
|
|
143
|
+
|
|
144
|
+
The default config location is per OS (macOS `~/Library/Application Support/Claude/`, Windows `%APPDATA%\Claude\`, Linux `$XDG_CONFIG_HOME` or `~/.config/Claude/`); use `--config` or the `CLAUDE_DESKTOP_CONFIG` environment variable to point elsewhere. On Windows the CLI lookup honours `PATHEXT`.
|
|
145
|
+
|
|
146
|
+
**Claude Desktop only reads its config at startup: fully quit and restart it afterwards** (closing the window is not enough).
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
120
150
|
## Try it: example Claude prompts
|
|
121
151
|
|
|
122
152
|
**Backup a build artifact**
|
|
@@ -330,6 +360,38 @@ proton-drive-cli share status /my-files/Projects
|
|
|
330
360
|
- Paths are always Drive-absolute: `/my-files/folder/file.pdf`. Relative paths are not supported.
|
|
331
361
|
- All calls include `--json` automatically, except `drive_version`, whose underlying CLI command ignores `--json` and always prints plain text — this MCP parses it directly.
|
|
332
362
|
|
|
363
|
+
### Token cost
|
|
364
|
+
|
|
365
|
+
`tools/list` is sent to the model in every session. Measured payload (JSON bytes): `full` 29.5 KB (38 tools; 39.5 KB before description trimming), `core` 13.3 KB (16 tools). Set `PROTON_DRIVE_TOOL_TIER=core` to load only:
|
|
366
|
+
|
|
367
|
+
`drive_auth_status`, `drive_version`, `drive_list`, `drive_info`, `drive_list_trash`, `drive_mkdir`, `drive_upload`, `drive_download`, `drive_rename`, `drive_move`, `drive_copy`, `drive_trash`, `drive_restore`, `drive_share_status`, `photos_list_timeline`, `photos_download`.
|
|
368
|
+
|
|
369
|
+
Left out of `core` (use `full`): permanent deletion (`drive_delete`, `drive_empty_trash`), `drive_auth_logout`, public links, invitations and invites, album management, `photos_upload` and the sync-file tools. A call to a hidden tool returns an error asking for `PROTON_DRIVE_TOOL_TIER=full`; no CLI command runs.
|
|
370
|
+
|
|
371
|
+
## Known limitations
|
|
372
|
+
|
|
373
|
+
These come from the upstream `proton-drive` CLI (v0.8.0), not from this server:
|
|
374
|
+
|
|
375
|
+
- Big folders cannot be copied yet (`drive_copy` fails with `InvalidRequirementsAPIError` code 2000).
|
|
376
|
+
- A `"` in a name becomes `_` when the item is downloaded locally.
|
|
377
|
+
- Public-link expiration can be at most ~90 days ahead, to the minute.
|
|
378
|
+
- `drive_share_status` only reports sharing set directly on the item — access inherited from a shared parent folder is not shown.
|
|
379
|
+
- Roots (`/my-files`, `/photos`, …) cannot be shared; `drive_share_status` on a root returns an error.
|
|
380
|
+
- `photos_list_timeline` pages are not a snapshot: photos added or removed between calls shift later pages.
|
|
381
|
+
- Upload/download counts include folders, not only files.
|
|
382
|
+
- `/albums/...` paths cannot be downloaded — download a photo via `/photos/<name>`.
|
|
383
|
+
- A name ending in a backslash (e.g. `tail\`, created by another client) cannot be used as a parent in a path: the CLI reads `tail\/child` as an escaped `/`, and has no escape for a literal backslash (`\\` does not work either). The folder itself is reachable; its children are not reachable by path.
|
|
384
|
+
- Trashed items are addressed by name only. When two items in `/trash` or `/photos-trash` share a name, `drive_restore` and `drive_delete` refuse (listing the uids) instead of acting on an arbitrary one — restore or delete that item in the Proton Drive web or desktop app.
|
|
385
|
+
- When several programs use the CLI at the same moment (e.g. Claude Desktop and Claude Code), its local cache can briefly report `database is locked`. Read-only calls are retried automatically; writes are not (a retry could repeat the change), so just run the write again. The same read-only retry (up to two more attempts, short jittered backoff, honoring a `Retry-After` of at most 5 s and the call's overall timeout) also covers rate limiting (HTTP 429), single-request timeouts (`Request timed out`) and transient network resets; auth and not-found errors are never retried.
|
|
386
|
+
|
|
387
|
+
## Platform support
|
|
388
|
+
|
|
389
|
+
Developed and live-tested on **macOS**. CI runs the test suite on Ubuntu and macOS (Node 22 and 24). **Windows is not supported yet**: the test suite has never passed there (CI runs it only as a non-blocking probe), and the Windows paths in `doctor` / `setup-claude-desktop` (`%APPDATA%`, `PATHEXT` lookup) are untested. The system-PATH warning in `doctor` only knows POSIX directories. Linux is covered by CI with the fake CLI but has not been live-tested against a real Proton account.
|
|
390
|
+
|
|
391
|
+
## Testing status
|
|
392
|
+
|
|
393
|
+
Every tool group was live-tested on 2026-09-28 against a real Proton account, **except** `drive_share_leave`, `drive_invitation_accept` and `drive_invitation_reject`. Those three have never been tested against a real account, because the maintainer has no second Proton account to share from; they are only unit-tested against a fake CLI.
|
|
394
|
+
|
|
333
395
|
---
|
|
334
396
|
|
|
335
397
|
## Environment variables
|
|
@@ -337,9 +399,12 @@ proton-drive-cli share status /my-files/Projects
|
|
|
337
399
|
| Variable | Required | Description |
|
|
338
400
|
|---|---|---|
|
|
339
401
|
| `PROTON_DRIVE_SYNC_PATH` | Optional | Absolute path to your local Proton Drive sync folder root (e.g. `/Users/you/Proton Drive`). Required only for `drive_read_file` and `drive_write_file`. The Proton Drive desktop app must be running to sync written files to the cloud. |
|
|
340
|
-
| `PROTON_DRIVE_BIN` | Optional | Override the `proton-drive` binary name or path (default: `proton-drive
|
|
402
|
+
| `PROTON_DRIVE_BIN` | Optional | Override the `proton-drive` binary name or path (default: `proton-drive`; an empty value counts as unset). Useful for non-standard installations. |
|
|
341
403
|
| `PROTON_DRIVE_LOCAL_ROOT` | Optional | Path-delimiter-separated list of local directories that upload/download/photos tools may touch. Unset = any path except the built-in credential denylist. |
|
|
342
404
|
| `PROTON_DRIVE_ALLOW_SENSITIVE_PATHS` | Optional | Set to `1` to disable the built-in credential-location denylist (not recommended). |
|
|
405
|
+
| `CLAUDE_DESKTOP_CONFIG` | Optional | Path of the Claude Desktop config that `doctor` and `setup-claude-desktop` read/write instead of the per-OS default. |
|
|
406
|
+
| `PROTON_DRIVE_RETRY_BASE_MS` | Optional | Test hook: base backoff in ms for retrying read-only calls (default 250). |
|
|
407
|
+
| `PROTON_DRIVE_TOOL_TIER` | Optional | `full` (default, all 38 tools) or `core` (16 everyday tools; see [Token cost](#token-cost)). Tools outside the active tier are hidden from `tools/list` and refused at call time. Read once at startup; an unknown value falls back to `full` with a warning on stderr. |
|
|
343
408
|
|
|
344
409
|
---
|
|
345
410
|
|
|
@@ -355,7 +420,7 @@ Download from [proton.me/download/drive/cli](https://proton.me/download/drive/cl
|
|
|
355
420
|
Run `proton-drive auth login` in your terminal. Auth state is stored in your OS keychain and persists across sessions.
|
|
356
421
|
|
|
357
422
|
**Claude can't see the connector**
|
|
358
|
-
Restart Claude Desktop fully after changing the MCP config. Check **`+` → Connectors → proton-drive**. The Proton Drive CLI must be in the `PATH` that Claude Desktop inherits (on macOS this may differ from your shell PATH — use the full binary path in config if needed).
|
|
423
|
+
Restart Claude Desktop fully after changing the MCP config. Check **`+` → Connectors → proton-drive-mcp**. The Proton Drive CLI must be in the `PATH` that Claude Desktop inherits (on macOS this may differ from your shell PATH — use the full binary path in config if needed).
|
|
359
424
|
|
|
360
425
|
**Upload fails on image files**
|
|
361
426
|
The CLI generates WebP thumbnails by default using Bun's image API. If Bun isn't installed or doesn't support thumbnails on your platform, the MCP passes `--skip-thumbnails` to bypass this. No action needed.
|
|
@@ -369,7 +434,7 @@ Or in Claude Desktop config:
|
|
|
369
434
|
```json
|
|
370
435
|
{
|
|
371
436
|
"mcpServers": {
|
|
372
|
-
"proton-drive": {
|
|
437
|
+
"proton-drive-mcp": {
|
|
373
438
|
"command": "npx",
|
|
374
439
|
"args": ["-y", "proton-drive-mcp"],
|
|
375
440
|
"env": { "PROTON_DRIVE_BIN": "/usr/local/bin/proton-drive" }
|
|
@@ -383,7 +448,7 @@ Use the full path to the `proton-drive.exe` binary in your Claude Desktop config
|
|
|
383
448
|
```json
|
|
384
449
|
{
|
|
385
450
|
"mcpServers": {
|
|
386
|
-
"proton-drive": {
|
|
451
|
+
"proton-drive-mcp": {
|
|
387
452
|
"command": "C:\\path\\to\\proton-drive-mcp.cmd"
|
|
388
453
|
}
|
|
389
454
|
}
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { DriveService, } from "./services/drive.js";
|
|
10
10
|
import { DriveCliError, DriveCliNotFoundError, DriveNotAuthenticatedError, DriveParseError } from "./utils/errors.js";
|
|
11
11
|
import { checkCliAvailable } from "./utils/subprocess.js";
|
|
12
|
+
import { runDoctor, formatDoctor } from "./utils/doctor.js";
|
|
13
|
+
import { buildEntry, defaultConfigPath, isDirectory, resolveDriveCli, writeEntry, SERVER_KEY } from "./utils/claudeConfig.js";
|
|
14
|
+
import { resolve } from "node:path";
|
|
12
15
|
import { validateRemotePath, validateLocalPath, validateEmail, validateMessage, validateName, validateFlagValue } from "./utils/validation.js";
|
|
13
16
|
const drive = new DriveService();
|
|
14
17
|
const args = process.argv.slice(2);
|
|
@@ -32,7 +35,7 @@ Commands:
|
|
|
32
35
|
'remove' deletes the existing LOCAL item and needs --confirm)
|
|
33
36
|
rename <path> <new-name> Rename in place, no move
|
|
34
37
|
move <src> <dst> Move and/or rename
|
|
35
|
-
delete <path> --confirm
|
|
38
|
+
delete <path>|--uid <uid> --confirm Delete a file/folder already in trash, permanently
|
|
36
39
|
share status <path> Show sharing info
|
|
37
40
|
share invite <path> <email> <role> Invite user (viewer/editor/admin)
|
|
38
41
|
share revoke <path> <email> Revoke one user's access
|
|
@@ -44,7 +47,7 @@ Commands:
|
|
|
44
47
|
trash <path> Move to trash
|
|
45
48
|
trash list List trash contents
|
|
46
49
|
trash empty --confirm Permanently delete all trash
|
|
47
|
-
restore <path>
|
|
50
|
+
restore <path>|--uid <uid> Restore from trash (uid from 'trash list')
|
|
48
51
|
invitation list List pending invitations
|
|
49
52
|
invitation accept <uid> Accept an invitation
|
|
50
53
|
invitation reject <uid> Reject an invitation
|
|
@@ -59,6 +62,12 @@ Commands:
|
|
|
59
62
|
photo download <photo>... <local> [--conflict X] [--confirm] Download photos (skip/rename/remove; 'remove' needs --confirm)
|
|
60
63
|
photo upload <local>... [--conflict X] Upload photos to your library (skip/rename)
|
|
61
64
|
|
|
65
|
+
doctor [--config <path>] Read-only diagnostics: Node, proton-drive CLI, auth, sync path, Claude Desktop config
|
|
66
|
+
(exit 1 if any check fails)
|
|
67
|
+
setup-claude-desktop [--config <path>] [--sync-path <dir>] [--write]
|
|
68
|
+
Print the Claude Desktop entry for this server (dry run); --write adds it to the
|
|
69
|
+
config (timestamped backup first). Restart Claude Desktop afterwards.
|
|
70
|
+
|
|
62
71
|
Flags:
|
|
63
72
|
--json Machine-readable JSON output (one line)
|
|
64
73
|
`);
|
|
@@ -100,6 +109,28 @@ function getFlag(flag) {
|
|
|
100
109
|
function print(data) {
|
|
101
110
|
console.log(jsonMode ? JSON.stringify(data) : JSON.stringify(data, null, 2));
|
|
102
111
|
}
|
|
112
|
+
function setupClaudeDesktop() {
|
|
113
|
+
const configPath = resolve(getFlag("--config") ?? defaultConfigPath());
|
|
114
|
+
const rawSync = getFlag("--sync-path");
|
|
115
|
+
const cliPath = resolveDriveCli();
|
|
116
|
+
if (!cliPath) {
|
|
117
|
+
console.error("Error: proton-drive CLI not found. Install it from https://proton.me/download/drive/cli/index.html, or set PROTON_DRIVE_BIN to its absolute path (`which proton-drive`).");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
const syncPath = rawSync !== undefined ? resolve(rawSync) : undefined;
|
|
121
|
+
if (syncPath && !isDirectory(syncPath)) {
|
|
122
|
+
console.error(`Error: --sync-path ${syncPath} is not an existing directory.`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
const entry = buildEntry(cliPath, syncPath);
|
|
126
|
+
const shown = JSON.stringify({ mcpServers: { [SERVER_KEY]: entry } }, null, 2);
|
|
127
|
+
if (!args.includes("--write")) {
|
|
128
|
+
console.log(`Dry run: nothing changed. Would set this entry in ${configPath}:\n${shown}\nRe-run with --write to apply.`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const backup = writeEntry(configPath, entry);
|
|
132
|
+
console.log(`Wrote ${SERVER_KEY} to ${configPath}${backup ? ` (backup: ${backup})` : ""}.\n${shown}\nFully quit and restart Claude Desktop for the change to take effect.`);
|
|
133
|
+
}
|
|
103
134
|
async function run() {
|
|
104
135
|
// Strip --json from positional parsing
|
|
105
136
|
const positional = args.filter((a) => a !== "--json");
|
|
@@ -115,11 +146,21 @@ async function run() {
|
|
|
115
146
|
usage();
|
|
116
147
|
return;
|
|
117
148
|
}
|
|
149
|
+
if (cmd === "doctor") {
|
|
150
|
+
const result = await runDoctor(getFlag("--config"));
|
|
151
|
+
console.log(jsonMode ? JSON.stringify(result) : formatDoctor(result));
|
|
152
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (cmd === "setup-claude-desktop") {
|
|
156
|
+
setupClaudeDesktop();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
118
159
|
const cliCheck = await checkCliAvailable();
|
|
119
160
|
if (!cliCheck.available) {
|
|
120
161
|
const msg = cliCheck.reason === "not_executable"
|
|
121
162
|
? "Error: proton-drive CLI found but not executable.\nRun: chmod +x $(which proton-drive)"
|
|
122
|
-
: "Error: proton-drive CLI not found in PATH.\nDownload from https://proton.me/download/drive/cli/index.html";
|
|
163
|
+
: "Error: proton-drive CLI not found in PATH.\nDownload from https://proton.me/download/drive/cli/index.html, or set PROTON_DRIVE_BIN to its absolute path (find it with `which proton-drive`).";
|
|
123
164
|
console.error(msg);
|
|
124
165
|
process.exit(1);
|
|
125
166
|
}
|
|
@@ -241,13 +282,15 @@ async function run() {
|
|
|
241
282
|
console.log("Folder created.");
|
|
242
283
|
break;
|
|
243
284
|
case "delete": {
|
|
244
|
-
const
|
|
285
|
+
const delUid = getFlag("--uid");
|
|
286
|
+
const delPath = delUid && (!sub || sub.startsWith("--")) ? undefined : requirePath(sub, "delete <path>|--uid <uid> --confirm");
|
|
287
|
+
const delLabel = delPath ?? `uid ${delUid}`;
|
|
245
288
|
if (!args.includes("--confirm")) {
|
|
246
|
-
console.error(`This permanently deletes an item already in trash: ${
|
|
289
|
+
console.error(`This permanently deletes an item already in trash: ${delLabel}\nPass --confirm to proceed.`);
|
|
247
290
|
process.exit(1);
|
|
248
291
|
}
|
|
249
|
-
await drive.delete(delPath);
|
|
250
|
-
console.log(`Deleted: ${
|
|
292
|
+
await drive.delete(delPath, delUid);
|
|
293
|
+
console.log(`Deleted: ${delLabel}`);
|
|
251
294
|
break;
|
|
252
295
|
}
|
|
253
296
|
case "share":
|
|
@@ -288,10 +331,11 @@ async function run() {
|
|
|
288
331
|
console.error(`This removes everyone's access to: ${removeAllPath}\nPass --confirm to proceed.`);
|
|
289
332
|
process.exit(1);
|
|
290
333
|
}
|
|
291
|
-
const
|
|
292
|
-
console.log(
|
|
334
|
+
const removeAll = await drive.shareRemoveAll(removeAllPath);
|
|
335
|
+
console.log((removeAll.removed === 0
|
|
293
336
|
? `Nothing to remove: ${removeAllPath} has no members or pending invitations.`
|
|
294
|
-
: `Removed all
|
|
337
|
+
: `Removed all members and invitations (${removeAll.removed}) from: ${removeAllPath}`) +
|
|
338
|
+
(removeAll.publicLink ? " A public link is still active — remove it with: share remove-url <path>" : ""));
|
|
295
339
|
}
|
|
296
340
|
else if (sub === "set-url") {
|
|
297
341
|
const setUrlPath = requirePath(rest[0], "share set-url <path> [--role] [--password] [--expiration]");
|
|
@@ -342,10 +386,13 @@ async function run() {
|
|
|
342
386
|
process.exit(1);
|
|
343
387
|
}
|
|
344
388
|
break;
|
|
345
|
-
case "restore":
|
|
346
|
-
|
|
389
|
+
case "restore": {
|
|
390
|
+
const restoreUid = getFlag("--uid");
|
|
391
|
+
const restorePath = restoreUid && (!sub || sub.startsWith("--")) ? undefined : requirePath(sub, "restore <path>|--uid <uid>");
|
|
392
|
+
await drive.restore(restorePath, restoreUid);
|
|
347
393
|
console.log("Restored.");
|
|
348
394
|
break;
|
|
395
|
+
}
|
|
349
396
|
case "copy": {
|
|
350
397
|
const copySrc = requirePath(sub, "copy <src> <dst>");
|
|
351
398
|
const copyDst = requirePath(rest[0], "copy <src> <dst>");
|
|
@@ -452,7 +499,7 @@ async function run() {
|
|
|
452
499
|
return;
|
|
453
500
|
}
|
|
454
501
|
await drive.updateAlbum(albumPath, newName, coverPhotoUid);
|
|
455
|
-
console.log(`Album updated: ${albumPath}`);
|
|
502
|
+
console.log(`Album updated: ${newName ? `/albums/${newName}` : albumPath}`);
|
|
456
503
|
}
|
|
457
504
|
else if (sub === "delete") {
|
|
458
505
|
const albumPath = requirePath(rest[0], "album delete <path> [--force] [--save] --confirm");
|