@piotr-agier/google-drive-mcp 1.7.0 → 1.7.1

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/README.md CHANGED
@@ -400,14 +400,10 @@ Add the server to your Claude Desktop configuration:
400
400
  - `revisionId`: Revision ID to restore
401
401
  - `confirm`: Must be `true` to execute restore
402
402
 
403
- #### Auth Diagnostics & Scope Presets (v1.7.0)
403
+ #### Auth Diagnostics (v1.7.0)
404
404
  - **authGetStatus** - Show token/scopes/auth health diagnostics (machine + human readable)
405
405
  - **authListScopes** - Show configured/requested scopes, granted scopes, missing scopes, and presets
406
406
  - **authTestFileAccess** - Test Drive access (optionally against a specific `fileId`)
407
- - **authClearTokens** - Clear saved OAuth tokens (`confirm=true` required)
408
- - **authSuggestScopePreset** - Get scope configuration instructions for a preset
409
- - `preset`: `readonly` | `content-editor` | `full`
410
- - `clearTokens`: optional best-effort token clear
411
407
 
412
408
  - **uploadFile** - Upload a local file (any type: image, audio, video, PDF, etc.) to Google Drive
413
409
  - `localPath`: Absolute path to the local file
package/dist/index.js CHANGED
@@ -699,7 +699,7 @@ __export(drive_exports, {
699
699
  });
700
700
  import { z } from "zod";
701
701
  import { existsSync as existsSync2, statSync as statSync2, createReadStream } from "fs";
702
- import { mkdtemp, readFile as readFile3, writeFile as writeFile2, rm, unlink as unlink2 } from "fs/promises";
702
+ import { mkdtemp, readFile as readFile3, writeFile as writeFile2, rm } from "fs/promises";
703
703
  import { tmpdir } from "os";
704
704
  import { basename as basename2, extname as extname2, join as join3 } from "path";
705
705
  import { PDFDocument } from "pdf-lib";
@@ -916,7 +916,8 @@ var BINARY_MIME_TYPES = {
916
916
  var SearchSchema = z.object({
917
917
  query: z.string().min(1, "Search query is required"),
918
918
  pageSize: z.number().int().min(1).max(100).optional(),
919
- pageToken: z.string().optional()
919
+ pageToken: z.string().optional(),
920
+ rawQuery: z.boolean().optional()
920
921
  });
921
922
  var CreateTextFileSchema = z.object({
922
923
  name: z.string().min(1, "File name is required"),
@@ -1050,13 +1051,6 @@ var RestoreRevisionSchema = z.object({
1050
1051
  var AuthTestFileAccessSchema = z.object({
1051
1052
  fileId: z.string().optional()
1052
1053
  });
1053
- var AuthClearTokensSchema = z.object({
1054
- confirm: z.boolean().optional().default(false)
1055
- });
1056
- var AuthSetScopePresetSchema = z.object({
1057
- preset: z.enum(["readonly", "content-editor", "full"]),
1058
- clearTokens: z.boolean().optional().default(false)
1059
- });
1060
1054
  function getGrantedScopesFromAuthClient(ctx) {
1061
1055
  const scopeRaw = ctx.authClient?.credentials?.scope;
1062
1056
  if (!scopeRaw || typeof scopeRaw !== "string") return [];
@@ -1065,13 +1059,14 @@ function getGrantedScopesFromAuthClient(ctx) {
1065
1059
  var toolDefinitions = [
1066
1060
  {
1067
1061
  name: "search",
1068
- description: "Search for files in Google Drive",
1062
+ description: "Search for files in Google Drive. Set rawQuery=true to pass a raw Google Drive API query supporting operators like modifiedTime, createdTime, mimeType, name contains, etc.",
1069
1063
  inputSchema: {
1070
1064
  type: "object",
1071
1065
  properties: {
1072
- query: { type: "string", description: "Search query" },
1066
+ query: { type: "string", description: "Search query. When rawQuery=true, this is passed directly to the Google Drive API as the q parameter." },
1073
1067
  pageSize: { type: "number", description: "Results per page (default 50, max 100)" },
1074
- pageToken: { type: "string", description: "Token for next page of results" }
1068
+ pageToken: { type: "string", description: "Token for next page of results" },
1069
+ rawQuery: { type: "boolean", description: "If true, pass query directly to Google Drive API without wrapping in fullText contains. Enables date filters, mimeType filters, etc." }
1075
1070
  },
1076
1071
  required: ["query"]
1077
1072
  }
@@ -1371,53 +1366,71 @@ var toolDefinitions = [
1371
1366
  fileId: { type: "string", description: "Optional file ID for targeted access check" }
1372
1367
  }
1373
1368
  }
1374
- },
1375
- {
1376
- name: "authClearTokens",
1377
- description: "Clear saved OAuth tokens (requires confirm=true)",
1378
- inputSchema: {
1379
- type: "object",
1380
- properties: {
1381
- confirm: { type: "boolean", description: "Must be true to clear tokens" }
1382
- }
1383
- }
1384
- },
1385
- {
1386
- name: "authSuggestScopePreset",
1387
- description: "Get scope configuration instructions for a preset",
1388
- inputSchema: {
1389
- type: "object",
1390
- properties: {
1391
- preset: { type: "string", enum: ["readonly", "content-editor", "full"], description: "Scope preset" },
1392
- clearTokens: { type: "boolean", description: "Also clear saved tokens now" }
1393
- },
1394
- required: ["preset"]
1395
- }
1396
1369
  }
1397
1370
  ];
1398
1371
  async function handleTool(toolName, args, ctx) {
1399
1372
  switch (toolName) {
1400
1373
  case "search": {
1374
+ let resolveParentPath2 = function(folderId, depth = 0) {
1375
+ if (depth >= 10) return Promise.resolve(folderId);
1376
+ if (folderId in pathCache) return pathCache[folderId];
1377
+ const promise = (async () => {
1378
+ try {
1379
+ const folderRes = await ctx.getDrive().files.get({
1380
+ fileId: folderId,
1381
+ fields: "name, parents",
1382
+ supportsAllDrives: true
1383
+ });
1384
+ const name = folderRes.data.name || folderId;
1385
+ const parents = folderRes.data.parents;
1386
+ if (parents && parents.length > 0 && parents[0] !== folderId) {
1387
+ const parentPath = await resolveParentPath2(parents[0], depth + 1);
1388
+ return `${parentPath}/${name}`;
1389
+ }
1390
+ return name;
1391
+ } catch {
1392
+ return folderId;
1393
+ }
1394
+ })();
1395
+ pathCache[folderId] = promise;
1396
+ return promise;
1397
+ };
1398
+ var resolveParentPath = resolveParentPath2;
1401
1399
  const validation = SearchSchema.safeParse(args);
1402
1400
  if (!validation.success) {
1403
1401
  return errorResponse(validation.error.errors[0].message);
1404
1402
  }
1405
- const { query: userQuery, pageSize, pageToken } = validation.data;
1406
- const escapedQuery = escapeDriveQuery(userQuery);
1407
- const formattedQuery = `fullText contains '${escapedQuery}' and trashed = false`;
1403
+ const { query: userQuery, pageSize, pageToken, rawQuery } = validation.data;
1404
+ let formattedQuery;
1405
+ if (rawQuery) {
1406
+ formattedQuery = /\btrashed\s*=/.test(userQuery) ? userQuery : `${userQuery} and trashed = false`;
1407
+ } else {
1408
+ const escapedQuery = escapeDriveQuery(userQuery);
1409
+ formattedQuery = `fullText contains '${escapedQuery}' and trashed = false`;
1410
+ }
1408
1411
  const res = await ctx.getDrive().files.list({
1409
1412
  q: formattedQuery,
1410
1413
  pageSize: Math.min(pageSize || 50, 100),
1411
1414
  pageToken,
1412
- fields: "nextPageToken, files(id, name, mimeType, modifiedTime, size)",
1415
+ fields: "nextPageToken, files(id, name, mimeType, createdTime, modifiedTime, size, parents)",
1413
1416
  corpora: "allDrives",
1414
1417
  includeItemsFromAllDrives: true,
1415
1418
  supportsAllDrives: true
1416
1419
  });
1417
- const fileList = res.data.files?.map((f) => `${f.name} (ID: ${f.id}, ${f.mimeType})`).join("\n") || "";
1418
- ctx.log("Search results", { query: userQuery, resultCount: res.data.files?.length });
1419
- let response = `Found ${res.data.files?.length ?? 0} files:
1420
- ${fileList}`;
1420
+ const pathCache = {};
1421
+ const files = res.data.files || [];
1422
+ const fileLines = await Promise.all(
1423
+ files.map(async (f) => {
1424
+ let folderPath = "";
1425
+ if (f.parents && f.parents.length > 0) {
1426
+ folderPath = await resolveParentPath2(f.parents[0]);
1427
+ }
1428
+ return `${f.name} (${f.mimeType}) [id: ${f.id}, path: ${folderPath || "/"}] [created: ${f.createdTime || "N/A"}, modified: ${f.modifiedTime || "N/A"}]`;
1429
+ })
1430
+ );
1431
+ ctx.log("Search results", { query: userQuery, rawQuery: !!rawQuery, resultCount: files.length });
1432
+ let response = `Found ${files.length} files:
1433
+ ${fileLines.join("\n")}`;
1421
1434
  if (res.data.nextPageToken) {
1422
1435
  response += `
1423
1436
 
@@ -2206,62 +2219,6 @@ ${JSON.stringify({ message }, null, 2)}` }],
2206
2219
  };
2207
2220
  }
2208
2221
  }
2209
- case "authClearTokens": {
2210
- const validation = AuthClearTokensSchema.safeParse(args);
2211
- if (!validation.success) return errorResponse(validation.error.errors[0].message);
2212
- const data = validation.data;
2213
- if (!data.confirm) return errorResponse("Refusing token clear: set confirm=true.");
2214
- const tokenPath = getSecureTokenPath();
2215
- const existed = existsSync2(tokenPath);
2216
- if (existed) {
2217
- try {
2218
- await unlink2(tokenPath);
2219
- } catch (e) {
2220
- const msg = e instanceof Error ? e.message : String(e);
2221
- return errorResponse(`Failed to remove token file at ${tokenPath}: ${msg}`);
2222
- }
2223
- }
2224
- return {
2225
- content: [{ type: "text", text: `Tokens cleared. File previously ${existed ? "existed" : "did not exist"} at ${tokenPath}. Re-run auth flow before next privileged operation.` }],
2226
- isError: false
2227
- };
2228
- }
2229
- case "authSuggestScopePreset": {
2230
- const validation = AuthSetScopePresetSchema.safeParse(args);
2231
- if (!validation.success) return errorResponse(validation.error.errors[0].message);
2232
- const data = validation.data;
2233
- const aliases = SCOPE_PRESETS[data.preset];
2234
- const resolved = aliases.map((a) => SCOPE_ALIASES[a]);
2235
- const envValue = aliases.join(",");
2236
- let clearMessage = "";
2237
- if (data.clearTokens) {
2238
- const tokenPath = getSecureTokenPath();
2239
- try {
2240
- await unlink2(tokenPath);
2241
- clearMessage = `
2242
- Tokens cleared at ${tokenPath}.`;
2243
- } catch (_e) {
2244
- clearMessage = `
2245
- Tokens clear requested, but no token file removed.`;
2246
- }
2247
- }
2248
- return {
2249
- content: [{
2250
- type: "text",
2251
- text: `Scope preset selected: ${data.preset}
2252
- Requested scopes: ${JSON.stringify(resolved, null, 2)}
2253
-
2254
- Next steps:
2255
- 1) Export scope env:
2256
- GOOGLE_DRIVE_MCP_SCOPES=${envValue}
2257
- 2) Restart MCP server
2258
- 3) Run auth flow to refresh consent (if prompted)
2259
-
2260
- This tool does not mutate process env automatically.${clearMessage}`
2261
- }],
2262
- isError: false
2263
- };
2264
- }
2265
2222
  default:
2266
2223
  return null;
2267
2224
  }