proton-drive-mcp 1.0.30 → 1.0.32
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 +34 -0
- package/dist/index.js +7 -5
- package/dist/services/drive.js +17 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.32 — 2026-08-21
|
|
4
|
+
|
|
5
|
+
Closed the last disclosed gap from v1.0.31 — tested `drive_share_invite` for real, against the user's own Gmail address, through the actual MCP JSON-RPC server. Found a real bug in the process.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **`drive_share_status` only read the `members` field, ignoring `protonInvitations` and `nonProtonInvitations` entirely.** Confirmed live: inviting a non-Proton address (a Gmail account) is filed by the CLI under `nonProtonInvitations`, not `members` — so a real, successfully-sent invite was completely invisible from this tool. `isShared` correctly reported `true`, but `members` came back empty, giving zero visibility into who the item was actually shared with. `members` now merges accepted members with both pending-invitation sources, each entry tagged `status: "accepted" | "pending"` so callers can still tell them apart. `drive_share_revoke` was separately confirmed to correctly cancel a pending invitation, not just remove accepted access, so no fix was needed there.
|
|
9
|
+
|
|
10
|
+
### Verified live (closing v1.0.31's disclosed gaps)
|
|
11
|
+
- `drive_share_invite` — sent a real invitation via the actual MCP server (not just the CLI wrapper), to a real external email.
|
|
12
|
+
- `drive_share_revoke` — confirmed it cancels a pending (not-yet-accepted) invitation as well as accepted access.
|
|
13
|
+
|
|
14
|
+
### Still not independently verified
|
|
15
|
+
- `drive_list_invitations`'s field mapping — still no real pending invitation exists from the *invitee's* side to test against (this release only tested from the inviter's side).
|
|
16
|
+
- `invitation_accept`/`invitation_reject`, `share_leave` — require a second real account to be on the receiving end.
|
|
17
|
+
- Sync-folder tools, `drive_empty_trash` — same reasons as v1.0.31.
|
|
18
|
+
|
|
19
|
+
## 1.0.31 — 2026-08-21
|
|
20
|
+
|
|
21
|
+
A second, more skeptical pass over v1.0.30 — re-verified everything live rather than trusting the previous session's own reasoning, and tested the actual MCP JSON-RPC protocol layer directly (not just the `DriveService` class underneath it), plus recursive multi-file transfers.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **`drive_download`'s tool description was misleading.** `localPath` is a destination *folder* — auto-created if missing, with the downloaded item placed inside it under its original remote name — not the file's exact final path. The old description said "must already exist," which is also simply false (confirmed live: the CLI creates it). An agent following the old description could reasonably expect a file at the literal `localPath` given and be confused to find a directory there instead.
|
|
25
|
+
|
|
26
|
+
### Verified live, no bug found (ruling out a false alarm)
|
|
27
|
+
- **`photos_upload`'s "non-photo files are silently skipped" claim is accurate.** An initial test using a `.jpg`-named text file *failed* rather than skipped — looked like a bug at first. Root cause: media-type detection is extension-based, so the mislabeled file was sniffed as `image/jpeg`, passed the photo-type check, and correctly failed at the actual upload stage since the content wasn't a valid image. A genuinely non-photo file (`.txt`) correctly produces `skippedItems: 1`, exactly as documented.
|
|
28
|
+
- **The real MCP server (JSON-RPC over stdio), not just the CLI wrapper.** Sent actual `tools/call` protocol messages to the compiled server for `drive_list` and `drive_share_status` — both correct, matching the CLI-wrapper output exactly. (One `drive_list` call failed transiently on a rapid-fire multi-request batch; immediately reproducible as a one-off, not a code bug — retried clean.)
|
|
29
|
+
- **Recursive folder upload/download** (nested subfolder, 3 files) — round-tripped correctly, structure preserved, transfer counts accurate.
|
|
30
|
+
|
|
31
|
+
### Still not independently verified (disclosed, not swept under the rug)
|
|
32
|
+
- `drive_list_invitations`'s field mapping — no pending invitation existed to test against; the fix is based on the SDK's TypeScript type definition, not an empirically observed response.
|
|
33
|
+
- Any sharing operation requiring a second real account (`share_invite`, `share_revoke` against a real member, `invitation_accept`/`reject`, `share_leave`) — correctly not exercised against a real third party without their consent.
|
|
34
|
+
- `drive_read_file`/`drive_write_file` (the `PROTON_DRIVE_SYNC_PATH`-gated tools) — not configured in this environment.
|
|
35
|
+
- `drive_empty_trash` — deliberately not executed for real; the test account's actual Trash held real, unrelated user data that a live run would have destroyed.
|
|
36
|
+
|
|
3
37
|
## 1.0.30 — 2026-08-21
|
|
4
38
|
|
|
5
39
|
**Root cause of this release:** every prior "verification" of this package (v1.0.24 through v1.0.29) diffed the CLI's *command source* against GitHub tags — confirming the right argv gets built — but never actually ran the CLI and inspected its *response* JSON. This release does that, live, against a real authenticated account. It found that several response-shape assumptions were wrong from day one, in ways no amount of source-reading would have caught, because the exact wire format (Result-wrapped names, nested fields, a CLI-side `undefined`-text quirk) isn't visible from the command classes — it only shows up in the actual output.
|
package/dist/index.js
CHANGED
|
@@ -154,10 +154,11 @@ const TOOLS = [
|
|
|
154
154
|
{
|
|
155
155
|
name: "drive_download",
|
|
156
156
|
description: "Download a file or folder from Proton Drive to the local filesystem. Requires authentication. " +
|
|
157
|
+
"localPath is a destination FOLDER, not the file's exact final path — the CLI creates it automatically if missing and places the downloaded item inside it under its original remote name. " +
|
|
158
|
+
"E.g. downloading /my-files/report.pdf with localPath '/tmp/out' produces /tmp/out/report.pdf, not /tmp/out itself as a file — confirmed live against the real CLI (v0.8.0). " +
|
|
157
159
|
"For folders, downloads recursively. " +
|
|
158
160
|
"Conflict strategies are set separately for files and folders (CLI v0.8.0+) — both default to 'skip'. " +
|
|
159
|
-
"
|
|
160
|
-
"Returns {downloaded} count. " +
|
|
161
|
+
"Returns {downloaded} count — not the actual local path; construct it as localPath + the remote item's basename if you need it. " +
|
|
161
162
|
"Do not use to move files within Drive (use drive_move) or to read a small text file's contents (use drive_read_file if PROTON_DRIVE_SYNC_PATH is set).",
|
|
162
163
|
annotations: { openWorldHint: true },
|
|
163
164
|
inputSchema: {
|
|
@@ -169,7 +170,7 @@ const TOOLS = [
|
|
|
169
170
|
},
|
|
170
171
|
localPath: {
|
|
171
172
|
type: "string",
|
|
172
|
-
description: "Absolute local
|
|
173
|
+
description: "Absolute local DESTINATION FOLDER (must start with '/'), not the file's final path. Created automatically if it doesn't exist. The downloaded item is placed inside it, keeping its original remote name.",
|
|
173
174
|
},
|
|
174
175
|
fileConflictStrategy: {
|
|
175
176
|
type: "string",
|
|
@@ -283,8 +284,9 @@ const TOOLS = [
|
|
|
283
284
|
{
|
|
284
285
|
name: "drive_share_status",
|
|
285
286
|
description: "Return the current sharing state of a Proton Drive path. Requires authentication. " +
|
|
286
|
-
"Returns {isShared: boolean, members: [{email, role, addedAt
|
|
287
|
-
"
|
|
287
|
+
"Returns {isShared: boolean, members: [{email, role, addedAt?, status: 'accepted'|'pending'}], shareUrl?}. " +
|
|
288
|
+
"members includes both accepted access and pending invitations that haven't been accepted yet (including invites sent to non-Proton addresses, e.g. Gmail) — check the status field to tell them apart. " +
|
|
289
|
+
"Always call this before drive_share_invite (to avoid duplicate invitations) and before drive_share_revoke (to confirm the member email — revoke also cancels pending invitations, not just accepted access). " +
|
|
288
290
|
"Do not call this to modify sharing — it is read-only.",
|
|
289
291
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
290
292
|
inputSchema: {
|
package/dist/services/drive.js
CHANGED
|
@@ -188,23 +188,30 @@ export class DriveService {
|
|
|
188
188
|
// isShared/email/addedAt/shareUrl field; those were all wrong names.
|
|
189
189
|
// When nothing is shared, the CLI prints literal "undefined" (see
|
|
190
190
|
// subprocess.ts) which now resolves to `result === null` here.
|
|
191
|
+
//
|
|
192
|
+
// `members` merges all three sources (accepted members + both invitation
|
|
193
|
+
// kinds), each tagged accepted/pending. Confirmed live: inviting a
|
|
194
|
+
// non-Proton email (e.g. a Gmail address) files it under
|
|
195
|
+
// nonProtonInvitations, not members — reading only `members` made a real,
|
|
196
|
+
// successfully-sent invite completely invisible from this tool.
|
|
191
197
|
async shareStatus(remotePath) {
|
|
192
198
|
const result = await this.run(["sharing", "status", remotePath]);
|
|
193
199
|
const r = (result ?? {});
|
|
194
200
|
const VALID_ROLES = new Set(["viewer", "editor", "admin"]);
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const
|
|
201
|
+
const toShareMember = (m, status) => ({
|
|
202
|
+
email: String(m.inviteeEmail ?? ""),
|
|
203
|
+
role: (VALID_ROLES.has(String(m.role)) ? String(m.role) : "viewer"),
|
|
204
|
+
addedAt: typeof m.invitationTime === "string" ? m.invitationTime : undefined,
|
|
205
|
+
status,
|
|
206
|
+
});
|
|
207
|
+
const accepted = Array.isArray(r.members) ? r.members.map((m) => toShareMember(m, "accepted")) : [];
|
|
208
|
+
const protonPending = Array.isArray(r.protonInvitations) ? r.protonInvitations.map((m) => toShareMember(m, "pending")) : [];
|
|
209
|
+
const nonProtonPending = Array.isArray(r.nonProtonInvitations) ? r.nonProtonInvitations.map((m) => toShareMember(m, "pending")) : [];
|
|
210
|
+
const members = [...accepted, ...protonPending, ...nonProtonPending];
|
|
204
211
|
const urlAccess = (r.urlAccess ?? undefined);
|
|
205
212
|
return {
|
|
206
213
|
path: remotePath,
|
|
207
|
-
isShared: members.length > 0 ||
|
|
214
|
+
isShared: members.length > 0 || !!urlAccess,
|
|
208
215
|
members,
|
|
209
216
|
shareUrl: typeof urlAccess?.url === "string" ? urlAccess.url : undefined,
|
|
210
217
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proton-drive-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "MCP server and CLI that gives Claude full access to Proton Drive — upload, download, share, and manage your end-to-end encrypted files without leaving the conversation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|