blob: db2e4790dec6c2a535e3c3f1e2035ed808b77101 [file] [view]
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Gmail — MCP operation catalogue](#gmail--mcp-operation-catalogue)
- [Pre-flight](#pre-flight)
- [Read](#read)
- [Search threads](#search-threads)
- [Get thread](#get-thread)
- [Write — drafts only, never send](#write--drafts-only-never-send)
- [Drafting backends](#drafting-backends)
- [Create draft — `claude_ai_mcp` backend](#create-draft--claude_ai_mcp-backend)
- [Create draft — `oauth_curl` backend](#create-draft--oauth_curl-backend)
- [Hard rules that apply to both backends](#hard-rules-that-apply-to-both-backends)
- [List drafts](#list-drafts)
- [Hard limitation — no update, no delete](#hard-limitation--no-update-no-delete)
- [Confidentiality of drafts](#confidentiality-of-drafts)
- [Error handling](#error-handling)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->
# Gmail — MCP operation catalogue
Shared reference for the `mcp__claude_ai_Gmail__*` tool calls the
skills make against the active user's Gmail account. The skills
reference this file for the call shape and for the limitations that
constrain their flow.
Placeholder convention used below:
- `<security-list>` — the project's private security mailing list (the
list the user's Gmail account subscribes to). For Airflow, the value
is `<project manifest>.security_list` =
`<security-list>`; see
[`../../<project-config>/project.md`](../../<project-config>/project.md#mailing-lists).
- `<threadId>` — an opaque Gmail thread identifier.
## Pre-flight
Every skill that talks to Gmail does a one-call pre-flight in Step 0
to confirm the MCP is reachable and the user's account subscribes to
the project's security list:
```
mcp__claude_ai_Gmail__search_threads(
query='list:<security-list-domain>',
pageSize=1,
)
```
Substitute the `<security-list-domain>` with the domain suffix of the
project manifest's `security_list` (for
`<security-list>`, the value is
`<security-list-domain>`).
A non-empty result means Gmail is connected and indexed; an empty
result means either the account does not subscribe, or the MCP is
misconfigured. In either case the skill stops and asks the user to
fix the setup rather than guessing.
## Read
### Search threads
```
mcp__claude_ai_Gmail__search_threads(
query='<gmail search expression>',
pageSize=<N>,
)
```
Returns an array of `{threadId, snippet, …}` objects. Use `pageSize`
deliberately — some skills (e.g. `sync-security-issue`) impose a
hard Gmail-call budget per issue to avoid running up the MCP quota
on many-tracker sweeps.
For the search expression syntax and the canonical query templates
the skills use, see [`search-queries.md`](search-queries.md).
### Get thread
```
mcp__claude_ai_Gmail__get_thread(
threadId='<threadId>',
messageFormat='FULL_CONTENT', # or 'METADATA' when bodies are not needed
)
```
Returns the full message history of a thread. Body reads are
expensive — most skills filter candidates down on metadata first and
only fetch bodies for the narrow set that actually warrants it
(`import-security-issue` does this explicitly at Step 3).
## Write — drafts only, never send
### Drafting backends
Draft creation runs through one of two backends, selected by the user
in [`config/user.md`](../../config/user.md) under
`tools.gmail.draft_backend`. The full comparison and rationale live
in [`draft-backends.md`](draft-backends.md); the call shape per
backend is here.
| Backend | Value | `threadId` attach? |
|---|---|---|
| claude.ai Gmail MCP | `claude_ai_mcp` (default) | **no** (see below) |
| OAuth + `curl` | `oauth_curl` | **yes** |
### Create draft — `claude_ai_mcp` backend
The claude.ai Gmail MCP's `create_draft` tool does **not** accept a
`threadId` parameter. The Gmail REST API supports it on
`drafts.create`, but the MCP does not plumb it through. Drafts
created via this backend always start a new conversation on the
**sender's** Gmail side; recipients' mail clients thread-attach via
subject + `In-Reply-To` / `References` matching, which usually works
but is not guaranteed.
```
mcp__claude_ai_Gmail__create_draft(
subject='Re: <root subject of the inbound message>',
to=['<primary>'],
cc=['<security-list>', ...],
body='<body>',
)
```
- **Subject is always `Re: <root subject>`**, never fabricated. A
drifted subject defeats subject-based threading on every client.
- **Never send.** The skills only *create* drafts; a human
review-and-send step is required before every outbound message.
- **Subject-fallback threading is the only path.** See the
[fallback rule](threading.md#fallback--subject-matched-draft-when-threadid-is-unavailable)
in `threading.md` for when this applies and when it does not.
When the user needs `threadId`-attached drafts (so the draft threads
on their own Gmail view and the drafts queue stays in lock-step with
the inbound thread), switch the backend to `oauth_curl` — see below.
### Create draft — `oauth_curl` backend
The `oauth-draft-create` console script (in
[`oauth-draft/`](oauth-draft/README.md)) creates drafts by talking
directly to the Gmail REST API with a user-provided OAuth refresh
token. It sets `threadId` on the Gmail API call **and** populates
`In-Reply-To` / `References` from the thread's last message, so every
client threads consistently.
```bash
uv run --project <framework>/tools/gmail/oauth-draft oauth-draft-create \
--thread-id <gmail-threadId> \
--to reporter@example.com \
--cc <security-list> \
--subject "Re: <root subject>" \
--body-file /tmp/body.txt
```
See [`oauth-draft/README.md`](oauth-draft/README.md) for one-time
setup (creating the Google Cloud OAuth client, obtaining a refresh
token, and populating the credentials file) and for the full flag
list.
Every bullet from the `claude_ai_mcp` section above applies to this
backend too — drafts only, never send; subject is always
`Re: <root subject>`; composition happens under user review.
### Hard rules that apply to both backends
- **Never send.**
- **Subject is always `Re: <root subject>`**, never fabricated.
- **Surface which backend was used** in the proposal / recap so the
user can tell at a glance whether the draft threads on their own
Gmail view (`oauth_curl`) or only on the recipient's
(`claude_ai_mcp` subject fallback).
- **Record the backend + draft ID on the tracker's status rollup**
so subsequent sync passes can find and (optionally) re-verify the
draft.
For the ASF-security-relay special case (different `to` /
`cc` shape), see [`asf-relay.md`](asf-relay.md).
### List drafts
```
mcp__claude_ai_Gmail__list_drafts(
query='<optional filter>', # e.g. 'list:<security-list-domain>'
)
```
Used by `sync-security-issue` to verify that a draft flagged as stale
in a previous status comment still exists before carrying the flag
forward. See the *"self-replicating stale-draft flag"* paragraph in
that skill.
## Hard limitation — no update, no delete
The Gmail MCP exposes **`create`, `list`, and `read` only** for
drafts. There is no `update_draft` and no `delete_draft` tool. The
skills must treat every existing draft as immutable:
- If a correction is needed, surface the existing draft's `draftId`
to the user with an explicit *"discard this one manually in Gmail"*
note, then create a fresh draft with the corrected content.
- Do **not** silently create a second draft that shadows the first —
that leaves two near-identical drafts in the user's Gmail and
invariably one of them gets sent by accident.
- On the sync skill's stale-draft-forward-flagging path: verify the
`draftId` still exists via `list_drafts` before copying the flag
into a new sync status comment. Without verification, a one-time
flag self-replicates forever.
## Confidentiality of drafts
Drafts land in the user's personal Gmail account and are visible only
to that user until sent. Draft content may reference the private
tracker's URL (reporter is on the private thread and is expected to
keep it confidential), but anything destined for a public list must
obey the confidentiality rules in
[`../../AGENTS.md`](../../AGENTS.md) — no `<tracker>` URLs, no CVE
IDs before publication, no *"security fix"* leakage.
## Error handling
If any Gmail call fails (MCP unreachable, 429, transient 5xx),
**stop** and report the failure. The skills explicitly budget Gmail
calls; silently retrying turns one flaky call into a quota-exhaustion
storm.