This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git
The following commit(s) were added to refs/heads/main by this push:
new 0951cab docs(gmail): strongly prefer oauth-draft over the claude.ai
Gmail MCP (#465)
0951cab is described below
commit 0951cab1c8658fc681f215e5e99afa04d5e6c4df
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sun Jun 7 10:56:34 2026 +0200
docs(gmail): strongly prefer oauth-draft over the claude.ai Gmail MCP (#465)
The claude.ai Gmail MCP create_draft started (2026-06-05) silently
rewriting every bare URL in the draft body into a Google tracking
redirect (https://www.google.com/url?q=...&source=gmail&...), baked into
the stored MIME (text/plain and text/html). Sent messages then carry the
redirect instead of the canonical link — leaking click metadata to a
third party and corrupting reporter-facing paste-ready blocks (e.g.
ASF-security relays pasted onto GHSA advisories) and CVE/advisory/PR
links.
Flip the drafting-backend preference to oauth_curl (oauth-draft), which
builds its own RFC822 MIME and preserves URLs verbatim, and add a
prominent privacy-warning section. The claude.ai Gmail MCP is now
documented as discouraged — a last-resort fallback only when oauth_curl
credentials are unavailable AND the body contains no URLs. Updates the
canonical draft-backends.md plus every cross-reference that asserted the
MCP was the default/recommended backend.
---
skills/security-cve-allocate/SKILL.md | 11 +-
skills/security-issue-import/SKILL.md | 8 +-
skills/security-issue-invalidate/SKILL.md | 11 +-
skills/security-issue-sync/apply-and-push.md | 14 +-
skills/security-issue-sync/gather.md | 3 +-
skills/security-issue-sync/signals-to-actions.md | 9 +-
tools/gmail/draft-backends.md | 181 +++++++++++++----------
tools/gmail/oauth-draft/README.md | 11 +-
tools/gmail/operations.md | 10 +-
tools/gmail/threading.md | 4 +-
tools/gmail/tool.md | 2 +-
11 files changed, 158 insertions(+), 106 deletions(-)
diff --git a/skills/security-cve-allocate/SKILL.md
b/skills/security-cve-allocate/SKILL.md
index e1ddd75..8ba3123 100644
--- a/skills/security-cve-allocate/SKILL.md
+++ b/skills/security-cve-allocate/SKILL.md
@@ -585,10 +585,13 @@ user to confirm. Numbered items:
**Never send.** Create a Gmail draft via the project's configured
drafting backend per
[`tools/gmail/draft-backends.md`](../../tools/gmail/draft-backends.md#how-the-skills-pick-a-backend).
- The default and recommended path is the `claude_ai_mcp` backend
- with `replyToMessageId` set to the chronologically-last message ID
- on the inbound thread (resolve it via `get_thread`); the opt-in
- `oauth_curl` backend uses `threadId` instead. Resolve the
+ The **preferred** path is the `oauth_curl` backend (it preserves
+ URLs verbatim), which uses `threadId`. The `claude_ai_mcp` backend
+ is **discouraged** because it rewrites embedded URLs into Google
+ tracking redirects (see
[`draft-backends.md`](../../tools/gmail/draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects));
if used as a
+ credentials-missing fallback, its `replyToMessageId` is the
+ chronologically-last message ID on the inbound thread (resolve it
+ via `get_thread`). Resolve the
`threadId` from the tracker's *security-thread* body field
(for Airflow, *"Security mailing list thread"*); subject is always
`Re: <root subject of the inbound report>`, never fabricated.
diff --git a/skills/security-issue-import/SKILL.md
b/skills/security-issue-import/SKILL.md
index 3070d71..103261a 100644
--- a/skills/security-issue-import/SKILL.md
+++ b/skills/security-issue-import/SKILL.md
@@ -1621,12 +1621,14 @@ For each confirmed `Report` or forwarder-relayed
candidate:
created on the inbound Gmail thread** via the project's configured
drafting backend per
[`tools/gmail/draft-backends.md`](../../tools/gmail/draft-backends.md#how-the-skills-pick-a-backend).
- The default `claude_ai_mcp` backend resolves the candidate's
- chronologically-last message ID (call
+ The preferred `oauth_curl` backend uses `--thread-id` directly and
+ preserves URLs verbatim. The `claude_ai_mcp` backend is discouraged
+ because it rewrites embedded URLs into Google tracking redirects
+ (see
[`draft-backends.md`](../../tools/gmail/draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects));
as a credentials-missing fallback
+ it resolves the candidate's chronologically-last message ID (call
`mcp__claude_ai_Gmail__get_thread(threadId=<candidate>,
messageFormat='MINIMAL')` and take `messages[-1].id`) and passes
it to `mcp__claude_ai_Gmail__create_draft` as `replyToMessageId`.
- The opt-in `oauth_curl` backend uses `--thread-id` directly.
Surface in the proposal which backend was used and which path the
draft took (thread-attached vs subject fallback).
diff --git a/skills/security-issue-invalidate/SKILL.md
b/skills/security-issue-invalidate/SKILL.md
index b6a43be..0ed3e77 100644
--- a/skills/security-issue-invalidate/SKILL.md
+++ b/skills/security-issue-invalidate/SKILL.md
@@ -677,11 +677,12 @@ the **recipient** and the **body shape**.
4. **Backend selection:** use the project's configured
drafting backend per
[`tools/gmail/draft-backends.md`](../../tools/gmail/draft-backends.md#how-the-skills-pick-a-backend).
- Default is `claude_ai_mcp` with `replyToMessageId` thread
- attachment; the opt-in `oauth_curl` backend is used when
- `tools.gmail.draft_backend: oauth_curl` is set and
- credentials are on disk (default path
- `~/.config/apache-magpie/gmail-oauth.json`).
+ Prefer `oauth_curl` (credentials at default path
+ `~/.config/apache-magpie/gmail-oauth.json`); it preserves URLs
+ verbatim. The `claude_ai_mcp` backend is discouraged because it
+ rewrites embedded URLs into Google tracking redirects (see
+
[`draft-backends.md`](../../tools/gmail/draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects))
— use it only when `oauth_curl`
+ credentials are missing AND the body has no links.
5. **Existing-draft check.** Before drafting, scan the inbound
thread for an existing pending draft per the
[*Detecting drafts that already exist on a
thread*](../../tools/gmail/draft-backends.md#detecting-drafts-that-already-exist-on-a-thread)
diff --git a/skills/security-issue-sync/apply-and-push.md
b/skills/security-issue-sync/apply-and-push.md
index 2fa5136..c69cbf7 100644
--- a/skills/security-issue-sync/apply-and-push.md
+++ b/skills/security-issue-sync/apply-and-push.md
@@ -253,10 +253,14 @@ before moving on to the next item. Use:
starts returning `not found`.
- **Gmail draft:** create via the project's configured drafting
backend per
[`tools/gmail/draft-backends.md`](../../tools/gmail/draft-backends.md#how-the-skills-pick-a-backend).
- The default and recommended backend is `claude_ai_mcp` with
- thread attachment via `replyToMessageId`. Per-backend call shape:
-
- - **`claude_ai_mcp`** (default) — first call
+ The **preferred** backend is `oauth_curl` — it preserves URLs in
+ the body verbatim. The `claude_ai_mcp` backend is **discouraged**
+ because it silently rewrites embedded URLs into Google tracking
+ redirects (see
[`draft-backends.md`](../../tools/gmail/draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects));
use it only when
+ `oauth_curl` credentials are missing AND the body has no links.
+ Per-backend call shape:
+
+ - **`claude_ai_mcp`** (discouraged — rewrites URLs) — first call
`mcp__claude_ai_Gmail__get_thread(threadId=<from Step 1c>,
messageFormat='MINIMAL')` to resolve the chronologically-last
message ID; then call `mcp__claude_ai_Gmail__create_draft` with
@@ -264,7 +268,7 @@ before moving on to the next item. Use:
and `replyToMessageId=<that message id>`. The draft attaches to
the inbound thread on the sender's Gmail and surfaces in both the
conversation view and the global Drafts folder.
- - **`oauth_curl`** (opt-in for users who set
+ - **`oauth_curl`** (preferred — for users who set
`tools.gmail.draft_backend: oauth_curl` and have credentials at
`tools.gmail.oauth_credentials_path` /
`$GMAIL_OAUTH_CREDENTIALS` / default
diff --git a/skills/security-issue-sync/gather.md
b/skills/security-issue-sync/gather.md
index 734ba87..58253e3 100644
--- a/skills/security-issue-sync/gather.md
+++ b/skills/security-issue-sync/gather.md
@@ -536,7 +536,8 @@ draft on the notification thread closes the loop:
changed in response, the CVE-tool URL, and one line asking
the reviewer to re-review when they have a moment. Same
backend selection as the reporter-draft path in Step 5d
- (`claude_ai_mcp` default, `oauth_curl` opt-in). Always a
+ (`oauth_curl` preferred, `claude_ai_mcp` discouraged — it rewrites
+ embedded URLs; see
[`draft-backends.md`](../../tools/gmail/draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects)).
Always a
draft — never sent.
Restrict this draft to comments that mapped cleanly to a
diff --git a/skills/security-issue-sync/signals-to-actions.md
b/skills/security-issue-sync/signals-to-actions.md
index 0003f5d..3b69957 100644
--- a/skills/security-issue-sync/signals-to-actions.md
+++ b/skills/security-issue-sync/signals-to-actions.md
@@ -1101,10 +1101,11 @@ will change and *why*. Group them by category:
thread.
**Never send.** Always create a draft. Prefer attaching it to the
- inbound mail thread (the default `claude_ai_mcp` backend resolves
- the latest message ID from the inbound `threadId` and passes it as
- `replyToMessageId`; the opt-in `oauth_curl` backend uses
- `--thread-id` directly). If Step 1c could not resolve a `threadId`,
+ inbound mail thread (the preferred `oauth_curl` backend uses
+ `--thread-id` directly and preserves URLs verbatim; the discouraged
+ `claude_ai_mcp` backend resolves the latest message ID from the
+ inbound `threadId` and passes it as `replyToMessageId` but rewrites
+ embedded URLs — see
[`draft-backends.md`](../../tools/gmail/draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects)).
If Step 1c could not resolve a `threadId`,
fall back to a subject-matched draft (thread-attachment parameter
omitted, `subject: Re: <root subject>`) per the threading rule in
[`tools/gmail/threading.md`](../../tools/gmail/threading.md).
diff --git a/tools/gmail/draft-backends.md b/tools/gmail/draft-backends.md
index c9c72e4..3f3b9e3 100644
--- a/tools/gmail/draft-backends.md
+++ b/tools/gmail/draft-backends.md
@@ -3,7 +3,8 @@
**Table of Contents** *generated with
[DocToc](https://github.com/thlorenz/doctoc)*
- [Gmail drafting backends](#gmail-drafting-backends)
- - [Why there are two](#why-there-are-two)
+ - [Privacy warning — the claude.ai Gmail MCP rewrites embedded URLs into
Google tracking
redirects](#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects)
+ - [Why `oauth_curl` is the preferred
backend](#why-oauth_curl-is-the-preferred-backend)
- [How the skills pick a backend](#how-the-skills-pick-a-backend)
- [Detecting drafts that already exist on a
thread](#detecting-drafts-that-already-exist-on-a-thread)
- [Limitations that apply to both
backends](#limitations-that-apply-to-both-backends)
@@ -26,70 +27,89 @@ user in `.apache-magpie-overrides/user.md` under
| Backend | Value | Thread attach? | Setup |
|---|---|---|---|
-| claude.ai Gmail MCP | `claude_ai_mcp` (default, recommended) | **yes** — via
`replyToMessageId` (a message ID resolved from the inbound thread) | none —
works as soon as the Gmail connector is authenticated on claude.ai |
-| OAuth + `curl` script | `oauth_curl` | **yes** — via `threadId` (and
explicit `In-Reply-To` / `References` headers) | one-time Google OAuth client +
refresh-token setup, automated via `uv run --project
<framework>/tools/gmail/oauth-draft oauth-draft-setup` — see
[`oauth-draft/README.md`](oauth-draft/README.md) |
+| OAuth + `curl` script | `oauth_curl` (**strongly preferred — use this**) |
**yes** — via `threadId` (and explicit `In-Reply-To` / `References` headers) |
one-time Google OAuth client + refresh-token setup, automated via `uv run
--project <framework>/tools/gmail/oauth-draft oauth-draft-setup` — see
[`oauth-draft/README.md`](oauth-draft/README.md) |
+| claude.ai Gmail MCP | `claude_ai_mcp` (**discouraged — do not use; see
privacy warning**) | **yes** — via `replyToMessageId` (a message ID resolved
from the inbound thread) | none — works as soon as the Gmail connector is
authenticated on claude.ai, **but silently rewrites embedded URLs into Google
tracking redirects (see below)** |
Both backends create **drafts** — never send. The human review-and-send
step is still required before any outbound message leaves the user's
Gmail.
-## Why there are two
-
-The first-party claude.ai Gmail MCP is easy to set up and now exposes
-everything the skills need for reading, searching, listing drafts,
-**and creating thread-attached drafts** via `replyToMessageId`. It is
-the default and the recommended backend for almost every adopter.
-
-Historically the MCP's `create_draft` tool did not plumb through a
-`threadId` parameter, which forced thread-attached drafts down the
-`oauth_curl` path. That gap closed when the MCP added
-`replyToMessageId` (a Gmail *message* ID — Gmail attaches the draft
-to the conversation containing that message). The `oauth_curl`
-backend remains in the toolchain because it offers two capabilities
-the MCP still does not:
-
-- **`threadId`-keyed draft creation** — useful when only a `threadId`
- is on hand (e.g. from an old tracker rollup) and re-fetching the
- thread to extract the latest message ID is not worth a round-trip.
-- **Bulk read/modify operations** — `oauth-draft-mark-read`
- (label-modify on a query result set) has no MCP equivalent.
-
-If you do not need either of those, **stay on the default
-`claude_ai_mcp` backend**. The OAuth setup, refresh-token rotation,
-and credentials-on-disk overhead is no longer required for thread
-attachment alone.
+## Privacy warning — the claude.ai Gmail MCP rewrites embedded URLs into
Google tracking redirects
+
+> **Use `oauth_curl`. Do not use the claude.ai Gmail MCP `create_draft`
+> for any draft whose body contains URLs.**
+
+As of **2026-06-05**, the claude.ai Gmail MCP `create_draft` tool
+**silently rewrites every bare URL in the draft body** into a Google
+tracking-redirect wrapper of the form:
+
+```text
+https://www.google.com/url?q=<original-url>&source=gmail&ust=<timestamp>&sa=E
+```
+
+The rewrite is **baked into the stored draft MIME** — both the
+`text/plain` and `text/html` parts — not merely a display artifact
+(confirmed by reading the draft back via `drafts.get?format=raw`). So
+when the message is sent, the recipient receives the Google redirect
+instead of the link the skill wrote. This is unacceptable for the
+project's correspondence:
+
+- **Privacy / tracking.** Every link the recipient clicks is routed
+ first through `google.com/url`, leaking click metadata (which
+ recipient, which link, when) to a third party — on security-sensitive
+ correspondence the project has no business funnelling through
+ Google's redirector.
+- **Reporter-facing and relay correctness.** Many drafts carry a
+ reporter-voice **paste-ready block** (e.g. an ASF-security relay the
+ recipient pastes onto a GHSA advisory). The rewrite would paste a
+ `google.com/url?q=...` redirect onto a public advisory instead of the
+ clean canonical URL.
+- **Integrity of CVE / advisory links.** Advisory URLs, CVE-record
+ URLs, and PR links must reach recipients verbatim; a wrapped link is
+ wrong on its face and erodes trust in the message.
+
+The `oauth_curl` backend builds the message with a plain RFC822
+`EmailMessage`, so **URLs are preserved verbatim** — no rewriting, no
+third-party redirector. That is the decisive reason `oauth_curl` is the
+preferred backend for **all** drafting, not just the threadId / bulk
+cases.
+
+If `oauth_curl` credentials are genuinely unavailable and a draft must
+be created via the MCP, the body **must not contain URLs** — inline the
+relevant text and tell the user to add the links by hand before
+sending, or (better) set up `oauth_curl` first.
+
+## Why `oauth_curl` is the preferred backend
+
+The `oauth_curl` script talks directly to the Gmail REST API on a
+user-provided OAuth refresh token and builds its own MIME, which gives
+it three advantages over the claude.ai Gmail MCP:
+
+- **Verbatim URLs (the decisive one)** — it does not rewrite links into
+ Google tracking redirects; see the privacy warning above.
+- **`threadId`-keyed draft creation** — attaches by `threadId`
+ directly; for a brand-new, non-reply message, omit `--thread-id` and
+ pass `--no-reply-headers`.
+- **Bulk read/modify + delete** — `oauth-draft-mark-read`
+ (label-modify on a query result set) has no MCP equivalent, and
+ `oauth_curl` is the only backend that can delete drafts via the
+ Gmail API.
+
+The one-time cost is a Google OAuth client + refresh-token setup
+(automated via `oauth-draft-setup`; see
+[`oauth-draft/README.md`](oauth-draft/README.md)). Treat the
+credentials file like an SSH key. It is worth it: `oauth_curl` is the
+only backend that keeps the project's outbound links clean and
+untracked.
## How the skills pick a backend
Every skill step that says *"create a Gmail draft via
-`mcp__claude_ai_Gmail__create_draft`"* means *"create a draft via the
-project's configured drafting backend"*.
+`mcp__claude_ai_Gmail__create_draft`"* is shorthand for *"create a draft
+via the project's configured drafting backend"* — which should be
+`oauth_curl`.
-**Default — `claude_ai_mcp` with `replyToMessageId`.** This is the
-recommended path. Resolution:
-
-1. **Resolve the latest message ID on the inbound thread.** Call
- `mcp__claude_ai_Gmail__get_thread(threadId=<inbound>,
- messageFormat='MINIMAL')` and take the `id` of the
- chronologically-last message. The tracker stores `threadId` (per
- the existing *security-thread* body field convention); the
- message-ID resolution is one extra round-trip and the skills
- absorb it.
-2. **Create the draft.** Call
- `mcp__claude_ai_Gmail__create_draft(..., replyToMessageId=<that
- message id>)`. The draft attaches to the inbound thread on the
- sender's Gmail and surfaces in both the conversation view and the
- global Drafts folder.
-3. **Fallback — omit `replyToMessageId`.** When the latest message
- cannot be resolved (thread archived, deleted, or stale `threadId`),
- create the draft with `replyToMessageId` omitted and rely on
- subject-matched threading (`Re: <root subject>` plus the recipient's
- own `In-Reply-To` / `References` chain). See
-
[`threading.md`](threading.md#fallback--subject-matched-draft-when-replytomessageid-is-unavailable).
-
-**Opt-in — `oauth_curl` for the cases the MCP cannot serve.** A user
-who has explicitly set `tools.gmail.draft_backend: oauth_curl` and
-who has a credentials file on disk:
+**Preferred — `oauth_curl`.** Resolution:
1. **Probe for `oauth_curl` credentials** in this order:
- `tools.gmail.oauth_credentials_path` from
@@ -97,34 +117,45 @@ who has a credentials file on disk:
- the `$GMAIL_OAUTH_CREDENTIALS` environment variable;
- the default path `~/.config/apache-magpie/gmail-oauth.json`.
- The probe is a single `test -f <path>` — actually parsing the file
- or doing a token-refresh probe at this stage would burn HTTP
- round-trips on every draft.
-2. **If credentials are found → use `oauth_curl`.** Invoke
+ The probe is a single `test -f <path>`.
+2. **Create the draft.** Invoke
`uv run --project <framework>/tools/gmail/oauth-draft oauth-draft-create`
- with `--thread-id`, `--to`, `--cc`, `--subject`, `--body-file` —
- see [`oauth-draft/README.md`](oauth-draft/README.md) for the full
- shape.
-3. **If credentials are not found despite the user opting in →
- fall back to `claude_ai_mcp` and surface the missing-credentials
- warning.** Do not silently swallow the configuration mismatch.
+ with `--to`, `--cc`, `--subject`, `--body-file`, and either
+ `--thread-id <threadId>` (reply on an existing thread — the tracker
+ stores `threadId` per the *security-thread* body field convention)
+ or, for a brand-new message, `--no-reply-headers` with no
+ `--thread-id`. See [`oauth-draft/README.md`](oauth-draft/README.md)
+ for the full shape. URLs in the body are preserved verbatim.
+
+**Last-resort fallback — `claude_ai_mcp`, only when `oauth_curl`
+credentials are unavailable.** Subject to the hard constraint in the
+[privacy
warning](#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects)
+above: **the body must not contain URLs.** When used:
+
+1. **Resolve the latest message ID on the inbound thread** (for
+ threading): call `mcp__claude_ai_Gmail__get_thread(threadId=<inbound>,
+ messageFormat='MINIMAL')` and take the `id` of the
+ chronologically-last message.
+2. **Create the draft** with
+ `mcp__claude_ai_Gmail__create_draft(..., replyToMessageId=<that
+ message id>)`, or omit `replyToMessageId` to fall back to
+ subject-matched threading (see
+
[`threading.md`](threading.md#fallback--subject-matched-draft-when-replytomessageid-is-unavailable)).
+3. **Warn the user** that the MCP backend was used because `oauth_curl`
+ credentials were missing, and that any links were omitted / must be
+ added by hand. Do not silently swallow the configuration mismatch.
The skills **surface which backend was used** in the proposal / recap
-so the user can tell at a glance how the draft is threaded. The format
-is one line:
-
-> *Draft created via `claude_ai_mcp` (replyToMessageId-attached to
-> message `<msg-id-prefix>...` on thread `<thread-id-prefix>...`)*
-
-or
+so the user can tell at a glance how the draft is threaded:
> *Draft created via `oauth_curl` (threadId-attached on
> `<thread-id-prefix>...`)*
-or, when fallback kicks in:
+or, only when credentials were missing:
-> *Draft created via `claude_ai_mcp` (subject-matched fallback —
-> `<reason: thread archived / latest message unresolved / etc.>`)*
+> *Draft created via `claude_ai_mcp` (URLs omitted per privacy policy —
+> `oauth_curl` credentials not found; threaded via
+> `<replyToMessageId / subject-matched fallback>`)*
## Detecting drafts that already exist on a thread
diff --git a/tools/gmail/oauth-draft/README.md
b/tools/gmail/oauth-draft/README.md
index d122648..14e73a2 100644
--- a/tools/gmail/oauth-draft/README.md
+++ b/tools/gmail/oauth-draft/README.md
@@ -41,10 +41,13 @@ user-provided OAuth refresh token. Three console scripts:
| `oauth-draft-create` | Create a Gmail draft with `threadId` attachment. (As
of the `replyToMessageId` parameter on the claude.ai Gmail MCP `create_draft`,
the MCP can also produce thread-attached drafts — see
[`../draft-backends.md`](../draft-backends.md). This script remains useful when
you have a `threadId` on hand and would rather skip the extra `get_thread`
round-trip the MCP path requires, and is the only path that lets the skills
delete drafts via the Gmail API afterwards.) |
| `oauth-draft-mark-read` | Bulk-modify Gmail threads matching a search query
(default: mark as read by removing the `UNREAD` label). No MCP equivalent
today. |
-The default and recommended drafting backend is the claude.ai Gmail
-MCP — see [`../draft-backends.md`](../draft-backends.md) for when to
-opt into `oauth_curl`. This README covers local-setup, day-to-day
-invocation, and the project's own test/lint workflow.
+The **strongly preferred** drafting backend is this `oauth_curl` tool:
+the claude.ai Gmail MCP `create_draft` silently rewrites embedded URLs
+into Google tracking redirects, so it must not be used for drafts that
+contain links — see
+[`../draft-backends.md`](../draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects).
+This README covers local-setup, day-to-day invocation, and the
+project's own test/lint workflow.
## Run
diff --git a/tools/gmail/operations.md b/tools/gmail/operations.md
index 1f3c14b..12e81fc 100644
--- a/tools/gmail/operations.md
+++ b/tools/gmail/operations.md
@@ -163,11 +163,17 @@ backend is here.
| Backend | Value | Thread attach? |
|---|---|---|
-| claude.ai Gmail MCP | `claude_ai_mcp` (default) | **yes** — via
`replyToMessageId` |
-| OAuth + `curl` | `oauth_curl` | **yes** — via `threadId` |
+| OAuth + `curl` | `oauth_curl` (**preferred**) | **yes** — via `threadId` |
+| claude.ai Gmail MCP | `claude_ai_mcp` (**discouraged — rewrites URLs; see
[`draft-backends.md`](draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects)**)
| **yes** — via `replyToMessageId` |
### Create draft — `claude_ai_mcp` backend
+> **Discouraged.** This backend silently rewrites embedded URLs into
+> Google tracking redirects (see
+>
[`draft-backends.md`](draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects)).
+> Prefer `oauth_curl`; use this only when `oauth_curl` credentials are
+> unavailable AND the body contains no URLs.
+
The claude.ai Gmail MCP's `create_draft` tool accepts a
`replyToMessageId` parameter (a Gmail *message* ID, not a thread ID).
When supplied, Gmail attaches the draft to the conversation that
diff --git a/tools/gmail/threading.md b/tools/gmail/threading.md
index 0a366dc..dbcaaca 100644
--- a/tools/gmail/threading.md
+++ b/tools/gmail/threading.md
@@ -30,8 +30,8 @@ Both supported drafting backends now provide
thread-attachment — see
| Backend | Thread attach | Mechanism |
|---|---|---|
-| `claude_ai_mcp` (default) | **yes** | `replyToMessageId` — the message ID of
the chronologically-last message on the inbound thread |
-| `oauth_curl` (opt-in) | **yes** | `threadId` plus explicit `In-Reply-To` /
`References` headers |
+| `oauth_curl` (**preferred**) | **yes** | `threadId` plus explicit
`In-Reply-To` / `References` headers |
+| `claude_ai_mcp` (discouraged — see
[`draft-backends.md`](draft-backends.md#privacy-warning--the-claudeai-gmail-mcp-rewrites-embedded-urls-into-google-tracking-redirects))
| **yes** | `replyToMessageId` — the message ID of the chronologically-last
message on the inbound thread |
The two threading paths available to the skills, in preferred order:
diff --git a/tools/gmail/tool.md b/tools/gmail/tool.md
index 0ea65db..c20b999 100644
--- a/tools/gmail/tool.md
+++ b/tools/gmail/tool.md
@@ -40,7 +40,7 @@ file in this directory:
| Capability | File | What it covers |
|---|---|---|
| MCP operations | [`operations.md`](operations.md) | The
`mcp__claude_ai_Gmail__*` tool catalogue (search, read, draft, list) + the
no-update / no-delete limitation |
-| Drafting backends | [`draft-backends.md`](draft-backends.md) | The two
drafting backends (claude.ai Gmail MCP — default and recommended, with thread
attachment via `replyToMessageId`; OAuth + `curl` — opt-in for bulk operations
and `threadId`-keyed drafts), why both exist, and the
`tools.gmail.draft_backend` config knob |
+| Drafting backends | [`draft-backends.md`](draft-backends.md) | The two
drafting backends (OAuth + `curl` — **strongly preferred**, preserves URLs
verbatim; claude.ai Gmail MCP — **discouraged**: silently rewrites embedded
URLs into Google tracking redirects), why `oauth_curl` is preferred, and the
`tools.gmail.draft_backend` config knob |
| Threading | [`threading.md`](threading.md) | The *"always attach the draft
to the inbound thread when possible"* rule — how drafts stay on the inbound
thread across reporter replies, ASF-security relays, PMC credit questions,
follow-ups |
| ASF-security-relay drafting | [`asf-relay.md`](asf-relay.md) | Special-case
drafting rules when the inbound report is relayed by the ASF security team
rather than sent by the external reporter directly |
| Search queries | [`search-queries.md`](search-queries.md) | Gmail
search-operator cheat-sheet + skill-specific query templates
(candidate-listing, reporter-thread lookup, CVE-review comments) |