# google-drive-files-mcp A focused [Model Context Protocol](https://modelcontextprotocol.io) server (and standalone CLI) for **Google Drive file management** (move, upload, organize), **Google Sheets editing** (cells, rows, tabs, format), and **surgical Google Docs editing** (replace a phrase, insert at an anchor, delete a range — leaving all other formatting untouched). Twenty-three granular, single-purpose tools — each an explicit operation, with writes returning before/after or precise indices so nothing changes silently. Built because the hosted Google Drive connectors can search, read, and **copy** files, but cannot **move** them (`copy` duplicates a file with a new ID rather than relocating it), cannot reliably **upload a real binary** (their inline-content API needs the bytes embedded as base64, which breaks for large/binary files), and cannot **edit spreadsheet or document contents in place**. This server adds a true move, a resumable upload from a local path, folder creation, a full set of Google Sheets edits, and range-scoped Google Docs edits — for any MCP client (Claude Code, Claude Desktop, Cursor, Cline, etc.). ## Tools ### Drive — files & folders - `drive_search(query, only_folders=False, max_results=20)` — find files/folders (and their IDs). - `drive_create_folder(name, parent=None)` — create a folder (`parent` accepts an ID, URL, `root`, or an unambiguous folder name). - `drive_move(file, dest_folder, keep_existing_parents=False)` — *true move* (removed from its current folder); `keep_existing_parents=True` adds without removing. - `drive_upload_file(local_path, parent=None, name=None, mime_type=None)` — upload a local file (any type, incl. large binaries) via a **resumable media upload**. Stored as-is (no Google-format conversion). ### Google Sheets — read, edit, structure, format - `sheets_get_info(spreadsheet)` — list tabs + sizes (call first to learn tab names). - `sheets_read(spreadsheet, range_a1, render_option="FORMATTED_VALUE")` — read a range (`FORMULA`/`UNFORMATTED_VALUE` available). - `sheets_write(spreadsheet, range_a1, values, value_input_option="USER_ENTERED")` — overwrite a range; returns `before`. `USER_ENTERED` parses numbers and makes `=SUM(..)` a formula; `RAW` writes literally. - `sheets_append(spreadsheet, range_a1, values, ...)` — append rows after a table. - `sheets_clear(spreadsheet, range_a1)` — clear values (keeps formatting); returns `before`. - `sheets_batch_write(spreadsheet, updates, ...)` — write many ranges atomically. - `sheets_add_tab` / `sheets_rename_tab` / `sheets_delete_tab` — manage tabs (delete is destructive). - `sheets_freeze_panes(spreadsheet, tab, rows=None, cols=None)` — freeze/unfreeze header rows/cols (0 = explicitly unfreeze; omit = untouched). - `sheets_format(spreadsheet, range_a1, number_format=None, bold=None, background=None, wrap=None)` — number pattern / bold / `#RRGGBB` background / text wrap. - `sheets_set_column_width(spreadsheet, range_a1, pixel_size)` — set column width in pixels for a whole-column range (e.g. `'A:C'`). - `sheets_set_row_height(spreadsheet, range_a1, pixel_size)` — set row height in pixels for a whole-row range (e.g. `'2:5'`). ### Google Docs — surgical in-place editing Every Docs edit is range-scoped (`documents.batchUpdate`), so it changes exactly what you target and leaves every other run's native formatting byte-for-byte intact. - `docs_get_info(document)` — title + structure (paragraph/table counts, end index). - `docs_read(document)` — full text **with per-segment character indices** (table cells include row/col). Read this first to target edits. - `docs_replace_text(document, find, replace, match_case=False)` — replace a phrase everywhere; surrounding content/formatting untouched. - `docs_insert_text(document, text, index=None, after_text=None, before_text=None)` — insert at an index **or** right after/before an anchor phrase. - `docs_delete_range(document, start_index, end_index)` — delete one precise range. - `docs_append_text(document, text)` — append at the end. No rename/copy/trash/delete of *files* (copy already exists in the hosted Drive connector); destructive ops (`sheets_clear`, `sheets_delete_tab`, `docs_delete_range`) are their own explicit tools so you control exactly when they run. ## Scope warning (read this) Moving an existing arbitrary file requires the **full `drive` scope** — read/write/delete on **all** your Drive files. There is no narrower scope that can change a file's parents (`drive.file` only covers files the app itself created). This tool is therefore far more powerful than a read-only connector. Treat its cached token like a password (stored `0600`), and prefer an isolated OAuth client/token rather than sharing one with read-only tools. ## Install ```bash pip install google-drive-files-mcp # or: uv tool install google-drive-files-mcp ``` ## One-time setup (~10 min) 1. [Google Cloud Console](https://console.cloud.google.com/) → create/pick a project. 2. Enable the **Drive API** ([link](https://console.cloud.google.com/apis/library/drive.googleapis.com)); for the Sheets tools also enable the **Sheets API** ([link](https://console.cloud.google.com/apis/library/sheets.googleapis.com)); for the Docs tools also enable the **Docs API** ([link](https://console.cloud.google.com/apis/library/docs.googleapis.com)). (The full `drive` scope already authorizes the Sheets and Docs APIs — no extra consent, just enable each API you want.) 3. OAuth consent screen → **Internal** (Workspace; no verification needed even for the restricted full-drive scope) or **External** + add yourself as a test user (personal Gmail). 4. Credentials → **OAuth client ID** → **Desktop app** → download JSON. 5. `google-drive-files-mcp setup --import-credentials ~/Downloads/client_secret_*.json` → consent in the browser. Verify: ```bash google-drive-files-mcp status google-drive-files-mcp search "Reports" --folders ``` ## Claude Code ```bash claude mcp add --scope user google-drive-files google-drive-files-mcp -- serve ``` > Find my "Q2 Report" doc and move it into the "2026 Reports" folder. ## Claude Desktop ```json { "mcpServers": { "google-drive-files": { "command": "google-drive-files-mcp", "args": ["serve"] } } } ``` ## CLI ```bash google-drive-files-mcp search "budget" # find a file + its ID google-drive-files-mcp search "Reports" --folders # find destination folders google-drive-files-mcp mkdir "2026 Reports" # create a folder google-drive-files-mcp move "2026 Reports" # move into it (by folder name) google-drive-files-mcp move root # move back to My Drive root google-drive-files-mcp upload ~/Downloads/report.zip --parent "2026 Reports" # upload a local file # Google Sheets google-drive-files-mcp sheet-info # list tabs + sizes google-drive-files-mcp sheet-read "Sheet1!A1:D10" # read a range google-drive-files-mcp sheet-write "Sheet1!B2:B4" '[[100],[200],[300]]' # set numbers google-drive-files-mcp sheet-format "Sheet1!B2:B4" --number-format '#,##0.00' --bold google-drive-files-mcp sheet-format "Sheet1!A1:B2" --wrap # wrap text google-drive-files-mcp sheet-freeze-panes Sheet1 --rows 1 # freeze header row google-drive-files-mcp sheet-set-column-width "Sheet1!A:C" 150 # column width (px) google-drive-files-mcp sheet-set-row-height "Sheet1!2:5" 40 # row height (px) # Google Docs (surgical, in-place) google-drive-files-mcp doc-read # text + character indices google-drive-files-mcp doc-replace "old phrase" "new phrase" # preserves all other formatting google-drive-files-mcp doc-insert $'\n\nNew paragraph.' --after "some anchor text" ``` `drive_move` returns the before/after parents so the move is auditable: ```json { "id": "1Abc…", "name": "Q2 Report", "moved_from": ["0OldFolderId"], "moved_to": "1NewFolderId", "parents_now": ["1NewFolderId"], "web_view_link": "https://docs.google.com/…" } ``` ## Configuration | Variable | Default | What | |---|---|---| | `GDRIVE_FILES_MCP_CREDENTIALS` | `~/.config/google-drive-files-mcp/credentials.json` | OAuth client secret | | `GDRIVE_FILES_MCP_TOKEN` | `~/.config/google-drive-files-mcp/token.json` | Cached refresh token | | `GDRIVE_FILES_MCP_SCOPES` | `https://www.googleapis.com/auth/drive` | OAuth scopes (comma-separated) | ## Safety notes - **True move removes the file from its current folder.** Drive items can have multiple parents; `keep_existing_parents=True` adds without removing. - **Folder-name destinations must be unambiguous.** If two folders share a name, `drive_move` refuses and lists the candidates so you can pass an explicit ID. - **The before/after parents are returned** on every move so an agent (or you) can verify the result. ## Troubleshooting **`HttpError 403: Google Drive API has not been used in project … or it is disabled`** Enable the Drive API on the project that owns your OAuth client: [console.cloud.google.com/apis/library/drive.googleapis.com](https://console.cloud.google.com/apis/library/drive.googleapis.com) → **Enable**, then wait ~1 min and retry. (This is the single most common first-run error.) **`multiple folders named '…'; pass the folder ID/URL instead`** Two or more of your folders share that name, so a name is ambiguous. Run `google-drive-files-mcp search "" --folders` to get the IDs and pass the exact one. **`no folder named '…' found`** Create it first (`google-drive-files-mcp mkdir ""`) or pass a folder ID/URL/`root`. **`HttpError 403: insufficientFilePermissions` when moving** Either the token lacks the full `drive` scope — re-authorize with `google-drive-files-mcp setup --reauth` — or you don't have edit rights on the file/destination (you can't move a file someone else owns unless they've granted you edit access). **`No valid Google token` from Claude Desktop / cron** The first consent needs a browser. Run `google-drive-files-mcp setup` once in a terminal; later headless runs reuse and auto-refresh the cached token. **A move "removed" a file from a shared folder unexpectedly** A true move removes the item from its current parent(s). If you meant to *also* keep it where it was, use `keep_existing_parents=true` (CLI `--keep`). ## Use it from other clients (Cursor, Cline, Continue, …) Any stdio MCP client works — see [docs/other-clients.md](docs/other-clients.md). ## Authentication — bring your own Google OAuth client There are **no API keys and no shipped secrets**. The server authenticates to *your* Google account with an OAuth client *you* create, and caches a refresh token locally. The author has zero access to your data. - **Why your own client?** Google's restricted scopes (here, the full `drive` scope) can't be redistributed in a shared app, and an unverified shared app is capped at 100 users. "Bring your own OAuth client" is the standard pattern for personal-data MCP servers. - **What you need:** a free Google Cloud project, the Drive API enabled, an OAuth consent screen, and a Desktop OAuth client. Full walkthrough → [docs/setup-google-oauth.md](docs/setup-google-oauth.md). - **Where your token lives:** `~/.config/google-drive-files-mcp/token.json` (mode `0600`). Delete it to revoke locally; revoke fully at [myaccount.google.com/permissions](https://myaccount.google.com/permissions). - **No hosted/SaaS option** — everything runs locally; your Drive data never touches a third-party server. ## More guides - [docs/setup-google-oauth.md](docs/setup-google-oauth.md) — full OAuth walkthrough (full-`drive` scope) + common errors - [docs/claude-code.md](docs/claude-code.md) · [docs/claude-desktop.md](docs/claude-desktop.md) · [docs/other-clients.md](docs/other-clients.md) - [examples/](examples/) — e.g. file a document into a `YYYY-MM` folder ## Related tools Part of a small family of focused, local MCP servers for Google Workspace data the hosted connectors don't expose: - **[gmail-attachments-mcp](https://github.com/zayansalman/gmail-attachments-mcp)** — download Gmail attachment bytes to disk - **[google-drive-comments-mcp](https://github.com/zayansalman/google-drive-comments-mcp)** — read comment threads on Docs/Sheets/Slides - **google-drive-files-mcp** — move/organize Drive files *(this repo)* They can share one OAuth login or stay isolated — see each repo's setup. ## License MIT. See [LICENSE](LICENSE).