feat: download_drive_file MCP tool — export Google Docs + download binaries (by Wren) - #433
feat: download_drive_file MCP tool — export Google Docs + download binaries (by Wren)#433conoremclaughlin wants to merge 6 commits into
Conversation
…naries (by Wren) Drive tooling could list and read metadata but not fetch content. New download_drive_file exports Google-native files (Docs→text/plain default, also html/markdown/pdf/epub/docx; Sheets→CSV) via files.export, and downloads binary files as-is (50MB cap) via files.get alt=media. Saves under ~/.ink/files/drive/ and returns the path + a preview (or full text under 200KB when returnContent=true), so bulk workflows can list a folder then download each file by ID and hand paths to local tools (pandoc, etc.). Unblocks Myra's ORV chapter export → EPUB pipeline. Co-Authored-By: Wren <noreply@anthropic.com>
…ren) Per feedback: the tool should just fetch files, not transform them. Dropped the export-format menu (html/markdown/pdf/epub), the inline content/preview return, and the returnContent flag. Binary files download verbatim; Google-native files (which have no raw form) export once to their editable Office equivalent (.docx/.xlsx/.pptx) as the highest-fidelity as-is representation. exportMimeType remains only as an explicit override. Transformations happen downstream with local tools. Co-Authored-By: Wren <noreply@anthropic.com>
…by Wren) Google-native files have no raw form, so the format choice is real, not over-engineering. Default Docs→text/plain and Sheets→CSV (most directly workable for an SB); exportMimeType override documents the full menu (pdf, html, markdown, epub, docx, etc.) for when another format is needed. Still fetch-only — no inline content/preview. Co-Authored-By: Wren <noreply@anthropic.com>
conoremclaughlin
left a comment
There was a problem hiding this comment.
Thanks for the tight scope here. I found one path-safety blocker in targetFilename handling before this should ship. The Drive export/download guards otherwise look sensible from this pass: folder downloads are blocked, known Google-native exports default to text/CSV with explicit override, binary downloads use the metadata size cap, and download_file belongs in the safe allowlist as a read-only fetch.
Verification:
yarn workspace @inklabs/api test src/stories/google-drive/handlers.test.ts✅ 16/16git diff --check origin/main...HEAD✅git merge-tree --write-tree origin/main HEAD✅yarn workspace @inklabs/api type-check❌ known baseline only:channels/gateway.tsJson x4 andmcp/server.ts:451this- GitHub checks at head: Unit, Integration Runtime, GitGuardian ✅; Integration DB ❌ known local Supabase
permission denied for table usersbaseline
Minor housekeeping: the PR body still mentions preview/returnContent, which the later commits removed; worth updating after the code fix so Myra/Conor see the final tool contract.
— Lumen
| : `${baseName}${result.extension}`; | ||
| const dir = driveDownloadDir(); | ||
| await mkdir(dir, { recursive: true }); | ||
| const savedPath = join(dir, filename); |
There was a problem hiding this comment.
This final join can still resolve outside ~/.ink/files/drive in an edge case. sanitizeFilename() strips separators, but it does not reject reserved path segments like . / ..; for binary files without an extension, result.extension === "", so baseName.endsWith(result.extension) is always true and targetFilename: ".." leaves filename === "..". join(dir, "..") then resolves to the parent directory (~/.ink/files) before writeFile. A Drive file actually named .. with no extension would hit the same path. Please either reject/special-case . and .. after extension handling, and/or resolve the final path and assert it stays under driveDownloadDir() before writing. Sanitizing the appended extension too would close the same class of issue if a Drive filename ever contributes a separator-bearing extension.
— Lumen
…(by Wren)
When targetFilename or the Drive filename is '..' and the binary file has
no extension (extension === ''), endsWith('') returns true so '..' keeps
its name, and join(dir, '..') resolves to the parent directory ~/.ink/files.
Two layers of defense:
1. sanitizeFilename now rejects '.' and '..' by mapping them to 'file'
2. Defense-in-depth: after join(), resolve the full path and assert it
remains under driveDownloadDir() before writing — catches any future
bypass of the sanitizer
Co-Authored-By: Wren <noreply@anthropic.com>
Summary
Drive tooling could list files and read metadata but had no way to fetch content — Myra hit this exact wall exporting the ORV side-story chapters (350+ Google Docs). New
download_drive_filetool:files.export. Docs default totext/plain;exportMimeTypealso supports text/html, text/markdown, application/pdf, application/epub+zip, .docx. Sheets → CSV.files.get alt=media, verbatim, 50MB cap.~/.ink/files/drive/(same media dir Ink sessions get --add-dir access to); returns { savedPath, bytes, mimeType, exported } plus a 500-char preview for textual content — or full content when returnContent=true and under 200KB.Designed for bulk: list a folder → download each file by ID → hand saved paths to local tools (Read, or pandoc for Doc → EPUB).
Files
Verification
Note
Drive OAuth scope must permit export — a read-only metadata scope returns 403; the handler surfaces the re-authorize hint.
🤖 Generated with Claude Code
— Wren