{
  "markdown": "<h1 align=\"center\">gdocs-comments-mcp</h1>\n\n<p align=\"center\">\n  <b>Inline, range-anchored comments for Google Docs — the one comment operation the Google APIs can't do.</b>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/stanislawherjan1/gdocs-comments-mcp/actions/workflows/ci.yml\"><img src=\"https://github.com/stanislawherjan1/gdocs-comments-mcp/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"></a>\n  <a href=\"https://www.npmjs.com/package/gdocs-comments-mcp\"><img src=\"https://img.shields.io/npm/v/gdocs-comments-mcp?color=cb3837&logo=npm\" alt=\"npm version\"></a>\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/license-MIT-blue\" alt=\"MIT license\"></a>\n  <img src=\"https://img.shields.io/badge/node-%E2%89%A518-brightgreen?logo=node.js\" alt=\"node >= 18\">\n  <img src=\"https://img.shields.io/badge/MCP-stdio-8A2BE2\" alt=\"MCP stdio\">\n</p>\n\n<p align=\"center\">\n  <sub>\n    <a href=\"#quickstart\">Quickstart</a>\n    &nbsp;·&nbsp;\n    <a href=\"#what-this-does--and-what-it-doesnt\">Scope</a>\n    &nbsp;·&nbsp;\n    <a href=\"#tools\">Tools</a>\n    &nbsp;·&nbsp;\n    <a href=\"#configuration\">Configuration</a>\n    &nbsp;·&nbsp;\n    <a href=\"#how-it-works\">How it works</a>\n    &nbsp;·&nbsp;\n    <a href=\"#troubleshooting\">Troubleshooting</a>\n    &nbsp;·&nbsp;\n    <a href=\"#running-on-a-server--datacenter-ip\">Server deployment</a>\n    &nbsp;·&nbsp;\n    <a href=\"#faq\">FAQ</a>\n  </sub>\n</p>\n\nYou're building an AI process around Google Docs — an agent that reviews drafts, audits contracts, gives editorial feedback. The natural way to deliver that feedback is how humans do it: **a comment pinned to the exact sentence it's about**, not a wall of text dumped at the end of the doc or into chat.\n\nThen you hit the wall: **the Google APIs can't create anchored comments.** The Docs API has no comment endpoints at all, and the Drive API accepts an `anchor` field only to have the Docs editor ignore it — the comment shows up as a general, whole-document comment.\n\nThis MCP server closes that gap. Your agent calls `add_comment` with a text fragment and a comment; the server posts it through a real, logged-in Google Docs session, so it lands anchored to that exact text — just as if a human had selected it and pressed `Ctrl+Alt+M`:\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/stanislawherjan1/gdocs-comments-mcp/main/assets/demo.png\" alt=\"A Google Doc with two inline comments posted by the agent, each anchored to a highlighted text fragment\" width=\"900\">\n</p>\n\n## Why not the official APIs?\n\nThis server does one thing: **add a comment to a Google Doc.** The interesting case is the anchored one — a comment pinned to a specific text range — which no Google API can do.\n\n| Add a comment… | Google Docs API | Google Drive API | this server |\n|---|:---:|:---:|:---:|\n| unanchored (whole document) | ❌ | ✅ | ✅ |\n| anchored to a text range | ❌ | ❌ | ✅ |\n\nThe Docs API has no comment endpoints at all. The Drive API's `comments.create` accepts an `anchor` field, but the Docs editor **ignores** it — the comment renders as an unanchored, whole-document comment. The editor's own anchor format (`kix.*`) is undocumented and can't be produced externally ([Drive API docs](https://developers.google.com/workspace/drive/api/guides/manage-comments), [issuetracker #292610078](https://issuetracker.google.com/issues/292610078), open since 2016). Driving the editor UI is the only way — so this server does exactly that, and nothing else. (Listing, replying, resolving, and deleting comments already work over the Drive API — use a Drive-based tool for those.)\n\n## What this does — and what it doesn't\n\nThis server is deliberately **one narrow thing**. Read this before wiring it in.\n\n**✅ It does:** add comments to a Google Doc — anchored to a specific text range (the part no API can do), or unanchored on the whole document.\n\n**❌ It does NOT:**\n\n| You want to… | Use instead |\n|---|---|\n| **Read the document's content** (so an agent can decide what to comment on, or get the exact text to anchor to) | **Google Docs API** (`documents.get`) or a Docs-reading MCP |\n| Export the doc (text / markdown / PDF) | **Google Drive API** (`files.export`) |\n| List / reply to / resolve / delete comments | **Google Drive API** (`comments.*`) — faster, no browser |\n\n> **Important:** this tool **never returns document content** — its output is structured-only (`{ ok, anchored, occurrence_used, verified }`), by design, so a malicious doc can't inject instructions into your agent. That means the agent is writing *blind*: to review a doc intelligently, pair this with a **read** capability (Docs API `documents.get`) and feed the exact quoted text back in as `find_text`. The two use different auth — this server drives a logged-in browser session (no OAuth), while the Docs/Drive APIs need an OAuth token or service account — but they can run against the same Google account.\n\n## Quickstart\n\n**One command** — signs you in (opens a browser once), then registers the server with Claude Code:\n\n```bash\nnpx -y gdocs-comments-mcp setup\n```\n\nThat's it — skip to **Use it** below. Prefer to do it by hand, or use another client? The manual steps are below.\n\n<details>\n<summary><b>Manual setup</b></summary>\n\n**1. Log in once** — opens a browser window; the Google session is saved to a local profile (`~/.gdocs-comments-mcp/profile`):\n\n```bash\nnpx -y gdocs-comments-mcp login\n```\n\n**2. Add the server to your MCP client:**\n\n```bash\n# Claude Code\nclaude mcp add gdocs-comments -- npx -y gdocs-comments-mcp\n```\n\n**Other MCP clients** — after `login`, add the config for your client:\n\n<details>\n<summary><b>Claude Desktop / Cowork</b></summary>\n\nAdd to `claude_desktop_config.json` (Settings → Developer → Edit Config):\n\n```json\n{\n  \"mcpServers\": {\n    \"gdocs-comments\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"gdocs-comments-mcp\"]\n    }\n  }\n}\n```\n</details>\n\n<details>\n<summary><b>Project-level <code>.mcp.json</code></b> (shared with your team via git)</summary>\n\nCreate `.mcp.json` in the project root — Claude Code, Cowork, and most MCP clients pick it up:\n\n```json\n{\n  \"mcpServers\": {\n    \"gdocs-comments\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"gdocs-comments-mcp\"]\n    }\n  }\n}\n```\n\nNote: every user of the project still runs `npx gdocs-comments-mcp login` once on their own machine — sessions are personal and never shared through git.\n</details>\n\n<details>\n<summary><b>Cursor</b></summary>\n\nAdd to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):\n\n```json\n{\n  \"mcpServers\": {\n    \"gdocs-comments\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"gdocs-comments-mcp\"]\n    }\n  }\n}\n```\n</details>\n\n<details>\n<summary><b>VS Code (GitHub Copilot)</b></summary>\n\nAdd to `.vscode/mcp.json`:\n\n```json\n{\n  \"servers\": {\n    \"gdocs-comments\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"gdocs-comments-mcp\"]\n    }\n  }\n}\n```\n</details>\n</details>\n\n**3. Use it** — ask your agent:\n\n> Add a comment to https://docs.google.com/document/d/1AbC…/edit — anchor it to \"quarterly numbers\" and say \"Update this before Friday\".\n\nThe agent calls `add_comment` and gets back:\n\n```json\n{ \"ok\": true, \"anchored\": true, \"occurrence_used\": 1, \"verified\": true }\n```\n\n…and the comment is sitting on the highlighted phrase in the doc, from the account you logged in with.\n\nNo Playwright browser download is needed — by default the server drives your installed Google Chrome via `playwright-core`.\n\n## Tools\n\n### `add_comment`\n\n| Param | Required | Description |\n|---|:---:|---|\n| `doc` | ✅ | Document id **or** full `docs.google.com/document/d/<id>/edit` URL |\n| `comment_text` | ✅ | Comment body (plain text, newlines OK) |\n| `find_text` | — | Exact, single-line text fragment to anchor to (must match the doc text). **Omit to add a general, unanchored comment on the whole document.** |\n| `occurrence` | — | Anchor to the N-th match when `find_text` appears multiple times (default 1) |\n\nReturns `{ ok, anchored, occurrence_used, verified }` — `verified: true` means the posted comment was observed in the page after submitting. If `find_text` is given but not found, the call fails with `TEXT_NOT_FOUND` and **nothing is posted**.\n\nThe tool **never returns document content**, so a malicious doc can't inject instructions into your agent through it.\n\n### `check_connection`\n\nProbes the Google session; returns `{ connected, mode }`. If `connected: false`, run `npx gdocs-comments-mcp login` again.\n\n> **Scope note:** this server does *one* thing — creating anchored comments. Listing, replying, resolving, and deleting comments all work fine through the Drive API (`comments.*`), which is faster and needs no browser — use a Drive-based MCP for those.\n\n## CLI\n\n```bash\nnpx gdocs-comments-mcp login    # one-time interactive Google sign-in\nnpx gdocs-comments-mcp status   # is the saved session still valid?\nnpx gdocs-comments-mcp logout   # delete the saved session/profile\n```\n\n## Configuration\n\nAll optional, via environment variables:\n\n| Env var | Default | Purpose |\n|---|---|---|\n| `GDOCS_COMMENTS_PROFILE_DIR` | `~/.gdocs-comments-mcp/profile` | Where the logged-in browser profile lives (set a different dir per Google account) |\n| `GDOCS_COMMENTS_BROWSER_CHANNEL` | `chrome` | `chrome` \\| `msedge` \\| `chromium` (bundled; needs `npx playwright install chromium`) |\n| `GDOCS_COMMENTS_HEADLESS` | `true` | Set `false` to watch the automation work |\n| `GDOCS_COMMENTS_IDLE_CLOSE_MIN` | `10` | Close the managed browser after N idle minutes (`0` = keep open) |\n| `GDOCS_COMMENTS_CDP_URL` | — | Attach to an existing browser over CDP instead of managing a profile ([see below](#running-on-a-server--datacenter-ip)) |\n| `GDOCS_COMMENTS_AUDIT_LOG` | off | JSONL audit log (hashes only, no content) |\n\n## Troubleshooting\n\n| Error | What it means | Fix |\n|---|---|---|\n| `NOT_CONNECTED` | No saved Google session yet | `npx gdocs-comments-mcp login` |\n| `SESSION_EXPIRED` | The saved session lapsed (idle sessions die after ~1–2 weeks) | `npx gdocs-comments-mcp login` again |\n| `TEXT_NOT_FOUND` | `find_text` doesn't occur in the doc (or `occurrence` > number of matches) | Pass a fragment that matches the doc text exactly — nothing was posted |\n| `NO_BROWSER` | No Chrome/Edge/Chromium found | Install Google Chrome, or `npx playwright install chromium` + `GDOCS_COMMENTS_BROWSER_CHANNEL=chromium` |\n| `PROFILE_LOCKED` | Another process holds the profile (usually a running server + a `login`/`status` attempt) | Stop one of them, or use a second `GDOCS_COMMENTS_PROFILE_DIR` |\n| Comment lands but `verified: false` | The post-submit check couldn't see the comment (can be a false negative on long comments) | Check the doc; rerun with `GDOCS_COMMENTS_HEADLESS=false` to watch |\n\nFirst run on a new machine? `npx gdocs-comments-mcp status` tells you exactly where you stand.\n\n## Running on a server / datacenter IP\n\nOn a residential machine the default profile mode just works. On datacenter IPs, Google's anti-fraud **rejects a freshly launched browser process reusing a saved session** — it bounces to `accounts.google.com/confirmidentifier`. Relaunching from a profile does not work there.\n\nWhat does work: keep the exact browser the operator logged into **alive**, and let this server attach to it:\n\n1. Start a long-lived Chrome/Chromium (under Xvfb if headless) with `--remote-debugging-port=9333` and log in to Google inside it once.\n2. Run the MCP server with `GDOCS_COMMENTS_CDP_URL=http://127.0.0.1:9333`.\n\nThe server then drives that live session over CDP and never launches its own browser. Keep the session warm by navigating it to `docs.google.com` every few hours, or an idle session expires after ~1–2 weeks.\n\n## How it works\n\nBecause the APIs can't anchor a comment, the server does exactly what a person would: it drives the real Google Docs editor. Here's the full path of one `add_comment` call.\n\n**1. Attach to a logged-in browser.** In the default *profile mode*, the server owns a persistent Chromium profile (your installed Google Chrome, via `playwright-core` — no extra browser download). You authenticate into it once with `gdocs-comments-mcp login`; the Google session cookies are saved to `~/.gdocs-comments-mcp/profile` and reused headlessly on every later run. In *CDP mode* (`GDOCS_COMMENTS_CDP_URL`) the server instead attaches over the Chrome DevTools Protocol to a browser you keep alive — see [server deployment](#running-on-a-server--datacenter-ip).\n\n**2. Open the document.** It navigates to `https://docs.google.com/document/d/<id>/edit?hl=en`. The `doc` id is validated first, and navigation is hard-pinned to `docs.google.com` — the tool can't be steered elsewhere. If the page bounces to `accounts.google.com`, the session is dead and the call returns `SESSION_EXPIRED`. `?hl=en` pins the UI language so the find-bar counter is parseable in the next step.\n\n**3. Find the anchor text.** The document body renders to an HTML `<canvas>`, not selectable DOM — so you can't just query for the text and click it. Instead the flow is entirely keyboard-driven (which also makes it locale- and CSS-class-independent):\n\n```\nCtrl/Cmd+F  →  type find_text  →  read the \"N of M\" match counter\n```\n\nReading the counter **before** commenting is the key safety step: if `find_text` has zero matches, or `occurrence` is larger than the match count, the call fails with `TEXT_NOT_FOUND` and **nothing is posted** — instead of the old failure mode where a blind key sequence would silently drop the comment wherever the cursor happened to sit.\n\n**4. Select the requested occurrence and comment.** It presses `Enter` N times to land on the N-th match, `Esc` to close the find bar (leaving that occurrence selected), then:\n\n```\nCtrl/Cmd+Alt+M  →  type comment_text  →  Ctrl/Cmd+Enter (submit)\n```\n\n`Ctrl/Cmd+Alt+M` is the Docs shortcut for \"insert comment on the current selection\" — which is precisely the anchored comment the APIs won't create. Keyboard shortcuts are chosen per-platform (`Cmd` on macOS, `Ctrl` elsewhere).\n\n(If `find_text` is omitted, steps 3–4 collapse to \"put the cursor at the document start and comment there\" — a general, unanchored comment.)\n\n**5. Verify and report.** After submitting, the server waits for the comment text to appear in the page and returns `{ ok, anchored, occurrence_used, verified }`. `verified: true` means the posted comment was actually observed; it never returns any document content, so a poisoned document can't smuggle instructions back into your agent. Concurrent calls are serialized behind a mutex, because two keyboard flows in the same window would interleave and corrupt each other.\n\n```\nadd_comment ─▶ attach browser ─▶ open doc (?hl=en) ─▶ Ctrl/Cmd+F find_text\n             ─▶ check match counter ─(0 matches)─▶ TEXT_NOT_FOUND, nothing posted\n             ─▶ Enter×N ─▶ Esc ─▶ Ctrl/Cmd+Alt+M ─▶ type ─▶ Ctrl/Cmd+Enter\n             ─▶ verify comment in page ─▶ { ok, occurrence_used, verified }\n```\n\n## Limitations\n\n- **UI automation is inherently less stable than an API.** If Google reworks the editor, this can break until updated. The `verified` flag tells you whether the comment was actually observed after posting; contributions reporting UI changes are very welcome.\n- The logged-in account needs **comment access** to the target doc.\n- `find_text` must be a **single-line** fragment (the find bar is one line) and must match the document text exactly.\n- An unused session expires after roughly 1–2 weeks; `status` tells you, `login` fixes it.\n- Calls are serialized, so bulk commenting is sequential by design (typically a few seconds per comment).\n- Automating your own Google account through its normal UI is your responsibility under Google's Terms of Service. Use your own account; don't use this for spam.\n\n## Security\n\n**What the AI agent (and the MCP client) can see.** Nothing sensitive. Sign-in happens in a real browser where you type your Google credentials directly to Google — the `login` step is a terminal CLI, *not* an MCP tool, so the model is never in that loop and never sees your password, 2FA, cookies, or OAuth tokens. During normal use the `add_comment` and `check_connection` tools return **structured status only** (`{ ok, anchored, occurrence_used, verified }` / `{ connected, mode }`) and **never any document content** — so a poisoned document can't smuggle instructions back into the model through this tool. The only things that reach the model's context are what the agent itself supplied (`doc`, `find_text`, `comment_text`) and, occasionally, a local file path inside an error message.\n\n**Where the sensitive material actually lives.** Your **Google session cookies** sit in the profile directory (`~/.gdocs-comments-mcp/profile`), created with `0700` permissions — treat that directory like a password. It stays on disk and is never returned to the model. `logout` deletes it; you can also revoke server-side via [Google device activity](https://myaccount.google.com/device-activity). The optional audit log stores **hashes only**, never content. The one residual risk is ordinary local-machine hygiene: any other process with read access to that directory could reuse the session — that's not an MCP data flow, just standard local security.\n\n**Containment.** Doc ids are strictly validated and navigation is hard-pinned to `docs.google.com` — the tool can't be steered to other sites.\n\n## FAQ\n\n**Can the Google Docs API or Drive API create anchored comments on a specific text range?**\nNo. The Google Docs API has no comment endpoints, and the Drive API's `comments.create` accepts an `anchor` field that the Docs editor ignores — the comment renders as an unanchored, whole-document comment. This project exists specifically to work around that. See [Why not the official APIs?](#why-not-the-official-apis).\n\n**How do I add inline / anchored comments to Google Docs programmatically?**\nUse this MCP server, or the same technique it uses: drive the Docs editor UI (find the text, press `Ctrl+Alt+M`) rather than calling the REST API. [How it works](#how-it-works) documents the full flow.\n\n**How do I let an AI agent (Claude, Cursor, Copilot) comment on Google Docs?**\nAdd this server to your MCP client ([Quickstart](#quickstart)) and the agent gets an `add_comment` tool that posts real, anchored comments — ideal for AI document review, contract/editorial feedback, and automated proofreading workflows.\n\n**Do I need a Google Cloud project, OAuth client, or service account?**\nNo. Auth is a one-time interactive browser login (`gdocs-comments-mcp login`) using your normal Google account — no API credentials, no OAuth consent screen, no service-account setup.\n\n**Does it work with Google Workspace / shared drives / commenter-only access?**\nYes, as long as the logged-in account has at least comment access to the document.\n\n**Can I run it on a server or in CI?**\nYes — see [Running on a server / datacenter IP](#running-on-a-server--datacenter-ip). On datacenter IPs you attach to a long-lived logged-in browser over CDP instead of relaunching one.\n\n## Contributing\n\nIssues and PRs welcome — especially reports of Docs UI changes that break the keyboard flow (please include your locale and whether `verified` was `false`). Plain ESM, no build step: `git clone`, `npm install`, `node src/cli.js`.\n\n## License\n\n[MIT](LICENSE) © Stanisław Herjan\n\n---\n\n<sub><b>Keywords:</b> Google Docs anchored comments · inline comments Google Docs API · add comments to Google Docs programmatically · Google Docs comment API workaround · MCP server Google Docs · Model Context Protocol · Claude / Cursor / Copilot Google Docs integration · AI document review · Playwright Google Docs automation · comment on text range Google Docs.</sub>\n",
  "bytes": 19697,
  "sha": "2b70d3b86d9334c0db66858f625e6645db3efc6472ded7dc529f3fadb19838e8",
  "repo_slug": "stanislawherjan1/gdocs-comments-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_stanislawherjan1_gdocs_comment_1fbc5fa4/readme"
}