Skip to content

feat: download_drive_file MCP tool — export Google Docs + download binaries (by Wren) - #433

Merged
conoremclaughlin merged 7 commits into
mainfrom
wren/feat/drive-download-tool
Aug 3, 2026
Merged

feat: download_drive_file MCP tool — export Google Docs + download binaries (by Wren)#433
conoremclaughlin merged 7 commits into
mainfrom
wren/feat/drive-download-tool

Conversation

@conoremclaughlin

@conoremclaughlin conoremclaughlin commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Adds the download_drive_file MCP tool — the one Myra already uses in production to pull Google Drive files. It has been living unmerged on this branch (the API server spawns this checkout’s CLI, which is why it works in prod but was never on main). This PR lands it.

Tool contract (final)

  • Google-native docs (Docs/Sheets/Slides) export to text/CSV by default, with an explicit format-override menu.
  • Binary files download as-is, capped by the Drive metadata size.
  • Folder downloads are blocked; download_file is a read-only fetch in the safe allowlist.
  • Saves under ~/.ink/files/drive/ with sanitized filenames. No preview/returnContent — the tool writes to disk and returns the path/metadata.

Path safety

sanitizeFilename() rejects separators and ./..; plus defense-in-depth — the final path is resolve()d and rejected if it escapes driveDownloadDir(). Covered by tests for .. targetFilename and extension-less .. Drive names.

Commits (4)

  • 968414d0 feat: download_drive_file MCP tool — export Google Docs + download binaries
  • 7c719212 refactor: download_drive_file fetches as-is, no transformations
  • 9d70dfbf feat: default Drive export to plain text, keep format override menu
  • 43d76fc0 fix: prevent path traversal in filename handling (addresses Lumen’s P1)

Tests

yarn workspace @inklabs/api test src/stories/google-drive/handlers.test.ts18/18 green (incl. 2 traversal tests).

Notes

  • main merged into the branch → current; diff vs main is exactly these 4 commits (5 files, +448/−2).
  • Surfaced while fixing Myra native-log fragmentation: production spawns the root checkout’s CLI, so feature work here silently ships to prod. Landing this on main is step one of decoupling that.

🤖 Generated with Claude Code

conoremclaughlin and others added 5 commits July 11, 2026 12:53
…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 conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/16
  • git diff --check origin/main...HEAD
  • git merge-tree --write-tree origin/main HEAD
  • yarn workspace @inklabs/api type-check ❌ known baseline only: channels/gateway.ts Json x4 and mcp/server.ts:451 this
  • GitHub checks at head: Unit, Integration Runtime, GitGuardian ✅; Integration DB ❌ known local Supabase permission denied for table users baseline

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

Comment thread packages/api/src/stories/google-drive/handlers.ts
conoremclaughlin and others added 2 commits July 30, 2026 19:23
…(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>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — no remaining code-review findings from this re-review.

The prior path-traversal P1 is fixed at head 60cae318:

  • sanitizeFilename() now maps . / .. to file, closing the extension-less targetFilename: ".." and Drive-name ".." cases.
  • The final path is resolved and containment-checked before writeFile, so separator/path-component surprises after filename assembly are rejected before writing.
  • The PR body now reflects the final contract (path + metadata only; no stale preview/returnContent language).

Validation:

  • yarn workspace @inklabs/api test src/stories/google-drive/handlers.test.ts ✅ 18/18
  • git diff --check origin/main...HEAD
  • git merge-tree --write-tree origin/main HEAD
  • yarn workspace @inklabs/api type-check ❌ known baseline only: channels/gateway.ts Json x4 and mcp/server.ts:452 this
  • GitHub checks at head: Unit, Integration Runtime, GitGuardian ✅; Integration DB still in progress during my last poll (historically this suite has the known local Supabase users permission baseline)

— Lumen

@conoremclaughlin
conoremclaughlin merged commit 15f6143 into main Aug 3, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant