proton-drive-mcp 1.1.0 → 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 CHANGED
@@ -1,5 +1,29 @@
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
+
3
27
  ## 1.1.0 — 2026-09-28
4
28
 
5
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.
package/README.md CHANGED
@@ -12,12 +12,12 @@
12
12
  [![npm version](https://img.shields.io/npm/v/proton-drive-mcp?color=%236d4aff&label=npm)](https://www.npmjs.com/package/proton-drive-mcp)
13
13
  [![CI](https://github.com/googlarz/proton-drive-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/googlarz/proton-drive-mcp/actions/workflows/ci.yml)
14
14
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
15
- [![Node.js 20+](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
15
+ [![Node.js 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org)
16
16
  [![TypeScript](https://img.shields.io/badge/TypeScript-5-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
17
17
  [![MCP](https://img.shields.io/badge/MCP-compatible-blueviolet)](https://modelcontextprotocol.io)
18
18
  [![GitHub stars](https://img.shields.io/github/stars/googlarz/proton-drive-mcp?style=social)](https://github.com/googlarz/proton-drive-mcp)
19
19
  [![Last commit](https://img.shields.io/github/last-commit/googlarz/proton-drive-mcp?color=brightgreen&label=last%20commit)](https://github.com/googlarz/proton-drive-mcp/commits/main)
20
- [![Platforms](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey)](https://github.com/googlarz/proton-drive-mcp)
20
+ [![Platforms](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey)](https://github.com/googlarz/proton-drive-mcp)
21
21
  [![proton-drive-mcp MCP server](https://glama.ai/mcp/servers/googlarz/proton-drive-mcp/badges/score.svg)](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 20 or later** — `node --version` to check.
63
+ **3. Node.js 22 or later** — `node --version` to check.
64
64
 
65
65
  ---
66
66
 
@@ -91,7 +91,7 @@ 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
96
  "args": ["-y", "proton-drive-mcp"],
97
97
  "env": { "PROTON_DRIVE_BIN": "/absolute/path/to/proton-drive" }
@@ -102,16 +102,20 @@ Add to your `claude_desktop_config.json`:
102
102
 
103
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
104
 
105
- Restart Claude Desktop. Check **`+` → Connectors → proton-drive** to confirm the server is connected.
105
+ Restart Claude Desktop. Check **`+` → Connectors → proton-drive-mcp** to confirm the server is connected.
106
106
 
107
107
  > **Tip:** Make sure `proton-drive auth login` has been run at least once before starting Claude Desktop.
108
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
+
109
113
  ### If installed globally
110
114
 
111
115
  ```json
112
116
  {
113
117
  "mcpServers": {
114
- "proton-drive": {
118
+ "proton-drive-mcp": {
115
119
  "command": "proton-drive-mcp"
116
120
  }
117
121
  }
@@ -120,6 +124,29 @@ Restart Claude Desktop. Check **`+` → Connectors → proton-drive** to confirm
120
124
 
121
125
  ---
122
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
+
123
150
  ## Try it: example Claude prompts
124
151
 
125
152
  **Backup a build artifact**
@@ -333,6 +360,14 @@ proton-drive-cli share status /my-files/Projects
333
360
  - Paths are always Drive-absolute: `/my-files/folder/file.pdf`. Relative paths are not supported.
334
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.
335
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
+
336
371
  ## Known limitations
337
372
 
338
373
  These come from the upstream `proton-drive` CLI (v0.8.0), not from this server:
@@ -347,7 +382,11 @@ These come from the upstream `proton-drive` CLI (v0.8.0), not from this server:
347
382
  - `/albums/...` paths cannot be downloaded — download a photo via `/photos/<name>`.
348
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.
349
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.
350
- - 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.
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.
351
390
 
352
391
  ## Testing status
353
392
 
@@ -360,9 +399,12 @@ Every tool group was live-tested on 2026-09-28 against a real Proton account, **
360
399
  | Variable | Required | Description |
361
400
  |---|---|---|
362
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. |
363
- | `PROTON_DRIVE_BIN` | Optional | Override the `proton-drive` binary name or path (default: `proton-drive`). Useful for non-standard installations. |
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. |
364
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. |
365
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. |
366
408
 
367
409
  ---
368
410
 
@@ -378,7 +420,7 @@ Download from [proton.me/download/drive/cli](https://proton.me/download/drive/cl
378
420
  Run `proton-drive auth login` in your terminal. Auth state is stored in your OS keychain and persists across sessions.
379
421
 
380
422
  **Claude can't see the connector**
381
- 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).
382
424
 
383
425
  **Upload fails on image files**
384
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.
@@ -392,7 +434,7 @@ Or in Claude Desktop config:
392
434
  ```json
393
435
  {
394
436
  "mcpServers": {
395
- "proton-drive": {
437
+ "proton-drive-mcp": {
396
438
  "command": "npx",
397
439
  "args": ["-y", "proton-drive-mcp"],
398
440
  "env": { "PROTON_DRIVE_BIN": "/usr/local/bin/proton-drive" }
@@ -406,7 +448,7 @@ Use the full path to the `proton-drive.exe` binary in your Claude Desktop config
406
448
  ```json
407
449
  {
408
450
  "mcpServers": {
409
- "proton-drive": {
451
+ "proton-drive-mcp": {
410
452
  "command": "C:\\path\\to\\proton-drive-mcp.cmd"
411
453
  }
412
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);
@@ -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,6 +146,16 @@ 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"