gdrive-write-mcp

gdrive-write-mcp

Enables AI assistants to make real in-place edits to Google Drive files—preserving file IDs, revision history, comments, and sharing—while also supporting appends, targeted find-and-replace, reads, searches, and file creation.

Category
访问服务器

README

gdrive-write-mcp

An MCP server that gives AI assistants real write access to Google Drive — in-place content updates, appends, and find/replace edits that preserve a file's ID, sharing settings, comments, and revision history.

CI License: MIT


The problem

Most Google Drive integrations for AI assistants are read-plus-create. They can search files, read them, make new ones, and move old ones to the bin — but they have no way to change the content of a file that already exists.

That sounds like a small gap. It isn't. Without in-place writes, "edit this document" becomes:

  1. Read the file.
  2. Create a new file with the corrected content.
  3. Trash the old one.

The result technically has the right text in it, and everything else about it is wrong:

after a real edit after create-and-trash
File ID unchanged new — every existing link, bookmark, and API reference now points at a trashed file
Revision history one more revision gone — no "restore previous version"
Comments preserved gone
Sharing preserved reset — collaborators silently lose access
Bin untouched fills with orphaned near-duplicates

gdrive-write-mcp fills that gap. Google's Drive API has always supported in-place content updates; this is a small, focused server that exposes them over MCP.


What it does

Editing

  • replace_in_file — exact-match find and replace. The tool to reach for by default: it doesn't require resending the whole document, and it can't accidentally drop content that was never mentioned.
  • append_to_file / prepend_to_file — add to either end, without resending what's already there. Built for logs, journals, and changelogs.
  • update_file_content — replace the whole document. Destructive by nature, so it's documented to the model as a last resort rather than the default.

Reading

  • read_file — content plus the revisionToken used to make the next write safe.
  • get_file_metadata — check whether a file moved on without downloading it.
  • search_files — Drive query syntax, so a file name can be turned into the ID the write tools need.
  • list_revisions — the history that in-place editing preserves.

Creating

  • create_file — for genuinely new documents, with optional conversion to a native Google Doc or Sheet.

Two things it gets right

1. Concurrent edits are refused, not silently swallowed

The failure mode of a naive write tool is quiet and expensive: you read a document, spend thirty seconds thinking, and write it back — overwriting the paragraph a colleague added in the meantime. Nobody gets an error. Nobody notices until the paragraph is missed, days later.

Every read here returns a revisionToken, and every write accepts one:

read_file(fileId)                    → revisionToken: "0B1a2…"
update_file_content(fileId, content, expectedRevisionToken: "0B1a2…")

If the file has changed, the write is refused with an error that tells the model exactly what to do — re-read, re-apply, write again — rather than a bare 409. The targeted tools (replace_in_file, append_to_file, prepend_to_file) read and write inside a single call, so they carry the guard automatically and you never handle a token yourself.

Drive only exposes headRevisionId for files with real binary content — Google-native Docs and Sheets don't have one, which is exactly where concurrent human editing is most likely, since those are the files someone has open in a browser tab. The token falls back to modifiedTime for those, so native files are guarded too.

2. Native Google files are handled honestly

Drive stores two very different kinds of thing, and conflating them is the most common source of bugs in Drive integrations:

  • Uploaded files (text/markdown, application/pdf, …) — bytes in, bytes out.
  • Native editor files (application/vnd.google-apps.document, …) — no bytes of their own. Read by exporting to a concrete format; written by uploading a format Drive converts back on ingest.

This server detects which is which and routes accordingly. Docs export to markdown rather than plain text specifically so that a read-modify-write round trip preserves headings, lists, and emphasis instead of silently flattening the document. Binary files are base64-encoded rather than decoded as UTF-8, so a PDF can never be corrupted by passing through a text tool.


Install

git clone https://github.com/anaborne/gdrive-write-mcp.git
cd gdrive-write-mcp
npm install
npm run build

Requires Node 18 or newer.


Setup

Step 1 — Create a Google OAuth client

  1. Open the Google Cloud Console and create a project (or pick an existing one).
  2. Enable the Google Drive API: APIs & Services → Library → Google Drive API → Enable.
  3. Configure the OAuth consent screen: APIs & Services → OAuth consent screen. Choose External, fill in the required fields, and add your own Google account under Test users. (While the app is in "Testing", only listed test users can authorize it — which is what you want for a personal tool.)
  4. Create credentials: APIs & Services → Credentials → Create Credentials → OAuth client ID → Desktop app.
  5. Copy the Client ID and Client secret.

Step 2 — Get a refresh token

cp .env.example .env
# put GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env
npm run authorize

This opens a one-time consent flow on http://localhost:4181 and prints a refresh token. Add it to .env:

GOOGLE_CLIENT_ID=1234567890-abcdef.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-…
GOOGLE_REFRESH_TOKEN=1//0g…

Step 3 — Point your MCP client at the server

Claude Desktop — claude_desktop_config.json:

{
  "mcpServers": {
    "gdrive-write": {
      "command": "node",
      "args": ["/absolute/path/to/gdrive-write-mcp/dist/index.js"],
      "env": {
        "GOOGLE_CLIENT_ID": "…",
        "GOOGLE_CLIENT_SECRET": "…",
        "GOOGLE_REFRESH_TOKEN": "…"
      }
    }
  }
}

Claude Code:

claude mcp add gdrive-write \
  --env GOOGLE_CLIENT_ID=… \
  --env GOOGLE_CLIENT_SECRET=… \
  --env GOOGLE_REFRESH_TOKEN=… \
  -- node /absolute/path/to/gdrive-write-mcp/dist/index.js

Anything else — the server speaks MCP over stdio. Launch node dist/index.js as a subprocess with those three environment variables set.

Step 4 — Verify it works

npm run verify

This runs a real end-to-end check against your Drive: it launches the server the same way an MCP client would, drives it over stdio with the official MCP client, and asserts the behaviour this project claims — including that a stale write is refused, that a refused write leaves the file untouched, that the file ID is unchanged after every edit, and that a native Google Doc survives a read-edit-read round trip as a Doc.

It creates two temporary files in your Drive and moves them to the bin when it finishes, including when it fails partway. Expect a green summary line:

✓ ALL 41 CHECKS PASSED — the server works against live Drive.

If anything fails, the output names the specific check and shows what came back. The conflict and native-Doc checks carry extra diagnostics explaining what a given failure implies — a backslash-escaped #, for instance, means the content was imported as plain text rather than markdown.

This is not a formality. The unit suite was green at 49 tests, and CI passed, while a real defect sat in the code: creating a native Doc from markdown silently produced a Doc containing the literal characters # Heading. Only the live run caught it, because the mock encoded the same wrong assumption as the implementation. Run this after any change to drive.ts or mime.ts.


Tool reference

read_file

Parameter Type Required Description
fileId string yes Drive file ID — the long string in the URL after /d/, not the file name

Returns content plus revisionToken, mimeType, and modifiedTime. Native files are exported (Docs → markdown, Sheets → CSV, Slides → plain text); binary files come back base64-encoded.

replace_in_file

Parameter Type Required Description
fileId string yes Drive file ID
oldString string yes Exact text to find, including whitespace and line breaks
newString string yes Replacement text; empty string deletes
replaceAll boolean no Replace every occurrence (default false)

Matching is literal, not regex — a . or $1 in your search text means exactly those characters. If oldString appears more than once and replaceAll is false, the call fails rather than guessing, because a silent wrong-occurrence edit is the kind of bug nobody catches.

append_to_file / prepend_to_file

Parameter Type Required Description
fileId string yes Drive file ID
text string yes Text to add
separator string no Explicit separator (default: a newline, only if one is needed)

Repeated appends stay evenly separated — no run-on lines, no widening gaps of blank lines.

update_file_content

Parameter Type Required Description
fileId string yes Drive file ID
content string yes The complete new content
expectedRevisionToken string no From your last read — strongly recommended

Replaces everything. Without expectedRevisionToken it will overwrite changes made since you last read the file.

create_file

Parameter Type Required Description
name string yes File name including extension
content string yes Initial content
parentId string no Folder ID (defaults to My Drive root)
mimeType string no Guessed from the file name if omitted
convertTo string no e.g. application/vnd.google-apps.document to upload markdown as a real Doc

search_files

Parameter Type Required Description
query string yes Drive query syntax
pageSize number no Max results, 1–100 (default 20)
name contains 'budget'
fullText contains 'quarterly review'
'FOLDER_ID' in parents
mimeType = 'application/vnd.google-apps.document'

get_file_metadata / list_revisions

Both take fileId; list_revisions also takes an optional pageSize.


Security

Why full Drive scope. This server requests https://www.googleapis.com/auth/drive by default. The narrower drive.file scope only grants access to files the app itself created, which cannot work for a tool whose entire purpose is editing documents you already have. That's a real trade-off, stated plainly rather than buried: the token can read and write everything in the authorized account's Drive.

If your workflow only ever touches files the assistant creates itself, request the narrower scope instead — for both the authorize step and the server:

GOOGLE_OAUTH_SCOPE=drive.file

The two must agree. A refresh token carries the scope it was granted with, so minting a token under one and running the server under the other produces confusing 403s at call time. The server prints a warning to stderr on startup when the per-file scope is active, so a later 404 on someone else's document isn't a mystery.

Ways to keep that bounded:

  • Authorize a dedicated Google account and share only the specific files or folders you want reachable.
  • Keep the OAuth app in Testing mode so only listed test users can authorize it.
  • Revoke access any time at myaccount.google.com/permissions.

Handling the refresh token. It's a password to your Drive. It never expires on its own. Keep it in .env (git-ignored here) or your MCP client's config, never in a committed file. If it leaks, revoke at the link above — that invalidates it immediately.

No telemetry. This server makes network calls to Google's APIs and nowhere else.


Troubleshooting

Symptom Cause and fix
Missing required environment variable… The server started without credentials. Check your MCP client passes all three env vars.
Google rejected the credentials (401) The refresh token is invalid, revoked, or from a different OAuth client. Re-run npm run authorize.
Permission denied (403) The account can see the file but not write to it, or the token has a read-only scope. Confirm Editor access and full drive scope.
File not found (404) Wrong ID, file is trashed, or the authorized account has no access. IDs come from the URL after /d/, not the file name.
Conflict: file … has changed Working as designed — someone edited the file after you read it. Re-read, re-apply, write again.
No refresh token during authorize The app was already authorized for this account. Revoke at myaccount.google.com/permissions and retry.
Error 403: access_denied at the consent screen Consent configuration, not code — see below.
Client shows a parse error on startup Something is writing to stdout. All diagnostics here go to stderr; a stray console.log in a fork will corrupt the protocol stream.

Error 403: access_denied

Google is refusing the consent screen before any of this code runs. auth/drive is a restricted scope — Google's strictest tier — and restricted scopes are blocked unless the app is configured to permit them. In Google Auth Platform, check in this order:

  1. Audience → publishing status is "Testing", not "In production". An unverified app in production cannot use restricted scopes at all, for anyone, including its own author. Testing mode allows them for up to 100 listed test users with no verification.
  2. Audience → Test users includes the exact account you sign in with.
  3. Branding → app name, user support email, and developer contact email are all saved. An incomplete consent screen is an invalid one.

Changes take a few minutes to propagate. If it still fails immediately after an edit, wait five minutes and retry.

To sidestep it entirely, request the non-restricted per-file scope, which is never blocked:

GOOGLE_OAUTH_SCOPE=drive.file npm run authorize

Every file npm run verify touches is one it creates itself, so the full verification suite passes under drive.file — useful for confirming the server works while the consent configuration is still being sorted out. It won't reach documents created elsewhere, so it's a diagnostic path rather than a permanent one.


Development

npm install
npm run build       # compile TypeScript to dist/
npm test            # build, then run the unit suite (no network, no credentials)
npm run verify      # end-to-end check against a real Drive account
npm run typecheck   # type-check without emitting
npm run watch       # rebuild on change

npm test and npm run verify answer different questions. The unit suite mocks the Drive API: it proves the logic is right, runs in CI, and needs no credentials. npm run verify proves the integration is right — that Google actually behaves the way this server assumes, particularly around native-file conversion and revision tokens. A change to drive.ts or mime.ts should be checked with both.

The code is organised so the parts that can silently corrupt a document are testable without touching the network:

src/
  index.ts    entry point; stdio transport
  auth.ts     OAuth client from environment
  drive.ts    Drive operations, incl. the concurrency guard
  edits.ts    pure text transforms — no I/O, fully unit-tested
  mime.ts     native vs. binary vs. textual classification
  tools.ts    MCP tool definitions and handlers
  errors.ts   error types written to be actionable by a model

The suite covers the find/replace edge cases (regex-looking literals, $& in replacements, multi-line targets, ambiguous matches), the append/prepend seam logic, MIME classification, and the concurrency guard — including that a conflicting write never reaches the API.


Contributing

Issues and pull requests are welcome. For a change of any size, please open an issue first so the approach can be agreed before the work.

If you add a tool, add tests for its pure logic, and write its description for the model that will read it — say when to reach for it over its neighbours, not just what it does.


License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选