Google Docs MCP for Hermes
This server exposes five MCP tools for working with native Google Docs: bounded reading, private document creation, Markdown replacement, exact text edits, and literal text insertion, all with revision guards and preview/verification safeguards.
docs_read — Read document metadata, tab inventory, the current Docs revision, and paged readable content from a selected tab.
docs_create — Create a private Google Doc from Markdown, with
persian(RTL, right-aligned, Vazirmatn) orplainformatting.docs_replace_markdown — Destructively replace a selected tab’s body from Markdown under an exact
expected_revision_idguard.docs_edit_text — Preview or apply exact, case-sensitive, revision-guarded text replacements with expected match counts.
docs_insert_text — Preview or insert literal text at the start/end or before/after an exact anchor, optionally applying Persian formatting.
Supports a small Markdown subset: headings, bold, HTTP(S) links, literal bullets/checkboxes, and pipe tables.
Enforces safety: stale-revision rejection, readback verification, non-mutating previews, and typed error codes for recovery decisions.
Allows reading Google Docs content and metadata, creating new private Google Docs from Markdown, replacing document bodies, and applying exact text edits with revision-based consistency checks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Google Docs MCP for HermesCreate a private Google Doc titled 'Meeting Notes' with Persian formatting."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Google Docs MCP for Hermes
A small local stdio MCP server for native Google Docs: bounded reading, private document creation, Markdown publication, and exact text edits. Existing-document writes require the Docs revision you reviewed and perform readback checks. The server is launched by Hermes; it does not expose an HTTP listener or a general Google API proxy.
MIT licensed · Python 3.12 · macOS/POSIX-oriented · Google OAuth required
Installation · Authentication · Hermes setup · Security
This is a community project, not an official Google or Nous Research product. The documented launcher requires Bash and a POSIX-style virtual environment; native Windows is not supported by these instructions. Persian formatting is the default; choose format_profile="plain" for documents that should not receive Persian formatting. There is no bundled OAuth client, login command, or hosted service.
Exactly eleven tools
Tool | Public arguments | Behavior |
|
| Return metadata, tabs, the current Docs revision when available, and bounded readable content when a tab is selected. |
|
| Create a private native Google document; render and verify nonempty Markdown. |
|
| Replace the selected tab's body while retaining the document ID. This is destructive, not an append or merge. |
|
| Preview exact replacements; write only with |
|
| Preview or insert literal text at the start/end or before/after one exact anchor, without replacing the tab. |
|
| Insert rendered Markdown at paragraph boundaries or replace the content below one heading, preserving the rest. |
|
| Repair layout on a whole tab or heading section without rewriting content; supports explicit Persian/English layout. |
|
| Create, rename, reorder or reparent tabs; no deletion. |
|
| Edit one cell or insert/delete one row/column; rejects merged/nested targets and last-row/column deletion. |
|
| Export native PDF/DOCX into a new private local file, with source-stability and explicit tab-scope checks. |
|
| Preview or insert an inline image from a public HTTPS URI without uploading local files or changing sharing. |
Exact edits accept Google's omitted zero-valued startIndex in header/footer/footnote text runs, including empty headers. Auxiliary text is still counted and verified; explicit malformed indices and inconsistent UTF-16 spans fail closed.
A replacement is {"old_text":"…","new_text":"…","expected_count":1}. Content-writing profiles are persian and plain; only docs_format uses persian and english. Booleans and integer controls use strict MCP input types: pass JSON false/true and integers, not strings. See scoped structured editing for the four new tools, selector rules, examples and recovery boundaries.
Input limits and document references
documentis an ID matching[A-Za-z0-9_-]{10,256}, or an HTTPS URL on exactlydocs.google.comwith a/document/d/<id>/…path. Whitespace, nonprinting characters, URL credentials/ports, encoded path components, and traversal components are not accepted. The server does not fetch arbitrary user-supplied URLs.title: nonblank, at most 200 characters, no NUL.markdown: at most 500000 characters, no NUL. Rendering also has request-count, payload-byte, and semantic-processing limits; a short but excessively fragmented input can still fail.max_chars: 1–100000, default 30000.startmust fall within the rendered content. Pagination uses character offsets, not Google Docs UTF-16 indices.replacements: 1–100 entries; eachexpected_countis 1–100.old_textmust be nonempty;new_textmay be empty to delete text. Neither may contain NUL.Insertion
text: 1–500000 characters; literal text, not Markdown. Tabs/newlines and whitespace are preserved; no separator is added automatically. C0 controls except tab/newline, surrogate code points, and BMP private-use characters are rejected rather than allowing Google to strip them. Anchors are 1–500000 characters in a single contiguous paragraph text span; control characters and surrogate code points are rejected.expected_revision_id: a nonempty Docs revision identifier, at most 1000 characters, no NUL. Copy it from a fresh read; it is not a Driveversion.
Related MCP server: minimal-gdocs
Markdown subset and non-goals
This is a deliberately small renderer, not a CommonMark/GFM implementation or a lossless document conversion system.
Input | Rendered behavior |
| Native heading levels 1–6; the heading marker must start the line and be followed by whitespace. |
| Explicit bold emphasis, including in supported link labels and table cells. |
| HTTP(S)-only link formatting; recognized destinations start with lowercase |
| A text bullet |
| Text checkbox glyphs |
Pipe tables with a header and separator row | Native tables with text, bold, and supported links in cells. Header and separator widths must match; shorter data rows are padded to the widest row. Rows must begin with ` |
Persian, mixed Persian/English, emoji and other astral characters | Unicode text is preserved; Google Docs edit/style ranges are calculated in UTF-16 code units, including offsets after emoji. |
Example supported Markdown:
# یادداشت 🧪
متن **پررنگ** و [پیوند](https://example.com).
- گزینه
- [ ] کار باز
- [x] کار انجامشده
| موضوع | وضعیت |
| --- | --- |
| **آزمون** | آماده |Additional normalization matters when preparing a write:
A leading block delimited by
---is removed as frontmatter; its metadata is not applied to Google Docs.Single-backtick inline code becomes literal text without backticks; no monospace/code style is applied.
Obsidian
[[target|alias]]becomes alias text; without an alias, the section after#, or otherwise the target, is displayed. It is not a linked-note integration.Backslash escapes are consumed; line endings are normalized. Unmatched or unsupported constructs are not universally rejected or preserved verbatim. They pass through the line/inline parser, so supported syntax inside them can still be interpreted.
Italics, underscore-based bold, strikethrough, ordered/nested/native lists, blockquotes, fenced-code semantics, HTML rendering, images, and rich embeds are not supported features. Image-like syntax does not upload an image; its link portion may still be parsed. A fenced block does not protect internal headings or emphasis from parsing.
Non-HTTP(S) link destinations are not turned into links. Reserved internal table-marker syntax is rejected with
invalid_markdown.Emitted body and table-cell text containing characters that Google
insertTextstrips (U+0000–U+0008, U+000C–U+001F, U+E000–U+F8FF), or surrogate code points, is rejected before creation/replacement. This does not change CRLF normalization, ignored frontmatter, or valid Persian half-spaces/emoji.Nonempty Markdown replacement removes inherited native bullets/numbering before applying the selected profile. Markdown bullets/checklists remain literal glyphs. Semantic verification rejects unexpected native lists, including empty paragraphs and table cells. Empty-model replacement retains its no-style-write behavior; this is not a general native-list editing tool.
There are no public tools for arbitrary batchUpdate, arbitrary HTTP, sharing/permission changes, document/tab deletion, comments, suggestions, named-range workflows, Office import/conversion, local-image upload, or free-form header/footer editing. Table/section editing is limited to the explicit scoped contracts above, not arbitrary layout manipulation. The internal cleanup/recovery helpers are not MCP tools; docs_export exposes only the two documented output formats. docs_edit_text can count and replace matching existing text within the selected tab's body/table cells and auxiliary header/footer/footnote segments; that is not a layout or segment-authoring API. Markdown image syntax remains unsupported; use docs_insert_image explicitly.
Why not Drive's native Markdown conversion?
Google Drive officially supports importing text/markdown to application/vnd.google-apps.document. This server still uses its explicit Docs request renderer: Drive import-update replaces the entire document, not just the selected tab, and the importer is not output-equivalent to our documented subset or Persian profile. A synthetic live characterization produced native headings, bold text, links, lists and a table, but no explicit RTL paragraphs or Vazirmatn runs. This is not a claim that native import can never be useful; it needs a separately defined import contract rather than a silent backend swap.
See the API audit for all Request categories, adoption decisions and evidence. The opt-in characterization and inherited-list regressions reuse the existing live-test cleanup helpers:
env -u PYTHONPATH -u PYTHONHOME RUN_GOOGLE_DOCS_MCP_LIVE=1 \
.venv/bin/python -m pytest tests/test_native_api.py -v -sRun only after explicitly authorizing private synthetic temporary Google documents and their deletion. A passing characterization proves conversion and cleanup, not equality with the current renderer. No import method or general-purpose API proxy is exposed through MCP.
Read first, then write with the revision you reviewed
All IDs, tabs, revisions, and result excerpts below are synthetic examples, not live output. abcDEF_123-xyz is a syntactically valid example ID; replace it with your own authorized document reference. Each JSON object is a tool's arguments unless explicitly labeled as a response excerpt.
Create privately
Call docs_create:
{
"title": "نمونهٔ خصوصی",
"markdown": "وضعیت: پیشنویس 🧪",
"format_profile": "persian"
}The creation request does not grant sharing permissions or create an anyone permission. Private-by-default creation is not a general permission-management API or a continuous audit of account/organization sharing policy. Changing access remains a separate Google Drive/Docs action.
Creation returns document_id, document_url, revision_id, tab_id, semantic, format_profile, formatting, and verified, with ok=true on success. Empty Markdown creates a blank document and returns formatting=null; it does not demonstrate that later manually added text has been formatted.
If initial publication fails after creation, the new document is retained for diagnosis, not silently deleted. Its error includes document_id, document_url, retained_for_diagnosis=true, and failure_code, with recovery details when available. Inspect that document before retrying creation to avoid duplicates.
Read the chosen tab
Google only returns revisionId when the caller has edit access. Viewer/commenter documents remain readable: docs_read returns revision_id: null when Google omits that field. This is not an authentication outage. An explicitly malformed revision field is still rejected. Never substitute Drive version for a Docs revision or pass a guessed revision to a write: all existing-document mutations continue to require an available, exact revision. verified: true on a read verifies the returned read result, not write permission or a revision-guarded snapshot. See Google's Document resource.
Call docs_read:
{
"document": "abcDEF_123-xyz",
"tab_id": "tab_example",
"start": 0,
"max_chars": 30000
}Synthetic response excerpt — not the complete response:
{
"ok": true,
"document_id": "abcDEF_123-xyz",
"revision_id": "revision_example_1",
"tab_id": "tab_example",
"content": "وضعیت: پیشنویس 🧪\n",
"next_start": null,
"verified": true
}The readable body key is content, not text. Selected-tab responses also include start, end, total_chars, and outline. Follow next_start with the same document and tab until it is null; if revisions change between pages, reread rather than treating the pages as one snapshot.
Privacy boundary: max_chars limits only the returned content page, not the complete response. The selected tab's entire heading outline, table-dimension tables inventory, links, images, document metadata, and tab inventory are returned separately and are not restricted to that page. Even a one-character page can reveal headings, linked text/destinations and image metadata outside the requested range. Do not use pagination as a total-output limit or as permission to disclose only one excerpt.
Metadata fields are document_id, document_url, name, mime_type, modified_time, version, and revision_id. Each tabs entry contains tab_id, title, and parent_tab_id.
Reading produces plain readable paragraph text, a separate heading outline, and Markdown-like pipe tables. It does not reconstruct all source Markdown or preserve every style. Unsupported non-text elements are represented with markers such as ⟦NON_TEXT:inlineObjectElement⟧. Do not feed readback blindly into a full replacement when the document contains content the renderer cannot reproduce.
Selected-tab reads also return metadata_scope="selected_tab_body" and:
links: exact visibletext, a typedtarget(externalurl, heading, bookmark or tab),tab_id, andstart_index/end_indexin Google UTF-16 units, not content-page character offsets. Adjacent styled runs of one contiguous link are joined; separate occurrences are retained. Unknown source indices arenull, not guessed. Links in body table cells are included; auxiliary headers/footers/footnotes are not. Destinations are returned as source data, never fetched or rewritten.images: resolved inline/positioned imageobject_id,kind,tab_id, source indices when available, title/description, PT size andsource_uriwhen supplied. Positioned indices describe the anchor paragraph. Google temporarycontentUridownload links are deliberately omitted because anyone holding one can access the image as the requester. Image pixels/OCR are not returned. Unresolved objects and drawings remain non-text markers, not claimed images.
The link/image projection is limited to 10,000 combined entries, 2,000,000 accumulated source-string characters and 100,000 traversal nodes. Over-budget metadata fails with read_metadata_limit; it is never silently truncated. The older text, outline and table limits remain separate.
Native PDF/DOCX export
docs_export accepts format="pdf" or "docx" and saves a new, unencrypted private file below ~/.hermes/google-docs-mcp-exports (0700 directories, 0600 files). It returns the absolute path, MIME type, byte count, SHA-256, document identity, revision when available, scope and covered tab IDs. It cannot overwrite a chosen destination or export arbitrary MIME types. Files are retained for delivery; the recovery cleaner does not delete them. Remove exact unneeded exports yourself after delivery.
The only supported scope, scope="all_tabs", uses the official Drive export endpoint. Root and child-tab coverage in both formats is verified by the opt-in native export tests. Individual tabs cannot be selected; there is no first_tab mode. Source identity, Drive version/modified time and available Docs revision are checked before and after export; source drift rejects publication. Read-only access does not require a fabricated Docs revision. Newly edited Docs can precede Drive's metadata update; on source_changed, reread before a fresh export. The official export limit is 10 MB. Format/container checks verify the downloaded artifact, not pixel-perfect visual layout or an atomic Google revision-specific export.
Export does not modify Google content, but does write a local file; its MCP annotation is not read-only. No document reconstruction, permission change or public download URL is used.
Public-URL image insertion
docs_insert_image takes an explicit public HTTPS image_uri (at most 2048 UTF-8 bytes), the reviewed revision, and the same start/end or exact before/after text-anchor positions as literal insertion. Anchors target body/table-cell paragraphs, not headers, footers or footnotes. Width and height are optional finite positive PT values up to 1440; Google fits the image while preserving its aspect ratio. An image occupies one UTF-16 index. No automatic paragraph separators or captions are added.
Preview is the default and performs no image insertion/download. Apply sends one non-replayed revision-guarded batch, verifies the inserted object/position, fitted dimensions, unaffected content/objects/tabs and affected paragraph layout. persian sets RTL, physical Right (START) and explicit logical indentation; plain inherits layout. Existing text/fonts are not rewritten. A timeout or failed verification can follow a successful insertion: reread the exact tab and image inventory before any retry.
Privacy and capability boundary: Google fetches the supplied URI; this process never sends its OAuth credentials to the image host, downloads the image locally, uploads private screenshots, hosts images or changes sharing. Lexical URL validation rejects credentials, unsafe schemes/ports and local/private numeric hosts; it is not DNS/socket/redirect control over Google's fetch. Google enforces PNG/JPEG/GIF, less than 50 MB and no more than 25 megapixels. A chart must already be an image at a public URL; local/private files and editable Sheets charts are outside this tool. The source URI is stored in the document: do not use private signed/bearer URLs as if they were public image assets.
Preview and apply an exact edit
Call docs_edit_text with the revision from that read:
{
"document": "abcDEF_123-xyz",
"tab_id": "tab_example",
"expected_revision_id": "revision_example_1",
"replacements": [
{
"old_text": "پیشنویس",
"new_text": "آماده",
"expected_count": 1
}
],
"apply": false
}Preview performs no document mutation. A valid result includes revision_id, valid=true, and replacement entries containing expected_count, actual_count, preexisting_new_count, and ranges. Range entries use start_index/end_index in UTF-16 units, with segment_id when applicable. These are not offsets into the rendered content string.
After reviewing the preview, call docs_edit_text:
{
"document": "abcDEF_123-xyz",
"tab_id": "tab_example",
"expected_revision_id": "revision_example_1",
"replacements": [
{
"old_text": "پیشنویس",
"new_text": "آماده",
"expected_count": 1
}
],
"apply": true
}Apply rechecks the live revision and match counts; preview does not reserve the document. Matching is case-sensitive and non-regex. Duplicate, overlapping, substring-related, or otherwise interfering replacement sets are rejected, including replacements that create another operation's old text. The selected tab's auxiliary segments can affect counts even when absent from the readable body.
An applied result reports before_revision_id, after_revision_id, and per-operation occurrences_changed, before/after old-text counts, and before/after new-text counts. All replacements use one revision-guarded batchUpdate. This path sends no additional formatting requests; Google Docs handles existing formatting during replaceAllText.
Insert text without replacing existing content
After a fresh docs_read, call docs_insert_text. This synthetic example previews a new paragraph at the end of a selected tab:
{
"document": "abcDEF_123-xyz",
"text": "\nیادداشت تکمیلی: این بند به سند موجود اضافه میشود.\n",
"expected_revision_id": "revision_example_1",
"position": "end",
"anchor_text": null,
"tab_id": "tab_example",
"format_profile": "persian",
"apply": false
}startinserts at body index 1;endinserts immediately before the mandatory final newline. Both forbidanchor_text.before/afterrequire one exact, case-sensitiveanchor_textmatch within a contiguous paragraph text span in the selected body or a table cell. Styled runs can form one match; an anchor cannot span paragraphs, cells, or inline objects. Repeated matches (including overlapping matches) fail withanchor_match_mismatch; provide a longer unique anchor rather than choosing an arbitrary occurrence.Headers, footers, and footnotes are not insertion targets. The other tabs are never searched to resolve an anchor.
textis literal:#,**, and pipe syntax do not create headings, emphasis, or tables. Include your own spaces and\nseparators. For example, insertingnoteafterwordproduceswordnote, notword note.Preview returns
applied=false,valid=true, the reviewedrevision_id, resolved UTF-16index, andinserted_utf16_length. It does not change the document or reserve the position. Apply the same arguments withapply=true; the live revision and anchor are checked again.Apply sends one atomic batch:
insertTextplus scoped styles when Persian is selected. It does not delete or replace existing content. The result includesapplied=true, before/after revision IDs, andverified=trueonly after exact indexed body-text readback.formatting_verified=trueadditionally means the affected paragraph settings and inserted font passed API readback; insertion does not perform a whole-document DOCX audit.persiansets RTL, explicit Right alignment, and right-side indentation on paragraphs touched by inserted text, and Vazirmatn on the inserted text. Existing text sharing those paragraphs shares their paragraph settings. Unrelated paragraphs are not reformatted.plainsends no formatting requests and inherits Google Docs formatting.
A stale revision must be reread, not silently replaced with a fresh token. A transport or verification failure may occur after a successful write; reread before retrying to avoid duplicate insertion. This tool is not a Markdown merge or a layout-authoring API.
Replace Markdown on the same document ID
Read again after the edit:
{
"document": "abcDEF_123-xyz",
"tab_id": "tab_example"
}Suppose that read returns the synthetic revision revision_example_2. Call docs_replace_markdown with that exact value:
{
"document": "abcDEF_123-xyz",
"tab_id": "tab_example",
"expected_revision_id": "revision_example_2",
"markdown": "# یادداشت تازه 🧪\n\nوضعیت: **آماده**\n\n- [ ] بازبینی نهایی\n",
"format_profile": "persian"
}This replaces the selected tab's body, including content absent from the candidate; it does not preserve old unsupported objects or perform a content merge. Empty Markdown clears the body while leaving the required terminal paragraph and its inherited styles. An already-empty body needs no mutation; semantic readback still runs and the revision may remain unchanged. A successful result includes before_revision_id, after_revision_id, semantic (schema, block_count, sha256), format_profile, formatting, and verified=true.
A non-table replacement uses one atomic mutation batch. Tables require several guarded phases with intermediate readbacks for actual cell indices; the complete table operation is not one transaction. Each phase consumes the revision produced by the preceding phase. Recovery exports remain until semantic/format verification and cleanup finish.
On a stale revision, stop and reread. Review the new content before forming a new write. Never substitute a newer revision into an old destructive request just to force it through. The server uses requiredRevisionId, not collaborator-style targetRevisionId; Drive version is only diagnostic metadata. A Google-side conflict after the preflight can surface as a sanitized upstream/verification error rather than stale_revision; reread after any uncertain write outcome.
Multi-tab documents
For a single tab, omission of
tab_idselects that tab automatically.For multiple tabs,
docs_readwithouttab_idreturns metadata and the tab inventory only — nocontent, pagination, or selected-tab outline. Select a returned tab ID and read again.All existing-document write tools require an explicit
tab_idfor a multi-tab document. They fail closed withmultiple_tabs_require_tab_idrather than writing to the first tab. An unknown tab producestab_not_found.tab_idis a separate argument: a URL fragment does not choose the write target. Revisions guard the document, not just an isolated tab.
Persian and plain profiles
persian is the default for create and Markdown replacement. The profile contract applies these fields independently:
{
"direction": "RIGHT_TO_LEFT",
"alignment": "START",
"indentStart": {"magnitude": 0, "unit": "PT"},
"indentEnd": {"magnitude": 0, "unit": "PT"},
"weightedFontFamily": {"fontFamily": "Vazirmatn"}
}RTL alone is not proof of right alignment, zero indentation, or the font. Google Docs alignment is logical: START means physical Right for RTL and physical Left for LTR; END does the opposite. The previous END contract was incorrect and is superseded. This correction affects future requested operations only; existing documents are not migrated automatically. In Google API readback, an omitted zero magnitude can mean protobuf zero; the indent object and unit="PT" still matter.
DOCX verification resolves inherited w:bidi and w:jc together: Google exports native RTL/START as bidi/left, not bidi/right. OOXML paragraph justification is interpreted in the paragraph's bidi context (reference). The opt-in tests/test_alignment.py captures actual START/END PDF and DOCX exports for independent comparison. API/XML agreement alone does not replace native PDF visual acceptance.
The formatting contract is:
Persian headings retain their native level and receive explicit
Vazirmatnandbold=true, even without**…**. Expected heading semantics account for this profile-specific bold.Body text, text bullets/checklists, mixed-language paragraphs, and all table-cell paragraphs — including empty/padded cells — receive the Persian formatting contract. Explicit body/table bold and link spans still need to match the candidate.
plainrenders semantic Markdown styles only: heading levels and explicit**bold**/links. It does not impose Persian direction, alignment, font, or blanket heading bold. It is not a command to force LTR or strip all document formatting.Replacement resets inherited heading/bold/link state before applying the candidate, so an old heading is not allowed to leak into a new ordinary paragraph. Plain mode does not gain Persian formatting as a side effect of this reset.
docs_edit_texthas no profile parameter and does not re-render or reformat the document.
The runtime write path checks semantic readback and, for nonempty Persian Markdown replacement, independently checks the selected tab's API styles and exported DOCX formatting. API style checks use the same revision-checked body as semantic verification. An empty candidate has no new formatting to verify through the API; semantic and Persian DOCX checks still run. This exception does not exempt empty/padded cells or blank paragraphs within nonempty publication. Live acceptance also independently inspects both channels. DOCX export is document-wide, whereas the write target is one tab: unrelated content in other tabs can affect document-wide format verification. formatting=null in plain mode means the Persian format checks are skipped, not that a Persian check passed. No automatic language detection is provided.
Typed errors and recovery decisions
Service failures use this shape; MCP schema-validation failures may instead be rejected by the SDK before the service runs:
{
"ok": false,
"error": {
"code": "stale_revision",
"message": "The Google document revision changed before the write.",
"retryable": false
}
}Code | Action |
| Supply an unchanged valid ID or canonical Google Docs URL. |
| Correct the rejected field or reduce the request. |
| Fix the exact replacement or its count. |
| Reauthenticate through the existing Google OAuth flow that provisions the token used by this installation, then reload/restart the MCP connection. This package has no new login subcommand. |
| Resolve private token-storage/atomic-write permissions safely; do not print or manually paste credentials into chat. |
| Check document access and granted Google scopes; this is distinct from an expired/missing authorization. |
| Use a native Google Doc. Office import/conversion is outside this MCP. |
| Check the reference and authorized account; the document may be deleted or unavailable. |
| Read the tab inventory and select the intended tab explicitly. |
| Reread and review changes before preparing a new write. |
| Review all selected-tab matches and set an intentional |
| Split/reformulate interfering replacements; do not bypass the check. |
| Inspect |
| The outcome was not verified; a write may already have occurred. Read back and diagnose. A failed initial creation may include a retained-document handle. |
| Stop writing and inspect the retained recovery exports and current document. Do not blindly retry or roll back over collaborator changes. |
Internal text/offset helpers also define invalid_text and invalid_utf16_offset; there are no public raw-text-offset tools.
Retry is bounded for safe-to-retry network failures and HTTP 408/429/500/502/503/504: up to five attempts, with exponential waits and bounded Retry-After. Non-idempotent document creation is not automatically retried. A retryable flag is not proof that repeating a whole user workflow is safe.
Recovery exports and retention
Before the first mutation of a replacement whose candidate contains tables, the server exports the current document as TXT and DOCX into:
~/.hermes/google-docs-mcp-recovery/
recovery-<timestamp>-<random>/
document.txt
document.docxThe root and per-backup directories use mode 0700; export files use 0600. These exports contain private document content and are not encrypted by this package. Backups are not made for every targeted edit or every non-table replacement; they are not a general backup/version-history service.
On full table-write success the backup is removed. On partial failure the error includes phase, revision_id, recovery_path, and recovery_action. The supplied revision is a recovery diagnostic, not an invitation to retry. If initial creation wraps the failure, inspect its recovery details and failure_code too. Automatic rollback is deliberately absent because it could overwrite a collaborator's edit.
Startup cleanup removes recognized, safely owned recovery directories older than seven days, based on their modification time. It is not a daily daemon: if the MCP does not start, cleanup is delayed. Unrecognized or unsafe entries are skipped. Keep a private copy elsewhere if you need a longer retention period.
For manual recovery, open the exact returned directory locally, inspect document.txt and document.docx, and reconcile/restore the intended content using Google Docs/Drive after reviewing current changes. Once recovery is complete, delete only those two files in that exact directory, then remove the now-empty directory. Use your file manager or an explicitly checked, single-directory operation. Do not recursively delete ~/.hermes, sweep all recovery-* entries, or delete a live document based on a shared title prefix. Do not paste exports or live recovery handles into public reports.
Authentication and privacy boundary
The server reuses ~/.hermes/google_token.json. It does not copy that token into the repository and has no credential arguments in its public tools. Token loading requires a regular, user-owned file on POSIX; the loader enforces mode 0600, refreshes when possible, and writes refreshed credentials atomically with mode 0600.
The existing OAuth client-secret file, ~/.hermes/google_client_secret.json, must also be private. Harden file modes without displaying their contents:
chmod 600 ~/.hermes/google_token.json ~/.hermes/google_client_secret.jsonRun your Google OAuth setup/reauthentication flow if authorization is missing, invalid, expired without a usable refresh token, or rejected by Google. No package-specific authentication command is supplied here. The client-secret file belongs to that setup; the MCP runtime loads the authorized-user token.
First-time OAuth setup
If you do not already have a compatible token, provisioning one is a separate prerequisite, not something uv sync or MCP registration performs:
Follow Google's Python Docs quickstart to create your own Google Cloud project, configure its OAuth consent screen and a Desktop app OAuth client. Enable both the Google Docs API and Google Drive API. For a personal account using an External app in testing, add yourself as a test user.
Run that OAuth setup in a separate private directory and environment, not this checkout or its
.venv. Keep your downloaded client configuration at~/.hermes/google_client_secret.jsonwith mode0600; adjust the quickstart's client-file path accordingly. Never commit the download.Choose scopes for your actual workflow; the quickstart's read-only Docs scope alone cannot support this server's writes and Drive exports. For editing existing authorized documents,
https://www.googleapis.com/auth/documentsplushttps://www.googleapis.com/auth/drive.readonlycovers Docs writes and Drive metadata/exports. These are broad account grants: review Google's Drive scope guidance.drive.fileis a narrower option for app-created/explicitly app-authorized files, but pasting an arbitrary existing document URL does not authorize it for that scope; this project has no Google Picker. The opt-in live harness also needs permission to delete its own temporary file.Complete browser consent with your own account. Configure your OAuth setup to persist the authorized-user JSON at
~/.hermes/google_token.jsonwith mode0600, rather than leavingtoken.jsonin a public checkout. The runtime usesgoogle.oauth2.credentials.Credentials.from_authorized_user_file: it expects an authorized-user token (including client ID, client secret and refresh token), not the downloadedinstalledclient configuration, a service-account key, or an API key. Never print or paste these values.Restart the MCP and try
docs_readon a document you own. A successful tool listing does not test Google authorization. If you change scopes, repeat consent through your OAuth setup; editing thescopesfield in a token file does not grant permissions.
The token and recovery paths are currently fixed under Path.home() / ".hermes"; setting HERMES_HOME or selecting a Hermes profile does not relocate them. Run under the intended OS account. You can install and run the offline tests without any Google credentials.
Use an account authorized for the intended documents and Docs/Drive operations. Existing OAuth grants may be broader than the eleven-tool surface; this server constrains its operations, not the token's global privileges. Keep both files out of source control, tool examples, logs, and config literals. Do not share full environment/config dumps to diagnose startup.
The package has no telemetry and sanitizes service errors rather than returning raw Google responses or credentials. Document content is still deliberately returned by docs_read and sent to Google for writes; Hermes/session retention and storage backups are separate from this package's recovery cleanup policy.
Install: Python 3.12, locked, physically installed, non-editable
Prerequisites: uv, Python 3.12 (or permission for uv to install it), the existing Google OAuth setup, and Hermes with MCP support. The project requires Python >=3.12,<3.13; use its isolated environment, not the Hermes application's Python environment. The project pins mcp[cli]==1.29.1 and locks dependencies in uv.lock.
Clone the repository, then install from the checkout (choose another location if preferred):
mkdir -p ~/Documents
git clone https://github.com/mohamadsmt/google-docs-mcp.git ~/Documents/google-docs-mcp
cd ~/Documents/google-docs-mcp
uv python install 3.12
env -u PYTHONPATH -u PYTHONHOME uv sync \
--python 3.12 --locked --all-groups --no-editable \
--reinstall-package google-docs-mcp --link-mode copy--no-editable is required: .venv must contain the full physical package, not a source-tree pointer. --reinstall-package google-docs-mcp refreshes the local project even when its version has not changed; --link-mode copy avoids relying on cache-linked package files. Do not replace this procedure with editable installation or uv run pytest, which can re-sync the project into an editable environment.
The canonical launcher is scripts/run-mcp. It locates the checkout-relative .venv/bin/google-docs-mcp, unsets PYTHONPATH and PYTHONHOME for that process, and executes the installed console entrypoint. This prevents Hermes-host Python packages from shadowing the project's dependencies; it does not modify Hermes's environment/config. Launch through this wrapper, not a source import or the Hermes interpreter.
Check installation freshness without loading credentials
From the checkout, compare the physical installed Python files with source:
env -u PYTHONPATH -u PYTHONHOME .venv/bin/python - <<'PY'
import importlib.metadata
import json
from pathlib import Path
import sysconfig
source = Path("src/google_docs_mcp").resolve()
installed = Path(sysconfig.get_path("purelib")) / "google_docs_mcp"
assert installed.is_dir() and not installed.is_symlink()
source_files = {p.relative_to(source) for p in source.rglob("*.py")}
installed_files = {p.relative_to(installed) for p in installed.rglob("*.py")}
assert source_files and installed_files == source_files
for relative in source_files:
target = installed / relative
assert target.is_file() and not target.is_symlink()
assert target.read_bytes() == (source / relative).read_bytes(), relative
metadata = importlib.metadata.distribution("google-docs-mcp")
direct_url = json.loads(metadata.read_text("direct_url.json") or "{}")
assert direct_url.get("dir_info", {}).get("editable", False) is False
print("physical installed source matches checkout; non-editable metadata")
PYThis is a source/install consistency check, not a substitute for wheel inspection, real stdio tests, or final acceptance. Its printed line is the command's success message, not a pre-recorded result.
If source bytes are proven stale after a forced reinstall, retry the same locked physical installation with an isolated fresh cache rather than clearing unrelated global caches:
fresh_cache="$(mktemp -d "${TMPDIR:-/tmp}/google-docs-mcp-uv-cache.XXXXXX")"
env -u PYTHONPATH -u PYTHONHOME UV_CACHE_DIR="$fresh_cache" uv sync \
--python 3.12 --locked --all-groups --no-editable \
--reinstall-package google-docs-mcp --link-mode copyRepeat the consistency check and offline tests. Keep that cache path private and remove only that exact temporary directory once no process needs it; a global cache purge or broad recursive deletion is not required.
Tests and updates
Offline tests
Use the installed project interpreter directly. Explicitly remove the live-test opt-in from the environment:
cd ~/Documents/google-docs-mcp
(
unset RUN_GOOGLE_DOCS_MCP_LIVE
env -u PYTHONPATH -u PYTHONHOME .venv/bin/python -m pytest -v
)For the real-protocol offline checks specifically:
env -u PYTHONPATH -u PYTHONHOME .venv/bin/python -m pytest \
tests/test_mcp_stdio.py tests/test_entrypoint.py -vThese commands are instructions to run, not claims about accepted test counts. Discovery/protocol checks alone do not prove that a Google write succeeded.
Build and update
After reviewing and applying a source update to this checkout, reinstall it physically before testing or restarting the MCP. Dependency changes must be reviewed together with the lockfile; do not silently regenerate the lock during deployment.
cd ~/Documents/google-docs-mcp
env -u PYTHONPATH -u PYTHONHOME uv sync \
--python 3.12 --locked --all-groups --no-editable \
--reinstall-package google-docs-mcp --link-mode copy
(
unset RUN_GOOGLE_DOCS_MCP_LIVE
env -u PYTHONPATH -u PYTHONHOME .venv/bin/python -m pytest -v
)
build_output="$(mktemp -d "${TMPDIR:-/tmp}/google-docs-mcp-build.XXXXXX")"
env -u PYTHONPATH -u PYTHONHOME uv build --out-dir "$build_output"Repeat the source/install consistency check. Fresh wheel/sdist output still needs archive and isolated-install verification for release acceptance. Reload the running MCP connection after a successful update; replacing files does not update code already imported by the old process.
Opt-in live acceptance — creates and deletes a document
Only run this when you authorize real Google API writes and the final source has been installed non-editably:
cd ~/Documents/google-docs-mcp
env -u PYTHONPATH -u PYTHONHOME RUN_GOOGLE_DOCS_MCP_LIVE=1 \
.venv/bin/python -m pytest tests/test_live_google.py::test_live_google_insertions_through_mcp -vThe live test uses real MCP ClientSession.call_tool calls through scripts/run-mcp. It creates a tiny private temporary document with synthetic Persian/English/emoji content, reads it, proves preview is non-mutating, applies an edit with independent readback, then injects an external sentinel edit. A stale replacement must fail without erasing that sentinel. It then replaces with a fresh revision, checks semantics and Persian API/DOCX formatting, deletes only the run's temporary document, and checks 404. Privacy is independently checked through bounded Drive permission readback in the harness; permission management is not exposed by the MCP.
The insertion acceptance above additionally exercises all four insertion positions on that same run-owned document, including a table-cell anchor and mixed Persian/English/emoji content. Each position proves non-mutating preview, independent exact text/revision readback, and stale-revision rejection; missing and ambiguous anchors must also leave the document unchanged. Persian API/DOCX checks after insertion require direction, right alignment, right indentation, and Vazirmatn; they do not impose Markdown's additional bold-heading rule on literal inserted text. Create/replace checks retain that stricter publication rule. The separate test_live_google_docs_end_to_end_through_mcp retains the original create/edit/replace regression; running the entire module creates and deletes one temporary document per test.
The harness attempts cleanup on failures and interrupts, but successful cleanup is an assertion to verify, not a promise that survives every network failure or forced process termination. It keeps a private per-run journal when the created document cannot be resolved safely. Resolve only that exact run's artifact/document; do not prefix-sweep other documents or recovery backups. Keep live IDs, URLs, credentials, and private content out of published test reports. A skipped test is not live acceptance, and old evidence does not validate changed code.
Register in Hermes
Hermes must use an absolute launcher path because its working directory and shell expansion cannot be assumed. Run the registration command from your checkout root; $(pwd) supplies your actual path. The YAML below uses an explicit placeholder that you must replace with that same absolute path.
First inspect existing server names:
hermes mcp listIf google_docs is absent, use the official CLI and confirm enabling it:
printf 'Y\n' | hermes mcp add google_docs \
--command "$(pwd)/scripts/run-mcp" \
--connect-timeout 30If it already exists, inspect that entry and update its existing launcher/policy rather than creating a duplicate. Set explicit policy fields:
hermes config set --force mcp_servers.google_docs.timeout 180
hermes config set --force mcp_servers.google_docs.connect_timeout 30
hermes config set --force mcp_servers.google_docs.sampling.enabled false
hermes config set --force mcp_servers.google_docs.tools.include '["docs_read","docs_create","docs_replace_markdown","docs_edit_text","docs_insert_text","docs_edit_section","docs_format","docs_manage_tab","docs_edit_table","docs_export","docs_insert_image"]'The resulting ~/.hermes/config.yaml entry must match this policy; merge only this server entry, not the whole config:
mcp_servers:
google_docs:
command: "/absolute/path/to/google-docs-mcp/scripts/run-mcp"
args: []
timeout: 180
connect_timeout: 30
sampling:
enabled: false
tools:
include:
- docs_read
- docs_create
- docs_replace_markdown
- docs_edit_text
- docs_insert_text
- docs_edit_section
- docs_format
- docs_manage_tab
- docs_edit_table
- docs_export
- docs_insert_imageNo credential literals or secret environment values belong in this entry. Read back the exact target entry locally after registration: confirm the launcher, empty args, timeout 180, connect timeout 30, disabled sampling, and exactly the eleven allowlisted names. Check an existing entry for exclusions or other policy that could prevent those tools from being exposed. Share only safe policy fields and environment key names, never values or a full config dump.
hermes mcp list
hermes mcp test google_docsRequire the server to be enabled, connection/discovery to succeed, and exactly the eleven expected tools to be discovered. hermes mcp test proves discovery, not Google write correctness. The opt-in insertion acceptance checks the original five paths; tests/test_live_structured.py covers the four structured tools. tests/test_live_exports.py characterizes native PDF/DOCX root/child-tab coverage; tests/test_live_media.py exercises link/image readback, image preview/apply/stale/error/lost-response recovery and exports through the installed MCP. Run these only with explicit private synthetic creation/deletion authorization. Offline tests alone are not proof of a live write.
After registration or an update, use /reload-mcp in the running Hermes chat, or start a fresh Hermes chat/process. Then verify the tool inventory. An existing long-lived session is not automatically proven to have new schemas merely because config was saved or a separate CLI test passed. Hermes normally prefixes server tools as mcp_google_docs_<tool_name>; use the actual discovered inventory in your client.
Hermes references: MCP integration and reload, CLI reference. The commands above are operating instructions, not a substitute for fresh acceptance evidence after an update.
Contributing and publication hygiene
Open an issue with a synthetic reproduction, or submit a focused pull request with offline test evidence. Do not attach real document content, IDs/URLs, recovery exports, OAuth files, environment dumps, or screenshots containing private data. See SECURITY.md for sensitive reports.
.gitignore excludes common credentials, local agent state, exports, recovery folders, build output and internal planning notes. It does not remove already tracked files or Git history, and it cannot recognize every secret filename. Before pushing, inspect git diff --cached and run a secret scanner such as gitleaks git . --log-opts="--all" --redact. Scan new/untracked candidate files too. Never force-add ignored credentials or artifacts. Source distributions use an explicit include list; inspect both wheel and sdist before distributing them. Examples and test fixtures must remain synthetic.
License
MIT — Copyright (c) 2026 Mohamad Takalloo. Third-party dependencies retain their own licenses.
Available Tools
4 toolsdocs_createCDestructive
Create a private native Google document and verify its initial content.
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | ||
| markdown | No | ||
| format_profile | No | persian |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds the useful detail that the tool verifies initial content, and annotations already indicate this is a mutating, non-idempotent call. However, it never explains the destructiveHint=true annotation, which is significant and potentially surprising for a create operation; an agent is left uncertain about what side effects beyond creation might occur.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single front-loaded sentence with no filler. It states the core action first and packs the important 'private native' and 'verify initial content' details efficiently.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given that this is a 3-parameter tool with 0% schema coverage and no usage guidance, the description leaves too much unresolved for an agent to invoke it correctly. The output schema may cover return values, but parameter semantics, routing guidance, and side-effect transparency are still missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, but it does not mention the title, markdown, or format_profile parameters. The phrase 'initial content' hints at markdown but leaves all parameter semantics, defaults, and the format_profile enum unexplained.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the action ('Create') and the resource ('private native Google document'), and it adds a post-condition ('verify its initial content'). The verb alone distinguishes it from the sibling tools docs_read, docs_replace_markdown, and docs_edit_text, though it doesn't explicitly name those alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance on when to use this tool versus the alternatives, no prerequisites, and no exclusions. The only implicit signal is the verb 'create,' which is not enough for an agent to make a confident routing decision.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
docs_edit_textADestructive
Preview or apply exact, revision-guarded replacements in one tab.
| Name | Required | Description | Default |
|---|---|---|---|
| apply | No | ||
| tab_id | No | ||
| document | Yes | ||
| replacements | Yes | ||
| expected_revision_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide destructiveHint=true, readOnlyHint=false, and idempotentHint=false. The description adds meaningful behavior beyond those flags: the operation can be previewed or applied, matching is exact, and it is revision-guarded. It does not disclose partial-application or expected_count mismatch behavior, which prevents a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single front-loaded sentence with no filler; every phrase adds information about behavior, exactness, or scope. 'In one tab' is slightly terse, but the overall structure is appropriately compact.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive mutation tool with an output schema and annotations, the main action is named and the revision-guard concept is present. However, the description does not clarify how expected_count is used, what 'one tab' means in relation to tab_id, or the exact contract of expected_revision_id, so it is adequate but not complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the prose must carry parameter semantics, but it only gestures at apply ('preview or apply') and expected_revision_id ('revision-guarded'). It leaves expected_count, tab_id, document, and the replacement structure to inference, which is not enough compensation for an undocumented schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific operation ('preview or apply exact, revision-guarded replacements') on a clearly identified resource (text in a document/tab). The qualifier 'exact, revision-guarded' distinguishes it from the markdown-style sibling without being a tautology.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The wording implies this is for exact, revision-checked text replacements and supports a preview-before-apply workflow, but it does not explicitly say when to choose this over docs_replace_markdown or docs_create. Exclusion conditions and alternative tools are not named in the description, so usage guidance is only implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
docs_readARead-onlyIdempotent
Read bounded content, tab metadata, and the current Docs revision.
| Name | Required | Description | Default |
|---|---|---|---|
| start | No | ||
| tab_id | No | ||
| document | Yes | ||
| max_chars | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint, and non-destructive behavior. The description adds useful context that reads are bounded and include tab metadata and revision info, going beyond the safety profile without contradicting it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, tight sentence that front-loads the action and resource. There is no filler, and every phrase adds meaning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The presence of an output schema and safety annotations covers return values and side effects, but the description leaves the semantics of start and tab selection underspecified. An agent can call the tool but may not know how to control the read window or target a specific tab without additional inference.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry the explanatory burden. It only hints at 'bounded content' (max_chars) and 'tab metadata' (tab_id), leaving start, document, and default behavior unexplained. This is insufficient for a 4-parameter tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb (Read) and concrete resources (bounded content, tab metadata, current Docs revision), which clearly distinguishes it from the write-oriented siblings docs_create, docs_replace_markdown, and docs_edit_text.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The use case is implied by the verb 'Read' and the contrast with sibling write tools, but the description does not explicitly state when to prefer this tool or when not to use alternatives. There are no exclusions or named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
docs_replace_markdownBDestructive
Replace one document tab from Markdown under an exact revision guard.
| Name | Required | Description | Default |
|---|---|---|---|
| tab_id | No | ||
| document | Yes | ||
| markdown | Yes | ||
| format_profile | No | persian | |
| expected_revision_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate this is destructive and non-idempotent, so the description does not need to restate mutation. It adds useful context about the revision guard and scoping to a single tab, but it does not disclose behavior on a stale revision mismatch or the full extent of what is overwritten.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single compact sentence with no filler, front-loading the verb and object. It loses a point for the slightly awkward 'from Markdown' phrasing and for omitting a bit of useful structural guidance, but it is otherwise efficiently written.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive write operation with five parameters, zero schema description coverage, and an exact-revision guard, one sentence is not enough. Missing context includes format_profile semantics, tab selection when tab_id is null, revision-mismatch behavior, and how this differs from docs_edit_text.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for the five parameters, and it only loosely clarifies the roles of markdown and expected_revision_id. It does not explain tab_id null behavior, format_profile choices, or how the replacement target is selected when tab_id is absent.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action ('Replace'), a clear object ('one document tab'), and the input format ('from Markdown'), which makes the core operation understandable. It is reasonably distinct from siblings like docs_read and docs_create, though it does not explicitly differentiate from docs_edit_text.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage context is only implied by the phrase 'under an exact revision guard,' suggesting a controlled overwrite. There is no explicit statement about when to prefer this over docs_edit_text or what conditions disqualify its use, so guidance remains implicit rather than direct.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
docs_create - First observed
docs_edit_text - First observed
docs_read - First observed
docs_replace_markdown
TDQS
Scored across 4 tools
Read and create are clearly distinct, while replace_markdown and edit_text both modify a document and could be confused. However, their descriptions draw a clear line between whole-tab Markdown replacement and precise revision-guarded text edits, so misselection is unlikely.
All tools use lowercase snake_case with a docs_ prefix and action-oriented verbs. There is a minor inconsistency because read and create omit explicit objects while replace_markdown and edit_text include them, but the pattern remains predictable.
Four tools is a tight, purposeful set for reading, creating, and editing Google Docs content. Each tool covers a distinct core operation and none feel redundant or unnecessary for the server's focused scope.
The set covers the core create/read/update workflows and revision-guarded edits. It lacks document deletion, listing, or full-content export, but agents can work around these gaps for most document editing tasks.
Maintenance
Related MCP Connectors
Google Docs MCP Pack — read, create, and edit Google Docs via OAuth.
Generate PDF, Word (.docx) and PowerPoint (.pptx) documents from Markdown over MCP.
Publish and share access-controlled Markdown documents from any MCP-enabled AI tool.
Notes, files, GitHub, and Drive through one MCP connection.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server for Google Docs — create, read, edit, format, and manage documents through 26 tools via the Model Context Protocol.10 npm1MIT
- AlicenseNot gradedqualityCmaintenanceA minimal MCP server for creating and updating Google Docs from markdown.4 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables reading and searching Google Drive files, Google Docs, and Google Sheets via a CLI or MCP server, with support for section-based content extraction and Markdown import.1MIT
- AlicenseBqualityAmaintenanceEnables interaction with Google Docs through typed MCP tools, supporting reading, writing, searching, and managing documents via OAuth integration.22MIT