Verified Googledocs Mcp

io.github.michaelrobertsutton由 GitHub 帳號 michaelrobertsutton 發布v0.1.0更新於 Oct 27, 2026

MCP server for Google Docs with verified writes — every mutation returns before/after proof.

發布方自己的服務:verified.fyi

已驗證STDIO僅桌面效率與工作流檔案與儲存

概覽

AI 產生的概覽

針對 Google Docs 的本機 MCP 伺服器,寫入附帶驗證:每次修改後重新讀取文件並回傳前後對照證據。

可以做什麼
  • 以 Markdown、結構化位置或大綱形式讀取文件、分頁、標題、章節與表格
  • 依分頁範圍執行文字、樣式、Markdown 區段、整頁、圖片與表格列的驗證式編輯
  • 以試跑預覽編輯,在真正寫入前預測結果片段或結構
  • 列出、讀取、新增、回覆與解決留言及待處理的建議修訂
  • 將分頁與本機 Markdown 檔案比對,並把文件匯出為 PDF
適用情境
適合需要助理可靠地修改 Google Docs,並希望取得每次變更確實生效的證據,而非只信任 API 成功回應的情境。適用於單一使用者的本機文件流程,尤其是改錯分頁或符合數量出錯代價很高的情況。
執行需求
透過 uvx 以 stdio 在本機執行(Python 套件)。需自備 Google Cloud OAuth 桌面用戶端密鑰 JSON,啟用 Docs 與 Drive API 並將自己的帳號加入測試使用者;路徑由 VERIFIED_GOOGLEDOCS_MCP_CREDENTIALS 指定。需一次性在瀏覽器完成授權以快取權杖,並需要連線 Google API 的網路。
安裝前請注意
要求較廣的 drive 權限及 documents 權限,並快取可長期使用的更新權杖,撤銷前持續有效。本機稽核記錄會寫入修改前後的內容片段,除非將 VERIFIED_GOOGLEDOCS_MCP_AUDIT_EXCERPTS 設為假值。修改類工具會改動真實文件,且沒有自動回復,未通過驗證的寫入需從 Docs 版本記錄還原。檔案比對與 PDF 匯出路徑受允許清單與憑證路徑黑名單限制。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Verified Googledocs Mcp,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

verified-googledocs-mcp

[CI] [Coverage] [Python 3.12+] [License: MIT]

An MCP server for Google Docs whose writes carry proof. Every mutating tool re-reads the affected content from the document after it writes and returns evidence of what actually changed: before/after excerpts, the match count, and the document revision before and after. A tool never reports success for an edit that did not land.

Status: all 20 tools are implemented, covered by an offline unit suite, and exercised against the real Google Docs and Drive APIs by the live test suite. The original fourteen passed the formal live acceptance gate; the gate is rerun before each release. Install with uvx verified-googledocs-mcp. See Status.

The problem

Driving Google Docs from an agent through a general Workspace MCP server tends to fail in quiet, expensive ways:

  • A findAndReplace meant for one tab silently edits every tab in the document.
  • A search returns "0 matches" because the document has curly quotes or a non-breaking space the query doesn't, with no hint why.
  • A replace meant for one occurrence hits a repeated sentence and collapses both.
  • A "resolve comment" call returns success while the comment stays open.
  • Listing comments misses suggested edits entirely.
  • A markdown merge injects garbled text that a human only catches days later.

Each of these has a procedural workaround: tell the agent to scope to a tab, retry with normalized quotes, re-read after every write, never trust a resolve result. Those instructions work until someone forgets one. This server moves the discipline into the protocol, where it is deterministic.

The verified-write contract

Every mutating tool runs the same pipeline: read the tab, locate the target, apply the edit under a revision precondition, read the tab again, and return evidence built from the second read. The return value is a claim about the document's state after the call, backed by a server-side re-read, not an echo of the API response.

jsonc
// replace_text(doc_id, tab_id, find="teh", replace="the", expected_matches=1){  "applied": true,  "match_count": 1,  "rung": "exact",                 // which normalization rung matched  "before": "...±200 chars around the edit, pre-write...",  "after":  "...the same span, re-read after the write...",  "revision_before": "ALm37BX...",  "revision_after":  "ALm37Cy...",  "audit_logged": true}

When something is wrong, the tool fails loud and diagnosed, with a typed error the agent can act on in one round trip rather than guessing:

jsonc
{  "error_code": "MATCH_COUNT_MISMATCH",  "message": "expected 1 match(es) but found 3 at rung 'exact'",  "diagnostics": { "expected": 1, "actual": 3, "spans": [ /* every location */ ] },  "retryable": false}

What backs the guarantee

  • Tab-scoped by default. Editing tools require an explicit tab_id. There is no whole-document replace, so a one-tab edit can never leak into a cover letter or an appendix tab.
  • Normalization ladder. A search tries exact match, then curly/straight quote equivalence, then non-breaking-space and whitespace-run equivalence, then soft-hyphen stripping, and reports which rung matched. A zero-match result includes the nearest near-miss span it found.
  • Match-count guard. expected_matches defaults to 1. If the real count differs, the tool makes no edit and returns every match location.
  • Revision preconditions. Writes carry writeControl.requiredRevisionId from the pre-read, so a document that changed underneath the operation is rejected by the API rather than edited blind. Section ranges from find_sections are stamped with the revision they were computed at and refuse to apply once stale.
  • UTF-16 correct. Match spans are mapped to the UTF-16 code units the Docs API indexes by, so emoji, combining marks, and other astral characters don't shift an edit onto the wrong text.
  • Audit trail. Every mutation appends a line to a local JSONL log. The append is best-effort: it never fails a write, and if it can't be written the evidence says so (audit_logged: false).

Evidence by family

The guarantee is not one universal payload — it is a per-family invariant. Each family re-reads the document after the write and proves the property that family is responsible for. Every mutating tool also carries revision_before, revision_after, and audit_logged.

FamilyToolsProves
Text editreplace_textmatch_count equals expected_matches; rung names the normalization pass that matched; before/after are ±200-char excerpts of the edited span, the after re-read post-write
Style editformat_textruns_before/runs_after — the actual textRun style flags overlapping each matched span, not a text diff; content_mutated: false proven by compiled_request_kinds containing only updateTextStyle; style_mutated false on an idempotent re-run (no write is issued)
Markdown rangereplace_range_markdown, replace_tab_markdown, append_markdownstructural_match (the written markdown round-trips — including list nesting depth, ordered-vs-unordered, and link targets), input_blocks vs post_blocks counts, and a structural_diff list naming any mismatch; every response also carries write_status (not_written / written_unverified / written_verified) and retry_safe
Structuralinsert_imageinline_object_confirmed — a post-write scan found the inline object at the anchor paragraph
Comment stateadd_anchored_comment, reply_to_comment, resolve_commentthe re-queried resolved flag, reply_count, content, quoted_text, and author — a resolve that didn't land returns COMMENT_STILL_OPEN, never success
Tablereplace_table_row, insert_tablereplace_table_row: row_before/row_after re-read with cells_match; insert_table: table_confirmed plus the confirmed dimensions and first_row

The read and sync tools (read_document, list_tabs, find_sections, list_open_items, get_comment_thread, diff_tab_vs_file, list_tables, get_table) make no changes and carry no applied/evidence payload. export_pdf is also a read/export tool in this sense — it returns file facts (bytes_written, sha256, page_count) rather than an applied key, since nothing in the document changes.

Dry run

Eight mutating tools (replace_text, format_text, replace_range_markdown, replace_tab_markdown, append_markdown, insert_image, replace_table_row, insert_table) accept dry_run=true. No API write is issued; the response carries applied: false, an empty revision_after (no write, so no new revision), audit_logged: false, and — for replace_text — a predicted after excerpt computed by splicing the replacement into the pre-read, or — for format_text — a predicted runs_after computed by overlaying the requested style onto the pre-read runs. format_text additionally never issues a write at all when the matched text already carries every requested style value, dry run or not — that's the tool's normal idempotent no-op path, not specific to dry_run. For replace_table_row and insert_table, dry_run is authoritative for index validity: the same assembled request list is index-simulated whether dry_run is true or false, so a passing dry run always means the real write will pass too. Use it to confirm a locate resolves to the right span before committing the edit.

For the three markdown tools, dry_run is also authoritative for structure prediction (issue #65): before any batchUpdate, the compiled requests' own predicted block structure — nesting, ordered-vs-unordered, headings, tables — is compared against the parsed input markdown, identically in dry_run and the real write. A mismatch refuses with STRUCTURE_PREDICTION_FAILED before the document is touched, rather than mutating it and only then discovering post-write verification would have failed. Every response from these three tools — dry run, refused, or written — carries write_status, one of not_written (dry run, or a pre-flight refusal: INDEX_SIMULATION_FAILED, STRUCTURE_PREDICTION_FAILED, or the structural-loss guardrail), written_unverified (a batchUpdate was sent and accepted, but the post-write re-read did not confirm it matched — VERIFICATION_FAILED), or written_verified (confirmed) — plus a retry_safe boolean (false only for written_unverified, since a caller that retries a call whose mutation status is unconfirmed risks a second mutation on top of an unconfirmed first one; there is no automatic rollback — Docs has no revision-restore endpoint, and a compensating markdown rewrite would itself be lossy, so needs_manual_restore: true still means restoring from Docs version history). write_status/retry_safe are additive: the pre-existing applied field keeps its documented meaning unchanged.

All eight require a suggestion-free target tab. Every mutating tool computes its write indices against a suggestionsViewMode=PREVIEW_WITHOUT_SUGGESTIONS read, but the actual write always lands in the document's real index space, which includes pending suggestions. If the target tab has a pending suggested insertion or deletion, those two index spaces diverge and a write would land at the wrong offset — so every mutating tool refuses with SUGGESTIONS_PRESENT instead (both in dry_run and live), rather than risking a silent, wrong-offset write (issue #56). Accept or reject the pending suggestion in the Docs UI first, then retry.

Tools

Twenty focused tools, each described by when to reach for it, replace the slice of a 150-tool Workspace server that document workflows actually use.

Reading and structure

ToolWhat it does
read_documentRead a tab as markdown, as structured positions and style runs, or as a headings-only outline
list_tabsList tab IDs, titles, and nesting
find_sectionsFind headings and return their ranges, stamped with the document revision
list_tablesList every top-level table in a tab, with position, size, and preceding-heading context
get_tableRead one table's full cell grid

Editing (verified, tab-scoped)

ToolWhat it does
replace_textFind/replace within a tab, with the normalization ladder and match guard
format_textApply bold/italic/underline to a matched text span via updateTextStyle only — no content mutation, safe inside merged-cell tables
replace_range_markdownReplace a section range with markdown
replace_tab_markdownReplace a whole tab's content with markdown
append_markdownAppend markdown to a tab
insert_imageInsert an image at a quoted anchor or heading
replace_table_rowOverwrite one row of an existing table with plain-text cells, in place
insert_tableInsert a new table populated from rows, anchored like insert_image

Comments and suggestions

ToolWhat it does
list_open_itemsOpen comments and pending suggested edits; pass tab_id or include_all_tabs=true
get_comment_threadRead a comment's full reply chain
add_anchored_commentAdd a comment anchored to quoted text
reply_to_commentReply to a comment
resolve_commentResolve a comment, re-query it, and confirm it actually closed

Sync and export

ToolWhat it does
diff_tab_vs_fileDiff a tab's markdown against a local file
export_pdfExport the whole document as a PDF to a local path, with a best-effort render-measured page count

Status

Built incrementally; each tool ships with its verification and tests rather than as a stub.

AreaState
OAuth (verified-googledocs-mcp auth), token cachedone
read_document, list_tabs, find_sectionsdone
Verification kernel (locator, error envelope, audit)done
replace_text (verified) + enforcement middlewaredone
Comment tools + list_open_itemsdone
Markdown write tools + diff_tab_vs_filedone
Table tools (list_tables, get_table, replace_table_row, insert_table) + export_pdfdone; ships in 0.2.0
format_text (style-only verified edit)done; ships in 0.2.0
Live acceptance gatedone for the initial release — report; rerun before release
PyPI packaging + publish workflowdone; first release v0.1.0
MCP registry listingpublished with v0.1.0

Install

The server talks to Google with your own OAuth credentials, so setup is a one-time Google Cloud step, then registering the server with your MCP client.

1. Google Cloud project (OAuth credentials)

  1. Create a Google Cloud project and enable the Google Docs API and Google Drive API (APIs & Services → Library).
  2. Configure the OAuth consent screen: User type External, publishing status Testing, and add your own Google account under Test users. (Testing mode is the point — the app stays private to the test users you list; you never submit it for Google verification.)
  3. Create an OAuth client ID of type Desktop app and download the client secret JSON to ~/.config/verified-googledocs-mcp/credentials.json. (Override the location with VERIFIED_GOOGLEDOCS_MCP_CREDENTIALS.)

2. Authorize once, in a terminal

bash
uvx verified-googledocs-mcp auth

This opens a browser and completes consent. Because the app is unverified and in Testing, Google shows a "Google hasn't verified this app" screen — click Advanced → Go to verified-googledocs-mcp (unsafe) and continue. This is expected for a personal Desktop client; you are granting access to your own app, running locally as you. It then caches a refreshable token at ~/.config/verified-googledocs-mcp/token.json. Auth runs only here, never inside the server, because MCP clients start the server headless.

3. Run it

bash
uvx verified-googledocs-mcp           # downloads + runs in one step# or: pip install verified-googledocs-mcp

Then register the server with your MCP client.

From source. To run from a local clone instead:

bash
git clone https://github.com/michaelrobertsutton/verified-googledocs-mcpcd verified-googledocs-mcpuv run verified-googledocs-mcp

Claude Code

A project-local .mcp.json is included in the repo. Clone and open the project and Claude Code picks it up automatically — no manual config required:

bash
git clone https://github.com/michaelrobertsutton/verified-googledocs-mcpcd verified-googledocs-mcpclaude  # .mcp.json is loaded automatically

Use it across all your projects (user scope). Register it once at user scope:

bash
claude mcp add verified-googledocs-mcp --scope user -- uvx verified-googledocs-mcp

This writes to ~/.claude.json and makes the server available in every Claude Code session on this machine. If uvx is not on Claude Code's PATH, use the full path (find it with which uvx).

Claude Desktop and other clients

Most clients use the standard mcpServers config block. Add the following to your client's config file:

jsonc
{  "mcpServers": {    "verified-googledocs-mcp": {      "command": "uvx",      "args": ["verified-googledocs-mcp"]    }  }}

PATH note for headless clients: Claude Desktop and similar clients launch the server as a subprocess with a minimal PATH that may not include Homebrew or user-local bins. If uvx is not found, use its full path ("command": "/opt/homebrew/bin/uvx"). Find it with which uvx. On Apple Silicon the Homebrew prefix is /opt/homebrew; on Intel Mac it is /usr/local.

Startup-timeout note. The first uvx launch downloads the package and its dependencies, which can exceed a client's MCP startup timeout and surface as a failed connection. Pre-warm the cache once in a terminal by running the auth command (uvx verified-googledocs-mcp auth) — you do this anyway, and it installs the package into the uvx cache so the client's launch is fast.

From source. If you prefer to run from a local clone instead of PyPI:

jsonc
{  "mcpServers": {    "verified-googledocs-mcp": {      "command": "/opt/homebrew/bin/uv",      "args": ["run", "verified-googledocs-mcp"],      "cwd": "/path/to/verified-googledocs-mcp"    }  }}

Logs / stderr. The server logs to stderr, which MCP clients capture rather than show inline. If a connection or a tool call fails, check the client's MCP logs — for Claude Desktop on macOS, ~/Library/Logs/Claude/mcp*.log. An AUTH_EXPIRED envelope there means the token is missing or expired; re-run the auth command.

The server uses the documents and drive scopes (comments require Drive). The credentials path is overridable with VERIFIED_GOOGLEDOCS_MCP_CREDENTIALS.

Security and permissions

This is a single-user, local server. It runs as you, over stdio, launched by your MCP client; there is no network listener, no hosted service, and no shared credentials. It acts entirely with your own Google authority.

  • Scopes. It requests documents and drive. The full drive scope is broader than editing alone needs, but the comment and suggestion tools (listing, replying to, and resolving comments on documents you already have) operate through the Drive API on arbitrary existing files, which the narrower drive.file scope cannot reach. drive is the minimum that covers the full tool set; if you don't need the comment tools, a fork could drop to a narrower scope.
  • Credentials at rest. The OAuth client secret lives at ~/.config/verified-googledocs-mcp/credentials.json; the cached token (including the refresh token) is written to ~/.config/verified-googledocs-mcp/token.json with owner-only permissions (0600, under a 0700 directory). Treat both as secrets: a leaked refresh token grants your full drive+documents access until you revoke it in your Google Account's security settings. Neither file is ever committed (both are gitignored).
  • Audit log. Every mutation appends to ~/.local/state/verified-googledocs-mcp/audit.jsonl (also 0600). Each line records the timestamp, document ID, tab ID, tool name, and the evidence payload — which includes before/after content excerpts. To log the metadata without the excerpts, set the environment variable VERIFIED_GOOGLEDOCS_MCP_AUDIT_EXCERPTS to a falsey value (0, false, no, or off); the before/after fields are then replaced with "[redacted; N chars]" and every other field is kept. Override the log location with XDG_STATE_HOME.
  • Local file diffs. diff_tab_vs_file reads a local file so it can compare a Doc tab with markdown on disk. It resolves symlinks before reading and only allows paths under VERIFIED_GOOGLEDOCS_MCP_ALLOWED_FILE_ROOTS (a platform path-list; defaults to the user's home directory, not the server process's working directory). The home-directory default exists because MCP clients typically register this server pinned to one repo (e.g. --directory /path/to/GoogleDocs-MCP), while the diff target is almost always in whichever other project the caller is actually working in — scoping to the launch directory made every cross-repo diff fail by default. Narrow it further (e.g. back to a single repo) or widen it by setting VERIFIED_GOOGLEDOCS_MCP_ALLOWED_FILE_ROOTS on the server process to a :-separated (; on Windows) list of directories, then restart the server — a rejected path's error names the env var and includes the currently configured allowed_roots so you can see exactly what's missing. It's still a real boundary, not unrestricted: an agent asking to diff against /etc/passwd or another user's home directory is refused. A home-directory-wide default also has to defend against a document's own content tricking an agent into reading credentials (prompt injection) — e.g. a paragraph instructing "diff against ~/.ssh/id_rsa" — so .ssh, .aws, .gnupg, .netrc, .git-credentials, .config/gh, .docker/config.json, and .npmrc under the home directory are denylisted unconditionally, regardless of the configured allowed roots. It also refuses files larger than VERIFIED_GOOGLEDOCS_MCP_MAX_DIFF_FILE_BYTES (default 1000000). export_pdf's output path is confined by this same policy — the same VERIFIED_GOOGLEDOCS_MCP_ALLOWED_FILE_ROOTS allow-list and the same unconditional credential-path denylist, so a PDF export can no more land in (or overwrite) ~/.ssh than a diff can read from it.

Error codes

Failures return a typed envelope (error_code, message, diagnostics, retryable):

CodeMeaning
ZERO_MATCHTarget not found after the full normalization ladder; diagnostics include the nearest near-miss
MATCH_COUNT_MISMATCHFound a different count than expected_matches; no edit made; all locations returned
REVISION_CONFLICTDocument changed between read and write; retry after re-reading
VERIFICATION_FAILEDA write was issued, but the post-write re-read did not verify the expected final state
STALE_RANGEA find_sections range was used after the document moved on; re-run find_sections
TAB_NOT_FOUNDUnknown tab_id; available tabs listed
STRUCTURAL_BOUNDARYMatch crosses a paragraph or table-cell boundary
UNSUPPORTED_MARKDOWNMarkdown outside the supported subset; the offending construct is named
QUOTE_NOT_FOUNDComment anchor text not found; nearest candidates returned
COMMENT_STILL_OPENA resolve was requested but re-query shows the comment open
INVALID_INPUTEmpty or contradictory arguments
IMAGE_SOURCE_UNSUPPORTEDImage source is a local path; a fetchable URL is required
AUTH_EXPIREDNo valid token; run verified-googledocs-mcp auth
INDEX_SIMULATION_FAILEDA markdown write's compiled requests would land at an invalid index; caught before the API call. Raised identically by dry_run and the real write, so a passing dry_run always means the write will pass too
TABLE_NOT_FOUNDThe requested table_index does not exist in the tab; call list_tables first
SUGGESTIONS_PRESENTThe target tab has pending suggested insertions/deletions; a write refuses rather than computing indices against the wrong index space (issue #56) — accept or reject the suggestions first, then retry
INVALID_RANGEA caller-supplied range doesn't fit the tab's current extent, or the Docs API itself rejected the write as index/range-invalid (the verbatim API message is included)
INDEX_MODEL_DIVERGENCEA text run's computed UTF-16 length disagrees with the Docs API's reported endIndex; offsets from that read cannot be trusted
STRUCTURE_PREDICTION_FAILEDA markdown write's compiled requests would not produce the input's block structure (nesting, ordered-vs-unordered, headings, tables) — caught before the API call by predicting the requests' own effect and comparing it against the parsed input (issue #65). Raised identically by dry_run and the real write, so a passing dry_run always means the write will verify too

Development

bash
uv run --extra dev pytest                 # unit tests (offline) + coverageuv run --extra dev ruff check src tests   # lintuv run --extra dev ruff format src tests  # formatuv run --extra dev mypy src               # type check

Unit tests run against synthetic Docs API fixtures and an in-memory MCP client, so the full suite is offline (it never runs the live tests). The live acceptance suite (under tests/live/) runs with pytest tests/live --run-live against a real scratch document and needs OAuth credentials; it is the pre-release gate and never runs in CI — see docs/acceptance-report.md.

See docs/architecture.md for the module map and the verification pipeline, PRD.md for the full specification, docs/cutover.md to migrate off a general Workspace MCP server, and CONTRIBUTING.md to build on it.

Limitations

  • Accepting or rejecting suggested edits is not possible through the generally-available Google Docs API. This server makes suggestions visible alongside comments; acting on them stays a manual step in the Docs UI. As of July 2026 Google documents accept/reject/delete suggestion requests — but only under the Workspace Developer Preview Program; the stable v1 surface rejects them (verified 2026-07-17). Verified accept_suggestion/reject_suggestion tools become buildable when that reaches GA.
  • Single user, local. stdio transport, one cached token, no hosted or multi-user mode.
  • Docs only. Gmail, Calendar, and Sheets are out of scope by design.
  • Markdown is a fixed subset (headings, bold/italic, lists, tables, links). Anything outside it is rejected with a clear error rather than approximated.

License

MIT, © 2026 Michael Sutton.

來源:README.md,提交 374bcf8

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

1
  1. v0.1.0最新Sep 16, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分

其他 Verified 整合