verified-googledocs-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| XDG_STATE_HOME | No | Override the base directory for the audit log (default: ~/.local/state) | |
| VERIFIED_GOOGLEDOCS_MCP_CREDENTIALS | No | Override the default path to the OAuth client secret JSON file (default: ~/.config/verified-googledocs-mcp/credentials.json) | |
| VERIFIED_GOOGLEDOCS_MCP_AUDIT_EXCERPTS | No | Set to a falsey value (0, false, no, off) to redact content excerpts in the audit log (default: true) | |
| VERIFIED_GOOGLEDOCS_MCP_ALLOWED_FILE_ROOTS | No | Colon-separated (semicolon on Windows) list of directories allowed for file diffs and exports (default: user home directory) | |
| VERIFIED_GOOGLEDOCS_MCP_MAX_DIFF_FILE_BYTES | No | Maximum file size in bytes for diff/export (default: 1000000) |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| logging | {} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| extensions | {
"io.modelcontextprotocol/ui": {}
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| list_tabsA | List the tabs in a Google Doc. Use this tool first when you need to read or edit a document but do not yet know its tab structure. Returns tab IDs, titles, nesting level, and index. Required before calling read_document or find_sections because every tool in this server requires an explicit tab_id. For documents created before Google's tabbed-docs feature, returns a single synthetic tab with id "_body" that covers the whole document. |
| read_documentA | Read the content of a specific tab in a Google Doc. Use this tool when you need to read the text, headings, tables, or structure of a document tab. Call list_tabs first to get the tab_id. format="markdown" (default): returns markdown text. Out-of-subset elements (images, smart chips, footnotes) appear as stable placeholder tokens and are listed in lossy_elements. format="structured": returns paragraph positions and style runs from the raw Docs JSON, suitable for computing exact edit ranges. format="outline": returns only the tab's headings (level, text, start_index, end_index), in document order. Use this when you only need geometry — e.g. to see the tab's section structure before deciding where to write — without pulling the whole tab as markdown. If you already know which heading you want to target, find_sections is the lighter tool: it filters to matches and returns section (not just heading) ranges. Drive's files.export cannot scope to a single tab, which is why this server uses its own Docs JSON converter for markdown output. |
| find_sectionsA | Find headings in a document tab and return their document ranges. Use this tool when you need to locate a section by its heading text before performing a targeted edit on that section. The returned ranges carry a computed_at_revision stamp; range-editing tools in later milestones will refuse stale ranges (ranges computed against an older document revision). Call find_sections immediately before editing — do not cache returned ranges across separate edits. Matching is case-insensitive and substring-based: a query of "intro" will match a heading "Introduction". |
| replace_textA | Replace occurrences of a text string in a Google Doc tab. Use this tool when you need to make an exact-text substitution in a document tab. Call list_tabs first to get the tab_id, then read_document to confirm the text you want to replace is present as-is. The tool locates every occurrence of Set On success the response carries before/after excerpts (±200 chars), the
normalization rung used, pre/post revision IDs, and Errors are returned as typed envelopes with |
| format_textA | Apply character styling (bold/italic/underline) to a matched text span. Use this tool when you need to style existing text in place — bold a
phrase, un-bold it, italicize it — without touching its content. Unlike
replace_text (which deletes and reinserts text and cannot express
styling), this tool compiles ONLY Call list_tabs first to get the tab_id, then read_document to confirm the
text you want to style is present as-is. Matching uses the same normalization ladder and match-count guard as
replace_text (exact → curly/straight quote equivalence → NBSP/whitespace
collapse → soft-hyphen strip; refuses unless the match count equals
The response includes, per matched span, the actual textRun style flags
before and after ( Set Errors are returned as typed envelopes with |
| list_open_itemsA | List all open comments and pending suggested edits on a document. Use this tool when you need a single unified view of all open review items on a document. Returns both Drive-level comments (labeled scope='document') and per-tab suggested edits in one response. Comments come from the Drive API and cannot be attributed to a specific tab — Drive comment anchors are opaque. If tab_id is provided it filters the suggestions returned to that tab only. To include suggestions from every tab, set include_all_tabs=true. Comments are always returned document-wide regardless of tab_id. Suggestions are extracted from the raw Docs JSON (suggestedInsertionIds / suggestedDeletionIds / suggestedTextStyleChanges) and are per-tab. The document is fetched with suggestionsViewMode=SUGGESTIONS_INLINE so that suggestion fields are populated. |
| get_comment_threadA | Retrieve the full reply chain for a comment. Use this tool when you need to read a comment thread in full before deciding on a response or resolution. Returns the comment content, all replies, quoted text, resolved status, and author. Requires both the doc_id (the Google Doc's file ID) and the comment_id from the Drive API. |
| add_anchored_commentA | Add a comment to a document, validated against a quoted passage. Use this tool when you need to create a comment on specific text in a document tab. The quote must exist in the tab — the tool locates it via the same normalization ladder as replace_text and returns QUOTE_NOT_FOUND with nearest candidate anchors if the quote is absent. NOTE: The Drive API may render the created comment as document-level even when quotedFileContent is supplied. This behaviour is pending confirmation from a live anchoring spike; for now the comment is created with the quote embedded in its content and the tool returns comment-state evidence. Returns comment-state evidence: applied, comment_id, resolved, reply_count, content, quoted_text, audit_logged. Errors: QUOTE_NOT_FOUND – quote not found in the tab; nearest candidates listed INVALID_INPUT – empty body or quote TAB_NOT_FOUND – tab_id not in document |
| reply_to_commentA | Add a reply to an existing comment thread. Use this tool when you need to respond to a reviewer comment without resolving it. The reply is added to the thread and the tool re-queries the comment to return post-state evidence. Returns comment-state evidence: applied, comment_id, resolved, reply_count, content, quoted_text, audit_logged. Errors: INVALID_INPUT – empty body or comment not found |
| resolve_commentA | Resolve a comment on a document and verify the resolution landed. Use this tool when you need to mark a reviewer comment as resolved. Resolves via Drive replies.create(action='resolve') — the only mechanism that actually resolves comments in Drive API v3. Using comments.update with resolved=true is silently ignored (resolved is a read-only field), which is the incumbent server's bug. After issuing the resolve the tool re-queries the comment and returns the actual final state. A comment that is still open after the resolve attempt is reported as COMMENT_STILL_OPEN — never as success. Returns comment-state evidence: applied, comment_id, resolved, reply_count, content, quoted_text, audit_logged. Errors: COMMENT_STILL_OPEN – comment did not resolve; post-state included INVALID_INPUT – comment not found |
| replace_range_markdownA | Replace a document range with compiled markdown. Use this tool when you need to replace a section of a document with new markdown content. Obtain start_index, end_index, and computed_at_revision from find_sections. The range stamp is validated against the current document revision — a stale stamp raises STALE_RANGE ("re-run find_sections"). The structural guardrail inventories tables, images, chips, and footnotes inside the target range before writing. If the replacement markdown does not account for them the write is refused unless allow_structural_loss=true. A blast-radius check compares structural element counts outside the edited range pre/post; any change there is a hard failure. Set dry_run=true to validate and preview without writing. The returned payload (applied, revisions, structural_match, input_blocks/ post_blocks) is itself the confirmation the write landed — it already re-read the document and diffed it against the input. A follow-up read_document to double-check is a redundant round-trip; only re-read if you need the content for a subsequent step. Errors: STALE_RANGE – range stamp is outdated; re-run find_sections UNSUPPORTED_MARKDOWN – markdown contains an unsupported construct INVALID_INPUT – structural guardrail refused or blast-radius violation INVALID_RANGE – start_index/end_index don't fit the tab's current extent, or the Docs API rejected the write as index-invalid TAB_NOT_FOUND – tab_id not in document REVISION_CONFLICT – document changed mid-call; re-read and retry SUGGESTIONS_PRESENT – tab has pending suggested edits; accept/reject them in the Docs UI first, then retry |
| replace_tab_markdownA | Replace the entire content of a document tab with compiled markdown. Use this tool when you need to completely replace a tab's content with new markdown. tab_id is required and must identify an existing tab. The structural guardrail refuses writes that would silently lose tables, images, chips, or footnotes unless allow_structural_loss=true. Set dry_run=true to validate and preview without writing. The returned payload (applied, revisions, structural_match, input_blocks/ post_blocks) is itself the confirmation the write landed — it already re-read the document and diffed it against the input. A follow-up read_document to double-check is a redundant round-trip; only re-read if you need the content for a subsequent step. Errors: UNSUPPORTED_MARKDOWN – markdown contains an unsupported construct INVALID_INPUT – structural guardrail refused TAB_NOT_FOUND – tab_id missing or not in document REVISION_CONFLICT – document changed mid-call; re-read and retry SUGGESTIONS_PRESENT – tab has pending suggested edits; accept/reject them in the Docs UI first, then retry |
| append_markdownA | Append compiled markdown at the end of a document tab. Use this tool when you need to add new content at the end of a tab without disturbing existing content. Inserts before the final trailing newline. Set dry_run=true to validate and preview without writing. The returned payload (applied, revisions, structural_match, input_blocks/ post_blocks) is itself the confirmation the write landed — it already re-read the document and diffed it against the input. A follow-up read_document to double-check is a redundant round-trip; only re-read if you need the content for a subsequent step. Errors: UNSUPPORTED_MARKDOWN – markdown contains an unsupported construct TAB_NOT_FOUND – tab_id not in document REVISION_CONFLICT – document changed mid-call; re-read and retry |
| insert_imageA | Insert an inline image after the paragraph containing anchor text. Use this tool when you need to add an image to a specific location in a document tab. The anchor resolves via the same normalization ladder as replace_text (exact → curly/straight quotes → NBSP/whitespace → soft-hyphen). The image is inserted as an inline object in a new paragraph immediately after the paragraph containing the resolved anchor. source must be a publicly fetchable URL (http/https). Local file paths are rejected with IMAGE_SOURCE_UNSUPPORTED — the Docs API fetches the image from the URL directly and cannot access local files. Set dry_run=true to preview the resolved anchor position without writing. Returns structural evidence: applied, revision_before/after, inline_object_confirmed (whether the post-read confirms an inline object near the anchor paragraph), audit_logged. Errors: QUOTE_NOT_FOUND – anchor not found; nearest candidates listed IMAGE_SOURCE_UNSUPPORTED – source is a local path, not a URL INVALID_INPUT – anchor is inside a table (anchor must be body text) TAB_NOT_FOUND – tab_id not in document REVISION_CONFLICT – document changed mid-call; re-read and retry SUGGESTIONS_PRESENT – tab has pending suggested edits; accept/reject them in the Docs UI first, then retry |
| diff_tab_vs_fileA | Export a document tab as markdown and diff against a local file. Use this tool when you need to compare a Google Doc tab against a local markdown file. The server reads the file directly (it runs locally). Returns a structured diff with tagged hunks (equal/insert/delete/replace) and a unified diff string. This is a read-only tool — it makes no changes to the document or file. Returns: doc_id, tab_id, file_path, revision_id, identical (bool), hunks (list of tagged diff blocks), unified_diff (unified diff string) Errors: TAB_NOT_FOUND – tab_id not in document INVALID_INPUT – file not found at file_path |
| list_tablesA | List every top-level table in a document tab. Use this tool to discover and address the tables in a tab before calling get_table or replace_table_row — both require a table_index, which this tool assigns in document order. Returns tables: a list of table_index, rows, columns, start_index, end_index, preceding_heading (the nearest heading above the table, or null if none), first_row, and has_merged_cells — plus doc_id, tab_id, and revision_id. Errors: TAB_NOT_FOUND – tab_id not in document AUTH_EXPIRED – no valid token |
| get_tableA | Read one table's full cell grid. Use this tool to read every cell of a specific table — identified by the table_index returned from list_tables — before editing a row with replace_table_row. Returns doc_id, tab_id, revision_id, table_index, rows, columns, cells (the row-major cell grid, list[list[str]]), and has_merged_cells. Errors: TAB_NOT_FOUND – tab_id not in document TABLE_NOT_FOUND – table_index does not exist in the tab AUTH_EXPIRED – no valid token |
| replace_table_rowA | Replace every cell in one row of an existing table with plain text. Use this tool — the workhorse for updating existing tables in place — to overwrite one row's cells after locating the table and row with list_tables and get_table. cells are PLAIN STRINGS, not markdown, and the list length must equal the row's column count. Each cell's pre-existing bold/italic/underline style is preserved on the replacement text. The tool refuses tables that contain merged cells and rows where a cell contains a nested table. Set dry_run=true to preview without writing. dry_run is authoritative for index validity: the same assembled request list is index-simulated whether dry_run is true or false, and the suggestion guard below runs identically on both paths, so a passing dry_run means the real write will pass too — provided nothing about the document changes in between (a new suggestion, a concurrent edit) before the real write is issued. Returns evidence: applied, table_index, row_index, row_before, row_after, cells_match, revision_before, revision_after, audit_logged. In dry-run mode row_after_preview and planned_requests replace row_after and cells_match. Errors: TAB_NOT_FOUND – tab_id not in document TABLE_NOT_FOUND – table_index does not exist in the tab INVALID_INPUT – merged cells, a nested table in a target cell, a wrong cell count, or a bad row_index REVISION_CONFLICT – document changed mid-call; re-read and retry SUGGESTIONS_PRESENT – tab has pending suggested edits; accept/reject them in the Docs UI first, then retry VERIFICATION_FAILED – post-write re-read does not match the requested cells INDEX_SIMULATION_FAILED – compiled requests would land at an invalid index; caught before the API call AUTH_EXPIRED – no valid token |
| insert_tableA | Create a new table populated from rows, inserted after an anchor. Use this tool to add a brand-new table to a document tab. rows is a list of rows of plain strings — no markdown, no bolding applied — with the first row treated as the header positionally. anchor must be exact text present in the tab; the table is inserted after the paragraph containing it, the same anchoring insert_image uses. Set dry_run=true to preview without writing. dry_run is authoritative for index validity: the same assembled request list is index-simulated whether dry_run is true or false, and the suggestion guard below runs identically on both paths, so a passing dry_run means the real write will pass too — provided nothing about the document changes in between (a new suggestion, a concurrent edit) before the real write is issued. Returns evidence: applied, table_index (use it for follow-up replace_table_row calls), rows, columns, first_row, table_confirmed, revision_before, revision_after, audit_logged. Errors: TAB_NOT_FOUND – tab_id not in document QUOTE_NOT_FOUND – anchor not found; nearest candidates listed INVALID_INPUT – empty or ragged rows, a non-string cell, or an anchor that falls inside a table REVISION_CONFLICT – document changed mid-call; re-read and retry SUGGESTIONS_PRESENT – tab has pending suggested edits; accept/reject them in the Docs UI first, then retry VERIFICATION_FAILED – post-write re-read did not confirm the inserted table at the expected location INDEX_SIMULATION_FAILED – compiled requests would land at an invalid index; caught before the API call AUTH_EXPIRED – no valid token |
| export_pdfA | Export the whole document as a PDF to a local path. Use this tool when a caller needs a render-measured page count to check against a page limit, or simply needs a PDF copy on disk. Drive's export is doc-level, not tab-scoped, so this exports every tab in the document, not just one. output_path must fall inside VERIFIED_GOOGLEDOCS_MCP_ALLOWED_FILE_ROOTS (defaults to the user's home directory) and must never resolve to a credential path; its parent directory must already exist. page_count is best-effort and is None when the PDF hides its page markers inside a compressed stream — never a guessed number. Drive refuses exports whose PDF would exceed roughly 10 MB. This is a read/export tool: nothing in the document changes, so the return value has no "applied" key. Returns doc_id, output_path, bytes_written, sha256, page_count, existed_before, audit_logged. Errors: INVALID_INPUT – a bad output_path (missing parent directory, outside the allowed roots, a denylisted credential path, or an existing target that isn't a regular file), or Drive refused the export (not found, permission denied, or the size limit) AUTH_EXPIRED – no valid token |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 20 tools
Most tools have clearly distinct purposes: reading, writing, styling, commenting, tables, and export are separated cleanly. A few pairs could be confused—read_document with format='outline' overlaps find_sections, and replace_text vs replace_range_markdown both replace content—but the descriptions provide enough guidance to disambiguate.
All tool names follow a consistent verb_noun (or verb_preposition) pattern in snake_case: list_tabs, read_document, replace_text, append_markdown, resolve_comment, insert_table. The naming style is uniform and predictable across all 20 tools, with no mixed conventions or vague verbs.
20 tools is slightly above the typical well-scoped range, but the server covers several distinct sub-domains—document reading, text editing, comments, tables, and export—so the count is reasonable. It is not bloated; each tool addresses a concrete operation, though a few could be consolidated without much loss.
The toolset covers the core document lifecycle well: read tabs, find sections, replace and append content, style text, manage comments, handle tables, and export PDFs. Minor gaps exist—no delete-section or delete-table operations, no arbitrary insertion of plain text without replacement, and no tab creation/renaming—but these are not critical dead ends for typical editing workflows.