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) |

Reply via email to