gogcli-mcp-gmail 2.22.0 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > [!WARNING]
4
4
  > **AI-developed project.** This codebase was built and is actively maintained by [Claude Code](https://www.anthropic.com/claude). Review all code and tool permissions before use.
5
5
 
6
- Extended Gmail [MCP](https://modelcontextprotocol.io) server via [gogcli](https://github.com/openclaw/gogcli). Includes auth tools plus 23 additional dedicated Gmail tools for threads, labels, drafts, attachments, forwarding, autoreply, and bulk operations.
6
+ Extended Gmail [MCP](https://modelcontextprotocol.io) server via [gogcli](https://github.com/openclaw/gogcli). Includes auth tools plus 46 additional dedicated Gmail tools for threads, labels, drafts, attachments, forwarding, autoreply, and bulk operations.
7
7
 
8
8
  ## Requirements
9
9
 
@@ -44,9 +44,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
44
44
  claude mcp add gogcli-gmail -- gogcli-mcp-gmail
45
45
  ```
46
46
 
47
- ## Extra Gmail Tools (45)
47
+ ## Extra Gmail Tools (46)
48
48
 
49
- Plus 8 auth tools and 4 base Gmail tools (search, get, send, run) — 57 in all.
49
+ Plus 8 auth tools and 4 base Gmail tools (search, get, send, run) — 58 in all.
50
50
 
51
51
  ### Read
52
52
 
@@ -94,12 +94,97 @@ Plus 8 auth tools and 4 base Gmail tools (search, get, send, run) — 57 in all.
94
94
 
95
95
  | Tool | Description |
96
96
  |------|-------------|
97
- | `gog_gmail_drafts_list` | List drafts |
97
+ | `gog_gmail_drafts_list` | List drafts (free `origin` + `rootsOwnThread`; `enrich` adds subject/from/date for one extra call) |
98
98
  | `gog_gmail_drafts_get` | Get a draft by ID |
99
99
  | `gog_gmail_drafts_create` | Create a new draft |
100
- | `gog_gmail_drafts_update` | Update an existing draft |
100
+ | `gog_gmail_drafts_update` | Update a draft — and re-thread it in place with `replyToThreadId`, keeping the same id, with a `threadingVerification` block reporting what gog actually wrote. `forkSiblingDraftId` refuses the write when the new body would drop text the other copy still holds |
101
101
  | `gog_gmail_drafts_delete` | Delete a draft |
102
- | `gog_gmail_drafts_send` | Send an existing draft |
102
+ | `gog_gmail_drafts_send` | Send an existing draft (a 404 comes back diagnosed — `DRAFT_FORKED`, or `GOOGLE_404_NOT_THE_DRAFT` when the draft is still listed) |
103
+ | `gog_gmail_drafts_diff` | Diff two named drafts — divergent body lines (with untruncated `onlyInACount`/`onlyInBCount`), threading loss, and a conservative fork verdict (2 gog calls) |
104
+
105
+ #### When a draft you created stops resolving
106
+
107
+ A draft edited in a real mail client is not updated in place: the client writes a **new** draft and abandons the
108
+ original, so the id you were given starts returning `Google API error (404 notFound)` — indistinguishable, at first
109
+ glance, from "someone deleted it".
110
+
111
+ `gog_gmail_drafts_update` and `gog_gmail_drafts_send` turn that 404 into a `DRAFT_FORKED` report: what happened, the
112
+ drafts that *do* exist (with the free `origin` / `rootsOwnThread` fields, plus subject/from/date), the other
113
+ explanations that produce the same 404 (deleted, already sent), and what to do next. It **names no replacement** — the
114
+ 404'd draft can no longer be fetched, so there is nothing to establish lineage against, and without lineage no pairing
115
+ verdict is possible. Naming a pair and running `gog_gmail_drafts_diff` is the only path here that issues a fork verdict.
116
+
117
+ **"Not listed" is not "does not exist".** That listing is capped at 20 drafts by construction — it is a failure path and
118
+ must not grow with the mailbox — so the report states, under `listingEvidence`, what its own evidence can carry. Only a
119
+ listing that came back *short* of the window covered the whole Drafts folder and earns the sentence "draft X no longer
120
+ resolves" (`basis: complete-listing`, `establishesTheDraftIsGone: true`). A window that came back *full*
121
+ (`capped-listing`) or a listing that *failed* (`listing-unavailable`) says so in words and claims nothing about the
122
+ draft: on a mailbox with more than 20 drafts, absence of evidence must not become the fork story by default. Widen it
123
+ with `gog_gmail_drafts_list` before concluding anything. When the call named a reply target, that target is echoed on
124
+ the report and its explanation is listed **first** — the branch with the least evidence about the draft is the last one
125
+ that should stay silent about the leading alternative.
126
+
127
+ **The 404 is attributed, not assumed.** `gmail drafts update` resolves up to three Google entities — the draft, the
128
+ thread behind `replyToThreadId`, and the message behind `replyToMessageId` — and gog renders all three 404s with the
129
+ same string. The listing the report already pays for settles it: if the draft id is still listed, the answer is
130
+ `GOOGLE_404_NOT_THE_DRAFT`, which claims no fork, names the reply target as the remaining explanation, and tells you
131
+ not to go hunting for a replacement draft. `DRAFT_FORKED` is only used when the draft really has stopped resolving.
132
+
133
+ **What counts as lineage** (`gog_gmail_drafts_diff`): only a link from the candidate *to the original* — the original
134
+ draft's own `Message-Id` inside the candidate's `In-Reply-To`/`References`, or agreement on text **neither draft
135
+ quoted**. A **shared reply root is not lineage**: it links both drafts to a common *ancestor*, which every reply in a
136
+ thread has, so it is reported as corroboration and can raise the answer no higher than an explicitly weak `candidate`.
137
+ Quoted lines are excluded from the agreement metric because Apple Mail quotes on every reply — two unrelated replies
138
+ into one thread carry the same 30-line block, which scores 0.79 on a whole-body line metric while proving nothing.
139
+
140
+ **Client boilerplate is excluded for the same reason.** The salutation, the closing formula, the name under it and the
141
+ signature block are reproduced identically on every message a client composes, whatever the message says —
142
+ `Sent from my iPhone` is Apple Mail's own default. `Hi Jennifer,` + `Thanks,` + `Chris` + `Sent from my iPhone` is 4
143
+ lines and 43 characters, enough on its own to clear a line-and-character threshold, so counting it paired two genuinely
144
+ unrelated one-sentence notes as `confirmed` — and short confirmation plus a signature is the dominant shape of this
145
+ mailbox. `bodyAgreement` reports `quotedLinesIgnored` and `boilerplateLinesIgnored` separately so the arithmetic can be
146
+ redone. The divergence report still counts those lines: a merge that drops the signature really did drop it. The cost
147
+ of the tighter metric is that a genuine fork of a *one-sentence* note now comes back `candidate` rather than
148
+ `confirmed`, with `missing` naming the shortfall — the deliberate direction, since a missed fork costs a re-check and a
149
+ wrong one sends the wrong text to the wrong thread.
150
+
151
+ Cost is bounded and constant: **at most 2 extra gog invocations**, only on a call that already failed, never scaling
152
+ with the number of drafts. A non-404 failure spends nothing.
153
+
154
+ Once you know which draft survived, `gog_gmail_drafts_update` with `replyToThreadId` adopts it back onto the original
155
+ conversation **in one call, keeping its draft id** — gog resolves `In-Reply-To`/`References` from that thread's latest
156
+ message and reports them back under `threadingVerification` (`ok`, the effective headers, and a note). Two things that
157
+ block make that safe to rely on: an explicit reply target *replaces* the draft's stored lineage rather than merging with
158
+ it, and gog requires a body on every update, so the call **overwrites the whole body**. Diff first, merge by hand, then
159
+ write.
160
+
161
+ #### Guarding the merge: `forkSiblingDraftId`
162
+
163
+ Because gog requires a body on every update there is **no header-only edit**, so adopting a draft back onto its thread is
164
+ always a full body overwrite — the exact operation that destroys the paragraph living only in the other copy. In the
165
+ observed fork *neither* copy was a superset: the mail-client copy had lost a paragraph and the Gmail copy had gained
166
+ sentences.
167
+
168
+ The line-based comparison cuts the other way too: a paragraph re-wrapped at a different width no longer matches line
169
+ for line, so `none` means *no evidence was found*, never *proven unrelated*, and the notes say so.
170
+
171
+ Pass `forkSiblingDraftId` (the id of the other copy) and `gog_gmail_drafts_update` reads that draft **before** writing and
172
+ refuses the write — `DRAFT_CONTENT_LOSS`, nothing changed — if your body omits any line the sibling still holds, naming the
173
+ exact lines. Merge them in and retry, or pass `acceptContentLoss: true` to write anyway (the lines are still reported, and
174
+ the sibling itself is never touched). A check that cannot be *run* — sibling unfetchable, unparseable, or with no readable
175
+ body — refuses too, as `DRAFT_CONTENT_LOSS_UNCHECKED`: an unrun check is not a passed check.
176
+
177
+ An override reports the write that **actually happened**, never a predicted one: `contentLossCheck.written` is set from
178
+ the result, so a write that failed comes back saying it was attempted and saved nothing, and says the listed lines are
179
+ still in the sibling. Believing the opposite is the destructive case — a caller who thinks the merged body is stored may
180
+ delete or overwrite the copy that now holds the only version of those lines.
181
+
182
+ It **makes no fork claim**. You name the sibling; nothing searches for it, identical bodies would not prove a pairing and
183
+ divergent ones would not disprove one. `contentLossCheck.forkClaim` is always `null` and points at
184
+ `gog_gmail_drafts_diff`, which is the only tool here that weighs identity, lineage and ordering.
185
+
186
+ **Cost:** opt-in, exactly **one** extra gog invocation (a `drafts get` on the id you named — never a scan, never scaling
187
+ with the mailbox), and **zero** when the param is absent. A refusal spends that one call and skips the write entirely.
103
188
 
104
189
  ### Write
105
190
 
package/SKILL.md CHANGED
@@ -5,7 +5,7 @@ description: Use when the user asks to read, organize, draft, forward, autoreply
5
5
 
6
6
  # gogcli-mcp-gmail
7
7
 
8
- Extended Gmail MCP server via [gogcli](https://github.com/openclaw/gogcli) — 57 tools: 8 auth + 4 base Gmail + 45 extra dedicated Gmail tools.
8
+ Extended Gmail MCP server via [gogcli](https://github.com/openclaw/gogcli) — 58 tools: 8 auth + 4 base Gmail + 46 extra dedicated Gmail tools.
9
9
 
10
10
  - **Source:** [github.com/chrischall/gogcli-mcp](https://github.com/chrischall/gogcli-mcp)
11
11
 
@@ -73,12 +73,40 @@ Extended Gmail MCP server via [gogcli](https://github.com/openclaw/gogcli) — 5
73
73
  ### Drafts
74
74
  | Tool | What it does |
75
75
  |------|-------------|
76
- | `gog_gmail_drafts_list` | List drafts |
76
+ | `gog_gmail_drafts_list` | List drafts (+ free `origin`/`rootsOwnThread`) |
77
77
  | `gog_gmail_drafts_get` | Get a draft |
78
78
  | `gog_gmail_drafts_create` | Create a draft |
79
- | `gog_gmail_drafts_update` | Update a draft |
79
+ | `gog_gmail_drafts_update` | Update a draft; `replyToThreadId` re-threads it in place (same id) + `threadingVerification`; `forkSiblingDraftId` blocks a body overwrite that would drop the other copy's text |
80
80
  | `gog_gmail_drafts_delete` | Delete a draft |
81
- | `gog_gmail_drafts_send` | Send a draft |
81
+ | `gog_gmail_drafts_send` | Send a draft (404 → `DRAFT_FORKED`, or `GOOGLE_404_NOT_THE_DRAFT` if the draft is still listed) |
82
+ | `gog_gmail_drafts_diff` | Diff two drafts (body divergence, threading loss, fork verdict) |
83
+
84
+ A draft edited in a mail client is replaced, not updated: the old id 404s. `drafts_update` / `drafts_send` answer that
85
+ 404 with a `DRAFT_FORKED` report (what happened, the drafts that exist, what to do) instead of a bare `notFound`, at a
86
+ bounded cost of ≤2 extra gog calls. It never names a replacement — the 404'd draft cannot be fetched, so no lineage
87
+ signal can exist; use `drafts_diff` on a named pair for that. The 404 is attributed first: `replyToThreadId` and
88
+ `replyToMessageId` resolve their own entities and 404 identically, so if the draft id is still in the listing the answer
89
+ is `GOOGLE_404_NOT_THE_DRAFT` — no fork claimed, reply target named. That listing is capped at 20, so `listingEvidence`
90
+ says what it can carry: only a `complete-listing` (the window came back short, i.e. it covered the folder) claims the
91
+ draft is gone; `capped-listing` and `listing-unavailable` say in words that they establish nothing. A reply target, when
92
+ one was passed, is echoed and explained first.
93
+
94
+ `drafts_diff` confirms a pairing only on a link from the candidate **to the original** (its `Message-Id` in the
95
+ candidate's `In-Reply-To`/`References`) or on agreement over text **neither draft quoted and neither client generated**
96
+ — the salutation, closing formula, name and signature block are excluded alongside quoting, because a client writes them
97
+ identically on every message (`Hi Jennifer,` + `Thanks,` + `Chris` + `Sent from my iPhone` is 4 lines and 43 characters
98
+ of pure apparatus). A shared reply root is corroboration only — every reply in a thread has one — and `none` means no
99
+ evidence was found, not "unrelated". To adopt the survivor back onto the thread, call
100
+ `drafts_update` with `replyToThreadId`: same draft id, gog resolves the reply headers, `threadingVerification` reports
101
+ whether it worked. It rewrites the whole body, so diff and merge first.
102
+
103
+ gog requires a body on every update, so there is no header-only edit and every adoption overwrites the body. Pass
104
+ `forkSiblingDraftId` (the other copy's id) and the update reads that draft first — one extra gog call, zero when the param
105
+ is absent — and refuses to write (`DRAFT_CONTENT_LOSS`, nothing changed) if your body drops a line the sibling holds,
106
+ naming the lines; `acceptContentLoss: true` overrides, and `contentLossCheck.written` then reports whether the write it
107
+ authorised actually succeeded — a failed write says so and says nothing was saved. A check that could not run refuses too
108
+ (`DRAFT_CONTENT_LOSS_UNCHECKED`). It compares text only and never claims one draft replaced another — that is
109
+ `drafts_diff`'s job.
82
110
 
83
111
  ### Write
84
112
  | Tool | What it does |