gogcli-mcp-drive 4.2.5 → 4.4.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/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-drive",
5
5
  "display_name": "gogcli (Drive)",
6
- "version": "4.2.5",
6
+ "version": "4.4.0",
7
7
  "description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -38,7 +38,8 @@
38
38
  ],
39
39
  "env": {
40
40
  "GOG_ACCOUNT": "${user_config.gog_account}",
41
- "GOG_PATH": "${user_config.gog_path}"
41
+ "GOG_PATH": "${user_config.gog_path}",
42
+ "GOG_FILE_ROOTS": "${user_config.gog_file_roots}"
42
43
  }
43
44
  }
44
45
  },
@@ -54,6 +55,12 @@
54
55
  "title": "gog Executable Path",
55
56
  "description": "Path to the gog executable (optional — defaults to 'gog' on your PATH)",
56
57
  "required": false
58
+ },
59
+ "gog_file_roots": {
60
+ "type": "string",
61
+ "title": "Allowed file directories",
62
+ "description": "Directories tools may read files from (attachments, uploads) or write files to (exports, downloads), separated by ':' (';' on Windows). Optional — defaults to ~/gogcli-mcp-files",
63
+ "required": false
57
64
  }
58
65
  },
59
66
  "tools": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-drive",
3
- "version": "4.2.5",
3
+ "version": "4.4.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-drive",
5
5
  "description": "Extended Google Drive MCP server via gogcli — auth + full Drive support (upload/download/permissions/comments/shared drives)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -24,7 +24,7 @@
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
- "@chrischall/mcp-utils": "^2.1.0",
27
+ "@chrischall/mcp-utils": "^2.4.0",
28
28
  "@modelcontextprotocol/server": "^2.0.0",
29
29
  "zod": "^4.6.1"
30
30
  },
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp-drive"
9
9
  },
10
- "version": "4.2.5",
10
+ "version": "4.4.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-drive",
15
- "version": "4.2.5",
15
+ "version": "4.4.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -1,12 +1,13 @@
1
1
  import { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
- import { accountParam, runOrDiagnose, paginationParams, pushPaginationFlags, pageTokenParam, pageAliasParam, resolvePageToken, inlineFileArg} from '../../../gogcli-mcp/src/lib.js';
3
+ import { accountParam, runOrDiagnose, paginationParams, pushPaginationFlags, pageTokenParam, pageAliasParam, resolvePageToken, inlineFileArg, pos, confinePath } from '../../../gogcli-mcp/src/lib.js';
4
4
  import type { GogArg } from '../../../gogcli-mcp/src/lib.js';
5
5
 
6
6
  export function registerExtraDriveTools(server: McpServer): void {
7
7
  server.registerTool('gog_drive_download', {
8
8
  description: 'Download a Drive file to the local filesystem. For Google Docs formats, specify an export format (pdf, csv, xlsx, pptx, txt, png, docx, md).',
9
- annotations: { readOnlyHint: true },
9
+ // Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
10
+ annotations: { readOnlyHint: false, destructiveHint: true },
10
11
  inputSchema: z.object({
11
12
  fileId: z.string().describe('File ID to download'),
12
13
  out: z.string().optional().describe('Output file path (default: gogcli config dir)'),
@@ -15,7 +16,8 @@ export function registerExtraDriveTools(server: McpServer): void {
15
16
  account: accountParam,
16
17
  }),
17
18
  }, async ({ fileId, out, format, overwrite, account }) => {
18
- const args = ['drive', 'download', fileId];
19
+ if (out) confinePath(out, 'out');
20
+ const args: GogArg[] = ['drive', 'download', pos(fileId)];
19
21
  if (out) args.push(`--out=${out}`);
20
22
  if (format) args.push(`--format=${format}`);
21
23
  if (overwrite) args.push('--overwrite');
@@ -36,7 +38,7 @@ export function registerExtraDriveTools(server: McpServer): void {
36
38
  'Conditional replacement refuses Google Workspace files (Docs/Sheets/Slides), which have no replaceable binary content.',
37
39
  annotations: { destructiveHint: false },
38
40
  inputSchema: z.object({
39
- localPath: z.string().optional().describe('Path to the file to upload, resolved ON THE GOG SERVER\'s filesystem — NOT this client\'s. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) this path does not exist and the call fails with "no such file or directory" — use content there. Exactly one of localPath / content is required.'),
41
+ localPath: z.string().optional().describe('Path to the file to upload, resolved ON THE GOG SERVER\'s filesystem — NOT this client\'s. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) this path does not exist and the call fails with "no such file or directory" — use content there. Exactly one of localPath / content is required. Must be inside the server\'s GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).'),
40
42
  content: z.string().optional().describe('The file\'s bytes, base64-encoded (standard alphabet, with padding) — upload a file you hold without it existing anywhere on the gog server. This is the only route that works when the caller and gog share no filesystem. Requires name (there is no path to take a filename from). Max 8 MiB; for anything larger use localPath from a local deployment. Exactly one of localPath / content is required — supplying both is an error, not a precedence rule.'),
41
43
  name: z.string().optional().describe('Override filename (create) or rename target (replace)'),
42
44
  parent: z.string().optional().describe('Destination folder ID (create only)'),
@@ -71,7 +73,7 @@ export function registerExtraDriveTools(server: McpServer): void {
71
73
  // they sent. gog cannot tell the two apart, which is the point.
72
74
  let pathArg: GogArg;
73
75
  if (localPath !== undefined) {
74
- pathArg = localPath;
76
+ pathArg = confinePath(localPath, 'localPath');
75
77
  } else {
76
78
  if (!name) {
77
79
  throw new Error('content requires name: there is no path to derive the Drive filename from.');
@@ -82,7 +84,7 @@ export function registerExtraDriveTools(server: McpServer): void {
82
84
  { positional: true, where: 'content' },
83
85
  ).arg;
84
86
  }
85
- const args: GogArg[] = ['drive', 'upload', pathArg];
87
+ const args: GogArg[] = ['drive', 'upload', typeof pathArg === 'string' ? pos(pathArg) : pathArg];
86
88
  if (name) args.push(`--name=${name}`);
87
89
  if (parent) args.push(`--parent=${parent}`);
88
90
  if (replace) args.push(`--replace=${replace}`);
@@ -98,14 +100,15 @@ export function registerExtraDriveTools(server: McpServer): void {
98
100
  description: 'Recursively push a local directory\'s contents into an existing Drive folder — uploads new files, updates changed ones (matched by name + MD5), and creates missing subfolders. One-way and additive: it never deletes anything on Drive. Use dryRun to preview the planned actions first.',
99
101
  annotations: { destructiveHint: true },
100
102
  inputSchema: z.object({
101
- localPath: z.string().describe('Local directory to push (its contents are mirrored into the parent folder)'),
103
+ localPath: z.string().describe('Local directory to push (its contents are mirrored into the parent folder). Must be inside the server\'s GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).'),
102
104
  parent: z.string().describe('Existing destination Drive folder ID'),
103
105
  dryRun: z.boolean().optional().describe('Preview the planned actions without making any changes'),
104
106
  allDrives: z.boolean().optional().describe('Include shared drives (default: true; set false for My Drive only)'),
105
107
  account: accountParam,
106
108
  }),
107
109
  }, async ({ localPath, parent, dryRun, allDrives, account }) => {
108
- const args = ['drive', 'sync', 'push', localPath, `--parent=${parent}`];
110
+ confinePath(localPath, 'localPath');
111
+ const args: GogArg[] = ['drive', 'sync', 'push', pos(localPath), `--parent=${parent}`];
109
112
  if (dryRun) args.push('--dry-run');
110
113
  if (allDrives === false) args.push('--no-all-drives');
111
114
  return runOrDiagnose(args, { account });
@@ -121,7 +124,7 @@ export function registerExtraDriveTools(server: McpServer): void {
121
124
  account: accountParam,
122
125
  }),
123
126
  }, async ({ fileId, name, parent, account }) => {
124
- const args = ['drive', 'copy', fileId, name];
127
+ const args: GogArg[] = ['drive', 'copy', pos(fileId), pos(name)];
125
128
  if (parent) args.push(`--parent=${parent}`);
126
129
  return runOrDiagnose(args, { account });
127
130
  });
@@ -134,7 +137,7 @@ export function registerExtraDriveTools(server: McpServer): void {
134
137
  account: accountParam,
135
138
  }),
136
139
  }, async ({ fileIds, account }) => {
137
- return runOrDiagnose(['drive', 'url', ...fileIds], { account });
140
+ return runOrDiagnose(['drive', 'url', ...fileIds.map(pos)], { account });
138
141
  });
139
142
 
140
143
  server.registerTool('gog_drive_permissions', {
@@ -148,7 +151,7 @@ export function registerExtraDriveTools(server: McpServer): void {
148
151
  account: accountParam,
149
152
  }),
150
153
  }, async ({ fileId, max, pageToken, page, account }) => {
151
- const args = ['drive', 'permissions', fileId];
154
+ const args: GogArg[] = ['drive', 'permissions', pos(fileId)];
152
155
  if (max !== undefined) args.push(`--max=${max}`);
153
156
  const token = resolvePageToken({ pageToken, page });
154
157
  if (token) args.push(`--page=${token}`);
@@ -164,7 +167,7 @@ export function registerExtraDriveTools(server: McpServer): void {
164
167
  account: accountParam,
165
168
  }),
166
169
  }, async ({ fileId, permissionId, account }) => {
167
- return runOrDiagnose(['drive', 'unshare', fileId, permissionId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
170
+ return runOrDiagnose(['drive', 'unshare', pos(fileId), pos(permissionId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
168
171
  });
169
172
 
170
173
  server.registerTool('gog_drive_drives_list', {
@@ -179,7 +182,7 @@ export function registerExtraDriveTools(server: McpServer): void {
179
182
  account: accountParam,
180
183
  }),
181
184
  }, async ({ max, pageToken, page, all, query, account }) => {
182
- const args = ['drive', 'drives'];
185
+ const args: GogArg[] = ['drive', 'drives'];
183
186
  if (max !== undefined) args.push(`--max=${max}`);
184
187
  const token = resolvePageToken({ pageToken, page });
185
188
  if (token) args.push(`--page=${token}`);
@@ -199,7 +202,7 @@ export function registerExtraDriveTools(server: McpServer): void {
199
202
  account: accountParam,
200
203
  }),
201
204
  }, async ({ fileId, since, includeQuoted, max, page, all, account }) => {
202
- const args = ['drive', 'comments', 'list', fileId];
205
+ const args: GogArg[] = ['drive', 'comments', 'list', pos(fileId)];
203
206
  if (since) args.push(`--since=${since}`);
204
207
  if (includeQuoted) args.push('--include-quoted');
205
208
  pushPaginationFlags(args, { max, page, all });
@@ -215,7 +218,7 @@ export function registerExtraDriveTools(server: McpServer): void {
215
218
  account: accountParam,
216
219
  }),
217
220
  }, async ({ fileId, commentId, account }) => {
218
- return runOrDiagnose(['drive', 'comments', 'get', fileId, commentId], { account });
221
+ return runOrDiagnose(['drive', 'comments', 'get', pos(fileId), pos(commentId)], { account });
219
222
  });
220
223
 
221
224
  server.registerTool('gog_drive_comments_add', {
@@ -227,7 +230,7 @@ export function registerExtraDriveTools(server: McpServer): void {
227
230
  account: accountParam,
228
231
  }),
229
232
  }, async ({ fileId, content, account }) => {
230
- return runOrDiagnose(['drive', 'comments', 'create', fileId, content], { account });
233
+ return runOrDiagnose(['drive', 'comments', 'create', pos(fileId), pos(content)], { account });
231
234
  });
232
235
 
233
236
  server.registerTool('gog_drive_comments_update', {
@@ -240,7 +243,7 @@ export function registerExtraDriveTools(server: McpServer): void {
240
243
  account: accountParam,
241
244
  }),
242
245
  }, async ({ fileId, commentId, content, account }) => {
243
- return runOrDiagnose(['drive', 'comments', 'update', fileId, commentId, content], { account });
246
+ return runOrDiagnose(['drive', 'comments', 'update', pos(fileId), pos(commentId), pos(content)], { account });
244
247
  });
245
248
 
246
249
  server.registerTool('gog_drive_comments_delete', {
@@ -252,7 +255,7 @@ export function registerExtraDriveTools(server: McpServer): void {
252
255
  account: accountParam,
253
256
  }),
254
257
  }, async ({ fileId, commentId, account }) => {
255
- return runOrDiagnose(['drive', 'comments', 'delete', fileId, commentId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
258
+ return runOrDiagnose(['drive', 'comments', 'delete', pos(fileId), pos(commentId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
256
259
  });
257
260
 
258
261
  server.registerTool('gog_drive_comments_reply', {
@@ -266,7 +269,7 @@ export function registerExtraDriveTools(server: McpServer): void {
266
269
  account: accountParam,
267
270
  }),
268
271
  }, async ({ fileId, commentId, content, action, account }) => {
269
- const args = ['drive', 'comments', 'reply', fileId, commentId, content];
272
+ const args: GogArg[] = ['drive', 'comments', 'reply', pos(fileId), pos(commentId), pos(content)];
270
273
  if (action) args.push(`--action=${action}`);
271
274
  return runOrDiagnose(args, { account });
272
275
  });
@@ -280,7 +283,7 @@ export function registerExtraDriveTools(server: McpServer): void {
280
283
  account: accountParam,
281
284
  }),
282
285
  }, async ({ fileId, commentId, account }) => {
283
- return runOrDiagnose(['drive', 'comments', 'resolve', fileId, commentId], { account });
286
+ return runOrDiagnose(['drive', 'comments', 'resolve', pos(fileId), pos(commentId)], { account });
284
287
  });
285
288
 
286
289
  server.registerTool('gog_drive_comments_reopen', {
@@ -292,7 +295,7 @@ export function registerExtraDriveTools(server: McpServer): void {
292
295
  account: accountParam,
293
296
  }),
294
297
  }, async ({ fileId, commentId, account }) => {
295
- return runOrDiagnose(['drive', 'comments', 'reopen', fileId, commentId], { account });
298
+ return runOrDiagnose(['drive', 'comments', 'reopen', pos(fileId), pos(commentId)], { account });
296
299
  });
297
300
 
298
301
  // --- gog 0.19.0 ---
@@ -310,7 +313,7 @@ export function registerExtraDriveTools(server: McpServer): void {
310
313
  account: accountParam,
311
314
  }),
312
315
  }, async ({ parent, depth, max, sort, order, noAllDrives, account }) => {
313
- const args = ['drive', 'du'];
316
+ const args: GogArg[] = ['drive', 'du'];
314
317
  if (parent) args.push(`--parent=${parent}`);
315
318
  if (depth !== undefined) args.push(`--depth=${depth}`);
316
319
  if (max !== undefined) args.push(`--max=${max}`);
@@ -331,7 +334,7 @@ export function registerExtraDriveTools(server: McpServer): void {
331
334
  account: accountParam,
332
335
  }),
333
336
  }, async ({ parent, depth, max, noAllDrives, account }) => {
334
- const args = ['drive', 'tree'];
337
+ const args: GogArg[] = ['drive', 'tree'];
335
338
  if (parent) args.push(`--parent=${parent}`);
336
339
  if (depth !== undefined) args.push(`--depth=${depth}`);
337
340
  if (max !== undefined) args.push(`--max=${max}`);
@@ -347,7 +350,7 @@ export function registerExtraDriveTools(server: McpServer): void {
347
350
  account: accountParam,
348
351
  }),
349
352
  }, async ({ drive, account }) => {
350
- const args = ['drive', 'changes', 'start-token'];
353
+ const args: GogArg[] = ['drive', 'changes', 'start-token'];
351
354
  if (drive) args.push(`--drive=${drive}`);
352
355
  return runOrDiagnose(args, { account });
353
356
  });
@@ -366,7 +369,7 @@ export function registerExtraDriveTools(server: McpServer): void {
366
369
  account: accountParam,
367
370
  }),
368
371
  }, async ({ token, max, pageToken, page, all, includeRemoved, drive, account }) => {
369
- const args = ['drive', 'changes', 'list', `--token=${token}`];
372
+ const args: GogArg[] = ['drive', 'changes', 'list', `--token=${token}`];
370
373
  if (max !== undefined) args.push(`--max=${max}`);
371
374
  // NOT `token` — this tool already binds that name to the start-page token
372
375
  // it takes as a required arg, which is a different value entirely.
@@ -387,7 +390,7 @@ export function registerExtraDriveTools(server: McpServer): void {
387
390
  account: accountParam,
388
391
  }),
389
392
  }, async ({ fileId, max, page, all, account }) => {
390
- const args = ['drive', 'revisions', 'list', fileId];
393
+ const args: GogArg[] = ['drive', 'revisions', 'list', pos(fileId)];
391
394
  pushPaginationFlags(args, { max, page, all });
392
395
  return runOrDiagnose(args, { account });
393
396
  });
@@ -401,7 +404,7 @@ export function registerExtraDriveTools(server: McpServer): void {
401
404
  account: accountParam,
402
405
  }),
403
406
  }, async ({ fileId, revisionId, account }) => {
404
- return runOrDiagnose(['drive', 'revisions', 'get', fileId, revisionId], { account });
407
+ return runOrDiagnose(['drive', 'revisions', 'get', pos(fileId), pos(revisionId)], { account });
405
408
  });
406
409
 
407
410
  server.registerTool('gog_drive_shortcut_create', {
@@ -414,7 +417,7 @@ export function registerExtraDriveTools(server: McpServer): void {
414
417
  account: accountParam,
415
418
  }),
416
419
  }, async ({ targetId, parent, name, account }) => {
417
- const args = ['drive', 'shortcut', 'create', targetId, `--parent=${parent}`];
420
+ const args: GogArg[] = ['drive', 'shortcut', 'create', pos(targetId), `--parent=${parent}`];
418
421
  if (name) args.push(`--name=${name}`);
419
422
  return runOrDiagnose(args, { account });
420
423
  });
@@ -434,7 +437,7 @@ export function registerExtraDriveTools(server: McpServer): void {
434
437
  account: accountParam,
435
438
  }),
436
439
  }, async ({ language, view, minimumRole, publishedOnly, adminAccess, max, pageToken, page, account }) => {
437
- const args = ['drive', 'labels', 'list'];
440
+ const args: GogArg[] = ['drive', 'labels', 'list'];
438
441
  if (max !== undefined) args.push(`--max=${max}`);
439
442
  const token = resolvePageToken({ pageToken, page });
440
443
  if (token) args.push(`--page=${token}`);
@@ -457,7 +460,7 @@ export function registerExtraDriveTools(server: McpServer): void {
457
460
  account: accountParam,
458
461
  }),
459
462
  }, async ({ name, language, view, adminAccess, account }) => {
460
- const args = ['drive', 'labels', 'get', name];
463
+ const args: GogArg[] = ['drive', 'labels', 'get', pos(name)];
461
464
  if (language) args.push(`--language=${language}`);
462
465
  if (view) args.push(`--view=${view}`);
463
466
  if (adminAccess) args.push('--admin-access');
@@ -475,7 +478,7 @@ export function registerExtraDriveTools(server: McpServer): void {
475
478
  account: accountParam,
476
479
  }),
477
480
  }, async ({ fileId, max, pageToken, page, account }) => {
478
- const args = ['drive', 'labels', 'file', 'list', fileId];
481
+ const args: GogArg[] = ['drive', 'labels', 'file', 'list', pos(fileId)];
479
482
  if (max !== undefined) args.push(`--max=${max}`);
480
483
  const token = resolvePageToken({ pageToken, page });
481
484
  if (token) args.push(`--page=${token}`);
@@ -498,7 +501,7 @@ export function registerExtraDriveTools(server: McpServer): void {
498
501
  account: accountParam,
499
502
  }),
500
503
  }, async ({ fileId, labelId, text, selection, integer, date, user, unset, fieldsJson, account }) => {
501
- const args = ['drive', 'labels', 'file', 'apply', fileId, labelId];
504
+ const args: GogArg[] = ['drive', 'labels', 'file', 'apply', pos(fileId), pos(labelId)];
502
505
  if (text) for (const t of text) args.push(`--text=${t}`);
503
506
  if (selection) for (const s of selection) args.push(`--selection=${s}`);
504
507
  if (integer) for (const i of integer) args.push(`--integer=${i}`);
@@ -518,7 +521,7 @@ export function registerExtraDriveTools(server: McpServer): void {
518
521
  account: accountParam,
519
522
  }),
520
523
  }, async ({ fileId, labelId, account }) => {
521
- return runOrDiagnose(['drive', 'labels', 'file', 'remove', fileId, labelId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
524
+ return runOrDiagnose(['drive', 'labels', 'file', 'remove', pos(fileId), pos(labelId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
522
525
  });
523
526
 
524
527
  server.registerTool('gog_drive_activity', {
@@ -539,7 +542,7 @@ export function registerExtraDriveTools(server: McpServer): void {
539
542
  account: accountParam,
540
543
  }),
541
544
  }, async ({ file, folder, actions, from, to, filter, max, pageToken, page, all, consolidate, account }) => {
542
- const args = ['drive', 'activity', 'query'];
545
+ const args: GogArg[] = ['drive', 'activity', 'query'];
543
546
  if (file) args.push(`--file=${file}`);
544
547
  if (folder) args.push(`--folder=${folder}`);
545
548
  if (actions) args.push(`--actions=${actions}`);
@@ -569,7 +572,7 @@ export function registerExtraDriveTools(server: McpServer): void {
569
572
  account: accountParam,
570
573
  }),
571
574
  }, async ({ file, parent, depth, max, internalDomain, publicOnly, externalOnly, noAllDrives, account }) => {
572
- const args = ['drive', 'audit', 'sharing'];
575
+ const args: GogArg[] = ['drive', 'audit', 'sharing'];
573
576
  if (file) args.push(`--file=${file}`);
574
577
  if (parent) args.push(`--parent=${parent}`);
575
578
  if (depth !== undefined) args.push(`--depth=${depth}`);
@@ -594,7 +597,7 @@ export function registerExtraDriveTools(server: McpServer): void {
594
597
  account: accountParam,
595
598
  }),
596
599
  }, async ({ user, file, parent, depth, max, noAllDrives, account }) => {
597
- const args = ['drive', 'audit', 'user', user];
600
+ const args: GogArg[] = ['drive', 'audit', 'user', pos(user)];
598
601
  if (file) args.push(`--file=${file}`);
599
602
  if (parent) args.push(`--parent=${parent}`);
600
603
  if (depth !== undefined) args.push(`--depth=${depth}`);