Skip to content

Releases: dasasian/gdocs-mcp

v0.6.0 — Drive as a filesystem

Choose a tag to compare

@damith-dasasian damith-dasasian released this 21 Aug 15:57

[0.6.0] — 2026-08-21

Changed

Breaking: five Drive tools became one. list_folder, search_drive, create_folder, copy_doc and update_doc are removed, replaced by drive({ cmd, args }). Anything calling them by name must be updated.

  • Drive navigation is now a filesystem (#44). The five Drive tools became drive({ cmd, args }) speaking ls / find / mkdir / cp / mv — 36 tools down to 32. Paths are / or ~ (My Drive), /shared/<drive name>, /shared-with-me and /lost+found; anything else is read as a Drive id or URL, so ids from other tools paste straight in. A whole path resolves in a single files.list call.
  • Drive is not a filesystem in three ways, and each refuses rather than half-working (#44). Two files may share a name in one folder and matching folds case, so an ambiguous path is refused with the candidates listed — and cp/mv refuse to create that state. cp -r does not exist: Drive's files.copy rejects folders outright. mv into /shared/… transfers ownership irreversibly, so it requires acceptOwnershipTransfer.
  • mv keeps update_doc's guard as expectName, and covers rename and move together the way a filesystem does: an existing folder as the destination means "into it", anything else means "to that name" (#44).
  • Flags and operands parse in any order, as a terminal accepts them: cp -r a b, cp a b -r and cp a -r b are one command, and -- ends the options. What an unrecognised -token means is set per command, because the commands disagree — ls -la /Work wants the flag ignored, find -2026 wants -2026 searched for (#44).
  • cp keeps the source name. Drive's files.copy defaults to "Copy of …", which is its UI convention; cp file /dir on a filesystem produces file (#44).
  • ls /lost+found replaces list_folder({ folder: "orphaned" }) — Unix's name for exactly this (#44, #46).
  • No destructive command ships. There is none in the surface to collapse, and host permissions are granted per tool name: allowlisting drive so ls stops prompting would allowlist rm too. Conditions that would change that are in #47 (#44).

Added

  • Files in no folder are listablels /lost+found. A parentless file opens and turns up in a search, but nothing that browses the tree will ever show it, so it is found only by someone who already remembers it exists. Drive has no query operator for "has no parent", so the scan pages the files you own and reports scanned/complete rather than passing a silent cap off as the whole answer. Re-home one with mv (#46).
  • The README shows what to ask for. Two chained examples — filling a template without rebuilding it, and a whole review pass in one instruction — now sit above the tool table, where the only pointer to docs/recipes.md used to be a blockquote below it. docs/recipes.md gains a Whole jobs section above the single-tool recipes (#44).

v0.5.0 — sharing you can actually see and revoke

Choose a tag to compare

@damith-dasasian damith-dasasian released this 16 Aug 17:42

Changed

Breaking: unshare_doc now requires expectRole. A call passing only documentId and email will be rejected. Run list_permissions first and echo the role back.

  • unshare_doc now requires expectRole. Revoking access is the one operation in the surface with no undo — verified live, a Drive revision carries no permission data at all, so version history restores content and never sharing. Echo back the role list_permissions reported and the call proceeds only if the grant is still that; otherwise nothing is removed. An optional expectTitle refuses a wrong document id as well.

Fixed

  • Two tool descriptions pointed at tools that do not exist. set_style told callers to run inspect_style, which was renamed get_style; list_suggestions pointed at apply_suggestion, whose singular form was deleted. Both sent the model somewhere there was nothing. A test now reads the descriptions and fails on any name that is not a registered tool (#45).
  • set_style and edit_doc did not say which to use. Since 0.3.0 both can style existing text, but edit_doc requires restating the whole run, and retyping is how text gets silently dropped. Each now names the other and says when it is the wrong choice (#45).
  • A domain or group grant could be seen but never revoked. unshare_doc matched on email, and a domain or anyone-with-link grant has none — so the domain-wide grant every doc gets under a Workspace was permanent as far as the tool surface was concerned. It now also accepts a permissionId from list_permissions, which covers every grant type, echoes back what it removed, refuses the owner, and lists what is present when nothing matches (#41).
  • list_permissions could not name a domain or link grant. It never asked Drive for domain or allowFileDiscovery, so a domain-wide grant came back with a null email and no way to tell which domain it covered — rendering as null:reader. Every entry now carries a subject naming its audience (alice@x.com, x.com (domain), anyone with the link), and domain/link grants report whether the file is discoverable in search rather than only reachable by link (#42).

Install: npx -y @dasasian/gdocs-mcp · npm · docs

Full diff: v0.4.0...v0.5.0

v0.4.0 — get_table_style

Choose a tag to compare

@damith-dasasian damith-dasasian released this 16 Aug 16:09

Added

  • get_table_style — reads a table's column widths, pinned header rows, and the matched cell's padding, background and per-side borders, located by cell text like its setter. set_table_style was the only setter with no getter, so everything it wrote was invisible on read. columnWidths comes back in the shape the setter accepts, so one table's layout can be copied onto another (#33).

No behaviour changes to existing tools, so nothing to check on upgrade.

Install: npx -y @dasasian/gdocs-mcp · npm · docs

Full diff: v0.3.0...v0.4.0

v0.3.0 — six correctness fixes, and the cleanup that found them

Choose a tag to compare

@damith-dasasian damith-dasasian released this 16 Aug 07:42

Six correctness fixes and the cleanup pass that uncovered them. Nearly all of it
is one bug wearing different clothes: a write whose effect the next read could
not see
, so an agent could not verify its own work. DESIGN.md §2 now states
the rule — a construct the writer accepts must be a construct the reader emits.

Changed

read_doc output format. Anything that parses it should be checked.

  • read_doc now shows text color, size and font. A run carrying any of them comes back wrapped in <span style="color:…;font-size:…pt;font-family:…"> — the exact spelling the writer already parsed, so it round-trips. DESIGN.md §2 made inline HTML the escape hatch for formatting markdown can't express and required it be "visible in the read", but the reader never emitted these, so a set_style colour change was invisible on the next read and an agent could not verify its own edit or preserve styling it was rewriting around. Emission is quiet by default: Google only populates these fields on runs that override them, so inherited text (including headings) is untouched (#30).
  • Embedded images read back with their size. read_doc now emits <img src="image:<objectId>" width="…" height="…"> (points) instead of a bare ![](image:<objectId>), and the writer accepts an <img> line — so image dimensions survive a round-trip. DESIGN.md §2 named <img width="400"> as the mechanism; it existed on neither side. The plain ![alt](src) form still works unchanged for authoring. Writing an image:<objectId> marker back is now refused with an explanation, rather than failing as a missing local file, because Docs stores the embedded bytes and not a re-fetchable URL (#30).

Added

  • segment/page on the table and suggestion toolsinsert_table, edit_table, set_table_style, list_suggestions and apply_suggestions can now target a header or footer, the same way the text tools already could. A letterhead table or a tracked change on a footer disclaimer was previously unreachable, and failed silently: the cell text simply never matched, so edit_table reported "no table cell containing …" as though the table did not exist. These now report no_segment with the list of segments the doc actually has. insert_table also takes createSegment (as insert_image does), so a table can be placed in a header the doc doesn't have yet (#28).
  • Inline code round-trips. Docs has no code style, so the writer maps `x` to a monospace font; the reader now maps it back. Previously the backticks were dropped on read (#30).
  • insert_table cells accept inline markdown, and take per-column align. Cell text now goes through the same renderer the markdown path uses, so **bold**, `code` and [links](url) work; align: ["center","right"] sets column alignment at creation (#29).
  • insert_image accepts a local file path, not just a public URL — it uploads to Drive, embeds, and removes the temp upload, the same way ![](./logo.png) in pushed markdown already did. Relative paths resolve against baseDir (#29).

Fixed

  • overwrite_doc and insert_content inherited the styling of the text they replaced. Google's insertText picks up the character formatting at the insertion point, and when a delete and an insert share one batchUpdate — which is exactly what an overwrite is — the new text inherits the formatting of the text just deleted. So pushing plain markdown into a bold, coloured or hyperlinked document produced bold, coloured, hyperlinked output that nothing in the markdown asked for. The rendered range now has its direct character styling cleared before the markdown's own styling is applied. Named styles still inherit, so a document's NORMAL_TEXT font is unaffected (#32).
  • read_doc wrapped every hyperlink in a redundant colour span. Docs writes its link blue in as a direct run colour, which the new colour rendering then surfaced. The default is now suppressed on links (as underline already was), while a deliberately coloured link still shows (#32).
  • insert_table wrote literal markdown into cells. data: [["**Bold**"]] inserted the asterisks as text. Because read_doc renders genuinely-bold text as **Bold** too, a read-back looked correct while the document held corrupt text, so callers had no way to notice (#29).
  • A read→write round-trip corrupted nested inline styles. The reader emits styles in layers (<u>**AAA**</u> for a bold+underlined run), but the writer's inline parser was one level only: it took a container's contents verbatim, so the inner style was dropped and its markers were baked into the text as literal characters. Each cycle added another layer (<u>****AAA****</u>), so a document degraded every time it was read and written back. Containers now re-parse their contents and layer their own styles on. Same-tag nesting remains unsupported and is documented in docs/limitations.md (#31).
  • edit_doc could not match text containing __. old_string is resolved by exact match first, then by a markup-stripped retry; the strip step ran its own copy of the markdown grammar, which had drifted from the writer's. The writer guards underscore-bold with CommonMark word boundaries, the strip step did not — so a signature rule (____ ____) or an intraword a__b__c copied out of read_doc was mangled into something the document never contained, and the edit was refused. The strip step now derives its plain text from the writer's own parser (#27).

Install: npx -y @dasasian/gdocs-mcp · npm · docs

Full diff: v0.2.0...v0.3.0

v0.2.0 — copy_doc, create_folder, table borders and pinned headers

Choose a tag to compare

@damith-dasasian damith-dasasian released this 16 Aug 15:40

Added

  • copy_doc — duplicate a Doc via Drive files.copy, with an optional new name and target folder. Copying preserves what a markdown round-trip cannot rebuild (headers/footers, image sizing, exact formatting), so a template can be reused instead of recreated. Kept as its own tool rather than an update_doc mode: it creates a file rather than mutating one (#24).

  • create_folder — create a Drive folder, optionally inside a parent (URL or id). Previously the only way to make a folder was the Drive UI (#25).

  • set_table_style({ border }) — cell border width (pt), color (hex), dash style, and which sides, over the same scope as padding/background. border: { width: 0 } makes a table borderless (#21).

  • set_table_style({ headerRows }) — repeat the top N rows on every page (Docs' "pin header rows"); 0 unpins (#19).

  • insert_content — insert new markdown-rendered content at a structural position: at: "end" (default), "top", or a unique text anchor to insert right after. This is the only path to content that edit_doc cannot anchor: a paragraph after a table that ends the doc (a table cell can't anchor an insert outside the table, and Docs' mandatory trailing empty paragraph has no text to match). Kept as its own tool rather than an edit_doc mode so edit_doc stays "replace this exact text" (#20).

  • export_doc — export a Doc to a local file: pdf (default), docx, odt, rtf, txt, html, epub, or md, via Drive files.export. Google renders server-side, so pagination and page setup match the editor. Note Drive refuses exports over 10 MB (#22).

Changed

  • Headers and footers are reachable everywhere text is (#23). read_doc, edit_doc, set_style, get_style, insert_content and insert_image all take segment: "body" | "header" | "footer" (plus page for first-/even-page variants); read_doc also takes segment: "all". Writes to a header/footer that doesn't exist return no_segment listing what does, and createSegment: true creates it (default header/footer only — the API cannot create first-/even-page ones). Implemented by threading segmentId through the existing request builders, not a parallel set of tools.
  • A body read no longer looks empty when it isn't (#23). read_doc now reports the headers/footers it did not render, with paragraph and image counts. This was a wrong answer, not a missing one: a letterhead's logo lives in the page header, so read_doc returned markdown with no image at all and the doc read as having no logo.
  • search_drive / list_folder results now carry parents — each entry lists its parent folder(s) as { id, name }, so a hit can be traced upward (e.g. to create a sibling folder). Parent names are resolved once per distinct id, and degrade to the bare id if a lookup fails (#26).

Install: npx -y @dasasian/gdocs-mcp@0.2.0 · npm · docs

Full diff: v0.1.1...v0.2.0

v0.1.1

Choose a tag to compare

@damith-dasasian damith-dasasian released this 31 Jul 22:14

Added

  • MCP Registry metadata — a server.json (registry schema) plus an mcpName field in package.json, so the server can be published to the official MCP Registry as io.github.dasasian/gdocs-mcp. No functional or API changes.

v0.1.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 31 Jul 19:58

First public release — a Model Context Protocol server that lets an AI agent treat a
Google Doc like a local file. The full tool surface is implemented and validated
against the live Docs/Drive API. As a 0.x release the tool surface may still change
between minor versions.

Reading & editing

  • read_doc — markdown + inline HTML, in clean / tracked (<ins>/<del>) / accepted / rejected modes.
  • edit_doc — string-anchored, markup-tolerant edits (no indices); new_string renders inline markdown + HTML.
  • overwrite_doc — guarded wholesale replace (refuses to orphan comments/suggestions); accepts inline content or a contentFile path read server-side.
  • create_doc — render a markdown doc (inline content or contentFile), optionally into a Drive folder.
  • update_doc — rename and/or move a doc, with a title-verification guard on move.

Styling

  • set_style — style existing text the way you select in Docs: a single from snippet, a from/to selection, or the whole_document; bold/italic/underline/strikethrough, color, font size/family, link, alignment, and paragraph spacing. Bold survives a whole-document font change (works around a Docs API quirk that otherwise drops it).
  • get_style — read the effective (inherited-resolved) style at a text anchor.
  • set_page_setup / get_page_setup — document margins, page size (preset or explicit), and orientation.

Suggestions (tracked changes)

  • list_suggestions — pending changes as before → after diffs.
  • apply_suggestions — accept/reject one or more in a single atomic update; resolves overlapping/adjacent clusters safely (resolving them one at a time corrupts neighbours) and surfaces genuine conflicts instead of reporting a clean merge.

Comments

  • list_comments / add_comment (replies via replyTo) / resolve_comment — with a quote-verification guard on resolve.

Tables & images

  • Markdown tables render on create/overwrite and round-trip via read_doc (inline formatting + column alignment).
  • insert_table, edit_table (insert/delete a row or column, located by cell text), set_table_style (padding, background, column widths).
  • insert_image (position/size/align); markdown images render on create/overwrite; download_images pulls embedded images to disk with an id→file map + sha256.

Tabs, Drive & accounts

  • list_tabs / add_tab / rename_tab / delete_tab, plus tab-targeting on read/edit/suggestion tools.
  • list_folder / search_drive; list_permissions / share_doc (a person or anyone-with-link) / unshare_doc.
  • list_accounts, add-account CLI, per-project defaults via .gdocs-mcp.json and GDOCS_DEFAULT_ACCOUNT.

Safety

  • Confirmation guards on destructive / opaque-id tools: a human-readable label (expectTitle / expectQuote) is shown in the permission prompt and verified against live state before mutating — a mismatch refuses without changing anything.
  • Every write is a direct (live-text) edit, not a tracked suggestion — tools say so in their descriptions and results.

Known limitations (Google-API constraints, not bugs)

See docs/limitations.md. Highlights: suggestion attribution
(author/time) is unavailable via any Google API; comment author email isn't returned
by Drive; images are inline-only and don't read back to a stable URL; embedded code
blocks aren't rendered yet (Tier-2 roadmap).