gogcli-mcp-drive 2.30.0 → 4.0.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +26592 -29857
- package/manifest.json +2 -2
- package/mint.yaml +37 -33
- package/package.json +3 -3
- package/server.json +2 -2
- package/src/index.ts +3 -4
- package/src/tools/drive-extra.ts +64 -64
- package/tests/tools/drive-extra.test.ts +4 -10
- package/tsconfig.json +1 -1
package/src/tools/drive-extra.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { accountParam, runOrDiagnose, paginationParams, pushPaginationFlags, pageTokenParam, pageAliasParam, resolvePageToken, inlineFileArg} from '../../../gogcli-mcp/src/lib.js';
|
|
4
4
|
import type { GogArg } from '../../../gogcli-mcp/src/lib.js';
|
|
@@ -7,13 +7,13 @@ 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
9
|
annotations: { readOnlyHint: true },
|
|
10
|
-
inputSchema: {
|
|
10
|
+
inputSchema: z.object({
|
|
11
11
|
fileId: z.string().describe('File ID to download'),
|
|
12
12
|
out: z.string().optional().describe('Output file path (default: gogcli config dir)'),
|
|
13
13
|
format: z.string().optional().describe('Export format for Google Docs: pdf, csv, xlsx, pptx, txt, png, docx, md (default: inferred)'),
|
|
14
14
|
overwrite: z.boolean().optional().describe('Overwrite the output file if it already exists (gog refuses otherwise)'),
|
|
15
15
|
account: accountParam,
|
|
16
|
-
},
|
|
16
|
+
}),
|
|
17
17
|
}, async ({ fileId, out, format, overwrite, account }) => {
|
|
18
18
|
const args = ['drive', 'download', fileId];
|
|
19
19
|
if (out) args.push(`--out=${out}`);
|
|
@@ -35,8 +35,8 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
35
35
|
'gog_drive_run { subcommand: "raw", args: ["<fileId>", "--fields=version"] } — it comes back as a JSON string, so pass it on as a number. ' +
|
|
36
36
|
'Conditional replacement refuses Google Workspace files (Docs/Sheets/Slides), which have no replaceable binary content.',
|
|
37
37
|
annotations: { destructiveHint: true },
|
|
38
|
-
inputSchema: {
|
|
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
|
|
38
|
+
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.'),
|
|
40
40
|
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
41
|
name: z.string().optional().describe('Override filename (create) or rename target (replace)'),
|
|
42
42
|
parent: z.string().optional().describe('Destination folder ID (create only)'),
|
|
@@ -51,7 +51,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
51
51
|
convert: z.boolean().optional().describe('Auto-convert to native Google format based on file extension (create only)'),
|
|
52
52
|
convertTo: z.string().optional().describe('Convert to a specific Google format: doc | sheet | slides (create only)'),
|
|
53
53
|
account: accountParam,
|
|
54
|
-
},
|
|
54
|
+
}),
|
|
55
55
|
}, async ({ localPath, content, name, parent, replace, ifVersion, mimeType, keepRevisionForever, convert, convertTo, account }) => {
|
|
56
56
|
if (ifVersion !== undefined && !replace) {
|
|
57
57
|
throw new Error('ifVersion requires replace: a version precondition only applies when replacing an existing Drive file.');
|
|
@@ -97,13 +97,13 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
97
97
|
server.registerTool('gog_drive_sync_push', {
|
|
98
98
|
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
99
|
annotations: { destructiveHint: true },
|
|
100
|
-
inputSchema: {
|
|
100
|
+
inputSchema: z.object({
|
|
101
101
|
localPath: z.string().describe('Local directory to push (its contents are mirrored into the parent folder)'),
|
|
102
102
|
parent: z.string().describe('Existing destination Drive folder ID'),
|
|
103
103
|
dryRun: z.boolean().optional().describe('Preview the planned actions without making any changes'),
|
|
104
104
|
allDrives: z.boolean().optional().describe('Include shared drives (default: true; set false for My Drive only)'),
|
|
105
105
|
account: accountParam,
|
|
106
|
-
},
|
|
106
|
+
}),
|
|
107
107
|
}, async ({ localPath, parent, dryRun, allDrives, account }) => {
|
|
108
108
|
const args = ['drive', 'sync', 'push', localPath, `--parent=${parent}`];
|
|
109
109
|
if (dryRun) args.push('--dry-run');
|
|
@@ -113,12 +113,12 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
113
113
|
|
|
114
114
|
server.registerTool('gog_drive_copy', {
|
|
115
115
|
description: 'Copy a Drive file to a new file with the given name.',
|
|
116
|
-
inputSchema: {
|
|
116
|
+
inputSchema: z.object({
|
|
117
117
|
fileId: z.string().describe('File ID to copy'),
|
|
118
118
|
name: z.string().describe('Name for the new copy'),
|
|
119
119
|
parent: z.string().optional().describe('Destination folder ID'),
|
|
120
120
|
account: accountParam,
|
|
121
|
-
},
|
|
121
|
+
}),
|
|
122
122
|
}, async ({ fileId, name, parent, account }) => {
|
|
123
123
|
const args = ['drive', 'copy', fileId, name];
|
|
124
124
|
if (parent) args.push(`--parent=${parent}`);
|
|
@@ -128,10 +128,10 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
128
128
|
server.registerTool('gog_drive_url', {
|
|
129
129
|
description: 'Print shareable web URLs for one or more Drive files.',
|
|
130
130
|
annotations: { readOnlyHint: true },
|
|
131
|
-
inputSchema: {
|
|
131
|
+
inputSchema: z.object({
|
|
132
132
|
fileIds: z.array(z.string()).min(1).describe('One or more file IDs to get URLs for'),
|
|
133
133
|
account: accountParam,
|
|
134
|
-
},
|
|
134
|
+
}),
|
|
135
135
|
}, async ({ fileIds, account }) => {
|
|
136
136
|
return runOrDiagnose(['drive', 'url', ...fileIds], { account });
|
|
137
137
|
});
|
|
@@ -139,13 +139,13 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
139
139
|
server.registerTool('gog_drive_permissions', {
|
|
140
140
|
description: 'List permissions on a Drive file.',
|
|
141
141
|
annotations: { readOnlyHint: true },
|
|
142
|
-
inputSchema: {
|
|
142
|
+
inputSchema: z.object({
|
|
143
143
|
fileId: z.string().describe('File ID'),
|
|
144
144
|
max: z.number().optional().describe('Max results (default: 100)'),
|
|
145
145
|
pageToken: pageTokenParam,
|
|
146
146
|
page: pageAliasParam,
|
|
147
147
|
account: accountParam,
|
|
148
|
-
},
|
|
148
|
+
}),
|
|
149
149
|
}, async ({ fileId, max, pageToken, page, account }) => {
|
|
150
150
|
const args = ['drive', 'permissions', fileId];
|
|
151
151
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -157,11 +157,11 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
157
157
|
server.registerTool('gog_drive_unshare', {
|
|
158
158
|
description: 'Remove a permission from a Drive file. Get the permissionId from gog_drive_permissions.',
|
|
159
159
|
annotations: { destructiveHint: true },
|
|
160
|
-
inputSchema: {
|
|
160
|
+
inputSchema: z.object({
|
|
161
161
|
fileId: z.string().describe('File ID'),
|
|
162
162
|
permissionId: z.string().describe('Permission ID to remove'),
|
|
163
163
|
account: accountParam,
|
|
164
|
-
},
|
|
164
|
+
}),
|
|
165
165
|
}, async ({ fileId, permissionId, account }) => {
|
|
166
166
|
return runOrDiagnose(['drive', 'unshare', fileId, permissionId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
167
167
|
});
|
|
@@ -169,14 +169,14 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
169
169
|
server.registerTool('gog_drive_drives_list', {
|
|
170
170
|
description: 'List shared drives (Team Drives) accessible to the account.',
|
|
171
171
|
annotations: { readOnlyHint: true },
|
|
172
|
-
inputSchema: {
|
|
172
|
+
inputSchema: z.object({
|
|
173
173
|
max: z.number().optional().describe('Max results (default: 100, max allowed: 100)'),
|
|
174
174
|
pageToken: pageTokenParam,
|
|
175
175
|
page: pageAliasParam,
|
|
176
176
|
all: z.boolean().optional().describe('Fetch all pages'),
|
|
177
177
|
query: z.string().optional().describe('Search query for filtering shared drives'),
|
|
178
178
|
account: accountParam,
|
|
179
|
-
},
|
|
179
|
+
}),
|
|
180
180
|
}, async ({ max, pageToken, page, all, query, account }) => {
|
|
181
181
|
const args = ['drive', 'drives'];
|
|
182
182
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -190,13 +190,13 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
190
190
|
server.registerTool('gog_drive_comments_list', {
|
|
191
191
|
description: 'List comments on a Drive file.',
|
|
192
192
|
annotations: { readOnlyHint: true },
|
|
193
|
-
inputSchema: {
|
|
193
|
+
inputSchema: z.object({
|
|
194
194
|
fileId: z.string().describe('File ID'),
|
|
195
195
|
since: z.string().optional().describe('Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)'),
|
|
196
196
|
includeQuoted: z.boolean().optional().describe('Include the quoted content each comment is anchored to'),
|
|
197
197
|
...paginationParams,
|
|
198
198
|
account: accountParam,
|
|
199
|
-
},
|
|
199
|
+
}),
|
|
200
200
|
}, async ({ fileId, since, includeQuoted, max, page, all, account }) => {
|
|
201
201
|
const args = ['drive', 'comments', 'list', fileId];
|
|
202
202
|
if (since) args.push(`--since=${since}`);
|
|
@@ -208,22 +208,22 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
208
208
|
server.registerTool('gog_drive_comments_get', {
|
|
209
209
|
description: 'Get a single comment on a Drive file by ID.',
|
|
210
210
|
annotations: { readOnlyHint: true },
|
|
211
|
-
inputSchema: {
|
|
211
|
+
inputSchema: z.object({
|
|
212
212
|
fileId: z.string().describe('File ID'),
|
|
213
213
|
commentId: z.string().describe('Comment ID'),
|
|
214
214
|
account: accountParam,
|
|
215
|
-
},
|
|
215
|
+
}),
|
|
216
216
|
}, async ({ fileId, commentId, account }) => {
|
|
217
217
|
return runOrDiagnose(['drive', 'comments', 'get', fileId, commentId], { account });
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
server.registerTool('gog_drive_comments_add', {
|
|
221
221
|
description: 'Add a new comment to a Drive file.',
|
|
222
|
-
inputSchema: {
|
|
222
|
+
inputSchema: z.object({
|
|
223
223
|
fileId: z.string().describe('File ID'),
|
|
224
224
|
content: z.string().describe('Comment text'),
|
|
225
225
|
account: accountParam,
|
|
226
|
-
},
|
|
226
|
+
}),
|
|
227
227
|
}, async ({ fileId, content, account }) => {
|
|
228
228
|
return runOrDiagnose(['drive', 'comments', 'create', fileId, content], { account });
|
|
229
229
|
});
|
|
@@ -231,12 +231,12 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
231
231
|
server.registerTool('gog_drive_comments_update', {
|
|
232
232
|
description: 'Update the text of an existing comment.',
|
|
233
233
|
annotations: { destructiveHint: true },
|
|
234
|
-
inputSchema: {
|
|
234
|
+
inputSchema: z.object({
|
|
235
235
|
fileId: z.string().describe('File ID'),
|
|
236
236
|
commentId: z.string().describe('Comment ID to update'),
|
|
237
237
|
content: z.string().describe('New comment text'),
|
|
238
238
|
account: accountParam,
|
|
239
|
-
},
|
|
239
|
+
}),
|
|
240
240
|
}, async ({ fileId, commentId, content, account }) => {
|
|
241
241
|
return runOrDiagnose(['drive', 'comments', 'update', fileId, commentId, content], { account });
|
|
242
242
|
});
|
|
@@ -244,24 +244,24 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
244
244
|
server.registerTool('gog_drive_comments_delete', {
|
|
245
245
|
description: 'Delete a comment from a Drive file.',
|
|
246
246
|
annotations: { destructiveHint: true },
|
|
247
|
-
inputSchema: {
|
|
247
|
+
inputSchema: z.object({
|
|
248
248
|
fileId: z.string().describe('File ID'),
|
|
249
249
|
commentId: z.string().describe('Comment ID to delete'),
|
|
250
250
|
account: accountParam,
|
|
251
|
-
},
|
|
251
|
+
}),
|
|
252
252
|
}, async ({ fileId, commentId, account }) => {
|
|
253
253
|
return runOrDiagnose(['drive', 'comments', 'delete', fileId, commentId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
254
254
|
});
|
|
255
255
|
|
|
256
256
|
server.registerTool('gog_drive_comments_reply', {
|
|
257
257
|
description: 'Reply to an existing comment on a Drive file. Pass `action: "resolve"` or `"reopen"` to atomically flip the parent comment\'s resolved state via the Drive API\'s Reply.action field — avoids the older workaround of deleting the comment (which destroys review-thread context).',
|
|
258
|
-
inputSchema: {
|
|
258
|
+
inputSchema: z.object({
|
|
259
259
|
fileId: z.string().describe('File ID'),
|
|
260
260
|
commentId: z.string().describe('Comment ID to reply to'),
|
|
261
261
|
content: z.string().describe('Reply text'),
|
|
262
262
|
action: z.enum(['resolve', 'reopen']).optional().describe('Optional action on the parent comment alongside the reply'),
|
|
263
263
|
account: accountParam,
|
|
264
|
-
},
|
|
264
|
+
}),
|
|
265
265
|
}, async ({ fileId, commentId, content, action, account }) => {
|
|
266
266
|
const args = ['drive', 'comments', 'reply', fileId, commentId, content];
|
|
267
267
|
if (action) args.push(`--action=${action}`);
|
|
@@ -271,11 +271,11 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
271
271
|
server.registerTool('gog_drive_comments_resolve', {
|
|
272
272
|
description: 'Resolve a comment on a Drive file (mark as done) without posting a reply. To resolve while replying, use gog_drive_comments_reply with action: "resolve".',
|
|
273
273
|
annotations: { destructiveHint: true },
|
|
274
|
-
inputSchema: {
|
|
274
|
+
inputSchema: z.object({
|
|
275
275
|
fileId: z.string().describe('File ID'),
|
|
276
276
|
commentId: z.string().describe('Comment ID to resolve'),
|
|
277
277
|
account: accountParam,
|
|
278
|
-
},
|
|
278
|
+
}),
|
|
279
279
|
}, async ({ fileId, commentId, account }) => {
|
|
280
280
|
return runOrDiagnose(['drive', 'comments', 'resolve', fileId, commentId], { account });
|
|
281
281
|
});
|
|
@@ -283,11 +283,11 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
283
283
|
server.registerTool('gog_drive_comments_reopen', {
|
|
284
284
|
description: 'Reopen a previously resolved comment on a Drive file. To reopen while replying, use gog_drive_comments_reply with action: "reopen".',
|
|
285
285
|
annotations: { destructiveHint: true },
|
|
286
|
-
inputSchema: {
|
|
286
|
+
inputSchema: z.object({
|
|
287
287
|
fileId: z.string().describe('File ID'),
|
|
288
288
|
commentId: z.string().describe('Comment ID to reopen'),
|
|
289
289
|
account: accountParam,
|
|
290
|
-
},
|
|
290
|
+
}),
|
|
291
291
|
}, async ({ fileId, commentId, account }) => {
|
|
292
292
|
return runOrDiagnose(['drive', 'comments', 'reopen', fileId, commentId], { account });
|
|
293
293
|
});
|
|
@@ -297,7 +297,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
297
297
|
server.registerTool('gog_drive_du', {
|
|
298
298
|
description: 'Summarize Drive folder sizes (disk-usage style) starting from a folder.',
|
|
299
299
|
annotations: { readOnlyHint: true },
|
|
300
|
-
inputSchema: {
|
|
300
|
+
inputSchema: z.object({
|
|
301
301
|
parent: z.string().optional().describe('Folder ID to start from (default: root)'),
|
|
302
302
|
depth: z.number().optional().describe('Depth for folder totals (default: 1)'),
|
|
303
303
|
max: z.number().optional().describe('Max folders to return (0 = unlimited; default: 50)'),
|
|
@@ -305,7 +305,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
305
305
|
order: z.enum(['asc', 'desc']).optional().describe('Sort order (default: desc)'),
|
|
306
306
|
noAllDrives: z.boolean().optional().describe('My Drive only — exclude shared drives (shared drives are included by default)'),
|
|
307
307
|
account: accountParam,
|
|
308
|
-
},
|
|
308
|
+
}),
|
|
309
309
|
}, async ({ parent, depth, max, sort, order, noAllDrives, account }) => {
|
|
310
310
|
const args = ['drive', 'du'];
|
|
311
311
|
if (parent) args.push(`--parent=${parent}`);
|
|
@@ -320,13 +320,13 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
320
320
|
server.registerTool('gog_drive_tree', {
|
|
321
321
|
description: 'Print a read-only folder tree starting from a folder.',
|
|
322
322
|
annotations: { readOnlyHint: true },
|
|
323
|
-
inputSchema: {
|
|
323
|
+
inputSchema: z.object({
|
|
324
324
|
parent: z.string().optional().describe('Folder ID to start from (default: root)'),
|
|
325
325
|
depth: z.number().optional().describe('Max depth (0 = unlimited; default: 2)'),
|
|
326
326
|
max: z.number().optional().describe('Max items to return (0 = unlimited; default: 0)'),
|
|
327
327
|
noAllDrives: z.boolean().optional().describe('My Drive only — exclude shared drives (shared drives are included by default)'),
|
|
328
328
|
account: accountParam,
|
|
329
|
-
},
|
|
329
|
+
}),
|
|
330
330
|
}, async ({ parent, depth, max, noAllDrives, account }) => {
|
|
331
331
|
const args = ['drive', 'tree'];
|
|
332
332
|
if (parent) args.push(`--parent=${parent}`);
|
|
@@ -339,10 +339,10 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
339
339
|
server.registerTool('gog_drive_changes_start_token', {
|
|
340
340
|
description: 'Get a Drive changes start page token — the cursor you pass to gog_drive_changes_list to enumerate changes since this point.',
|
|
341
341
|
annotations: { readOnlyHint: true },
|
|
342
|
-
inputSchema: {
|
|
342
|
+
inputSchema: z.object({
|
|
343
343
|
drive: z.string().optional().describe('Shared drive ID for a shared-drive change log'),
|
|
344
344
|
account: accountParam,
|
|
345
|
-
},
|
|
345
|
+
}),
|
|
346
346
|
}, async ({ drive, account }) => {
|
|
347
347
|
const args = ['drive', 'changes', 'start-token'];
|
|
348
348
|
if (drive) args.push(`--drive=${drive}`);
|
|
@@ -352,7 +352,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
352
352
|
server.registerTool('gog_drive_changes_list', {
|
|
353
353
|
description: 'List Drive changes since a page token (for sync/automation). Get the initial token from gog_drive_changes_start_token; the response includes a newStartPageToken to persist for the next poll.',
|
|
354
354
|
annotations: { readOnlyHint: true },
|
|
355
|
-
inputSchema: {
|
|
355
|
+
inputSchema: z.object({
|
|
356
356
|
token: z.string().describe('Start page token or next page token'),
|
|
357
357
|
max: z.number().optional().describe('Max results (default: 100)'),
|
|
358
358
|
pageToken: pageTokenParam,
|
|
@@ -361,7 +361,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
361
361
|
includeRemoved: z.boolean().optional().describe('Include removed changes'),
|
|
362
362
|
drive: z.string().optional().describe('Shared drive ID for a shared-drive change log'),
|
|
363
363
|
account: accountParam,
|
|
364
|
-
},
|
|
364
|
+
}),
|
|
365
365
|
}, async ({ token, max, pageToken, page, all, includeRemoved, drive, account }) => {
|
|
366
366
|
const args = ['drive', 'changes', 'list', `--token=${token}`];
|
|
367
367
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -378,11 +378,11 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
378
378
|
server.registerTool('gog_drive_revisions_list', {
|
|
379
379
|
description: 'List a file\'s revision history — paged revision metadata plus provider export links for each revision.',
|
|
380
380
|
annotations: { readOnlyHint: true },
|
|
381
|
-
inputSchema: {
|
|
381
|
+
inputSchema: z.object({
|
|
382
382
|
fileId: z.string().describe('File ID'),
|
|
383
383
|
...paginationParams,
|
|
384
384
|
account: accountParam,
|
|
385
|
-
},
|
|
385
|
+
}),
|
|
386
386
|
}, async ({ fileId, max, page, all, account }) => {
|
|
387
387
|
const args = ['drive', 'revisions', 'list', fileId];
|
|
388
388
|
pushPaginationFlags(args, { max, page, all });
|
|
@@ -392,23 +392,23 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
392
392
|
server.registerTool('gog_drive_revisions_get', {
|
|
393
393
|
description: 'Get one revision of a Drive file by revision ID (metadata + export links). Find revision IDs with gog_drive_revisions_list.',
|
|
394
394
|
annotations: { readOnlyHint: true },
|
|
395
|
-
inputSchema: {
|
|
395
|
+
inputSchema: z.object({
|
|
396
396
|
fileId: z.string().describe('File ID'),
|
|
397
397
|
revisionId: z.string().describe('Revision ID'),
|
|
398
398
|
account: accountParam,
|
|
399
|
-
},
|
|
399
|
+
}),
|
|
400
400
|
}, async ({ fileId, revisionId, account }) => {
|
|
401
401
|
return runOrDiagnose(['drive', 'revisions', 'get', fileId, revisionId], { account });
|
|
402
402
|
});
|
|
403
403
|
|
|
404
404
|
server.registerTool('gog_drive_shortcut_create', {
|
|
405
405
|
description: 'Create a Drive shortcut to a file or folder inside a destination folder. Shortcuts are classified distinctly in listing/tree output and are never followed by tree scans.',
|
|
406
|
-
inputSchema: {
|
|
406
|
+
inputSchema: z.object({
|
|
407
407
|
targetId: z.string().describe('File or folder ID the shortcut points to'),
|
|
408
408
|
parent: z.string().describe('Destination folder ID for the shortcut'),
|
|
409
409
|
name: z.string().optional().describe('Shortcut name (default: the target\'s name)'),
|
|
410
410
|
account: accountParam,
|
|
411
|
-
},
|
|
411
|
+
}),
|
|
412
412
|
}, async ({ targetId, parent, name, account }) => {
|
|
413
413
|
const args = ['drive', 'shortcut', 'create', targetId, `--parent=${parent}`];
|
|
414
414
|
if (name) args.push(`--name=${name}`);
|
|
@@ -418,7 +418,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
418
418
|
server.registerTool('gog_drive_labels_list', {
|
|
419
419
|
description: 'List Drive label schemas available to the account.',
|
|
420
420
|
annotations: { readOnlyHint: true },
|
|
421
|
-
inputSchema: {
|
|
421
|
+
inputSchema: z.object({
|
|
422
422
|
language: z.string().optional().describe('BCP-47 language code'),
|
|
423
423
|
view: z.enum(['LABEL_VIEW_BASIC', 'LABEL_VIEW_FULL']).optional().describe('Label view (default: LABEL_VIEW_BASIC)'),
|
|
424
424
|
minimumRole: z.string().optional().describe('Minimum role filter (e.g. READER, APPLIER, ORGANIZER)'),
|
|
@@ -428,7 +428,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
428
428
|
pageToken: pageTokenParam,
|
|
429
429
|
page: pageAliasParam,
|
|
430
430
|
account: accountParam,
|
|
431
|
-
},
|
|
431
|
+
}),
|
|
432
432
|
}, async ({ language, view, minimumRole, publishedOnly, adminAccess, max, pageToken, page, account }) => {
|
|
433
433
|
const args = ['drive', 'labels', 'list'];
|
|
434
434
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -445,13 +445,13 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
445
445
|
server.registerTool('gog_drive_labels_get', {
|
|
446
446
|
description: 'Get a single Drive label schema by name (e.g. "labels/abc123").',
|
|
447
447
|
annotations: { readOnlyHint: true },
|
|
448
|
-
inputSchema: {
|
|
448
|
+
inputSchema: z.object({
|
|
449
449
|
name: z.string().describe('Label schema name (e.g. labels/abc123)'),
|
|
450
450
|
language: z.string().optional().describe('BCP-47 language code'),
|
|
451
451
|
view: z.enum(['LABEL_VIEW_BASIC', 'LABEL_VIEW_FULL']).optional().describe('Label view (default: LABEL_VIEW_FULL)'),
|
|
452
452
|
adminAccess: z.boolean().optional().describe('Use admin access for Workspace admin accounts'),
|
|
453
453
|
account: accountParam,
|
|
454
|
-
},
|
|
454
|
+
}),
|
|
455
455
|
}, async ({ name, language, view, adminAccess, account }) => {
|
|
456
456
|
const args = ['drive', 'labels', 'get', name];
|
|
457
457
|
if (language) args.push(`--language=${language}`);
|
|
@@ -463,13 +463,13 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
463
463
|
server.registerTool('gog_drive_labels_file_list', {
|
|
464
464
|
description: 'List labels applied to a Drive file.',
|
|
465
465
|
annotations: { readOnlyHint: true },
|
|
466
|
-
inputSchema: {
|
|
466
|
+
inputSchema: z.object({
|
|
467
467
|
fileId: z.string().describe('File ID'),
|
|
468
468
|
max: z.number().optional().describe('Max results (default: 100)'),
|
|
469
469
|
pageToken: pageTokenParam,
|
|
470
470
|
page: pageAliasParam,
|
|
471
471
|
account: accountParam,
|
|
472
|
-
},
|
|
472
|
+
}),
|
|
473
473
|
}, async ({ fileId, max, pageToken, page, account }) => {
|
|
474
474
|
const args = ['drive', 'labels', 'file', 'list', fileId];
|
|
475
475
|
if (max !== undefined) args.push(`--max=${max}`);
|
|
@@ -480,7 +480,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
480
480
|
|
|
481
481
|
server.registerTool('gog_drive_labels_file_apply', {
|
|
482
482
|
description: 'Apply or update a label on a Drive file, optionally setting field values. Each field flag takes "fieldId=value" entries (repeatable). selection/integer/date/user values may be comma-separated within one entry for multi-valued fields.',
|
|
483
|
-
inputSchema: {
|
|
483
|
+
inputSchema: z.object({
|
|
484
484
|
fileId: z.string().describe('File ID'),
|
|
485
485
|
labelId: z.string().describe('Label ID to apply'),
|
|
486
486
|
text: z.array(z.string()).optional().describe('Text fields as fieldId=value (repeatable)'),
|
|
@@ -491,7 +491,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
491
491
|
unset: z.array(z.string()).optional().describe('Field IDs to unset (repeatable)'),
|
|
492
492
|
fieldsJson: z.string().optional().describe('Simple JSON object of fieldId to string/number/bool/string-array values'),
|
|
493
493
|
account: accountParam,
|
|
494
|
-
},
|
|
494
|
+
}),
|
|
495
495
|
}, async ({ fileId, labelId, text, selection, integer, date, user, unset, fieldsJson, account }) => {
|
|
496
496
|
const args = ['drive', 'labels', 'file', 'apply', fileId, labelId];
|
|
497
497
|
if (text) for (const t of text) args.push(`--text=${t}`);
|
|
@@ -507,11 +507,11 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
507
507
|
server.registerTool('gog_drive_labels_file_remove', {
|
|
508
508
|
description: 'Remove a label from a Drive file.',
|
|
509
509
|
annotations: { destructiveHint: true },
|
|
510
|
-
inputSchema: {
|
|
510
|
+
inputSchema: z.object({
|
|
511
511
|
fileId: z.string().describe('File ID'),
|
|
512
512
|
labelId: z.string().describe('Label ID to remove'),
|
|
513
513
|
account: accountParam,
|
|
514
|
-
},
|
|
514
|
+
}),
|
|
515
515
|
}, async ({ fileId, labelId, account }) => {
|
|
516
516
|
return runOrDiagnose(['drive', 'labels', 'file', 'remove', fileId, labelId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
517
517
|
});
|
|
@@ -519,7 +519,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
519
519
|
server.registerTool('gog_drive_activity', {
|
|
520
520
|
description: 'Query the Drive Activity API for audit events (edits, creates, deletes, moves, shares, etc.) scoped to a file or folder and/or time range.',
|
|
521
521
|
annotations: { readOnlyHint: true },
|
|
522
|
-
inputSchema: {
|
|
522
|
+
inputSchema: z.object({
|
|
523
523
|
file: z.string().optional().describe('Drive file ID to query'),
|
|
524
524
|
folder: z.string().optional().describe('Drive folder ID; includes descendants'),
|
|
525
525
|
actions: z.string().optional().describe('Comma-separated action filters: edit,create,delete,move,rename,restore,comment,share,label,dlp,reference,settings'),
|
|
@@ -532,7 +532,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
532
532
|
all: z.boolean().optional().describe('Fetch all pages'),
|
|
533
533
|
consolidate: z.boolean().optional().describe('Use Drive Activity legacy consolidation strategy'),
|
|
534
534
|
account: accountParam,
|
|
535
|
-
},
|
|
535
|
+
}),
|
|
536
536
|
}, async ({ file, folder, actions, from, to, filter, max, pageToken, page, all, consolidate, account }) => {
|
|
537
537
|
const args = ['drive', 'activity', 'query'];
|
|
538
538
|
if (file) args.push(`--file=${file}`);
|
|
@@ -552,7 +552,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
552
552
|
server.registerTool('gog_drive_audit_sharing', {
|
|
553
553
|
description: 'Audit Drive sharing without mutation — find public (anyone-with-link) or external permissions across a folder tree or a single file.',
|
|
554
554
|
annotations: { readOnlyHint: true },
|
|
555
|
-
inputSchema: {
|
|
555
|
+
inputSchema: z.object({
|
|
556
556
|
file: z.string().optional().describe('Audit one file ID instead of a folder tree'),
|
|
557
557
|
parent: z.string().optional().describe('Folder ID to scan (default: root)'),
|
|
558
558
|
depth: z.number().optional().describe('Max folder depth (0 = unlimited; default: 2)'),
|
|
@@ -562,7 +562,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
562
562
|
externalOnly: z.boolean().optional().describe('Only report external user/group/domain permissions'),
|
|
563
563
|
noAllDrives: z.boolean().optional().describe('My Drive only — exclude shared drives (shared drives are included by default)'),
|
|
564
564
|
account: accountParam,
|
|
565
|
-
},
|
|
565
|
+
}),
|
|
566
566
|
}, async ({ file, parent, depth, max, internalDomain, publicOnly, externalOnly, noAllDrives, account }) => {
|
|
567
567
|
const args = ['drive', 'audit', 'sharing'];
|
|
568
568
|
if (file) args.push(`--file=${file}`);
|
|
@@ -579,7 +579,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
579
579
|
server.registerTool('gog_drive_audit_user', {
|
|
580
580
|
description: 'Audit Drive sharing without mutation — find permissions granted to a specific user across a folder tree or a single file.',
|
|
581
581
|
annotations: { readOnlyHint: true },
|
|
582
|
-
inputSchema: {
|
|
582
|
+
inputSchema: z.object({
|
|
583
583
|
user: z.string().describe('User email to audit permissions for'),
|
|
584
584
|
file: z.string().optional().describe('Audit one file ID instead of a folder tree'),
|
|
585
585
|
parent: z.string().optional().describe('Folder ID to scan (default: root)'),
|
|
@@ -587,7 +587,7 @@ export function registerExtraDriveTools(server: McpServer): void {
|
|
|
587
587
|
max: z.number().optional().describe('Max files/folders to scan (0 = unlimited; default: 500)'),
|
|
588
588
|
noAllDrives: z.boolean().optional().describe('My Drive only — exclude shared drives (shared drives are included by default)'),
|
|
589
589
|
account: accountParam,
|
|
590
|
-
},
|
|
590
|
+
}),
|
|
591
591
|
}, async ({ user, file, parent, depth, max, noAllDrives, account }) => {
|
|
592
592
|
const args = ['drive', 'audit', 'user', user];
|
|
593
593
|
if (file) args.push(`--file=${file}`);
|
|
@@ -189,7 +189,7 @@ describe('gog_drive_upload', () => {
|
|
|
189
189
|
// and it is reachable only through the gog_drive_run escape hatch — so the
|
|
190
190
|
// description has to say so or the flag is unusable.
|
|
191
191
|
it('description explains how to obtain the current version', async () => {
|
|
192
|
-
const { McpServer } = await import('@modelcontextprotocol/
|
|
192
|
+
const { McpServer } = await import('@modelcontextprotocol/server');
|
|
193
193
|
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
194
194
|
const configs = new Map<string, { description?: string }>();
|
|
195
195
|
vi.spyOn(server, 'registerTool').mockImplementation((name, config) => {
|
|
@@ -803,15 +803,9 @@ describe('gog_drive_shortcut_create', () => {
|
|
|
803
803
|
// conditional replace. The description has to say to convert it.
|
|
804
804
|
describe('gog_drive_upload ifVersion description', () => {
|
|
805
805
|
it('warns that the version reads back as a JSON string', async () => {
|
|
806
|
-
const
|
|
807
|
-
const
|
|
808
|
-
const
|
|
809
|
-
vi.spyOn(server, 'registerTool').mockImplementation((name, config) => {
|
|
810
|
-
schemas.set(name, (config as { inputSchema: Record<string, { description?: string }> }).inputSchema);
|
|
811
|
-
return undefined as never;
|
|
812
|
-
});
|
|
813
|
-
registerExtraDriveTools(server);
|
|
814
|
-
const desc = schemas.get('gog_drive_upload')?.ifVersion?.description ?? '';
|
|
806
|
+
const listed = (await harness.client.listTools()).tools.find((tool) => tool.name === 'gog_drive_upload');
|
|
807
|
+
const properties = listed?.inputSchema.properties as Record<string, { description?: string }> | undefined;
|
|
808
|
+
const desc = properties?.ifVersion?.description ?? '';
|
|
815
809
|
expect(desc).toMatch(/string/i);
|
|
816
810
|
expect(desc).toMatch(/number/i);
|
|
817
811
|
});
|
package/tsconfig.json
CHANGED