Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
XDG_STATE_HOMENoOverride the base directory for the audit log (default: ~/.local/state)
VERIFIED_GOOGLEDOCS_MCP_CREDENTIALSNoOverride the default path to the OAuth client secret JSON file (default: ~/.config/verified-googledocs-mcp/credentials.json)
VERIFIED_GOOGLEDOCS_MCP_AUDIT_EXCERPTSNoSet to a falsey value (0, false, no, off) to redact content excerpts in the audit log (default: true)
VERIFIED_GOOGLEDOCS_MCP_ALLOWED_FILE_ROOTSNoColon-separated (semicolon on Windows) list of directories allowed for file diffs and exports (default: user home directory)
VERIFIED_GOOGLEDOCS_MCP_MAX_DIFF_FILE_BYTESNoMaximum 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

CapabilityDetails
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

NameDescription
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 find using a normalization ladder (exact → curly/straight quote equivalence → NBSP/whitespace collapse → soft-hyphen strip) and refuses the write if the match count does not equal expected_matches. This prevents accidental multi-replacement and duplicate-sentence collapse.

Set dry_run=True to preview the operation without writing; the response carries applied: false and the matched span information but makes no API call.

On success the response carries before/after excerpts (±200 chars), the normalization rung used, pre/post revision IDs, and audit_logged.

Errors are returned as typed envelopes with error_code, message, diagnostics, and retryable so the caller can act on them precisely: ZERO_MATCH – find string not found; near-miss span included MATCH_COUNT_MISMATCH – wrong number of matches; all locations listed REVISION_CONFLICT – document changed mid-call; re-read and retry STRUCTURAL_BOUNDARY – match crosses a paragraph boundary INVALID_INPUT – empty find, or find equals replace TAB_NOT_FOUND – tab_id not in document; available tabs listed SUGGESTIONS_PRESENT – tab has pending suggested edits; accept/reject them in the Docs UI first, then retry (a pending suggestion makes the write's computed indices unsafe — see verified writes, below)

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 updateTextStyle requests, so it is safe to use inside a table cell even when the table has hand-merged cells: no delete/insert/merge request is ever compiled, and the response reports every compiled_request_kinds entry so that claim is checked, not just asserted.

Call list_tabs first to get the tab_id, then read_document to confirm the text you want to style is present as-is. style maps any of bold/italic/underline to true or false — e.g. {"bold": true} to bold, {"bold": false} to un-bold. At least one key is required.

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 expected_matches). If the matched text already carries every requested style value, the call is a no-op: it returns applied: true, style_mutated: false, and issues no write at all (so an idempotent re-run does not create a new document revision).

The response includes, per matched span, the actual textRun style flags before and after (runs_before/runs_after) — not a markdown or plain-text diff, since genuine bold and a literal **word** both render identically as text. content_mutated is always false, proven by compiled_request_kinds containing only updateTextStyle. Style reflects textRun.textStyle only: a heading rendered bold by its named/paragraph style rather than an explicit run style will not show bold: true here, matching every other style read in this server. A pending style-only suggestion on the target span is allowed through (see SUGGESTIONS_PRESENT below) and evidence reflects the base, non-suggested style, same as every read in this server.

Set dry_run=True to preview the operation without writing; the response carries applied: false and a predicted runs_after (the requested fields overlaid on the current runs) but makes no API call.

Errors are returned as typed envelopes with error_code, message, diagnostics, and retryable so the caller can act on them precisely: ZERO_MATCH – find string not found; near-miss span included MATCH_COUNT_MISMATCH – wrong number of matches; all locations listed STRUCTURAL_BOUNDARY – match crosses a paragraph or table-cell boundary REVISION_CONFLICT – document changed mid-call; re-read and retry VERIFICATION_FAILED – the write landed but could not be confirmed on re-read; check diagnostics before assuming a manual restore is needed — a concurrent edit near the target text is a common, benign cause INVALID_INPUT – empty find, or style is empty/unknown-key/non-bool TAB_NOT_FOUND – tab_id not in document; available tabs listed SUGGESTIONS_PRESENT – tab has a pending suggested insertion/deletion (not a style suggestion); accept/reject it in the Docs UI first, then retry

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

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A4.4/5.0

Scored across 20 tools

Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count4/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessResponsive