From ad06bf999bb9c72e85206f57fbd330b302ecd2d2 Mon Sep 17 00:00:00 2001 From: kevin9327 <5299031+kevin9327@users.noreply.github.com> Date: Thu, 17 Sep 2026 06:57:01 +0900 Subject: [PATCH] Reach files in shared drives from the Google Drive connector Drive leaves shared drive items out of any files.get or files.list that does not say it supports shared drives, and none of the connector's requests said so. A document the person could open in a shared drive was File not found by id and never appeared in a search or the recent list. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 9 +++++ server/src/plugins/google-drive-rest.ts | 21 +++++++++-- server/tests/google-drive-rest.test.ts | 46 +++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1edb6a53..258128027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. ## Unreleased +### The Google Drive connector reaches files in shared drives + +Drive leaves shared drive items out of any `files.get` or `files.list` request that does not say it +supports shared drives, and none of the connector's requests said so. A document the person could +open in a shared drive was "File not found" to `get_file_metadata` and `read_file_content`, and never +appeared in `search_files` or `list_recent_files`. Those requests now say they support shared drives, +and the listings ask for shared drive items. Listings keep Drive's default `user` scope rather than +searching every shared drive. + ### The New chat shortcut works on a Russian or Greek keyboard layout Settings lists New chat as Shift+N, and the app matched the character the keystroke wrote. A layout diff --git a/server/src/plugins/google-drive-rest.ts b/server/src/plugins/google-drive-rest.ts index e020c0553..1f7b263cb 100644 --- a/server/src/plugins/google-drive-rest.ts +++ b/server/src/plugins/google-drive-rest.ts @@ -45,6 +45,21 @@ const PAGE_SIZE = 25; const FILE_FIELDS = "id,name,mimeType,modifiedTime,webViewLink,size,owners(emailAddress)"; +/** + * Shared drives as well as My Drive. + * + * Drive leaves shared drive items out of every `files.get` and `files.list` that does not say it + * supports them. A company's documents often live in shared drives, so a file the person could open + * came back from `get_file_metadata` and `read_file_content` as a 404 "File not found", and never + * came back from a search at all. The listing also has to ask for those items. It keeps Drive's + * default `user` corpus, which Google recommends over `allDrives`. + */ +const SHARED_DRIVES = { supportsAllDrives: "true" } as const; +const SHARED_DRIVE_ITEMS = { + ...SHARED_DRIVES, + includeItemsFromAllDrives: "true", +} as const; + /** * Google's editor formats, and the plain-text export each one has. * @@ -331,6 +346,7 @@ export async function callTool( } const result = await request(connection, "/files", { + ...SHARED_DRIVE_ITEMS, pageSize: String(PAGE_SIZE), fields: `files(${FILE_FIELDS})`, // Drive's own ordering for "recent". Search leaves it to relevance. @@ -350,7 +366,7 @@ export async function callTool( const result = await request( connection, `/files/${encodeURIComponent(fileId)}`, - { fields: FILE_FIELDS }, + { ...SHARED_DRIVES, fields: FILE_FIELDS }, ); if (!result.ok) return failure(result.message); @@ -379,7 +395,7 @@ export async function callTool( const metadata = await request( connection, `/files/${encodeURIComponent(fileId)}`, - { fields: "id,name,mimeType" }, + { ...SHARED_DRIVES, fields: "id,name,mimeType" }, ); if (!metadata.ok) return failure(metadata.message); const file = (await metadata.response.json()) as DriveFile; @@ -412,6 +428,7 @@ export async function callTool( { mimeType: exportAs }, ) : await request(connection, `/files/${encodeURIComponent(fileId)}`, { + ...SHARED_DRIVES, alt: "media", }); if (!content.ok) return failure(content.message); diff --git a/server/tests/google-drive-rest.test.ts b/server/tests/google-drive-rest.test.ts index 5ffd6a4c9..847e77fc5 100644 --- a/server/tests/google-drive-rest.test.ts +++ b/server/tests/google-drive-rest.test.ts @@ -394,3 +394,49 @@ describe("reading a file asks Drive what it is first", () => { expect(result.text.split("\n\n[truncated")[0]).toBe(`${heading}${filler}`); }); }); + +/* + * Drive leaves shared drive items out of any `files.get` or `files.list` that does not say it supports + * them. Without these parameters a document the person could open in a shared drive was a 404 by id + * and missing from every search, and shared drives are where many companies keep their documents. + */ +describe("a file in a shared drive is reached like one in My Drive", () => { + test("both listings ask Drive for shared drive items", async () => { + const calls = stubFetch({ files: [] }); + await callTool(connection, "search_files", { query: "roadmap" }); + await callTool(connection, "list_recent_files", {}); + + expect(calls).toHaveLength(2); + for (const call of calls) { + const params = new URL(call.url).searchParams; + expect(params.get("supportsAllDrives")).toBe("true"); + expect(params.get("includeItemsFromAllDrives")).toBe("true"); + } + }); + + test("looking a file up says the caller supports shared drives", async () => { + const calls = stubFetch({ id: "shared1", name: "Plan" }); + await callTool(connection, "get_file_metadata", { fileId: "shared1" }); + + expect(calls).toHaveLength(1); + expect(new URL(calls[0].url).searchParams.get("supportsAllDrives")).toBe( + "true", + ); + }); + + test("reading a file says so on the lookup and on the download", async () => { + const calls = stubFetch({ + id: "shared2", + name: "notes.txt", + mimeType: "text/plain", + }); + await callTool(connection, "read_file_content", { fileId: "shared2" }); + + expect(calls).toHaveLength(2); + for (const call of calls) { + expect(new URL(call.url).searchParams.get("supportsAllDrives")).toBe( + "true", + ); + } + }); +});