proton-drive-mcp 1.0.34 → 1.0.35
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 +9 -0
- package/dist/services/drive.js +27 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.35 — 2026-09-03
|
|
4
|
+
|
|
5
|
+
Continued the live-testing bug sweep. This round found a serious silent-data-corruption bug in `drive_move`, confirmed by a full live reproduction against the real account, plus the same root cause in `drive_copy`. The same fix hardens three more structurally identical code paths (`trash`, `restore`, `delete`) against the same failure mode.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **`drive_move` could silently rename the WRONG file and report false success**, when moving across folders into a destination that already has an item with the same name. Root cause confirmed live: `filesystem move` exits **0** with `[{ok:false, error:{name:"NodeWithSameNameExistsValidationError", ...}}]` on a name collision — not a non-zero exit code. `move()`'s cross-folder composition (`move` the item, then `rename` it) discarded this result entirely, assumed the move always succeeded, and blindly renamed *whatever item happened to already exist* at the computed destination path — which, on a collision, was the pre-existing unrelated file, not the one that was supposed to move. Reproduced the full scenario live: source file stayed in its original folder, untouched, while a completely different file in the destination folder got silently renamed, and the tool reported "Moved: ..." success throughout. Now `move()` checks the result and fails cleanly with a clear error instead of proceeding.
|
|
9
|
+
- **`drive_copy` reported false success on the same kind of destination-name collision.** Same root cause, confirmed live the same way (`filesystem copy` also returns `[{ok:false, ...}]` with exit 0) — `copy()` discarded the result and unconditionally reported success. Now checked the same way.
|
|
10
|
+
- **Same defensive check added to `trash`, `restore`, and `delete`**, which share the identical CLI response shape (all support batch operation via `path...` and report per-item `{uid, ok, error}` results) — hardening them against the same silent-failure class even though a live collision wasn't independently reproduced for each.
|
|
11
|
+
|
|
3
12
|
## 1.0.34 — 2026-09-03
|
|
4
13
|
|
|
5
14
|
Continued the live-testing bug sweep from v1.0.33. 7 more real, confirmed bugs across two rounds — every one reproduced live against the real Proton Drive CLI and account before being called a bug, and every fix re-verified live afterward.
|
package/dist/services/drive.js
CHANGED
|
@@ -29,6 +29,27 @@ function unwrapResult(value, fallback = "") {
|
|
|
29
29
|
function escapeNameForPath(name) {
|
|
30
30
|
return name.replace(/\//g, "\\/");
|
|
31
31
|
}
|
|
32
|
+
// filesystem move/copy/trash/restore/delete all accept multiple paths in one
|
|
33
|
+
// call and report per-item success as an array of {uid, ok, error} — NOT via
|
|
34
|
+
// a non-zero exit code. Confirmed live: a name collision at the destination
|
|
35
|
+
// makes `filesystem move`/`filesystem copy` exit 0 with
|
|
36
|
+
// [{ok:false, error:{name:"NodeWithSameNameExistsValidationError", ...}}].
|
|
37
|
+
// Every one of these wrappers previously discarded the result outright, so a
|
|
38
|
+
// collision was silently reported as success. Worst case was move(): it then
|
|
39
|
+
// went on to rename whatever OTHER item happened to already be sitting at the
|
|
40
|
+
// computed destination path — silently renaming an unrelated file while the
|
|
41
|
+
// real source never moved.
|
|
42
|
+
function assertItemsOk(result, action) {
|
|
43
|
+
if (!Array.isArray(result))
|
|
44
|
+
return;
|
|
45
|
+
for (const item of result) {
|
|
46
|
+
if (item && item.ok === false) {
|
|
47
|
+
const err = (item.error ?? {});
|
|
48
|
+
const code = typeof err.code !== "undefined" ? ` (code ${err.code})` : "";
|
|
49
|
+
throw new Error(`${action} failed: ${err.name ?? "unknown error"}${code} — likely a name collision at the destination.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
32
53
|
// Strips "<package-name>@" and "+<hash>" from a version token like
|
|
33
54
|
// "cli-drive@0.8.0+06e8c605", leaving "0.8.0". Falls back to the raw
|
|
34
55
|
// token if it doesn't match, so an unexpected future format still shows
|
|
@@ -185,7 +206,8 @@ export class DriveService {
|
|
|
185
206
|
await this.rename(sourcePath, dstName);
|
|
186
207
|
return;
|
|
187
208
|
}
|
|
188
|
-
await this.run(["filesystem", "move", sourcePath, dstParent]);
|
|
209
|
+
const moveResult = await this.run(["filesystem", "move", sourcePath, dstParent]);
|
|
210
|
+
assertItemsOk(moveResult, "Move");
|
|
189
211
|
if (srcName !== dstName) {
|
|
190
212
|
const movedPath = posixPath.join(dstParent, srcName);
|
|
191
213
|
await this.rename(movedPath, dstName);
|
|
@@ -195,7 +217,7 @@ export class DriveService {
|
|
|
195
217
|
// /trash or /photos-trash (the CLI rejects live paths). No --confirm flag
|
|
196
218
|
// exists on the CLI side; our own confirmed-gate lives in the MCP layer.
|
|
197
219
|
async delete(remotePath) {
|
|
198
|
-
await this.run(["filesystem", "delete", remotePath]);
|
|
220
|
+
assertItemsOk(await this.run(["filesystem", "delete", remotePath]), "Delete");
|
|
199
221
|
}
|
|
200
222
|
// Sharing
|
|
201
223
|
//
|
|
@@ -285,16 +307,16 @@ export class DriveService {
|
|
|
285
307
|
return this.list("/trash");
|
|
286
308
|
}
|
|
287
309
|
async trash(remotePath) {
|
|
288
|
-
await this.run(["filesystem", "trash", remotePath]);
|
|
310
|
+
assertItemsOk(await this.run(["filesystem", "trash", remotePath]), "Trash");
|
|
289
311
|
}
|
|
290
312
|
async restore(remotePath) {
|
|
291
|
-
await this.run(["filesystem", "restore", remotePath]);
|
|
313
|
+
assertItemsOk(await this.run(["filesystem", "restore", remotePath]), "Restore");
|
|
292
314
|
}
|
|
293
315
|
async emptyTrash() {
|
|
294
316
|
await this.run(["filesystem", "empty-trash"]);
|
|
295
317
|
}
|
|
296
318
|
async copy(remoteSrc, remoteDst) {
|
|
297
|
-
await this.run(["filesystem", "copy", remoteSrc, remoteDst]);
|
|
319
|
+
assertItemsOk(await this.run(["filesystem", "copy", remoteSrc, remoteDst]), "Copy");
|
|
298
320
|
}
|
|
299
321
|
async listInvitations() {
|
|
300
322
|
const result = await this.run(["invitation", "list"]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proton-drive-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
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",
|