gdocs-mcp
Summary: An MCP server that gives an AI agent file-like control over Google Docs and Drive, covering content, formatting, tables/images, existing suggestions, comments, tabs, sharing, and account/project defaults.
Manage multiple authorized Google accounts and set per-project default account/folder via
.gdocs-mcp.json.Read Google Docs as markdown plus inline HTML, with clean/tracked/accepted/rejected modes and body/header/footer/all segments.
Edit documents by unique text snippet, insert new markdown content, create new docs, or overwrite docs with guard checks.
Apply text/paragraph styling in place or to a whole document, and read computed style at a text anchor.
Read and set page setup: margins, page size, orientation.
Insert images and tables, structurally edit tables, style tables, and read table styles.
Download embedded doc images, and export docs to PDF, DOCX, ODT, RTF, TXT, HTML, EPUB, or MD.
List existing suggestions as before→after diffs and accept/reject them, including atomic cluster handling and verification.
List comments, add comments/replies, and resolve/reopen comment threads with quote verification.
Use Drive like a filesystem:
ls,find,mkdir,cp,mv, including shared drives, shared-with-me, and lost+found paths.List, share, and unshare doc permissions with role/title verification and ownership protections.
Work with document tabs: list, add, rename, delete, nest, and target specific tabs across doc tools.
Target headers and footers across content, comments, suggestions, tables, images, and styling tools.
Built-in safety checks include ambiguous-match refusal, expected title/change/role verification, overwrite loss guards, and shared-drive ownership-transfer confirmation.
Allows treating Google Docs like local files, with capabilities for reading, editing, managing suggestions and comments, handling tabs, and sharing documents.
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., "@gdocs-mcp show suggestions on the project proposal"
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.
@dasasian/gdocs-mcp
An MCP server that lets an AI agent (like Claude Code) treat a Google Doc like a local file — read it, edit it by content, review and resolve suggestions, manage comments, and work across tabs and multiple Google accounts.
Status: beta. The tool surface is complete and every change is verified against the live API, not just unit tests.
Why this exists
Most Google Docs MCP servers treat a doc as flat text. This one fills the gap nobody else does:
Suggestions you can read and act on.
list_suggestionsshows each pending tracked change with Docs' own summary, the change in its sentence (Pay within [30→45] days), its author and time;apply_suggestionsaccepts or rejects each one on its own, as the check and the cross do in Docs.read_doccan render them inline as<ins>/<del>.File-like editing.
edit_docmatches a unique snippet of text (like a localEdit) and rewrites it — indices are never exposed.Comments on their text. Read threads (author, time, the text each is on, replies, resolved status), reply, resolve, and add a comment on the text you quote, which stays on that text as it moves.
Tabs as sub-files. A doc is a folder of tabs:
drive ls <doc>lists them,drive mvrenames, reorders and nests them.Multiple accounts. Authorize several Google accounts; pick a default per project.
Related MCP server: LLM2Docs (Unofficial)
What you can ask for
You don't call tools. You ask in plain language and the agent drives them.
Fill a template without rebuilding it
"Copy the Proposal Template into /Clients/Acme, name it 'Acme Q3 Proposal', then fill in the client name, the dates, and the pricing table."
The copy keeps the letterhead, the logo at its exact size, the fonts and the page setup — everything a markdown round-trip cannot rebuild. Then only the words that change are edited. The alternative is retyping a document you already have.
A whole review pass in one instruction
"Show me the pending suggestions as a diff, accept the typo fixes, reject anything that changes meaning, then reply to the open comments and resolve the ones that are settled."
Four tools, one sentence, and the judgement in the middle is the part you wanted help with. Most Docs integrations cannot see tracked changes at all, let alone act on them safely.
More: publishing a folder of markdown as a Drive tree, auditing many docs for style drift, pulling a doc back into your repo with its images → docs/recipes.md.
Install
npm install -g @dasasian/gdocs-mcpOr from source:
git clone https://github.com/dasasian/gdocs-mcp && cd gdocs-mcp
npm install && npm run build
npm link # makes `gdocs-mcp` available globally, same as the published packageThen follow the setup below exactly as an installed user would. (npm link symlinks the
gdocs-mcp binary to your build; see CONTRIBUTING.md for the dev workflow.)
Setup
You need a Google Cloud OAuth client (one-time). Each user creates their own — this keeps your credentials yours and avoids Google app-verification for the restricted Drive scope.
Create a project + enable APIs (or use the setup guide):
gcloud projects create my-gdocs-mcp gcloud config set project my-gdocs-mcp gcloud services enable docs.googleapis.com drive.googleapis.comOAuth consent screen (Cloud Console → APIs & Services → OAuth consent screen): User type External, add yourself as a Test user. For long-lived tokens, set publishing status to In production (avoids the 7-day refresh-token expiry).
OAuth client → Create credentials → OAuth client ID → Desktop app. Download the JSON and save it as
~/.config/gdocs-mcp/client_secret.json.Authorize an account (opens a browser):
gdocs-mcp add-accountRepeat for each Google account you want to use.
Configure your MCP client
In a project's .mcp.json (Claude Code) or equivalent:
{
"mcpServers": {
"gdocs": {
"command": "gdocs-mcp",
"env": { "GDOCS_DEFAULT_ACCOUNT": "you@example.com" }
}
}
}GDOCS_DEFAULT_ACCOUNT sets which authorized account this project uses by default — so a work project and a personal project can point at different accounts without re-authorizing. Any tool call can override it with an account argument.
Prefer it available in every project? Register once at user scope: claude mcp add gdocs -s user -e GDOCS_DEFAULT_ACCOUNT=you@example.com -- gdocs-mcp. Then a project can pin its own defaults with a .gdocs-mcp.json — the account, and a default folder for new docs:
{ "account": "work@company.com", "folder": "https://drive.google.com/drive/folders/…" }write_doc creates only from a path (/Reports/Q3 Report). Given a bare name (Q3 Report) it creates nothing: it refuses, tells the agent to tell you the doc will go in the default folder, and hands back the full path to call. A new doc at the top of My Drive (/Q3 Report) is refused once the same way, and the identical call repeated goes through if you want it there. The default folder's path is also in write_doc's description, read when the server starts. With no default it says to name a folder (~/Q3 Report). See docs/setup.md for the full resolution order.
You don't have to edit that file by hand — just tell the agent "make damithsc@gmail.com my default account for this project" or "make my Manuscripts folder the default here," and it writes the .gdocs-mcp.json for you via set_project_default (searching for the folder by name if needed).
Tools
The table below is the reference. For what to actually ask for, see What you can ask for above, or the full set in docs/recipes.md.
Tool | Description |
| Read as markdown + inline HTML. Opens with a |
| Replace a unique text snippet (string-anchored, markup-tolerant; a table matches as its rows, a pipe table or the HTML |
| Read / set document-level page setup: margins, page size (preset or explicit), orientation (File > Page setup) |
| Write like the local |
| Export a doc to a local file — pdf (default), docx, odt, rtf, txt, html, epub, md (rendered server-side by Google). A doc path exports every tab; a tab path ( |
| Pending suggestions in document order: |
| Accept or reject one or more suggestions, each on its own (Docs' |
| Download a doc’s embedded images to a local folder (pairs with |
| Comments, written and read through the Docs API so each sits on a range. |
| Drive as a filesystem: |
| Sharing ( |
| Authorized Google accounts |
| Set/show this project’s default account + folder (writes |
Every doc tool takes one path for the doc — an id, a URL, /Work/Contract or /Work/Contract/Ch.4 — and an optional account (override the default). A doc with one tab needs no tab step; a doc with several is refused until one is named, and the refusal lists every tab's path. Two tabs (or a folder and a doc) with one name are refused with each candidate's id.
Drive as a filesystem
Drive navigation is one tool speaking shell, because the model already knows shell. Arguments are positional and differ per command, exactly as they do in a terminal.
{ "cmd": "ls", "args": ["/Work/2026"] }
{ "cmd": "find", "args": ["quarterly report", "-type", "d"] }
{ "cmd": "mkdir", "args": ["-p", "/Work/2027/Q1"] }
{ "cmd": "cp", "args": ["/Work/Template", "/Work/2027/Q1/Report"] }
{ "cmd": "mv", "args": ["/Work/Roof", "/Archive"], "expectName": "Roof" }path | is |
| My Drive |
| a shared drive |
| files others shared with you, which you never filed |
| files you own that are in no folder — see below |
anything else | a Drive id or URL, so ids from any other tool paste straight in |
Flags and operands parse in any order (cp -r a b, cp a b -r, cp a -r b), and -- ends the options.
Three places Drive is not a filesystem. The vocabulary is borrowed only where it is honest, and refuses where it is not:
Two files may share a name in one folder, and matching folds case. No filesystem the model learned from does either, so it would not think to check. A path matching more than one thing is refused with the candidates listed, never guessed — and
cp/mvrefuse to create that state too, rather than manufacturing an ambiguity the resolver would then decline to resolve.cp -rdoes not exist. Drive'sfiles.copyrefuses folders (its own web UI cannot copy one either), socpon a folder explains that rather than half-working.mvinto/shared/…gives the file away. Shellmvacross filesystems leaves you owning the file; moving into a shared drive transfers ownership to that organization and cannot be undone from your side. It requiresacceptOwnershipTransfer: true.
Paths see less than find does. A file with no parent still opens and still turns up in a search, but no path can name it — nothing that browses the tree will ever show it. find is the complete view; /lost+found is where those files surface.
There is no rm. See #47.
Known limitations
These are Google-API constraints, not bugs — the highlights are below; the complete reference (with the API reason and the workaround for each) is in docs/limitations.md.
Some changes cannot be suggested.
edit_docandwrite_doctakesuggest: trueto send an edit as suggestions, but Docs cannot suggest a new tab or doc, a table column width, deleting a header or footer, or a named range; an edit that needs one is refused before anything is sent, and is never split into a suggested part and a direct part. A header or footer that does not exist yet cannot be created as a suggestion either.A comment made in Drive has no range, only its quote, and the author is a display name, never an email. Docs has no comment on the whole doc, so
add_commentneeds aquotedText.Images are inline only (no floating/x,y positioning), and Google downscales/re-encodes embedded images, so pulled copies aren't byte-identical.
Headers and footers are separate content trees — not part of the body. Every content tool (
read_doc,edit_doc,write_doc,list_suggestions,apply_suggestions) takessegment: "header" | "footer"to reach them, andpagewhen a doc defines more than one.Style is CSS inside the markdown —
read_docopens with a<style>block of the named styles and showsstyle="…"where a paragraph or run differs from its rule;edit_docchanges it (same words with new markup is a style-only edit) and the write tools accept the same CSS. It is a small set (text-align,line-height,margin-*,text-indent,font-*,text-decoration,color, in pt); anything else is refused before a request is sent. A table with nothing but Docs defaults reads as a pipe table; one with any cell or row style, set column widths or row heights, pinned header rows or merged cells reads as an HTML<table>with the same CSS (border,padding,background,vertical-align,widthon<col>,heighton<tr>,colspan,rowspan) andedit_docchanges it, a style-only edit sending only style requests. Lines follow CommonMark: a single newline inside a paragraph, list item or quote is a space, so a hard-wrapped file keeps its structure; a fenced or four-space-indented code block is one Courier New paragraph with its line breaks and spaces, and reads back as a fence; a>quote is a paragraph indented 36pt with a gray left border, a bare>line separating its paragraphs (>>, and a list or code block inside a quote, are refused).
See docs/limitations.md for the full table, including how each is mitigated or surfaced.
Roadmap
The "manuscript sync" use case (chapter files ⇄ tabs, reviewing suggestions, merging) is intentionally not a server feature — an AI agent orchestrates it over these primitives. See DESIGN.md §10b.
Development
npm install
npm run build # tsc
npm test # vitest
npm run typecheck
npm run eval:agent # a real agent does each task in eval/tasks/; graded from the doc
npm run eval:agent -- --full # Sonnet and Haiku, 3 runs each, before a merge
npm run eval:agent -- --tag tables
npm run eval:agent -- --check-graders # every grader must fail on an untouched doceval:agent is not part of npm test: it uses real model calls and a real Google
account. Each run creates its docs through the API, runs claude -p from a temp
folder with only the gdocs tools and no skills, reads the result through the API,
and trashes every doc it made, even after a failure. A run that fails
with "Quota exceeded" in its log is retried once on its own after 60 s and reported as a
quota hit, not a plain failure; a run that passed despite a quota error is a pass.
Each claude -p is killed, process group included, after 10 minutes and reported as a
failure ("timed out after 10 min"); GDOCS_EVAL_RUN_TIMEOUT_MS overrides that, for
testing only. Every Google call the harness itself makes (setup, grading, trashing) waits
60 s and retries up to 3 times on a quota error; a run whose harness call still fails is
reported as "could not run", apart from passes and failures, and still exits non-zero.
--full runs 3 at a time (5 by default) to keep that rare.
See DESIGN.md for the full architecture and the empirical findings behind it.
Acknowledgements
Architecture and approach informed by prior open-source Google Docs MCP servers — notably @a-bonus/google-docs-mcp and taylorwilsdon/google_workspace_mcp.
License
MIT © Dasasian
Available Tools
19 toolsadd_commentAdd a comment or replyA
Comment on text in a Google Doc: quotedText is the text the comment sits on, found the way edit_doc finds old_string (markup-tolerant; text that matches more than once is refused with the context of each match, so quote more of it). The comment stays on that text as it moves. Docs has no comment on the whole doc, so quotedText is required; put overall feedback on the title, or on its first words. The tab comes from path, and segment: "header" or "footer" comments in the page header or footer. To reply to an existing comment instead, pass replyTo (a comment id from list_comments) and no quotedText; the reply goes to the doc named by path, whatever its tab.
Inputs: path (required), content (required), page, account, +3 more.
apply_suggestionsAccept/reject one or more suggestionsA
Accept or reject pending suggestions (from list_suggestions) in one update. Each one is resolved on its own, exactly as clicking the check or the cross in Docs: accept keeps the proposed change, reject restores the original. Two suggestions that touch each other (by different people) are decided one at a time, and the text comes out as Docs makes it; to decide them together, pass both. A style-only suggestion resolves like any other. expectTitle is checked against the live document first (status "wrong_doc" on a mismatch, e.g. an id from a different, similarly-titled document), and each expectContext is checked against the live suggestion (status "error" on a mismatch or an id that is gone); nothing is changed unless all match. Pass the title and each suggestion’s context from list_suggestions, verbatim.
Inputs: path (required), expectTitle (required), resolutions (required), page, +2 more.
download_imagesDownload a doc’s imagesA
Download every embedded image in a Google Doc to a local folder. Returns the objectId→filename mapping, which correlates with read_doc’s <img src="image:<objectId>"> markers so you can rewrite them to local paths (the inverse of publishing).
Inputs: dir (required), path (required), account.
driveDrive as a filesystemA
Navigate and reorganise Google Drive with shell commands: ls, find, mkdir, cp, mv. Arguments are positional and follow the usual shell forms. ls [path] — list a folder (default My Drive root). find [-type d|f] — search everything by name, including files no path can reach. mkdir [-p] — create a folder. cp — duplicate a file (preserves headers/footers, image sizing and exact formatting, which a markdown round-trip cannot rebuild). mv — move and/or rename, as on a filesystem: an existing folder as means "into it", anything else means "to that name". Paths start with / or ~ (My Drive); /shared/ is a shared drive, /shared-with-me the files others shared with you, and /lost+found the files you own that are in no folder at all. Anything not starting with / or ~ is read as a Drive id or URL, so ids from any other tool can be pasted straight in. Drive permits two files with the same name in one folder and folds case when matching, unlike any real filesystem — a path that matches more than one thing is refused with the candidates listed, never guessed. A doc is a folder of tabs: ls lists its tabs, ls shows each doc with its tab count, and a path continues into tabs (/Work/Contract/Part 2/Ch.4; a tab step may also be a tabId). mv on a tab renames it, nests it (dst is another tab, or a new name under one) or un-nests it (dst is the doc), and index reorders it; a tab cannot leave its doc, and cp of a tab is refused. Content is edited with edit_doc and written with write_doc, not here; there is no rm, and nothing here deletes a tab. A tab is made by write_doc to a new path.
Inputs: cmd (required), args, index, account, +2 more.
edit_docEdit a Google DocA
Replace an exact unique snippet of text in a Google Doc (like a local file Edit). old_string is matched markup-tolerantly; ambiguous matches return surrounding context to disambiguate; a table is matched as its rows, in the form read_doc shows it (a pipe table, or the HTML of a styled one). Elements read_doc shows are kept when old_string and new_string both hold them as read (a person chip inside a sentence you reword survives), deleted when new_string leaves them out, and created when new_string adds one: [^label] with a [^label]: note line, Nov 15, 2026 (the text’s shape picks the date format), for a person or for a Drive file, with its line, and --- for a rule. A column break, equation, page-number field, or link chip to anything but a Drive file cannot be made: it is refused before anything is sent, and a chip’s text is Google’s (a chip given other words is refused). A footnote’s note is edited by quoting its [^1]: line, and a dropdown’s list by quoting its line. INSERTING IS AN EDIT: quote the text to add after (or before), write it back in new_string, and add the new content to it. Text from old_string that comes back unchanged is kept as it is, with its style, comments and suggestions, so old_string | Total | $4,200 | and new_string | Total | $4,200 |\n\nPayment due within 30 days. leaves the table alone and adds the paragraph after it; a match that ends in a table puts the new blocks after the whole table. An anchor in the middle of a paragraph splits the paragraph there. A new block carries its own style, not its anchor’s: a paragraph added after a heading, a Title or a list item is Normal text with no bullet unless the markdown says otherwise, while text added inside an existing paragraph keeps that paragraph’s style. An empty doc, tab, header or footer has nothing to anchor on: use write_doc. new_string is the same markdown and inline HTML write_doc takes: headings, lists, tables, images (<img src width height> or ; a centered one is <p style="text-align:center"><img …></p>, local files resolve against baseDir), page breaks, and inline constructs (bold, italic, code, text, <u>, <span style="color:…;font-size:…pt">, <p style="…">, <p class="title">, and a page break as a line of its own: <div style="page-break-after:always"></div>, so new_string Title page\n<div style="page-break-after:always"></div> puts what follows on a new page; old_string can span a break) — the same spelling read_doc emits, so a read can be edited and written back. STYLE IS CSS, and this is the only way to change it. Restyle text you are not otherwise changing by giving the same words with different markup: old_string <p>4. Term, new_string <p style="margin-left:36pt; text-indent:-18pt">4. Term sends only style requests — no delete, no insert, so a long paragraph is never retyped and its words cannot change; markup missing from new_string is cleared (drop the wrapper to remove an indent). To restyle a whole named style, edit its rule in read_doc’s block: old_string p { font-family: Arial; font-size: 11pt, new_string p { font-family: Arial; font-size: 12pt becomes one updateNamedStyle and every paragraph that does not override it follows. An unsupported property (border, float, a px length, a typo) fails the edit before any request is sent, listing every offending line and the supported set. TABLE STYLE is CSS on the table’s HTML, changed the same way: read_doc shows a table with any cell or row style, wide or short columns, pinned rows or merged cells as with , (a minimum), (exactly the pinned rows), (border, padding, background, vertical-align) and colspan or rowspan, one row per line, while a table with nothing but Docs defaults stays a pipe table. old_string <td>Client</td>, new_string <td style="background:#eeeeee">Client</td> sends only the style request, and every cell whose words come back unchanged is kept, not retyped; styling a pipe table is giving the whole table back as HTML with the same words. A table edit that changes rows, columns or merges is a comparison: quote the rows around the change, write the rows you want, and only the difference is sent, an insertTableRow, deleteTableRow, insertTableColumn, deleteTableColumn, mergeTableCells or unmergeTableCells plus the text of the cells that changed, so cells you did not change keep their comments and style. A new row or column copies the style of the row above (the row below when the row above is a header row) or the column to its left (to its right for a new first column), and a style you write on it wins; links are not copied. A row or column you move is a delete and an insert: when comments quote its text the first call is refused with a confirmLoss string, so ask the user and pass it back as confirmLoss after their yes. A change that cannot be read cleanly (a row that could be an edit of either of two new rows, a table that shares no row with the old one, a column added without its line) is refused and says what to quote; a table is never rewritten whole. SUGGEST: pass suggest: true to send the edit as suggestions, for a person to accept or reject in Docs, when the user wants to review the change or the doc is not theirs; the result lists in suggestions the ids of the suggestions that now hold the edited text (a change next to your own pending suggestion joins it, and then its id is the one listed). It defaults to false, and then the edit is applied as live text: if the doc has pending suggestions from other reviewers, tell the user your edit sits alongside them as an accepted change. A change Docs cannot suggest (a table column width, for one) refuses the whole edit before anything is sent.
Inputs: path (required), old_string (required), page, account, +7 more.
export_docExport a doc to a fileA
Export a Google Doc to a real file on disk — pdf (default), docx, odt, rtf, txt, html, epub, or md. Google renders it server-side (File > Download in the UI), so page setup, pagination and layout match the editor. A doc path exports every tab; a tab path (/folder/doc/tab) exports only that tab, through an endpoint Google does not document and rate-limits — if it fails the call is refused and says so, it never falls back to the whole doc. Returns the local path and byte size. Note: Drive refuses to export files larger than 10 MB.
Inputs: dir (required), path (required), format, account, +1 more.
get_page_setupRead document page setupA
Read a doc’s (or tab’s) page setup — margins, page size (in points, plus a preset name if it matches letter/legal/a4/tabloid), and orientation. The read counterpart to set_page_setup; use it to mirror another document’s layout onto a new doc.
Inputs: path (required), account.
get_project_configShow this project’s gdocs defaultsA
Show the effective .gdocs-mcp.json defaults (account/folder) for the current working directory, and where the file is.
Inputs: no named parameters.
list_accountsList authorized Google accountsA
List the Google accounts that have been authorized for this server.
Inputs: no named parameters.
list_commentsList comments on a docA
List the comments on a Google Doc, each with the text it is on (quotedText), the sentence around it (context, the text in ⟦⟧), its author (a display name, never an email) and time, whether it is open or resolved, its body and its replies. A comment with a range is on that part of the doc, in the body or in a header or footer, and moves with its text; a comment with none was made in Drive and has only its quoted text. Pass a comment’s id as replyTo to add_comment, and its quotedText as expectQuotedText to resolve_comment.
Inputs: path (required), account.
list_permissionsList who a doc is shared withA
List the permissions on a Google Doc (people, groups, domain, anyone-with-link) with their roles. Each entry carries a subject naming who it covers — an email, " (domain)", or "anyone with the link" — since a domain or link grant has no email. For those, allowFileDiscovery: true means the file also surfaces in that audience's search, not merely that it opens with the link. Note a doc created under a Workspace domain may already carry a domain grant before you share it.
Inputs: path (required), account.
list_suggestionsList suggestions in a docA
List the pending suggestions (tracked changes) in a Google Doc, in document order. Each has an id, a summary (Docs’ own words for it, as in the Docs sidebar, e.g. Replace: “30” with “45”), a context (the change in its sentence, [deleted→inserted], so two suggestions with the same summary can be told apart), and its author and time. Returns the doc title. Pass the title as expectTitle and each context as expectContext to apply_suggestions. segment: "header" or "footer" lists that one’s suggestions.
Inputs: path (required), page, account, segment.
read_docRead a Google DocA
Read a Google Doc as markdown + inline HTML. The read opens with a block — one CSS rule per named style (p = Normal text, h1–h6, .title, .subtitle), in pt — and a paragraph or run shows style="…" only where it differs from its rule (text-align, line-height, margin-top/bottom/left/right, text-indent; a hanging indent is margin-left:36pt with a negative text-indent). Title and Subtitle paragraphs read as . A page break reads as on its own line, where the break is. Every element in a paragraph reads, so an edit that quotes one can give it back: a footnote is [^1] with its note as a [^1]: line at the end; a date is Nov 15, 2026; a person or a link chip is Jane Doe (the text is Google’s); a dropdown is with its list as a Draft line after the block; a horizontal rule is --- on a line of its own; a column break is ; an equation and a page-number field are empty and whose title says to edit them in Google Docs. A table of contents is not read. List items show no indent: their nesting owns it. mode: clean (committed text, default) · tracked (suggestions shown as /) · accepted · rejected. segment picks the content tree: body (default), header, footer, or all (body plus every header/footer, each labelled). Text that would otherwise read as markup is marked as literal: a paragraph wrapped in … ("4. Term" is a paragraph, not a list) or a backslash before a character (5 * 3), and \ is a real backslash — write them back as read, and the document keeps only the words. A body read always reports which headers/footers exist and what they hold, since their content — a letterhead logo, a page number — is NOT part of the body and would otherwise be invisible.
Inputs: path (required), mode, page, account, +1 more.
resolve_commentResolve or reopen a commentA
Resolve (or reopen) a comment thread by comment id. expectQuotedText is REQUIRED: the quotedText list_comments gave for that comment, exactly. It is checked against the live comment first, so a stale or wrong id is refused instead of resolving the wrong thread.
Inputs: path (required), commentId (required), expectQuotedText (required), reopen, +1 more.
set_page_setupSet document page setupA
Set document-level page setup for a doc (or tab): page margins, page size, and orientation — the File > Page setup controls. Margins and explicit page sizes are in points (72 pt = 1 inch). pageSize is a preset (letter/legal/a4/tabloid) or an explicit {width,height} in points; orientation (portrait/landscape) swaps the page dimensions. Always a direct change: page geometry is a document-style change, so there is no suggest option.
Inputs: path (required), account, pageSize, marginTop, +4 more.
set_project_defaultSet this project’s default account/folderA
Write this project’s defaults to a .gdocs-mcp.json in the current working directory (or update an existing one up the tree). Set a default account and/or a default folder (URL or id). The folder is where write_doc is told new docs go: a new doc given as a bare name or at the top of My Drive is refused once, naming the folder and the full path to call. It is read when the server starts, so write_doc’s description shows a change after a restart; the refusal reads it fresh. To set a folder by name, find it first with drive({ cmd: 'find' }) and pass its id.
Inputs: folder, account.
write_docWrite a doc or a tabA
Write markdown-rendered content to a path, like the local Write tool. A path that names nothing is CREATED and never asks, but only an absolute path (or a Drive id or URL start) creates: a bare name like Contract is refused with the full path to call. When the project has a default folder for new docs, a new doc at the top of My Drive (/Contract) is refused once too, with the default folder’s full path to call, so the user is told where it goes first; the same call repeated goes through: /Work/Contract creates a doc in the folder /Work, /Work/Contract/Notes a tab in that doc, /Work/Contract/Part 2/Ch.4 a child tab under Part 2 (only the last step may be new; make the folder with drive mkdir first). A path that names something is REPLACED, and the first call is refused with a loss summary — the paragraphs of text and everything a read cannot carry that the replace would take with it: comments, suggestions, tab stops, links to bookmarks, section breaks, a table of contents, column breaks, equations, page-number fields, a date in a format the API can only read, and a link chip to anything but a Drive file — plus a confirmLoss string. Ask the user before anything else — show them what would be lost and wait for a yes; never pass confirmLoss on your own. Only after their yes, call again with the same content and confirmLoss set to that string exactly. If the doc changed in between, the string no longer matches and the call is refused again with a fresh summary. A doc with several tabs needs a tab step (the refusal lists them); a doc with one tab means that tab. Nothing to lose, nothing to ask: an empty doc, tab, header or footer is written without the refusal. segment: "header" or "footer" writes the page header or footer of an existing doc instead of its body: a missing default one is created and written, an existing one with content is replaced after the same confirmLoss round-trip, and a missing first-page or even-page one (page: "first" / "even") is refused with the instruction to turn on Different first page in Docs. Replacing keeps the doc’s title, sharing and other tabs but the new paragraphs start unstyled, so anything not in the markdown (including indents) is gone; prefer edit_doc to change a document, which keeps everything its anchor does not cover. Style is CSS, in the spelling read_doc emits: a leading block sets the named styles (p, h1–h6, .title, .subtitle), / / style a paragraph, a run. Supported: text-align, line-height, margin-top/bottom/left/right, text-indent (pt; text-indent is relative to margin-left), and on spans/rules font-family, font-size, font-weight, font-style, text-decoration, color. Any other property fails the whole write before anything is sent, listing every offending line. Footnotes, dates, person chips, Drive link chips and dropdowns are written back as read_doc spells them ([^1] and a [^1]: note line; ; ; with its line); --- is written as an empty paragraph with a thin grey bottom border. A new column break, equation, page-number field, or link chip to anything but a Drive file (YouTube, Calendar, Maps, a web page) is refused before anything is sent: the API cannot make it, so write text for a link, or ask the user to insert the rest in Google Docs. A chip’s text is Google’s, so a chip given other words is refused. A page break is on a line of its own (also accepted: , break-before: page, break-after: page); any other or is refused. For long documents, pass contentFile instead of content so the server reads the body directly (retyping a long doc inline can silently drop text). Returns images ({src, objectId}) for pushed images. suggest: true sends the replace as suggestions for a person to accept or reject in Docs, and never asks for confirmLoss: nothing is lost until a person accepts, and rejecting brings back the text, images, tables and comments as they were. It needs a path that already names something (a new doc or tab cannot be a suggestion), refuses a table column width (Docs cannot suggest one) before anything is sent, and lists the suggestion ids in suggestions; use it when the user wants to review the rewrite or the doc is not theirs. It defaults to false: the replace is applied as live text.
Inputs: path (required), page, account, baseDir, +5 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 19 tools
Most tools target clearly distinct resources or actions: document reading, snippet editing, comments, suggestions, permissions, page setup, and Drive navigation are well separated. The main potential confusion is between write_doc and edit_doc for writing markdown, and read_doc versus export_doc, though the descriptions make the intended boundaries mostly clear.
Nearly all tools use a consistent snake_case verb_noun pattern such as read_doc, edit_doc, list_comments, add_comment, and share_doc. The single noun-only tool, drive, is a deliberate command multiplexer but is the one clear deviation from the pattern.
At 19 tools the surface is slightly heavier than the ideal compact set, but the server covers a broad Google Docs domain including editing, comments, suggestions, sharing, page setup, export, Drive navigation, and configuration. Each tool corresponds to a meaningful operation, and drive usefully bundles several filesystem commands.
The tool set covers the core document lifecycle well: create, read, edit, replace, export, comments, suggestions, sharing, page setup, images, and Drive navigation. Notable gaps remain around deleting documents, tabs, or comments and around revision history, but these are relatively minor for the main Docs workflows.
Maintenance
Related MCP Connectors
Online office suite for AI agents: edit docs, sheets, slides and PDFs in browser or via MCP.
An MCP server that gives your AI access to the source code and docs of all public github repos
An agent-first office suite Claude & ChatGPT read and write over one MCP URL.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP (Multi-Agent Conversation Protocol) Server that enables AI agents to interact with Google Docs via natural language, automatically generated using AG2's MCP builder.-
- AlicenseNot gradedqualityDmaintenanceAn unofficial MCP server for Google Docs that lets large language models securely access, read, and interact with documents, enabling smarter workflows and AI-assisted editing.14 npmMIT
- AlicenseNot gradedqualityDmaintenanceA powerful Model Context Protocol (MCP) server implementation for seamless Google Docs API integration, enabling AI assistants to create, read, update, and manage Google Docs.14 npmMIT
- FlicenseNot gradedqualityDmaintenanceAn MCP server that enables AI assistants to interact with Google Docs, Sheets, and Drive using the user's own Google account.-