mailwarden
mailwarden
안정적이고 네이티브한 Gmail MCP 서버 — AI 어시스턴트를 위한 전체 사서함 트리아지를 제공하며, 다른 어떤 Gmail MCP 서버도 제공하지 않는 기능인 사서함 측 snooze를 포함합니다.
주요 기능
Snooze — Gmail MCP 서버 중 유일한 사서함 측 snooze. 지금 스레드를 보관 처리하고, 지정한 날짜에 받은편지함에 다시 떠오르게 합니다. 날짜 기반 레이블 + 스윕(sweep) 방식으로 구현되어 어떤 클라이언트에서도 동작하고, Gmail 자체에서도 표시되며, 재시작 후에도 유지됩니다. (다른 서버가 제공하는 "snooze"는 로컬 알림 목록일 뿐입니다 — 메일이 받은편지함을 떠나거나 다시 들어오지 않습니다.)
신뢰할 수 있는 검색. 모든 스레드 검색이 거치는 Gmail의
threads.list는 오래된 스레드 수준 읽음 상태를 기준으로is:unread에 응답할 수 있습니다. 실제 사서함 하나에서 측정한 결과, 이 호출이 반환한 스레드 중 86%는 읽지 않은 메시지가 전혀 없었고, 두 번째 사서함에서는 불일치가 전혀 없었습니다. 직접 확인하기 전에는 현재 사서함이 어느 쪽인지 알 수 없으므로search는 모든 결과를 라이브 레이블과 대조해 재검증합니다.pageToken/nextPageToken으로 페이지네이션됩니다.확장 가능한 대량 작업.
bulk_modify는 쿼리와 일치하는 모든 메시지를 API 요청당 1000개 단위로 보관/레이블 처리합니다 — 전부 아니면 전무(all-or-nothing) 방식 대신 청크별 부분 성공을 보고합니다. snooze 스윕도 동일한 배치 경로를 사용합니다.구조화된 출력. 모든 도구는
outputSchema를 선언하고, 검증된structuredContent를 펜스 처리된(fenced) JSON 텍스트와 함께 반환합니다 — 클라이언트는 파싱을 추측할 필요가 없습니다. 실패도 구조화되어 있습니다.code와retryable플래그를 통해 클라이언트는 설명 문구를 읽지 않고도 "나중에 다시 시도"와 "재인증"을 구분할 수 있습니다.작은 공격 표면. 전송 도구 없음(프롬프트 인젝션된 메일의 유출 경로 차단), 선택적 읽기 전용 모드, 텔레메트리 없음, 기본적으로 열린 포트 없음, 심링크에 안전한 다운로드 펜싱, 인젝션 차단 출력. 그리고 보낼 수 있는 코드 경로 자체가 없습니다. 모든 Gmail 요청은
messages.send, 모든 초안 엔드포인트, 영구 삭제 및 전달 설정을 거부하는 이그레스(egress) 체크포인트를 통과합니다 — 손상되었거나 부주의한 호출자가 무엇을 요청하든 말입니다. 의도적인 예외 하나:unsubscribe/bulk_unsubscribe(manage 티어)는 메시지 자체 헤더에 명시된 수신 거부(opt-out) 엔드포인트에 연락합니다 — mailwarden이 도달하는 유일한 비(非)구글 호스트이며,read티어 배포는 외부 요청을 전혀 보내지 않습니다. 자세한 내용은 보안 및 개인정보 보호 및 수신 거부를 참조하세요.실제 메일에서도 정확하게 동작합니다. RFC 2047 헤더 디코딩(
=?UTF-8?B?…?=→ 읽을 수 있는 텍스트), 본문은 선언된 문자셋으로 디코딩(ISO-8859-1/Shift_JIS 메일도 글자 깨짐 없음), 429/5xx는 지수 백오프로 재시도합니다.
Related MCP server: Gmail MCP
왜 필요한가
사서함을 동기화하거나 캐시하는 커넥터는 실제 사서함보다 뒤처질 수 있습니다. Gmail 자체의 검색 인덱스도 때때로 정확하지 않습니다(아래 참조). mailwarden은 라이브 Gmail API에 직접 연결되며(캐시된 스냅샷 없음) 인덱스가 반환한 결과를 재검증하므로, 보이는 것이 실제 상태입니다. 범용 Gmail 기능 레이어입니다 — 자체 규칙/로직은 서버가 아니라 AI 클라이언트에 유지하세요.
search는 원시 API보다 한 단계 더 나아갑니다. Gmail의 threads.list 인덱스는 읽음 상태 연산자에 대해 해당 상태의 오래된 사본을 기준으로 응답할 수 있으므로 is:unread가 몇 주 전에 이미 다 읽은 스레드를 반환할 수 있습니다 — 측정한 한 사서함에서는 반환된 결과의 대다수가 그랬습니다. 모든 결과를 어차피 라이브로 가져오기 때문에 search는 명확한 조건자(is:unread/is:read/is:starred/in:inbox/category:…, 부정 포함)를 각 스레드의 실제 레이블과 대조해 재확인하고 인덱스의 오탐(false positive)을 걸러냅니다.
다른 Gmail MCP 서버와의 비교
대부분의 Gmail MCP 서버는 동일한 읽기/레이블/전송 기능을 제공합니다. 두 가지 기능은 여전히 mailwarden에만 고유합니다(사서함 측 snooze, 검색 재검증). 그리고 한 가지 의도적인 생략은 빈틈이 아니라 보안 기능입니다. Google 자체 서버도 보기보다 좁습니다. 초안(draft) 전용이며 휴지통, 필터, 수신 거부 기능이 없습니다.
기능 | mailwarden | ||||
사서함 측 snooze — 지금 보관 처리하고, 지정한 날짜/시간 또는 프리셋에 받은편지함에 다시 표시 | ✅ | — | — | — | — |
검색 결과 재검증 — 스레드 인덱스의 오탐을 라이브 레이블 기준으로 걸러냄 | ✅ | — | — | — | — |
쿼리 기반 스윕 / 대량 작업 — 검색이 반환한 모든 스레드를 한 번의 작업으로 처리 | ✅ 1000개/요청, 부분 성공 | — | ⚠️ 명시적 ID 배치 | — | ⚠️ 명시적 ID 배치 |
수신 거부 — 발신자별 개요 + RFC 8058 원클릭 수신 거부, 전송 권한 불필요 | ✅ | — | ⚠️ 헤더만 표시, 동작 없음 | — | — |
받은편지함 트리아지 개요 — 대기 중인 메일을 분류하는 단일 호출 | ✅ 발신자/레이블/경과 시간 + 헤더 신호 | — | — | ✅ 휴리스틱 플래그 + 통계 | — |
서버 측 필터 — 어시스턴트 개입 없이 트리아지를 계속 수행하는 규칙 | ✅ 전달(forwarding) 없음 | — | ✅ | — | ✅ |
전송 도구 없음 — 설계상 — 프롬프트 인젝션된 메일에는 유출 경로가 없음 | ✅ 작성(compose) 기능 자체 없음 | ⚠️ 초안 전용 | ❌ 전송 가능 | ❌ 전송 가능 | ❌ 전송 가능 |
최소 권한 도구 티어 — 활성화한 도구에서 파생되는 OAuth 범위 | ✅ | ⚠️ 범위 분할 | ⚠️ | — | ⚠️ 역방향: 부여된 범위에 따라 도구가 제한됨 |
저장 시 토큰 암호화(선택 사항) | ✅ AES-256-GCM | n/a(호스팅) | ✅ | — | — |
벤더 클라우드 없음 — 서버를 직접 운영 | ✅ | ❌ Google 호스팅 | ✅ | ✅ | ✅ |
구조화된 출력 — 모든 도구가 | ✅ | — | — | — | ⚠️ 도구 하나( |
2026년 8월 26일 기준 스냅샷이며, 각 프로젝트의 공개 문서와 소스에서 가져왔습니다. — = 제공되지 않음 / 문서화되지 않음. 각 열은 독자가 가장 먼저 찾게 될 서버들입니다 — Google의 퍼스트파티 서버, 가장 규모가 큰 커뮤니티 서버 두 개, 그리고 mailwarden의 최소 권한 설계에 가장 근접한 klodr입니다. 전송 기능은 보안 속성으로 표시했습니다. mailwarden에 전송 기능이 없는 것은 의도적입니다(보안 및 개인정보 보호 참조). 마지막 행은 서버가 어디에서 실행되는지가 아니라 누가 운영하는지를 묻습니다. 자체 호스팅은 공통분모이며, 이 표의 모든 커뮤니티 서버는 klodr(stdio 전용)을 제외하고 어떤 형태로든 원격 배포를 제공합니다 — mailwarden은 --http로, taylorwilsdon은 OAuth 2.1을 사용하는 streamable HTTP로, a-bonus는 Cloud Run에서 실행합니다. 이 중 하나를 자체 호스트에서 실행하는 것은 클라우드 사본이 아닙니다. 벤더의 호스트에서 실행하는 것이야말로 클라우드 사본입니다.
해자는 표의 어느 한 행이 아닙니다 — snooze + 라이브 재검증의 결합입니다. 캐시된 스냅샷이 아닌 사서함의 현재 상태를 기준으로 동작하는 실제 받은편지함 워크플로 레이어라는 점이죠. 다른 서버들이 따라잡은 부분은 위에 정직하게 명시했습니다. 저장 시 암호화(taylorwilsdon), 범위 기반 도구 게이팅(klodr은 역방향, taylorwilsdon은 우리와 같은 방향이지만 그만큼은 아님 — 그의 --read-only는 실제로 OAuth 흐름을 읽기 전용 범위 맵으로 전환하지만, 요청되는 범위 집합은 도구별이 아니라 서비스별로 구성되므로, 티어는 토큰이 할 수 있는 일을 줄이지 않으면서 등록되는 도구만 줄입니다. --tool-tier core --tools gmail은 여전히 전체 Gmail 범위를 요청합니다. 이 내용은 2026년 8월 26일 그의 auth/scopes.py와 main.py에서 확인했고, 같은 날 csitte.at이 우리 말을 액면 그대로 믿지 않고 자체 클론으로 검증한 뒤 그 자리에서 수정했습니다), 더 풍부한 메시지별 트리아지 휴리스틱(a-bonus), 그리고 사서함 전체에 대한 대량 정리(호스팅형 mcpemails.com — 여기에도 snooze는 없습니다). 그 어떤 서버도 쿼리를 기준으로 동작하면서, 동작하기 전에 사서함의 답변을 확인하지 않습니다.
Workspace 서버와 함께 실행하기
mailwarden은 Gmail 서버이지 Workspace 제품군이 아닙니다. 한곳에서 Calendar, Drive, Docs, Sheets를 사용하고 싶다면 taylorwilsdon/google_workspace_mcp 같은 광범위한 서버가 이 서버가 절대 다루지 못할 범위를 커버하며, 둘은 상호 배타적이지 않습니다. 둘 다 추가하는 것은 합리적인 구성이고, 그 이유는 도구 수가 아니라 토큰입니다. 메일을 보낼 수 있는 스위트 서버는 연결된 모든 사서함에 대해 메일을 보낼 수 있는 자격 증명을 보유합니다. 대신 Gmail을 mailwarden에 맡기면 구성의 메일 부분에는 작성, 답장, 전달, 보내기 도구가 전혀 없습니다. 그 약속이 어디에 근거하는지는 등급에 따라 다르며, 이 구분은 중요합니다. read에서는 Google이 토큰 수준에서 이를 강제하고(gmail.readonly, 보내기 엔드포인트가 이를 거부합니다), manage에서는 도구 표면에 의존합니다. Gmail은 보내기에 gmail.modify를 실제로 허용하므로, 스코프만으로는 보장이 되지 않습니다. 두 경우 모두 송신 가드가 서버 자체에서 messages.send와 모든 초안 엔드포인트를 거부하므로, 받은편지함에 주입된 메시지는 사용할 도구도, 도달할 엔드포인트도 없습니다.
실제 구성은 다음과 같습니다. 원하는 서비스에 스위트 서버를 연결하고 그 Gmail 도구를 비활성화한 다음(--disabled-tools, 또는 해당 도구를 생략하는 등급), 메일은 mailwarden을 함께 실행하세요. 등급 규칙을 명심하세요. 클라이언트 구성당 최대 하나의 사서함만 쓰기 등급을 가져야 합니다.
재검증이 왜 중요한가 — 구체적인 사례
어시스턴트에게 *"받은편지함을 건너뛴 읽지 않은 홍보 메일을 보관 처리해줘"*라고 요청하면, 그것은 당연한 쿼리인 category:updates is:unread -in:inbox를 사용할 것입니다. Gmail의 인덱스를 신뢰하는 서버는 이미 읽은 스레드까지 보관 처리합니다. 건드릴 생각이 없던 메일이, 쉽게 되돌릴 수 없는 대량 작업으로 사라집니다.
측정된 결과이지, 주장이 아닙니다. 실제 사서함 하나(~70,000개 메시지), 2026년 8월 15일, 읽기 전용:
쿼리 ( | 반환된 스레드 | 읽지 않은 메시지가 있는 스레드 | 부실 |
| 131 | 17 | 87% |
| 128 | 14 | 89% |
| 235 | 99 | 58% |
인덱스는 조건을 무시하는 것이 아닙니다. is:unread가 없는 동일한 쿼리는 800개 이상의 스레드를 반환하므로 조건이 적용되고 있습니다. 이 조건은 뒤처져 있는 스레드 수준 읽음 상태를 상대로 적용됩니다. 모든 메시지가 읽음 처리된 스레드도 그곳에서는 읽지 않은 것으로 계산됩니다. 반환된 스레드 하나는 SENT라는 단일 레이블만 달고 있었습니다. 그리고 이것은 특이한 연산자 조합 때문에 나타난 현상이 아닙니다. 세 쿼리 중 가장 단순한 쿼리에서도 동일하게 나타나며, 그 비율은 가장 낮지만(58%) 절대 수치로는 가장 많은 잘못된 스레드(136개)를 보여줍니다.
문제는 정확히 스레드 인덱스입니다. 같은 쿼리, 같은 사서함, 같은 시각에 대신 messages.list로 물어보면 19개 메시지, 부실 없음입니다. 따라서 이것은 'Gmail 검색이 신뢰할 수 없다'는 뜻이 아니라, 읽음 상태에 대한 스레드 관점은 지연되는 반면 메시지별 관점은 그렇지 않다는 뜻입니다. search는 threads.list를 거치며, 바로 그 때문에 재검증을 수행합니다.
두 번째 사서함은 같은 날 같은 방식으로 측정했을 때 전혀 어긋나지 않았습니다. 하루에도 여러 번 API를 통해 읽음 표시가 되는데도 is:unread에 대한 원시 인덱스 적중이 0이었습니다. 따라서 이것은 모든 Gmail의 속성이 아니라 특정 사서함의 속성입니다. 무엇이 이들을 구분하는지는 열려 있습니다. 두 사서함은 규모(대략 세 자릿수 차이)와 연식이 다르고, 두 번째 사서함에는 더 기본적인 것이 빠져 있습니다. 즉, 그 사서함에서는 읽지 않은 상태로 보관된 스레드가 하나도 없었습니다. 그것이 부실한 읽음 상태가 나타날 수 있는 유일한 형태이기 때문입니다. 따라서 이는 특정 원인에 대한 반례가 아니라, 그 원인 후보가 없는 사서함입니다.
이것이 바로 핵심입니다. 서버는 자신이 어떤 종류의 사서함에 있는지 알 수 없습니다. 재검증은 어긋남이 없는 곳에서는 비용이 들지 않고, 어긋남이 있는 곳에서는 당신을 구해 줍니다. 위 측정에서 search가 버린 모든 스레드는 진짜로 읽은 것이었고, 진짜로 읽지 않은 메일은 하나도 버리지 않았습니다.
비용이 없지 않은 곳: 대량 도구. search는 어차피 모든 적중을 가져오므로 재검증합니다. bulk_modify(와 create_filter의 applyToExisting 일괄 처리)는 수천 개 메시지 규모로 설계되어, 적중당 한 번씩 가져오는 것은 전혀 다른 비용 차원입니다. 그 도구들은 인덱스가 반환한 결과를 대상으로 동작하므로, 이제 unverifiedPredicates를 보고합니다. 이는 쿼리에서 인덱스의 말을 그대로 받아들인 조건들(+UNREAD, -INBOX, …)입니다. 비어 있다면 신뢰할 수 없는 것이 없었다는 뜻입니다. 비어 있지 않고 결과가 읽음 상태와 정확해야 한다면? 먼저 search로 해당 집합을 확정한 뒤 그 스레드 id들에 대해 작업하세요. dryRun은 이 간극을 메우지 못합니다. 같은 인덱스를 다시 읽을 뿐이므로 집합이 얼마나 큰지만 확인할 뿐, 그것이 올바른지는 결코 확인하지 못합니다.
mailwarden은 어차피 모든 적중을 실시간으로 가져오므로, search는 모호하지 않은 조건들(is:unread, is:read, in:inbox, category:…, 부정 포함)을 각 스레드의 실제 레이블과 대조해 다시 확인하고, 어떤 도구가 그 결과를 보기 전에 인덱스의 오탐을 제거합니다. 그런 다음 대량 작업은 당신이 요청한 바로 그 집합에 대해 실행됩니다. 이것이 Gmail이 인덱싱한 내용을 대상으로 하는 것과 지금 실제 사서함에 있는 내용을 대상으로 하는 것의 차이이며, snooze/sweep을 어시스턴트에게 맡겨도 안전한 이유입니다. sweep은 실행 시점에 실시간 레이블로 검증하여 snooze가 실제로 만료된 스레드만 다시 표면화합니다.
직접 확인해 보세요 — Gmail 계정이 필요 없습니다. 저장소를 클론한 상태에서 (데모는 저장소에만 있는 검증 스크립트로, npm 패키지에 포함되지 않습니다):
git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node scripts/demo-reverify.mjs그 옆에 두 번째 스크립트인 node scripts/probe-reverify.mjs가 있습니다. 이 스크립트는 가짜 사서함 대신 당신의 사서함에서 같은 것을 측정합니다. 읽기 전용, 메타데이터만 사용하며(제목, 발신자, 본문은 가져오지 않음), 개수와 레이블 이름을 출력합니다. 위 수치들이 이렇게 생성되었으며, 당신의 사서함이 어긋나는지도 이렇게 확인할 수 있습니다.
데모는 실제 search()를 인덱스가 의도적으로 부실한 가짜 Gmail API를 상대로 구동합니다(is:unread 쿼리에 읽은 스레드를 반환하며, Gmail이 하는 것과 정확히 같습니다). 그리고 mailwarden이 그 오탐을 걸러내는 모습을 보여줍니다. 결과를 단언하므로 동작이 회귀하면 0이 아닌 종료 코드로 끝납니다. 같은 사례는 test/gmail.test.ts의 단위 테스트로도 고정되어 있습니다("live-label 재검증으로 인덱스 오탐 제거").
도구
도구 | 기능 |
| Gmail 쿼리 구문 → 스레드 요약 (보낸사람/제목/날짜/라벨/스니펫); 읽음 상태/카테고리 조건은 각 결과의 실시간 라벨로 다시 검증됩니다; |
| 전체 스레드: 헤더, 일반 텍스트 + HTML 본문, 첨부파일 메타데이터. |
| 모든 라벨 (시스템 + 사용자) |
| 연결된 계정의 주소 + 전체 메시지/스레드 수 — 작업 전에 어느 사서함이 연결되어 있는지 확인 |
| 결정을 위한 사서함 일부의 구조적 개요: 상위 발신자(각 발신자의 스레드가 지니는 신호 포함), 라벨 및 기간 버킷, 읽지 않음 + 첨부파일 수, 그리고 뉴스레터 / 자동 발신 / 캘린더 초대 / 회신 주소 불일치 스레드 수 — 원시 스레드 목록 대신 |
| 스레드가 광고하는 옵트아웃 옵션( |
| 발신자별로 그룹화된 사서함 일부: 스레드/읽지 않음 수, 각 발신자가 관찰된 기간, 각각의 옵트아웃 옵션 — 발신자당 헤더를 한 번 가져오며 아무에게도 연락하지 않습니다. |
| 사용자 라벨 생성(멱등적; |
| 이름 또는 id로 라벨 추가/제거 — |
| 쿼리와 일치하는 모든 메시지에 대한 일괄 라벨 변경 — API 요청당 1000개 메시지, 청크별로 부분 성공 보고(스레드 ID 목록은 500개로 제한, |
| 편의 래퍼 |
| 휴지통으로 이동 / 휴지통에서 복원 |
| 첨부파일을 로컬 경로에 저장(덮어쓰지 않음 — 충돌 시 숫자 접미사가 붙음) |
| 메시지 자체 헤더의 엔드포인트를 사용하는 원클릭 옵트아웃(RFC 8058) — Google이 아닌 호스트에 연락하는 유일한 도구 (자세한 내용) |
| 여러 스레드에 대해 동일하게, 순차적으로 그리고 발신자당 최대 한 번의 요청(서버가 실행되는 동안 호출 간에 기억되므로 재시도해도 두 번 연락하지 않음); 스레드별로 부분 성공 보고. |
| 지금 보관하고, 날짜( |
| 스누즈 취소, 지금 받은편지함으로 복귀 |
| 모든 스누즈된 스레드 + 예약 날짜 |
| 스누즈가 기한이 된 스레드를 다시 표시(요청 시, cron, 또는 데몬으로 실행); 일괄 처리, 부분 실패 보고 포함. |
| 모든 Gmail 필터(조건 + 라벨 작업); 감사 목적으로 기존 필터의 |
| 서버 측 자동 분류 규칙 생성(조건 → 라벨 작업만; 전달 없음 — 아래 참조). 선택적으로 |
| ID로 필터 삭제 |
모든 도구는 outputSchema를 선언하고 구조화된 콘텐츠(검증되고 기계가 읽을 수 있는 형태)를 동일한 JSON을 코드 펜스로 감싼 텍스트와 함께 반환합니다. 클라이언트는 산문을 파싱할 필요가 없습니다.
실패도 구조화되어 있습니다. isError와 함께 펜스 처리된 JSON 본문에 code(not_authorized, needs_reauth, insufficient_scope, forbidden_operation, not_found, rate_limited, upstream_unavailable, network_error, invalid_input, internal_error)와 retryable 플래그가 포함되고, 사람이 읽는 문장도 함께 옵니다. 따라서 "잠시 후 다시 시도하세요"와 "mailwarden --auth를 다시 실행하세요"는 클라이언트가 판단할 수 있는 것이지, 다음 릴리스에서 바뀔 수 있는 문구에서 유추해야 하는 것이 아닙니다. (오류에는 structuredContent가 없습니다. 이는 성공을 설명하는 도구의 outputSchema에 대해 검증되기 때문입니다.)
snooze 작동 방식 (Gmail API에는 snooze가 없습니다 — 직접 구현합니다)
snooze는 INBOX를 제거하고 날짜가 표시된 라벨 MCP/Snoozed/<key>를 적용합니다. 여기서 키는 YYYY-MM-DD(하루 종일 예약) 또는 YYYY-MM-DDTHHMM(그 로컬 분에 예약)입니다. until 인자는 명시적 날짜, 날짜+시간(2026-06-20 9am, …T17:00), 또는 서버에서 해석되는 프리셋(today, tomorrow, weekend(다음 토요일), next week(다음 월요일), 요일 이름(monday–sunday, 다음 발생일), in N days, 또는 in N hours)을 받습니다. 날짜 프리셋에는 뒤에 시간이 붙을 수 있으므로(tomorrow 9am, monday 8:30) 호출하는 쪽이 그 시점을 직접 계산할 필요가 없습니다. sweep_snoozed는 기한이 지난 라벨을 찾아 해당 스레드를 받은편지함으로 돌려보냅니다(읽지 않음으로 표시). 시간 지정 snooze는 해당 분 이후 첫 sweep에서 깨어나므로, 깨어나는 대기 시간은 sweep 주기와 같습니다. sweep을 실행하려면:
주문형(on demand) (
sweep_snoozed도구),cron을 통해:
mailwarden --sweep,또는 자동으로:
MAILWARDEN_AUTO_SWEEP=1설정(서버가 실행되는 동안 매시간 sweep).
필터 (지속형 자동 분류 규칙)
create_filter는 Gmail 서버 측 규칙을 설정합니다. 기준에 맞는 메일에는 주어진 라벨 작업이 자동으로 적용됩니다. 즉, 어시스턴트가 개입하지 않아도 사서함이 스스로 분류를 계속합니다.
기준(Criteria):
from,to,subject,query(전체 Gmail 검색 문법),negatedQuery,hasAttachment,excludeChats, 그리고size+sizeComparison(smaller/larger, 함께 지정). 최소 하나는 필수입니다.동작(라벨만):
addLabels/removeLabels, 이름이나 id로 지정합니다(addLabels에서 알 수 없는 이름은/로 중첩되어 자동 생성됨). 일반적인 사용법: 받은편지함 건너뛰기 →removeLabels: ["INBOX"]; 자동 읽음 처리 →removeLabels: ["UNREAD"]; 자동 휴지통 →addLabels: ["TRASH"]; 별표 →addLabels: ["STARRED"]; 스팸 금지 →removeLabels: ["SPAM"]; 라벨로 분류 →addLabels: ["Receipts"].기존 메일: 필터는 생성 이후 도착하는 메시지에만 적용됩니다.
applyToExisting: true를 전달하면 이미 사서함에 있는 메일에 동일한 동작을 한 번 적용할 수도 있습니다. mailwarden은 기준에서 Gmail 검색을 구성하고 일괄 수정을 실행합니다(maxMessages까지, 기본값 1000;bulk_modify와 동일한 미검증 인덱스 주의사항이 적용되며, 이 일회성 패스는 스팸/휴지통을 제외합니다). 이 옵션은 긍정 기준(from/to/subject/query/hasAttachment:true/size)이 최소 하나 있어야 합니다. 제외 전용 규칙(negatedQuery또는hasAttachment:false)은applyToExisting에 대해 거부됩니다. 사서함 전체와 거의 일치할 수 있기 때문입니다. 이런 필터는 플래그 없이 생성하세요. 결과는applied아래로 반환됩니다(사용된query,matchedMessages/submittedMessages/submittedThreadCount개수, 일치 집합이maxMessages에 도달하면capped, 청크별failed, 전체 패스가 실패하면error문자열).applyToExisting이 설정되지 않았으면null입니다. 백로그 패스는 실제로 적용된 것을 검증하지 않습니다.bulk_modify의verify가 검증합니다. sweep 결과를 확실히 해야 한다면 동일한 쿼리로bulk_modify를 다시 실행하세요. 필터는 먼저 생성되므로 부분적이거나 실패한 백로그 패스는applied에 보고될 뿐, 절대 예외로 처리되지 않습니다. 규칙은 그대로 유지됩니다.전달 없음 — 보안 및 개인정보 보호 참조.
gmail.settings.basic범위가 필요합니다. 이전 버전을 인증했다면--auth를 한 번 다시 실행하세요. 읽기 전용 모드에서는 사용할 수 없습니다.
구독 해지 — 유일한 외부 요청
list_unsubscribe(읽기 계층)는 아무에게도 연락하지 않고 발신자가 제공하는 옵션을 보고합니다. 실제로 List-Unsubscribe 헤더가 있는 가장 최신 메시지를 읽습니다. 뉴스레터에 답장으로 이어진 스레드는 끝에 답장이 있고 아무것도 광고하지 않으므로, 그렇지 않으면 "이 목록에는 수신 거부 옵션이 없다"로 읽힐 수 있습니다. list_subscriptions(읽기 계층)는 전체 구간에 걸쳐 동일한 작업을 발신자별로 그룹화하여 수행하므로, 누가 계속 보내는지, 그중 실제로 떠날 수 있는 대상이 누구인지 확인할 수 있습니다. 스레드별이 아니라 발신자별로 헤더를 한 번 가져옵니다. unsubscribe와 bulk_unsubscribe(관리 계층)가 이 정보를 바탕으로 동작합니다. 그리고 그것이 mailwarden이 Google이 아닌 호스트에 통신하는 유일한 지점이므로, 규칙이 엄격합니다:
URL 매개변수가 없습니다. 엔드포인트는 메시지 자체의 헤더에서만 가져오며 다른 곳에서 오지 않습니다. URL 인자를 허용하면 프롬프트 주입된 메일이 도구를 외부 유출 채널로 만들 수 있습니다(쿼리 문자열에 사서함 내용 포함). 헤더는 모델이 선택한 데이터를 담을 수 없습니다.
RFC 8058 원클릭만 수행됩니다. 발신자가
List-Unsubscribe-Post를 통해 옵트인해야 합니다. 일반https:링크는 브라우저에서 사람이 사용하도록 만들어진 것이므로, 가져오지 않고 돌려줍니다.mailto:수신 거부는 절대 수행되지 않습니다. 메일을 보내야 하기 때문이며, mailwarden은 메일을 보낼 수 없습니다. 주소는 보고되므로 직접 처리할 수 있습니다.고정된 요청, 버려지는 응답. POST 본문은 항상
List-Unsubscribe=One-Click이며 어떤 것에서도 파생되지 않습니다. 응답 본문은 읽히지 않고 취소됩니다. 모델에게 돌아가는 것은 상태 코드와 실제로 호출된 URL뿐입니다. 엔드포인트의 콘텐츠는 없으므로 지침으로 답할 수 없습니다. (301/302/303 리디렉션은 GET, 즉 본문 없이 따라갑니다.)발신자당 요청 1회, 순차적으로, 하나의 예산 안에서.
bulk_unsubscribe는 스레드 id를 받습니다(절대 쿼리를 받지 않습니다. 쿼리 기반 일괄 처리는 누구도 살펴보기 전에 일치하는 발신자마다 요청을 발사할 것입니다). 이미 요청이 나간 발신자의 스레드는duplicateOf로 보고되며 두 번째 요청 비용이 들지 않습니다. 한 목록의 두 스레드는 수신 거부를 공유하며, 두 번 호출하면 주소를 두 번 확인할 뿐입니다. 발신자는 요청이 실제로 엔드포인트에 도달했을 때만 기록되므로, 거부되거나 연결이 끊겨도 다음 스레드는 자체 시도를 할 수 있습니다. 그리고 건너뛴 스레드가 다른 엔드포인트를 광고하면 그 이유가 표시됩니다. 한 발신자가 여러 목록을 운영할 수 있기 때문입니다. 그 기억은 서버가 실행되는 동안 호출을 넘나들며 유지되며,unsubscribe도 이를 공유합니다. 타임아웃된 호출은 반복해도 안전하며, 같은 뉴스레터에 두 번 요청해도 발신자에게는 한 번 연락됩니다. 예를 들어 엔드포인트가 500으로 응답한 후 의도적인 두 번째 시도를 하려면unsubscribe에force: true를 전달하세요. 이 정보는 메모리에만 유지됩니다. 이를 영속화하면 토큰 외에 또 다른 종류의 로컬 상태가 생기게 되는데, 이 서버는 의도적으로 그런 상태를 유지하지 않으므로 재시작하면 잊어버립니다. 호출당 스레드 25개와 60초로 제한됩니다. 예산이 모두 사용되지 않은 부분은 조용히 실행되지 않고skippedOutOfTime으로 반환됩니다. 이 중 어떤 것도 되돌릴 수 없기 때문에 세 가지 제한이 모두 존재합니다.SSRF 방어. https 전용, 기본 포트 전용, URL에 자격 증명 없음, 그리고 리디렉션을 포함한 모든 홉(최대 3회)은 전역적으로 도달 가능한 주소로만 확인되어야 합니다. 검사는 각 주소를 바이트로 파싱하여 IANA 특수 용도 레지스트리와 대조하므로, 같은 주소의 모든 표기법이 동일한 판정을 받습니다(
::1과0:0:0:0:0:0:0:1도 동일). 파싱되지 않는 주소는 거부됩니다. DNS 확인과 모든 홉은 하나의 10초 예산을 공유합니다. 리바인딩 방어는 아닙니다(fetch는 연결될 때 다시 확인합니다) — SECURITY.md 참조. 그 공백을 통과하는 것은 응답이 결코 읽히지 않는 블라인드 POST입니다.
신뢰하기 전에 자신의 메일로 확인하세요. 리포지토리 클론에서(npm 패키지에는 없는 저장소 전용), npm run build와 mailwarden --auth 후:
node scripts/probe-unsubscribe.mjs --vet # category:promotions, 25 threads
node scripts/probe-unsubscribe.mjs "from:substack.com" --max 50 --vet파서가 해당 헤더를 어떻게 해석했는지와 함께 실제 List-Unsubscribe 헤더를 각각 출력하며, --vet는 또한 URL 검증과 주소 방어를 통해 엔드포인트를 실행합니다. 따라서 파서가 헤더를 이해했는지, 그리고 방어 장치가 해당 수신 거부를 통과시켰을지 모두 볼 수 있습니다. 엄격히 읽기 전용입니다. 발신자에게 어떤 요청도 보내지 않으며 사서함의 어떤 것도 변경되지 않습니다.
되돌릴 수 없는 것: 요청은 발신자에게 사용자의 주소가 유효함을 알립니다. 자신의 수신 거부를 무시하는 발신자는 어떤 클라이언트도 어쩔 수 없습니다. 그런 경우에는 unsubscribe를 create_filter나 trash와 함께 사용하세요. 자동화 가능한 옵션을 제공하지 않는 경우는 오류가 아니라 unsubscribed:false와 대안들로 보고됩니다. read 전용 배포는 list_unsubscribe와 list_subscriptions를 제공하며, 요청 자체를 결코 만들지 않습니다.
스팸 확인하기
장소를 명시하지 않은 쿼리는 스팸이나 휴지통을 절대 보지 못합니다. Gmail은 in:spam / in:trash를 명시하지 않은 모든 검색에서 둘 다 제외하므로, from:someone은 스팸 폴더에 있는 메일에 대해 아무것도 반환하지 않습니다. 그리고 답변에는 그 사실이 표시되지 않습니다. 실제 사서함에서 측정한 결과: 동일한 from: 쿼리가 기본적으로 0건, 스팸 포함 시 1건을 반환했습니다.
이것이 중요한 이유는 메일이 잘못 분류되는 이유 때문입니다. 스팸 필터는 메시지를 그 자체만으로 판단합니다. 몇 분 전에 무언가에 가입했는지, 비밀번호 재설정을 요청했는지, 주문했는지 알 수 없습니다. 따라서 기다리는 확인 메일이 정확히 그런 곳에 들어가는 것입니다. 당신은 방금 무엇을 했는지 압니다. 필터는 모릅니다.
그러므로 누군가 기대하는 메일이 보이지 않으면, 장소를 명시해서 다시 물어보세요:
search("in:spam newer_than:2d") # what got filed as spam recently
search("in:spam from:example.com") # the confirmation that never arrived스레드는 modify_labels로 받은편지함으로 돌아옵니다(SPAM 제거, INBOX 추가). 계속 오판되는 발신자는 스팸 금지 규칙, 즉 removeLabels: ["SPAM"]이 있는 create_filter로 영구히 해결하는 것이 가장 좋습니다(필터 참조).
이 서버가 의도적으로 하지 않는 두 가지가 있습니다. 스팸 폴더를 스캔하여 무엇이 거기 있어야 하는지 판단하지 않습니다. 실제 스팸 폴더 하나를 측정했을 때, 89%는 메일링 리스트 메커니즘이 전혀 없었습니다. 그래서 "대량 메일과 안 닮음"은 폴더 전체를 거의 표시하고 아무것도 필터링하지 않습니다. 그리고 그 판단에 따라 스스로 행동하지도 않습니다. 스팸에서 메일을 꺼내는 것은 결정이며, 그것을 명백하게 만드는 맥락("방금 거기에 등록했어")은 사서함이 아니라 대화에 있습니다.
보안 및 개인정보 보호
전체 위협 모델(신뢰 경계, 위협별 완화 조치, 명시적 비목표, 취약점 신고 방법)은 **SECURITY.md**를 참조하세요. 주요 내용:
텔레메트리 없음. 어떤 것도 외부로 전송되지 않습니다 — 분석, 크래시 리포팅, 추적이 전혀 없습니다.
기본적으로 열린 포트 없음. stdio만 사용합니다. 선택적
--http리스너는127.0.0.1(LAN 아님)에 바인딩되며MAILWARDEN_TOKEN베어러 토큰 없이는 시작을 거부합니다 — 신뢰할 수 있는 격리된 네트워크에서MAILWARDEN_ALLOW_NO_TOKEN=1을 설정하면 재정의할 수 있습니다. 루프백 바인딩에서는Host헤더도 검증합니다(DNS-리바인딩 방어). 원격 호스팅의 경우MAILWARDEN_HOST를 설정하고 TLS로 앞단을 구성하세요.설계상 전송 도구 없음. mailwarden은 작성, 회신, 전달을 할 수 없습니다. 이메일 내부에 프롬프트 인젝션된 지침은 이 서버를 통한 유출 경로가 없습니다.
create_filter도 같은 규칙을 따릅니다: 라벨링, 보관, 휴지통, 별표, 읽음 표시는 할 수 있지만 절대 전달 필터(유출 경로가 될 수 있는)를 만들지 않습니다.list_filters는 계정에 이미 있는 전달 필터를 계속 표시하므로 이를 발견할 수 있습니다. 이는 그러한 도구가 존재하지 않고 런타임에 등록될 수도 없기 때문입니다. Google이 mailwarden이 거부하는 대신 전송 자체를 거부하는 더 강력한 변형에 대해서는 아래 읽기 전용 모드를 참조하세요.단일 아웃바운드 호스트, 모델이 선택한 URL 없음.
unsubscribe도구는 Google이 아닌 호스트에 접촉하는 유일한 코드 경로입니다. 그 엔드포인트는 메시지의List-Unsubscribe헤더에서 읽어오며 — 도구 인자에서 절대 가져오지 않습니다 — 요청 본문은 고정되어 있고 응답 본문은 폐기되므로 데이터 채널이 될 수 없습니다. https/기본 포트만 허용되고, 리디렉션은 재검증되며, private, loopback, link-local 또는 metadata 주소로 연결되는 모든 홉은 거부됩니다. 구독 해제를 참조하세요.도구 계층(점진적 공개 + 최소 범위).
MAILWARDEN_TOOLS는 지정한 계층만 광고합니다 —read(읽기 도구),manage(사서함 변경, snooze, 다운로드),filters(서버 측 필터 CRUD, 도구에gmail.settings.basic이 필요한 유일한 계층). 기본값은 세 가지 모두입니다. 예:read,manage는 필터 관리 없이 전체 트리아지 표면을 제공합니다.--auth에서 요청되는 OAuth 범위는 활성화된 계층에서 파생됩니다 —read배포는gmail.readonly만 요청하고,gmail.settings.basic은filters계층이 켜져 있을 때만 요청됩니다. 그리고 필터 도구는 저장된 토큰에gmail.settings.basic이 없으면(예: 계층을 활성화하기 전에 승인된 토큰) 자동으로 숨겨집니다 — 이를 부여하려면--auth를 다시 실행하세요. 기록된 범위가 없는 이전 토큰은 이전과 같이 광고되며, 런타임 불충분 범위 메시지가 폴백입니다.읽기 전용 모드.
MAILWARDEN_READONLY=1(MAILWARDEN_TOOLS=read의 약칭)을 설정하면 읽기 도구(search,get_thread,list_labels,list_snoozed,get_profile,triage_digest,list_unsubscribe,list_subscriptions)만 등록됩니다 — 사서함을 변경하거나 파일을 쓸 수 있는 것은 클라이언트에 광고조차 되지 않습니다(더 넓은gmail.settings.basic범위가 필요한 필터 도구도 제외됩니다). 트리아지만 수행하는 공유/HTTP 배포에 권장됩니다. 또한 Google이 무전송 속성을 강제하는 유일한 계층입니다:gmail.readonly토큰을 보유하며, Gmail의 전송 엔드포인트는 이를 완전히 거부합니다.manage는gmail.modify가 필요하며, Gmail은 실제로 그 범위를 전송에 허용합니다 — mailwarden은 단지 그렇게 할 도구를 노출하지 않을 뿐입니다. 따라서read배포는 이 바이너리가 교체되어도 전송할 수 없고,manage배포는 호출할 수 있는 것이 없기 때문에 전송할 수 없습니다. (전환할 수 있는 전송 없는 쓰기 범위는 없습니다 — SECURITY.md, 위협 1 참조.)이그레스 가드. "호출할 것이 없음"은 더 이상 도구 목록에 대한 진술만이 아닙니다. 모든 인증된 Gmail 요청은 mailwarden이 사용하는 엔드포인트만 정확히 허용하고 나머지를 거부하는 하나의 체크포인트를 통과합니다 —
messages.send,drafts.*,messages.import/insert, 영구 삭제 및 모든 비필터settings엔드포인트가 거부 목록에 먼저 명명되어 확인되므로, 나중에 allowlist를 편집해도 실수로 다시 열 수 없습니다 — 메서드에media가 전달될 때googleapis가 사용하는/upload/gmail/v1/...경로를 통해서도 마찬가지입니다. 호스트가 재작성된 요청(GOOGLE_CLOUD_UNIVERSE_DOMAIN,rootUrl옵션)은 토큰이 프로세스를 떠나기 전에 거부됩니다. Gmail 디스커버리 문서의 모든 메서드가 가드에 대해 테스트됩니다. 이 가드는 이 서버를 보호하지 토큰을 보호하지는 않습니다: 도난당한gmail.modify토큰은 다른 곳에서 여전히 전송할 수 있습니다.격리된 다운로드.
MAILWARDEN_DOWNLOAD_DIR을 설정하면 첨부 파일 쓰기가 해당 디렉토리(실제 경로 표준화, 심링크 인식)로 제한되며 기존 파일을 절대 덮어쓰지 않습니다.신뢰할 수 없는 콘텐츠 격리. 모든 도구 결과는
<untrusted-tool-output>마커로 감싸지고 보이지 않는 문자/BiDi-오버라이드 문자가 제거되므로, 클라이언트는 인용된 메일 콘텐츠와 지침을 구분할 수 있습니다. 제거는 유니코드 태그 문자와 변형 선택자 보충 문자(보이지 않는 ASCII 밀수)도 포함하며, 텍스트 사본뿐만 아니라structuredContent에도 적용됩니다 — 기계가 읽을 수 있는 절반을 읽는 클라이언트도 동일한 정화된 콘텐츠를 받습니다.실시간 API, 복사본 없음. 사서함 미러나 검색 인덱스는 어디에도 저장되지 않습니다. 유일한 로컬 상태는
~/.mailwarden/의 OAuth 토큰입니다.선택적 저장 토큰 암호화.
token.json은 리프레시 토큰을 보유합니다. 디스크에서는mode 0o600(Windows에서는 no-op)으로만 보호됩니다.MAILWARDEN_TOKEN_PASSPHRASE에 암호를 설정하면 토큰은 AES-256-GCM 암호화(scrypt 파생 키)로 저장되므로, 파일의 사본 — 백업, 동기화된 폴더, 다른 기계 — 은 암호 없이는 무용지물입니다. 설정 후mailwarden --auth를 한 번 다시 실행하여 기존 토큰을 암호화하세요. 경계를 유의하세요: 이것은 파일 도난을 방어하지, 사용자로 실행되는 맬웨어(환경에서 암호도 읽을 수 있는)를 방어하지 않습니다.
빠른 시작
claude mcp add mailwarden -- npx -y mailwarden그것이 설치의 전부입니다 — npx가 게시된 패키지를 가져와 실행하며, 클론이나 빌드 단계가 없습니다. Google OAuth 자격 증명은 한 번만 있으면 됩니다(아래).
설정
처음으로 Google OAuth 앱을 설정하는 중이신가요? **단계별 설정 가이드**를 따르세요 — Google Cloud Console을 정확한 클릭 경로로 안내하고, "확인되지 않은 앱" 화면을 설명하며, 토큰이 7일 후 만료되는 함정을 다룹니다. 요약:
Google Cloud: 프로젝트 생성 → Gmail API 활성화 → OAuth 동의 화면 구성 및 프로덕션으로 게시( 테스트 상태에서는 Google이 7일 후 리프레시 토큰을 만료시킵니다) → 데스크톱 앱 유형의 OAuth 클라이언트 ID 생성 →
credentials.json으로 다운로드.credentials.json을~/.mailwarden/에 넣습니다(또는MAILWARDEN_CREDENTIALS=/path/to/credentials.json설정).한 번 승인합니다 — 브라우저가 열리고, 리프레시 토큰이
~/.mailwarden/token.json에 저장됩니다:npx -y mailwarden --auth요청되는 범위:
gmail.modify(읽기 + 라벨/보관/휴지통) 및gmail.settings.basic(필터 관리 전용). 필터가 존재하기 전 버전을 승인했다면--auth를 한 번 다시 실행하여 추가된 범위를 부여하세요. Gmail 자체가 전송을 거부하는 토큰을 보유하려면MAILWARDEN_TOOLS=read로 승인하세요 — 위의 읽기 전용 모드 참조.설정 확인은 언제든지 내장된 doctor로:
npx -y mailwarden --checkcredentials.json을 확인하고, 토큰이 존재하는지(그리고 암호화되었는지), 부여된 범위가 활성화된 계층을 포함하는지 확인하며, 토큰이 여전히 작동함을 증명하기 위해 하나의 실시간 Gmail 호출을 수행합니다 — 잘못된 것이 있으면 구체적인 수정 방법을 출력하고 그렇다면 0이 아닌 코드로 종료합니다(CI/헬스 체크에 유용). 일반적인 함정을 진단합니다: 자격 증명 파일 없음/wrong파일, 승인 안 됨,MAILWARDEN_TOKEN_PASSPHRASE가 없는 암호화된 토큰, 누락된 범위, 또는 7일 "테스트" 동의 토큰 만료.
연결
Claude Code(로컬 stdio):
claude mcp add mailwarden -- npx -y mailwardenClaude Code 플러그인 — 동일한 서버에 OAuth 설정을 안내하고 고장난 설정을 진단하는 /mailwarden:setup 스킬이 추가된 것입니다. 저장소 루트가 플러그인(.claude-plugin/plugin.json)이므로, 클론에서:
claude --plugin-dir /path/to/mailwardenAnthropic의 커뮤니티 마켓플레이스에 제출되어 있습니다. 등록되면 /plugin marketplace add anthropics/claude-plugins-community 후 /plugin install mailwarden@claude-community로 클론 없이 동일하게 수행할 수 있습니다. 플러그인은 전체 도구 표면을 실행합니다 — 더 좁은 계층(MAILWARDEN_TOOLS=read)이나 두 번째 계정을 원하면 원하는 환경 변수와 함께 claude mcp add를 대신 사용하세요(구성 및 여러 계정 참조).
Claude Desktop — claude_desktop_config.json에 추가:
{
"mcpServers": {
"mailwarden": { "command": "npx", "args": ["-y", "mailwarden"] }
}
}또는 MCPB 번들(mailwarden-<version>.mcpb, GitHub 릴리스에서 0.10.0부터 첨부)을 Desktop 확장 프로그램으로 설치하세요 — 설정 → 확장 프로그램 → 확장 프로그램 설치… — 동일한 서버이며, 런타임에 자체 포함됩니다(npx 불필요; Claude Desktop이 Node 런타임을 제공), 도구 계층은 설정으로 제공됩니다. 번들은 패키징된 npm 패키지(게시된 것과 동일한 파일 세트; npm run mcpb, CI에서 검증: 검증, 압축 해제, 부팅)로 빌드되며 Smithery가 배포하는 것과 동일한 파일 세트입니다. 일회성 npx -y mailwarden --auth는 여전히 적용됩니다(그 한 번만 Node 필요) — 번들은 동일한 ~/.mailwarden/ 토큰을 읽습니다.
Smithery — csitte/mailwarden으로 등록되어 있으며, 해당 번들을 제공합니다:
npx -y @smithery/cli install csitte/mailwarden --client claude # local stdio entry in the client's configSmithery의 두 경로 중 어느 것을 택하는지 유의하세요. 위의 설치는 일반 로컬 서버 항목을 작성합니다: 프로세스, 토큰, 메일은 npx와 정확히 동일하게 사용자 기계에 유지됩니다. 대신 Smithery의 툴박스에 추가하면(smithery mcp add) 번들도 로컬에서 실행되지만, 원격 클라이언트가 도달할 수 있도록 도구 트래픽을 Smithery의 게이트웨이를 통해 중계합니다 — 그 응답의 사서함 콘텐츠는 제3자를 통과하게 됩니다. 이것은 mailwarden이 아닌 게이트웨이의 속성입니다. 제3자 없는 보장을 원한다면 로컬 설치, npm 패키지, 또는 릴리스 페이지의 .mcpb를 사용하세요.
원격(HTTP) — VPS / claude.ai 사용자 지정 커넥터용:
# Loopback + token required by default. For real hosting, bind outward and keep the token:
MAILWARDEN_TOKEN=<secret> MAILWARDEN_HOST=0.0.0.0 npx -y mailwarden --http # :8787/mcp그런 다음 claude.ai에서: 설정 → 커넥터 → 사용자 지정 커넥터 추가 → https://your-host/mcp URL. Claude Code에서는: claude mcp add --transport http mailwarden https://your-host/mcp.
여러 계정
하나의 OAuth 앱(하나의 credentials.json)이 여러 Gmail 계정을 승인할 수 있습니다. 각 계정은 MAILWARDEN_ACCOUNT로 선택되는 별도의 파일에 자체 리프레시 토큰을 유지합니다:
mailwarden --auth --account work # stores token.work.json
mailwarden --auth --account personal # stores token.personal.json각 계정마다 자체 MAILWARDEN_ACCOUNT를 사용하여 서버를 계정당 한 번씩 등록하여 나란히 실행하세요. 각 인스턴스는 자체 토큰, 자체 부여된 범위, 자체 도구 표면을 가지며, 도구 호출은 해당 항목이 제공하는 계정에만 적용되고 다른 계정에는 적용되지 않습니다:
{
"mcpServers": {
"gmail-work": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "work" } },
"gmail-personal": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "personal" } }
}
}계정 이름은 대소문자를 구분하지 않습니다 — 파일 이름이 되므로 Work와 work는 Windows/macOS에서 같은 파일이 됩니다. mailwarden은 이를 소문자로 변환하므로(--account Work → token.work.json) 이름은 항상 정확히 하나의 사서함에 매핑됩니다.
--auth가 쓰는 파일은 --account / MAILWARDEN_ACCOUNT에만 의존합니다 — 브라우저에서 선택한 계정에는 절대 의존하지 않습니다. 따라서 --account 없이 두 번째 사서함을 승인하면 첫 번째 사서함의 토큰 파일을 직접 겨냥하게 되므로, --auth는 먼저 확인하고 다른 사서함의 토큰을 대체하는 대신 거부합니다. --force는 의도적으로 이를 재정의합니다. 두 손잡이는 상호 교환할 수 없습니다: MAILWARDEN_ACCOUNT는 하나의 구성 디렉토리에서 여러 사서함을 위한 것입니다(token.<name>.json을 선택), 반면 MAILWARDEN_DIR은 전체 디렉토리를 이동합니다 — 설정을 완전히 분리하는 데 유용하지만, 하나의 디렉토리 안에 두 번째 계정을 제공하지는 않습니다. 저장소 클론에서 npm run auth는 둘 다 전달하지 않으므로 항상 기본 계정을 제공합니다.
mailwarden --check는 활성 계정을 표시하고 찾은 다른 계정들을 나열합니다. MAILWARDEN_ACCOUNT가 설정되어 있지 않으면 모든 것이 이전과 똑같이 기본 token.json을 사용합니다 — 완전히 하위 호환됩니다.
계정이 둘보다 많은 경우
항목이 두 개뿐이면 간단합니다. 그 이상에서는 이 설계의 두 가지 속성이 중요해지기 시작합니다.
각 인스턴스는 자신만의 도구를 가져옵니다. 티어 분할은 8 read + 14 manage + 3 filters이므로, 전체 인스턴스는 25개의 도구를 광고하고 그런 인스턴스 네 개는 합쳐서 100개를 광고합니다. 도구 표면을 필요에 따라 검색하는 클라이언트는 그 부담을 흡수하지만, 모든 정의를 컨텍스트에 보관하는 클라이언트는 그렇지 못합니다.
한 클라이언트의 여러 계정은 하나의 모델 컨텍스트를 공유합니다. 계정 경계는 호출을 하나의 사서함에 묶을 뿐입니다 — 한 사서함에서 읽은 텍스트가 다른 사서함에 대한 호출을 유발하는 것을 막지는 못합니다. 그 모든 도구 표면이 같은 모델 앞에 있기 때문입니다. 이는 경계의 한계이지 결함이 아닙니다. SECURITY.md의 위협 8을 참고하세요.
한 가지 조치가 두 문제를 모두 해결합니다: 정확히 하나의 사서함에만 쓰기 도구를 주고 나머지는 read로 두십시오.
{
"mcpServers": {
"gmail-main": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "main" } },
"gmail-work": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "work", "MAILWARDEN_TOOLS": "read" } },
"gmail-club": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "club", "MAILWARDEN_TOOLS": "read" } },
"gmail-archive":{ "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "archive","MAILWARDEN_TOOLS": "read" } }
}
}세 가지 결과가 동시에 나타납니다. read 항목은 오직 gmail.readonly만 요청합니다. 이는 발송 금지가 mailwarden의 도구 표면이 아니라 Google에 의해 강제되는 유일한 범위입니다. 네 인스턴스는 100개가 아니라 총 49개의 도구가 되며, 그중 어느 인스턴스에 주입된 지시도 다른 사서함을 겨냥할 쓰기 도구를 찾지 못합니다. 읽기 전용 사서함 중 하나를 실제로 정리해야 한다면, 그 항목에 영구적으로가 아니라 작업이 끝날 때까지 manage를 부여하세요.
별도의 클라이언트를 사용하면 — 또는 별도의 세션을 사용하면 — 공유 컨텍스트가 완전히 제거되지만, 두 사서함을 동시에 볼 수는 없게 됩니다. 여러 사서함에 실제로 쓰기 도구가 필요하다면 그럴 가치가 있습니다. 그렇지 않다면 티어 분할이 더 저렴한 경계입니다.
어떤 도구도 사서함을 넘어 읽지 않습니다. search, triage_digest, list_subscriptions는 각각 해당 인스턴스가 구성된 단일 계정만 대상으로 하므로, "어느 뉴스레터가 네 개 모두에 보내는가" 같은 질문은 호출자가 답을 직접 조합해야 하는 네 번의 호출이 됩니다. 이 정도 규모의 구성에서는 무언가를 변경하는 첫 작업 전에 get_profile을 호출할 가치가 있습니다 — 실제 상대편에 있는 사서함이 무엇인지 알려주기 때문입니다.
소스에서 빌드
git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node dist/index.js --auth구성 (환경 변수)
Var | Meaning |
| 구성 디렉터리(기본값 |
|
|
| 이름 있는 계정을 선택합니다(해당 토큰은 |
| 패스프레이즈 → 저장 중인 |
|
|
|
|
|
|
| 광고할 도구 티어를 쉼표로 구분해 지정합니다: |
|
|
| HTTP 포트(기본값 8787) |
| HTTP 바인드 주소(기본값 |
| HTTP 엔드포인트용 베어러 토큰 — 재정의하지 않는 한 |
|
|
| 루프백 |
상태
작동 중이며 일상적인 사서함 자동화에 사용되고 있습니다. 핵심 Gmail 도구 + 스누즈는 googleapis 기반으로 구현되어 있고 vitest 스위트로 테스트됩니다(1001개 테스트 — npm run coverage). 현재 버전: 위의 npm 배지, 변경 로그, 또는 릴리스를 참고하세요. PR은 환영합니다 — CONTRIBUTING.md에 빌드/테스트 루프와 변경할 수 없는 설계 규칙이 설명되어 있습니다.
라이선스
MIT © C.Sitte Softwaretechnik
Available Tools
26 toolsarchiveAIdempotent
Archive a thread (remove it from the inbox). USE WHEN: inbox triage — the thread is handled and should leave the inbox. DO NOT USE: to delete (use trash) or to defer to a date (use snooze). SIDE EFFECTS: thread leaves the inbox; reversible via modify_labels add INBOX.
| Name | Required | Description | Default |
|---|---|---|---|
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses side effect 'thread leaves the inbox' and reversibility via 'modify_labels add INBOX', adding value beyond annotations (idempotentHint=true, destructiveHint=false). No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Concise, front-loaded with action, then usage, then side effects. Every sentence contributes meaning with no waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, usage guidelines, side effects, and reversibility; output schema exists so return values need not be explained. Complete for a simple tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter (threadId) with 0% schema coverage, but the description does not elaborate on it beyond the schema; the parameter is self-explanatory, so minimal added value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states 'Archive a thread (remove it from the inbox)' with specific verb and resource, and distinguishes from siblings 'trash' and 'snooze' by naming them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly provides when-to-use ('inbox triage') and when-not-to-use ('to delete use trash, to defer use snooze'), with alternatives named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bulk_modifyADestructiveIdempotent
Bulk-apply label changes to every message matching a Gmail query, batched at 1000 messages per API request. Labels may be given by name or by id: an unknown name in add is created automatically (use '/' for nested labels), an unknown name in remove is ignored. Returns matched/submitted counts, matched and submitted thread IDs (both lists capped at 500 — matchedThreadCount/submittedThreadCount hold the true totals), and per-chunk failures (partial success is reported, not hidden). IMPORTANT: submittedMessages is how many ids were handed to the API, NOT how many messages changed — messages.batchModify answers 204 with no body and ignores ids it does not recognise without a word, so an accepted request is not a performed one. Set verify:true to read the labels back afterwards and get verified {applied, notApplied[], unverifiable[]} — the only field here that reports an observed outcome. It costs one extra read per affected thread, so it is off by default; use it when a wrong 'done' would be acted on (trashing, or anything the user is told completed). If more messages match than maxMessages, only the first maxMessages are processed and 'capped' is true — raise maxMessages or re-run to finish the rest. NOTE: the query hits Gmail's search index as-is, WITHOUT the live re-verification search performs. The staleness that makes search re-verify was measured on threads.list (132 threads returned, 114 carrying no unread message at all); the same query through the message index this tool uses returned 19 hits, none stale — same mailbox, same minute. So the known drift does not reach this path, but that is one measurement, not a guarantee: unverifiedPredicates in the result names the conditions taken on the index's word, and when the outcome must be read-state-precise, resolve the set with search (which verifies against live labels) and act on those thread ids instead. Set crossCheck:true to ask Gmail the same question a second way before writing: each derived predicate is re-run as a label filter (labelIds) instead of a query operator, and any message the two routes disagree about is left untouched and listed in crossChecked.dropped. It costs one extra list per predicate — flat, not per message — so unlike verify it stays cheap on a large sweep. Read it as a contradiction detector: a disagreement is real, agreement proves nothing, because both routes read the same index. unverifiedPredicates therefore stays as it is even when this runs. A capped match set is not cross-checked at all (crossChecked.capped), since a message missing from a page is not a message missing the label. Set dryRun:true to rehearse: the same query resolution, matched counts/threads and the labels that would be created — and no message or label is touched. A dry run reads the SAME unverified index, so it confirms the size of the set, never its correctness. USE WHEN: mass operations — 'archive all newsletters older than 30 days' (query + remove INBOX), bulk labeling, bulk mark-read; dryRun first when the query is broad or the user should see the set before it changes. DO NOT USE: for a single thread (use modify_labels or the dedicated tools), or with neither add nor remove. SIDE EFFECTS: modifies up to maxMessages messages in one call (none with dryRun); label changes are reversible by the inverse call.
| Name | Required | Description | Default |
|---|---|---|---|
| add | No | ||
| query | Yes | ||
| dryRun | No | ||
| remove | No | ||
| verify | No | ||
| crossCheck | No | ||
| maxMessages | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| capped | Yes | |
| dryRun | Yes | |
| failed | Yes | |
| verified | No | |
| crossChecked | No | |
| labelsToCreate | No | |
| matchedThreads | Yes | |
| matchedMessages | Yes | |
| submittedThreads | Yes | |
| submittedMessages | Yes | |
| matchedThreadCount | Yes | |
| submittedThreadCount | Yes | |
| unverifiedPredicates | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the annotations by disclosing partial success reporting, the 204-with-no-body caveat of messages.batchModify, the difference between submitted and applied changes, verification costs, index staleness measurements, and the exact meaning of verified and crossChecked results. The destructiveHint and idempotentHint annotations are consistent with the described reversible but mutating behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Though long, the description is information-dense with no filler; every sentence addresses a real operational concern for a high-risk bulk mutation tool. It is structured with clear sections (IMPORTANT, USE WHEN, DO NOT USE, SIDE EFFECTS) that make the content scannable, and the core purpose is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 7 parameters, bulk effects, subtle Gmail API behavior, and a rich output schema, the description is exceptionally complete: it covers result fields, capping behavior, failure reporting, verification options, costs, index staleness, alternatives, and side effects. An agent has enough context to decide whether to call it, set flags appropriately, and interpret results correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description fully compensates by explaining every parameter: query, add/remove label creation and unknown-name handling, maxMessages capping, dryRun rehearsal behavior, verify's read-back semantics, and crossCheck's second query route. It adds meaning far beyond the bare schema types and defaults.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Bulk-apply label changes to every message matching a Gmail query, batched at 1000 messages per API request.' It clearly differentiates from sibling tools in DO NOT USE, naming modify_labels and dedicated single-thread tools, so an agent can select it correctly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The USE WHEN section explicitly lists the intended scenarios ('mass operations — archive all newsletters older than 30 days, bulk labeling, bulk mark-read') and advises dryRun first for broad queries. The DO NOT USE section gives concrete exclusions and alternatives, including not using it for a single thread and requiring at least one of add or remove.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bulk_unsubscribeAIdempotent
Unsubscribe from several mailing lists in one call, one thread id per list. Threads are processed SEQUENTIALLY, and at most ONE request is made per sender — a second thread from a sender whose request already went out is reported with duplicateOf and no request (and says so when it advertises a DIFFERENT endpoint, i.e. is probably a separate list from the same sender). The whole call shares a 60-second budget; threads left over when it runs out come back with skippedOutOfTime and a reason, so re-running with the remaining ids finishes the job. Like unsubscribe, there is no URL parameter: every endpoint comes from that thread's own List-Unsubscribe header. Only RFC 8058 one-click senders are contacted; the rest come back with their alternatives in options. Partial success is reported, never hidden: a thread that cannot be read or whose endpoint fails becomes an entry with a reason, and the remaining threads still run. Set dryRun:true to rehearse: same header reads, same per-sender dedupe (as a real run with every request succeeding), and each entry a real run would contact reports the endpoint it wouldCall; refusals and duplicates carry none — and nobody is contacted. USE WHEN: clearing out several newsletters at once — pair with list_subscriptions, which gives you the sender rows and their newestThreadId; dryRun first to show the user which senders would be contacted. DO NOT USE: for one thread (use unsubscribe), or to find candidates (use list_subscriptions — it contacts nobody). SIDE EFFECTS: up to one outbound HTTPS request per DISTINCT sender (plus up to 3 redirects each) — the only non-Google hosts mailwarden ever contacts (none with dryRun). Each confirms to that sender that the address is live, and none of it can be undone. The mailbox itself is not changed.
| Name | Required | Description | Default |
|---|---|---|---|
| dryRun | No | ||
| threadIds | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| dryRun | Yes | |
| results | Yes | |
| requests | Yes | |
| attempted | Yes | |
| requested | Yes | |
| unsubscribed | Yes | |
| skippedOutOfTime | Yes | |
| skippedDuplicates | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses sequential processing, per-sender deduplication, a 60-second time budget, dryRun behavior, external HTTP requests (with redirects), confirmation of address liveness, and irreversibility. These details go well beyond the annotations (readOnlyHint:false, openWorldHint:true, idempotentHint:true) and provide necessary caveats about side effects. No contradiction with annotations is apparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is lengthy but well-structured with bolded section headers (USE WHEN, DO NOT USE, SIDE EFFECTS). It front-loads the core purpose and packs each sentence with meaningful details. Slightly verbose, but each part contributes to understanding a complex tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers all major edge cases: duplicateOf, skippedOutOfTime, options for non-RFC 8058, partial failure handling, dryRun output, and side effects. Given the complexity and the existence of an output schema, the description provides comprehensive operational knowledge for correct usage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 0%, the description explains the meaning of threadIds ('one thread id per list') and dryRun ('rehearse' with no contact). It also clarifies that no URL parameter is used and that endpoint comes from List-Unsubscribe headers. This adds useful context beyond the raw schema, though it could be more explicit about max 25 items and preconditions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action (unsubscribe) and the scope (several mailing lists in one call, one thread id per list). It distinguishes itself from the sibling tool 'unsubscribe' by explicitly noting it is for multiple threads, and the phrase 'one thread id per list' adds specificity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'USE WHEN' and 'DO NOT USE' sections, naming alternative tools (list_subscriptions for candidate discovery, unsubscribe for single thread) and pairing recommendations with list_subscriptions. This gives agents clear decision criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_filterADestructive
Create a Gmail filter: matching incoming mail automatically gets the given label actions. Give at least one criterion and at least one action. Actions are label add/remove only (labels by name or id; an unknown name in addLabels is auto-created). Common recipes: skip the inbox → removeLabels ['INBOX']; auto-mark-read → removeLabels ['UNREAD']; auto-trash → addLabels ['TRASH']; star → addLabels ['STARRED']; file under a label → addLabels ['Receipts']. A filter only affects mail arriving AFTER it's created; set applyToExisting:true to ALSO apply the same actions once to mail already in the mailbox (builds a Gmail search from the criteria and runs a bulk modify — same unverified-index caveat as bulk_modify — the sweep acts on what the index returns, which can be badly stale on read state; up to maxMessages, default 1000). The sweep's applied.submittedMessages is how many ids were handed to the API, NOT how many messages changed; set verify:true alongside applyToExisting to read the labels back afterwards and get applied.verified {applied, notApplied[], unverifiable[]}. It costs one extra read per affected thread, so it is off by default; a read-back that fails lands in unverifiable and never fails the call — the filter stands either way. USE WHEN: setting up a persistent auto-triage rule (e.g. 'always archive + label newsletters from x'), optionally cleaning up the existing backlog too. NOTE: forwarding filters are intentionally not supported — mailwarden creates no send/exfiltration path. SIDE EFFECTS: adds a server-side rule affecting future mail (reversible via delete_filter); with applyToExisting also modifies existing messages. Requires gmail.settings.basic.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | ||
| from | No | ||
| size | No | ||
| query | No | ||
| verify | No | ||
| subject | No | ||
| addLabels | No | ||
| maxMessages | No | ||
| excludeChats | No | ||
| negatedQuery | No | ||
| removeLabels | No | ||
| hasAttachment | No | ||
| sizeComparison | No | ||
| applyToExisting | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | |
| applied | Yes | |
| forward | No | |
| criteria | Yes | |
| addLabelIds | Yes | |
| removeLabelIds | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the annotations. It explains that a filter affects only mail arriving after creation, that applyToExisting runs a bulk sweep with a stale-index caveat, that submittedMessages is not the number of changed messages, that verify costs an extra read and failures are non-fatal, that labels can auto-create unknown names, and that the operation requires gmail.settings.basic. This is rich, honest behavioral disclosure consistent with destructiveHint=true.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but nearly every sentence adds a distinct operational detail: core semantics, common recipes, applyToExisting caveats, verification behavior, use case, and side effects. It is front-loaded with the primary purpose and structured with labeled sections such as 'USE WHEN' and 'SIDE EFFECTS'. It could be tighter, but the density is justified for a tool with 14 parameters and subtle edge cases.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex tool with 14 parameters, no required fields, and an output schema, this description is exceptionally complete. It covers what happens on creation, how existing mail is handled, the semantics of verification, failure behavior, permission requirements, reversibility, and unsupported forwarding. An agent can safely construct a valid call and predict the consequences; the output schema can fill in remaining return-value details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema description coverage, the description carries the semantic burden and does so well for the action parameters: it defines addLabels/removeLabels semantics, gives concrete recipes like removeLabels ['INBOX'] and addLabels ['TRASH'], and explains applyToExisting, maxMessages, and verify. The criteria parameters (from, to, subject, query, etc.) are not individually detailed, but their Gmail meanings are conventional and the overall parameter guidance is well above the minimum viable level.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb-resource pair — 'Create a Gmail filter' — and immediately states what the filter does: matching incoming mail automatically gets label actions. It also distinguishes the tool from siblings by emphasizing persistent auto-triage rules and explicitly noting that forwarding filters are not supported, so there is no confusion with bulk_modify, modify_labels, or other Gmail mutators.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is a clear 'USE WHEN' statement defining the intended scenario: setting up a persistent auto-triage rule, with the optional applyToExisting use case explained. It also gives an explicit 'not supported' exclusion for forwarding. It stops short of naming a sibling alternative such as bulk_modify for one-time bulk label changes, but the context is strong enough for an agent to choose this tool for filter creation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_labelAIdempotent
Create a user label and return its id. Idempotent: if the name already exists (case-insensitive), its existing id is returned and nothing is created. Nested labels: separate levels with '/' (e.g. 'Clients/Acme') — each missing parent level is created too. Colour: pass backgroundColor AND textColor together as '#rrggbb' (Gmail rejects one without the other, and accepts only colours from its own palette). Giving a colour for a label that already exists RECOLOURS it — that is how an existing label, such as the snooze label, gets a colour. Gmail colours only labels you created yourself; system labels like INBOX or CATEGORY_UPDATES refuse one. USE WHEN: you want a label's id up front, to pre-create a label without applying it to anything, or to make an existing label visible at a glance in Gmail. DO NOT USE: just to file mail under a new label — modify_labels/bulk_modify already auto-create an unknown name passed in add. SIDE EFFECTS: creates the label if missing, and sets its colour when one is given; no mail is changed.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| textColor | No | ||
| backgroundColor | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | |
| name | Yes | |
| color | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations only declare idempotentHint=true, readOnlyHint=false, and destructiveHint=false. The description goes far beyond this by explaining case-insensitive name matching, nested label creation, recoloring existing labels, system label color restrictions, and the fact that no mail is changed. This is rich behavioral context with no contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every section earns its place: core action, idempotency, nesting, color behavior, usage guidance, and side effects. It is front-loaded with the most important information and uses clear section markers to keep the density navigable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of label creation in Gmail, the description covers all relevant caveats: idempotency, color pairing, palette restrictions, recoloring, system label refusals, and side effects. Since an output schema exists, the return value need not be further explained. Nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden of explaining parameters. It explains name with nesting syntax, and describes that textColor and backgroundColor must be passed together as '#rrggbb' and must come from Gmail's palette. This is far more than the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Create a user label and return its id.' It also distinguishes itself from modify_labels by explicitly stating that filing mail under a new label should use modify_labels/bulk_modify, which auto-create unknown names.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description includes explicit USE WHEN and DO NOT USE sections, giving clear conditions for tool selection. It names the exact alternative tools (modify_labels/bulk_modify) and explains when they should be used instead, leaving no ambiguity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_filterA
Delete a Gmail filter by id (get ids from list_filters). USE WHEN: removing an auto-triage rule. SIDE EFFECTS: removes the server-side rule; future mail is no longer auto-processed by it. Requires gmail.settings.basic.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotation Contradiction: The description says 'removes the server-side rule' and the tool is named delete_filter, indicating destructive behavior, yet annotations declare destructiveHint=false. This is a direct contradiction and severely undermines trust in safety-critical metadata.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and well-structured, with labeled sections (USE WHEN, SIDE EFFECTS) that immediately convey the core action, when to use it, and consequences. Every sentence contributes valuable information without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple single-parameter delete operation, the description covers purpose, usage context, side effects, and required permissions. An output schema exists so return values are not required. It lacks explicit error-handling/idempotency details, but annotations and simplicity compensate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With zero schema description coverage, the description adds essential meaning to the 'id' parameter by explaining it is a Gmail filter id obtainable from list_filters. This gives the agent a clear source and purpose, going beyond just the schema's string type.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description starts with 'Delete a Gmail filter by id', a specific verb and resource, and directly distinguishes itself from sibling tools like create_filter and list_filters by noting how to obtain ids. It clearly states the tool's scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'USE WHEN: removing an auto-triage rule' provides a clear context. It also instructs the agent to get ids from list_filters, implicitly guiding when to use this tool vs its sibling. However, it does not explicitly state when not to use it or mention alternatives beyond listing filters.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
download_attachmentA
Download an attachment to a local file path. If MAILWARDEN_DOWNLOAD_DIR is set, destPath is resolved inside (and restricted to) that directory. USE WHEN: the user wants an attachment saved to disk (IDs come from get_thread's attachment metadata). SIDE EFFECTS: writes a local file; never overwrites — an existing file gets a numeric suffix (file-1.pdf). The response's 'saved' field is the path actually used. Mailbox unchanged.
| Name | Required | Description | Default |
|---|---|---|---|
| destPath | Yes | ||
| messageId | Yes | ||
| attachmentId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| saved | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses critical behaviors beyond annotations: writes a local file, never overwrites (numeric suffix file-1.pdf), explains the MAILWARDEN_DOWNLOAD_DIR path restriction, confirms mailbox unchanged. The 'saved' field semantics are explained. Slight gap: doesn't mention permission/error behavior for the download operation, but coverage is strong given annotations already signal non-readOnly, non-destructive, non-idempotent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four tight sentences, each earning its place: definition, directory restriction, usage trigger, side effects with concrete example, and response field meaning. Zero waste, well front-loaded with the primary action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a non-readOnly, non-idempotent write tool with zero param documentation, the description provides strong behavioral disclosure: overwrite policy, path resolution, mailbox unaffected. An output schema exists, so return details needn't be enumerated. Minor gap: no mention of failure modes (unresolvable path, missing attachment) but solid overall.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and there are 3 required parameters with no descriptions. The description does explain destPath semantics well (directory resolution, suffix behavior) and touches on IDs coming from get_thread. However, messageId and attachmentId meanings are only implied via the get_thread reference, not explicitly defined. Description partially compensates but doesn't fully cover all three params.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clear verb+object: 'Download an attachment to a local file path.' It distinguishes from siblings by noting IDs come from get_thread's attachment metadata, explicitly differentiating from search/read-only tools. Purpose is immediately unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'USE WHEN' statement defines the trigger condition (user wants attachment saved to disk) and cites the exact source of input IDs (get_thread's attachment metadata). This gives strong selection guidance among the sibling tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_profileARead-only
Return the authorized account: email address plus total message/thread counts. USE WHEN: confirming WHICH mailbox is connected before a bulk or filter action, or as a cheap liveness check. DO NOT USE: to enumerate messages — this returns only counts, not a listing (use search). SIDE EFFECTS: none.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| historyId | Yes | |
| emailAddress | Yes | |
| threadsTotal | Yes | |
| messagesTotal | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, and the description reinforces this with 'SIDE EFFECTS: none.' It adds useful behavioral context beyond the annotation by stating the tool returns only counts, not a message listing, and positions it as a liveness check.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and well-structured: the core function is first, followed by USE WHEN, DO NOT USE, and SIDE EFFECTS. Every sentence carries actionable information with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple no-parameter, read-only tool with an output schema present, the description provides all necessary context: what it returns, when to use it, when not to use it, and that there are no side effects. Nothing important is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters and full schema description coverage, so there are no parameter semantics to document. The description usefully clarifies what the response contains, satisfying the baseline for no-parameter tools.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ('Return the authorized account') and specifies exactly what is returned: email address plus total message/thread counts. It also explicitly distinguishes itself from message enumeration by pointing to 'search' as the tool for listings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit USE WHEN guidance (confirming which mailbox is connected, cheap liveness check) and explicit DO NOT USE guidance (enumerating messages), with an alternative named. This gives clear decision criteria for an agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_threadARead-only
Fetch a thread by ID: headers, plaintext + HTML bodies, and attachment metadata. full defaults to true and should stay true whenever content matters. full: false is a headers-and-labels fetch for when a thread is too large to read or only its metadata is of interest — it does NOT fetch bodies or attachment metadata, and the result then omits plaintextBody, htmlBody and attachments entirely and sets metadataOnly: true. The fields are omitted rather than empty on purpose: an empty attachment list from a request that never looked is indistinguishable from a message that truly has none, and treating it as 'no attachment' has already nearly caused an invoice to be archived as attachment-less. If search reported hasAttachments: true, or the sender matters, use full: true. EVERY MESSAGE CARRIES authentication — SPF/DKIM/DMARC as the RECEIVING server reported them, for answering 'is this mail really from who it says?'. Read dmarc first: it is the only one of the three that ties a passing check to the visible From domain, so spf: pass alone proves nothing about the sender the user sees (a lookalike domain gets that trivially). signedBy/mailedBy/headerFrom name the domains each check actually validated. They do NOT have to match each other or the From address, and a mismatch is not by itself suspicious: a forwarded message legitimately shows the forwarder in mailedBy (the envelope sender is rewritten in transit) and in signedBy (the forwarder signs with its own key), while only headerFrom still names the original sender — which is exactly why dmarc is the result that carries meaning. Read those three as the explanation of a result, not as a test of their own. authservId says WHO asserts all this: a message can carry forged Authentication-Results headers of its own, and only the receiving server's report (for Gmail, mx.google.com) counts; otherReports counts further reports that were NOT read, and alsoReported lists results that disagree with the ones above. unchecked: true means the message carried no report at all — that is 'nobody looked', NOT 'nothing wrong'; unauthenticated does not imply forged, and authenticated does not imply honest (a phisher can hold a passing DMARC on his own lookalike domain). USE WHEN: reading a thread's content after finding it via search. DO NOT USE: with a message ID — this takes thread IDs. SIDE EFFECTS: none (does not mark as read).
| Name | Required | Description | Default |
|---|---|---|---|
| full | No | ||
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| messages | Yes | |
| threadId | Yes | |
| metadataOnly | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare `readOnlyHint: true`, and the description reinforces this with 'SIDE EFFECTS: none (does not mark as read).' It goes well beyond annotations by disclosing that `full: false` omits fields entirely and sets `metadataOnly: true`, explaining the rationale for omission rather than empty lists. It also warns about authentication-header forgery and how to interpret `authservId`, which is valuable behavioral context for downstream decisions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded and well structured, with the core purpose in the first sentence and clear USE WHEN / DO NOT USE / SIDE EFFECTS sections. It is verbose, especially the detailed authentication-fields discussion, which goes beyond what is strictly needed for tool selection and invocation. Still, the extra detail is purposeful and organized rather than redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema exists and the two-parameter input schema is minimal, the description is thorough: it covers both parameters, side effects, use cases, exclusions, and even edge-case semantics like omitted fields and authentication-result interpretation. An agent has everything needed to call this tool correctly and interpret the result safely.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries full parameter-semantics responsibility. It thoroughly explains `full` semantics, including defaults, behavior differences, omitted fields, and when to set it. For `threadId`, it clarifies that this tool expects thread IDs and not message IDs. Both parameters are meaningfully documented beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific verb and resource: 'Fetch a thread by ID: headers, plaintext + HTML bodies, and attachment metadata.' It also distinguishes this from message-level or search tools with 'DO NOT USE: with a message ID — this takes thread IDs.' The purpose is unmistakable and well differentiated from siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit usage guidance: 'USE WHEN: reading a thread's content after finding it via search' and 'DO NOT USE: with a message ID.' It also provides clear conditions for when to use `full: true` vs `full: false`, including the case where search reported `hasAttachments: true`. This is exactly the kind of actionable selection guidance an agent needs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_filtersARead-only
List all Gmail filters — the server-side rules that auto-apply label actions to incoming mail. Shows each filter's criteria and label actions, and (for auditing) any forward address an existing filter carries. USE WHEN: reviewing existing automation, or to get a filter's id before delete_filter. SIDE EFFECTS: none. Requires the gmail.settings.basic scope — re-run mailwarden --auth if you authorized an earlier version.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| filters | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description explicitly declares 'SIDE EFFECTS: none' and adds the required scope ('gmail.settings.basic') with a re-authentication note ('re-run mailwarden --auth'). This goes beyond the readOnlyHint annotation by providing operational auth context and confirming no side effects, which is valuable for an agent deciding to invoke the tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, structured with clear segments (purpose, output details, usage, side effects/scope), and front-loaded with the core action. Every sentence earns its place without repetition, and the formatting improves readability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given zero parameters and an existing output schema, the description provides ample context: it explains what filters are, what information is returned, when to use the tool, that there are no side effects, and what scope is required. This is complete for a simple list tool with no input complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, so the baseline is 4 per the rubric. The description adds no parameter-specific details but instead clarifies what the tool returns (criteria, label actions, forward addresses), which compensates for the lack of parameters and enriches the agent's understanding of the output.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('List all Gmail filters') and the resource (filters), with a definition of what filters are and what information is shown (criteria, label actions, forward addresses). It distinguishes the tool from siblings like create_filter and delete_filter by focusing on listing and auditing existing filters.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'USE WHEN' guidance: reviewing existing automation or getting a filter's id before delete_filter. It gives clear context but does not explicitly name alternative tools for exclusion, so it falls short of the highest calibration example. The intent is clear enough for an agent to select this tool appropriately.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_labelsARead-only
List all Gmail labels (system + user). USE WHEN: inspecting the mailbox structure, or to get exact label names/ids — though modify_labels/bulk_modify/create_label all accept a plain label name directly, so a lookup is rarely required. SIDE EFFECTS: none.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| labels | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations include readOnlyHint=true, and the description adds the explicit 'SIDE EFFECTS: none' statement, reinforcing the safe read-only nature. It also provides behavioral context that the listing includes both system and user labels, which is not in the structured annotations. This adds value beyond the annotations without contradicting them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise: two short segments ('List all Gmail labels (system + user)' and 'SIDE EFFECTS: none') plus a focused usage clause. It is front-loaded with the core action, with no wasted words, making it easy to parse quickly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (no parameters, read-only, output schema exists), the description covers all necessary context: purpose, usage, side effects, and scope. It explicitly mentions the labels' scope (system + user) and when to use it, making it fully complete for an agent to select and invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the schema is empty, so schema description coverage is 100%. With 0 params, the baseline is 4. The description doesn't discuss parameters because none exist, which is appropriate. No additional parameter semantics are needed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'List all Gmail labels (system + user).' It uses a specific verb ('List') and resource ('Gmail labels'), and the scope is explicit. This distinguishes it from sibling tools like modify_labels, bulk_modify, and create_label, which perform mutations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description includes an explicit 'USE WHEN' clause, specifying appropriate contexts (inspecting mailbox structure, getting exact label names/ids). It also names alternative tools (modify_labels/bulk_modify/create_label) and notes that a lookup is rarely required because they accept plain label names. This is clear guidance on when to use the tool vs. alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_snoozedARead-only
List all snoozed threads with their due dates. SIDE EFFECTS: none.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| snoozed | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Explicitly states 'SIDE EFFECTS: none', which complements the readOnlyHint=true annotation and adds clarity beyond the annotation alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no fluff. Front-loads the main function and includes side-effect note concisely.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no parameters and an existing output schema, the description adequately covers the tool's behavior. Mentions 'due dates' hinting at output content.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
No parameters exist, so baseline is 4. The description adds no param info, but none is needed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists snoozed threads with due dates, distinguishing it from sibling tools like snooze or trash which perform actions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance on when to use this tool versus alternatives such as search or sweep_snoozed. The description is purely descriptive without usage context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_subscriptionsARead-only
Who keeps writing, how often, and whether you can get off the list — a mailbox slice grouped by SENDER, each row carrying its opt-out options. Contacts nobody: opt-out options come from the List-Unsubscribe header of each sender's newest thread (one metadata fetch per sender, not per thread). optOut is 'one-click' (the unsubscribe tool can perform it), 'link' (a human opens it in a browser), 'mailto' (would need sending, which mailwarden never does), 'none', or 'unknown' when that sender's header fetch failed or was never made because Gmail's quota ran out mid-listing. oldestDate/newestDate bound what the SAMPLE saw of that sender, not the sender's whole history — a query capped at max reaches back only as far as those threads go, which on a busy mailbox is days. There is deliberately NO precomputed frequency: judge it from threads across that span, with the sampling caveat in view. newestThreadId is what to hand to unsubscribe or bulk_unsubscribe. sendersFound is how many DISTINCT senders the sample held — when it exceeds topN, the list is truncated and raising topN shows more. USE WHEN: 'what am I subscribed to', 'which newsletters flood me', or picking targets before a bulk unsubscribe. DO NOT USE: for a general inbox overview (use triage_digest — it buckets by label and age too), or for one known thread (use list_unsubscribe). SIDE EFFECTS: none.
| Name | Required | Description | Default |
|---|---|---|---|
| max | No | ||
| topN | No | ||
| query | No | category:promotions |
Output Schema
| Name | Required | Description |
|---|---|---|
| query | Yes | |
| hasMore | Yes | |
| sampled | Yes | |
| sendersFound | Yes | |
| subscriptions | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations provide readOnlyHint=true, but the description goes well beyond that, disclosing that opt-out options come from metadata fetches, that Gmail quota exhaustion can produce 'unknown', that dates bound the sample not the sender's history, and that there is deliberately no precomputed frequency. These are real behavioral traits an agent needs to interpret results correctly.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense and front-loads the purpose, but it runs long with several sentences that could be tightened, and the flow moves between purpose, mechanics, field semantics, and usage guidance without clean visual separation. Every sentence earns its place informationally, but the size is above ideal conciseness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values needn't be fully explained, and the description covers most of what's missing: field semantics for optOut, oldestDate/newestDate, newestThreadId, sendersFound, and sampling caveats. It's comprehensive for a 3-param list tool, though the query parameter's behavior could be explained slightly more.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does substantially: it explains that max bounds the sample ('a query capped at max reaches back only as far as those threads go'), that topN truncation happens when sendersFound exceeds topN, and implies the query parameter's role through the 'mailbox slice' framing. The query parameter itself ('category:promotions' default) isn't explicitly discussed, leaving a minor gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly establishes the resource (sender-grouped mailbox slice with opt-out options) and distinguishes itself from siblings by naming triage_digest and list_unsubscribe as alternatives. The opening is slightly metaphorical ('Who keeps writing, how often, whether you can get off the list'), but it resolves into a precise function. It's clear what the tool does, though the verb is less direct than an ideal 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'USE WHEN' and 'DO NOT USE' sections name the exact scenarios and the sibling tools to prefer in each case: triage_digest for general inbox overview, list_unsubscribe for one known thread. This is textbook when/when-not guidance with alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_unsubscribeARead-only
Report the opt-out options a thread advertises (List-Unsubscribe / RFC 8058), without contacting anyone. Reads the newest message that carries the header, so a reply threaded onto a newsletter does not hide it. oneClick means the sender supports the automatable one-click opt-out — the unsubscribe tool can perform it. httpsUrls without oneClick are links for a human to open in a browser; mailtos would require sending mail, which mailwarden never does. bodyCandidates appears only when the headers advertise nothing: unsubscribe links found in the message TEXT, which is written by the sender and therefore untrusted. Show them to the user to open; never fetch one, and do not treat them as an opt-out that happened. hasUnsubscribe stays false for them — it describes the headers. USE WHEN: checking whether a newsletter can be unsubscribed from, or showing the user the link to click. SIDE EFFECTS: none — no request is made to the sender.
| Name | Required | Description | Default |
|---|---|---|---|
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| from | Yes | |
| mailtos | Yes | |
| subject | Yes | |
| oneClick | Yes | |
| threadId | Yes | |
| httpsUrls | Yes | |
| messageId | Yes | |
| bodyCandidates | Yes | |
| hasUnsubscribe | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, and the description strongly reinforces this with 'without contacting anyone' and 'SIDE EFFECTS: none — no request is made to the sender.' It also discloses important nuances: it reads the newest message carrying the header, bodyCandidates come from untrusted message text, and they must not be treated as an actual opt-out. This goes well beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every major section earns its place: field meanings, safety warnings, use cases, and side effects. It is front-loaded with the core purpose before diving into details. There is minor redundancy between 'without contacting anyone' and 'SIDE EFFECTS: none,' but overall the length is justified by the nuanced output behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's purpose, when to use it, how it behaves regarding headers and body text, safety constraints, side effects, and relationships to sibling tools. It also explains the special cases of `hasUnsubscribe`, `bodyCandidates`, and untrusted content. Given the output schema exists, no important gap remains for an agent to invoke this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema only defines `threadId` as a required string with no description, so the description carries the burden. It adds meaning by explaining that the tool operates on a thread and reads the newest message in that thread, even noting how a reply threaded onto a newsletter is handled. This gives enough context for an agent to understand what `threadId` represents, though no explicit format guidance is provided.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Report the opt-out options a thread advertises (List-Unsubscribe / RFC 8058), without contacting anyone.' It clarifies this is a read-only inspection tool and distinguishes it from the sibling `unsubscribe` tool by explicitly noting that `unsubscribe` can perform the one-click opt-out.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides an explicit 'USE WHEN' section: 'checking whether a newsletter can be unsubscribed from, or showing the user the link to click.' It also gives alternative routing by pointing to the `unsubscribe` tool for oneClick cases and instructs the agent to never fetch bodyCandidates, establishing clear boundaries for safe use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mark_readAIdempotent
Mark a thread as read. SIDE EFFECTS: removes UNREAD; reversible via mark_unread.
| Name | Required | Description | Default |
|---|---|---|---|
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes beyond annotations by detailing the specific side effect and reversibility, fully disclosing the tool's behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise with two sentences, front-loading the purpose and listing side effects efficiently.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter, clear annotations, and an output schema, the description covers all necessary context including side effects and reversibility.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter 'threadId' is self-explanatory given the tool name, and with 0% schema coverage the description adds no extra meaning, which is adequate for this simple case.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action 'Mark a thread as read', which directly matches the tool name and distinguishes it from siblings like 'mark_unread'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explains the side effect (removes UNREAD) and reversibility via mark_unread, providing clear context for usage, though it does not explicitly state when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mark_unreadAIdempotent
Mark a thread as unread. SIDE EFFECTS: adds UNREAD; reversible via mark_read.
| Name | Required | Description | Default |
|---|---|---|---|
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate idempotent and non-destructive behavior. The description adds value by explicitly stating the side effect 'adds UNREAD' and noting reversibility, which aligns with and supplements the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise: one sentence plus a bullet for side effects. Every word is necessary, and the structure immediately conveys the core action and notable consequences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simplicity of the tool (1 parameter, no enums, output schema present), the description covers the essential behavior. It could benefit from explaining what 'threadId' represents, but the context from sibling tools and the name likely suffices for an AI agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has one parameter 'threadId' with no description, and schema description coverage is 0%. The description does not explain the parameter meaning, format, or constraints. While the name is self-explanatory, the description should add more context to compensate for the schema gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action: 'Mark a thread as unread.' The verb 'Mark' and resource 'thread' are specific. It distinguishes from sibling 'mark_read' implicitly through the opposite action, and the name itself clarifies the purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description mentions reversibility via 'mark_read,' which hints at when not to use it (if you want to undo). However, it does not provide explicit context on when to use this tool over alternatives like 'archive' or 'trash,' leaving some ambiguity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
modify_labelsAIdempotent
Add/remove labels on a thread. Archive = remove 'INBOX'; mark read = remove 'UNREAD'. Labels may be given by name or by id: an unknown name in add is created automatically (use '/' for nested labels), an unknown name in remove is ignored. USE WHEN: applying custom labels or label combinations in one call. DO NOT USE: for plain archive/read/unread — the dedicated tools are clearer. SIDE EFFECTS: changes the thread's labels (and may create a label named in add); reversible by the inverse call.
| Name | Required | Description | Default |
|---|---|---|---|
| add | No | ||
| remove | No | ||
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations, the description discloses side effects ('changes the thread's labels'), the auto-creation of labels in 'add', the ignoring of unknown names in 'remove', and reversibility via inverse call. This adds meaningful behavioral context not present in the idempotentHint or readOnlyHint annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tightly written with front-loaded purpose and structured segments (USE WHEN, DO NOT USE, SIDE EFFECTS). Every sentence conveys needed information without fluff, making it easy for an agent to parse quickly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simple scope (single thread labels) and the presence of an output schema, the description covers all essential usage aspects: operation semantics, edge cases (unknown labels), side effects, and reversibility. It is complete enough to guide correct invocation without requiring external knowledge.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must bear the full weight of parameter explanation. It does so by explaining that labels can be given by name or id, describing the special INBOX/UNREAD mapping, and clarifying the behavior of unknown names in each array. This goes well beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Add/remove labels on a thread,' a specific verb and resource. It distinguishes itself from siblings by explicitly mapping archive to removing 'INBOX' and mark read to removing 'UNREAD,' and by pointing out that dedicated tools exist for simpler operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'USE WHEN' and 'DO NOT USE' guidance, naming alternatives ('dedicated tools') for plain archive/read/unread. This clearly tells an agent when to select this tool over similar ones like mark_read or archive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchARead-only
Search Gmail with native query syntax (e.g. 'in:inbox from:foo@bar.com newer_than:7d'). Returns thread summaries; read-state/category predicates are re-verified against each hit's live labels. Each summary carries signals derived from the thread's first message headers/MIME — newsletter (List-Id/List-Unsubscribe/Precedence bulk or list), automated (Auto-Submitted, auto-reply/suppress headers, no-reply-style senders), calendar (text/calendar or .ics part), replyToMismatch (a Reply-To on another domain than From — a subdomain of the same domain counts as the same); empty when nothing is declared. Paginated: when more results exist, the response carries a nextPageToken — pass it back via pageToken to fetch the next page. A page can come back with FEWER threads than maxResults and still have a nextPageToken: false positives from the index consume the scan window, and on a mailbox whose read state the index has fallen behind on, most candidates for an is:unread query can be already-read mail. A short page is therefore not evidence that the result set is exhausted — only an absent nextPageToken is. SPAM AND TRASH ARE EXCLUDED unless the query names them. Gmail leaves both out of any query that does not say in:spam / in:trash, so a plain from:someone returns nothing for a mail that is sitting in spam — measured against a live mailbox, not assumed. Nothing in the result marks the omission, so treat 'no hits' as 'none outside spam and trash'. When mail the user expects is missing, retry with in:spam before reporting that it does not exist: mail is often filed as spam because of something the user just did — a signup, a password reset, an order confirmation — which is precisely what a spam filter cannot know and the caller often can. USE WHEN: locating threads by sender, subject, date, label, or read state. DO NOT USE: to fetch a thread you already have the ID of (use get_thread). SIDE EFFECTS: none.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| pageToken | No | ||
| maxResults | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| threads | Yes | |
| nextPageToken | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the readOnlyHint annotation, disclosing that spam/trash are excluded unless named, that pages can be short with a nextPageToken, that signals are derived from headers, and that no hits may mean 'none outside spam/trash'. It also explicitly states 'SIDE EFFECTS: none', aligning with readOnlyHint. No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every sentence earns its place, covering critical behavioral nuances. It is front-loaded with the core purpose and query syntax, then dives into pagination and spam/trash caveats. The USE WHEN/DO NOT USE structure aids scannability. Slightly verbose but justified by the complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (pagination, spam/trash exclusion, signals derivation, false positives), the description is remarkably complete. It covers return values (signals, nextPageToken), edge cases (short pages, spam), and usage guidance. The output schema exists, so return format details are not needed in the description.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It explains the query parameter with examples and describes the pageToken behavior and maxResults pagination semantics. It doesn't detail maxResults bounds (schema has that), but it adds crucial context about short pages and nextPageToken, which is more valuable than repeating schema constraints.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool searches Gmail with native query syntax and returns thread summaries. It distinguishes itself from siblings by explicitly noting it is for locating threads by sender, subject, date, label, or read state, and not for fetching a thread by ID (which is get_thread's job).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'USE WHEN' and 'DO NOT USE' sections, naming the alternative tool (get_thread) and clarifying when not to use this tool. It also gives detailed guidance on handling spam/trash exclusions and pagination, which are critical usage contexts.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
snoozeAIdempotent
Snooze a thread until a date or time: archives it now, resurfaces when it comes due and sweep_snoozed runs. until accepts an explicit date (YYYY-MM-DD), a date+time (YYYY-MM-DD HH:MM or e.g. '2026-06-20 9am'), OR a preset resolved server-side: today, tomorrow, weekend (next Saturday), next week (next Monday), a weekday name (monday–sunday, next occurrence), 'in N days', or 'in N hours'. A preset may carry a trailing time ('tomorrow 9am', 'monday 8:30'). A timed snooze wakes at the next sweep on/after that minute. USE WHEN: deferring a thread to a later date/time instead of leaving it in the inbox. DO NOT USE: for permanent removal (use archive or trash). SIDE EFFECTS: removes INBOX, adds a dated MCP/Snoozed label; reversible via unsnooze.
| Name | Required | Description | Default |
|---|---|---|---|
| until | Yes | ||
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| threadId | Yes | |
| snoozedUntil | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Explains side effects in detail ('removes INBOX, adds a dated MCP/Snoozed label'), notes reversibility ('reversible via unsnooze'), and describes the resurfacing trigger ('sweep_snoozed runs'). This goes well beyond the annotations (readOnly=false, destructive=false, idempotent=true) and is consistent with them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Structured and front-loaded: one-sentence summary, then detailed `until` format, then usage guidance, then side effects. Every sentence adds value; the length is justified by the complexity of the `until` parameter.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers the operation, timing semantics, side effects, reversibility, and usage boundaries. With an output schema present, no return-value explanation is needed, and the description is sufficient for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has no descriptions for either parameter (0% coverage), but the description provides exhaustive detail for `until`, including explicit date/time formats, presets, and trailing times. `threadId` is self-explanatory from its name, so the description compensates fully.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly identifies the operation ('Snooze a thread') and its resource ('thread'), and explains the mechanism (archives now, resurfaces later). It also distinguishes from siblings by stating 'DO NOT USE for permanent removal (use archive or trash).'
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use ('USE WHEN: deferring a thread to a later date/time instead of leaving it in the inbox') and when not to ('DO NOT USE: for permanent removal (use archive or trash)'), naming alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sweep_snoozedAIdempotent
Resurface all snoozed threads whose date is due (<= today), batched at 1000 messages per API request. Set dryRun:true to rehearse: reports the due labels and threads (dueLabels/dueThreads) as the sweep would find them (from the live label listing; a single snooze label with more than 5000 messages is under-counted in the rehearsal), and wakes nothing. USE WHEN: the user asks to process due snoozes, or as a scheduled maintenance call; dryRun to answer 'what is due right now?' without acting. SIDE EFFECTS: due threads return to the inbox marked unread (none with dryRun); safe to run repeatedly. failedCount/errors report messages a batch could not wake (their label is kept for the next sweep).
| Name | Required | Description | Default |
|---|---|---|---|
| dryRun | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| date | Yes | |
| woken | Yes | |
| dryRun | Yes | |
| errors | Yes | |
| dueLabels | Yes | |
| dueThreads | Yes | |
| wokenCount | Yes | |
| failedCount | Yes | |
| dueThreadCount | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes well beyond the annotations by disclosing concrete side effects: due threads return to the inbox marked unread, dryRun wakes none, and failures keep the label for the next sweep. It also discloses a real limitation (under-counting in rehearsal for labels with >5000 messages), the batch size, and idempotence, giving the agent an accurate behavioral model.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with labeled sections (USE WHEN, SIDE EFFECTS) and front-loaded with the core function. Every sentence adds value—batch size, dryRun behavior, side effects, and error handling—without fluff. It is longer than ideal but earns its length through density of useful information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and the presence of an output schema, the description covers all critical aspects: behavior, batch limits, dryRun rehearsal details, side effects, idempotence, and failure handling ('failedCount/errors report messages a batch could not wake'). No significant gaps remain for the agent to make an informed invocation decision.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Even though the schema only defines a single boolean dryRun, the description thoroughly explains its purpose and outcome: setting dryRun:true produces a rehearsal that reports dueLabels/dueThreads and wakes nothing. This fully compensates for the schema's minimal parameter info, adding essential semantics for correct invocation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb ('Resurface') and a precise target ('all snoozed threads whose date is due (<= today)'), making it unmistakable what the tool does. It clearly differentiates from sibling tools such as list_snoozed, snooze, and unsnooze by focusing on acting on due threads rather than listing or individual operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description includes an explicit 'USE WHEN' section stating clear triggers: 'the user asks to process due snoozes, or as a scheduled maintenance call,' and also covers the dryRun rehearsal use case for answering 'what is due right now?' without acting. It lacks an explicit 'when not to use' or direct alternative names, but the context is sufficiently clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
trashADestructiveIdempotent
Move a thread to Trash. USE WHEN: the thread should be discarded. DO NOT USE: for inbox cleanup of mail worth keeping (use archive). SIDE EFFECTS: thread moves to Trash; recoverable via untrash for ~30 days, then Gmail deletes it permanently.
| Name | Required | Description | Default |
|---|---|---|---|
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate destructiveHint=true, but the description adds critical context: thread is recoverable via untrash for ~30 days, then permanently deleted. This goes beyond annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences, front-loaded with the action, and every sentence adds value with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter and an output schema present, the description covers purpose, usage guidelines, and side effects completely.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage for the single required string parameter 'threadId'. While the description doesn't detail its format, the tool's context makes it clear. A format hint (e.g., 'Gmail thread ID') would improve clarity.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states 'Move a thread to Trash' with a specific verb and resource, and distinguishes from the sibling 'archive' tool by noting when not to use it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear USE WHEN ('the thread should be discarded') and DO NOT USE conditions ('for inbox cleanup of mail worth keeping') with explicit alternative (archive). This is exemplary guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
triage_digestARead-only
Structured overview of a mailbox slice for triage DECISIONS — sender / label / age buckets, unread and attachment counts, and header-derived signals (newsletter / automated / calendar / replyToMismatch — thread counts overall, and per sender the set of signals its threads carry), instead of a raw thread list. USE WHEN: deciding what to bulk-archive/snooze/label, or summarizing inbox state ('what's in my inbox?'). DO NOT USE: to read a specific thread (use search/get_thread). Samples up to max most-recent matches; hasMore flags that more matched than were sampled. byAge buckets by each thread's FIRST message date (thread age, not last activity). SIDE EFFECTS: none.
| Name | Required | Description | Default |
|---|---|---|---|
| max | No | ||
| topN | No | ||
| query | No | in:inbox |
Output Schema
| Name | Required | Description |
|---|---|---|
| byAge | Yes | |
| query | Yes | |
| unread | Yes | |
| hasMore | Yes | |
| sampled | Yes | |
| signals | Yes | |
| topLabels | Yes | |
| topSenders | Yes | |
| withAttachments | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, but the description adds valuable behavioral details: sampling up to 'max' with hasMore, age bucketing by first message date, and explicit 'SIDE EFFECTS: none'. This goes beyond annotations and answers likely agent questions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense yet well-organized into clear sections: overview, use cases, behavior, and side effects. Every sentence contributes new information, with no redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's purpose, usage, sampling, age bucketing, and side effects, while an output schema likely handles return-value documentation. However, it leaves parameter semantics incomplete (especially topN/query), a notable gap given the low schema coverage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must explain parameters. It only mentions 'max' (sampling limit) but leaves 'topN' and 'query' undefined. This forces the agent to infer their meaning, which is insufficient for correct invocation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it provides a structured overview of a mailbox slice for triage decisions, listing specific aggregated fields (sender/label/age buckets, counts, signals) and explicitly contrasts with a raw thread list. It also distinguishes from reading a specific thread by referencing search/get_thread.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Includes explicit 'USE WHEN' and 'DO NOT USE' sections, naming alternatives (search/get_thread) and giving concrete examples like bulk-archiving/snoozing/labeling. This provides clear guidance on when to choose this tool over siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
unsnoozeAIdempotent
Cancel a snooze: return the thread to the inbox now. SIDE EFFECTS: removes the snooze label, restores INBOX.
| Name | Required | Description | Default |
|---|---|---|---|
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| threadId | Yes | |
| unsnoozed | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses side effects beyond annotations: 'removes the snooze label, restores INBOX'. Annotations already indicate idempotentHint=true and destructiveHint=false, and the description aligns with these, adding valuable behavioral context without contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise with two sentences. The first sentence front-loads the primary action, and the second lists side effects. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter tool with an output schema (though not shown), the description covers the essential effect. However, it omits any mention of error conditions or prerequisites (e.g., thread must be snoozed). Still, given low complexity and presence of output schema, it is largely complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter threadId has no description in the schema (0% coverage) and the tool description does not add any meaning beyond the name. It fails to explain what threadId represents or its format, which is necessary for correct usage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action: 'cancel a snooze' and 'return the thread to the inbox', with a specific verb and resource. It distinguishes from sibling tools like snooze (the reverse operation).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use the tool (to cancel a snooze) and mentions side effects, but does not explicitly state when not to use it or list alternatives like mark_read or archive. However, the sibling list includes snooze as the inverse, making usage clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
unsubscribeAIdempotent
Unsubscribe from the mailing list a thread came from, via the sender's RFC 8058 one-click endpoint. There is deliberately no URL parameter: the endpoint is taken from the message's own List-Unsubscribe header and nowhere else. Only https one-click endpoints are called (fixed request body, response body discarded); a plain link is reported for the user to open, and a mailto: opt-out is never performed because mailwarden cannot send mail. If the sender offers nothing automatable this returns unsubscribed:false with the alternatives in options — it is not an error. USE WHEN: the user wants off a newsletter. Pair with archive/trash or create_filter to deal with mail already in the mailbox. DO NOT USE: to check whether unsubscribing is possible (use list_unsubscribe — it contacts nobody). A sender already contacted in this session is reported with duplicateOf and NOT contacted again — safe to retry after a timeout. Pass force:true for a deliberate second attempt (e.g. the endpoint answered 500). SIDE EFFECTS: makes an outbound HTTPS request to the sender's unsubscribe endpoint (plus up to 3 redirects) — the only non-Google host mailwarden ever contacts. This confirms to the sender that the address is live, and it cannot be undone. The mailbox itself is not changed.
| Name | Required | Description | Default |
|---|---|---|---|
| force | No | ||
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| url | No | |
| from | Yes | |
| reason | No | |
| status | No | |
| options | Yes | |
| threadId | Yes | |
| messageId | Yes | |
| duplicateOf | No | |
| unsubscribed | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=false, openWorldHint=true, idempotentHint=true, destructiveHint=false), the description discloses crucial side effects: makes an outbound HTTPS request, confirms the address is live, cannot be undone, and never modifies the mailbox. It also details the duplicateOf behavior and force:true for retries. No contradictions with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but well-organized with 'USE WHEN', 'DO NOT USE', and 'SIDE EFFECTS' sections. Every sentence adds value: security protocol, lacking URL parameter, redirect limits, non-error on failure. Despite length, it remains front-loaded and efficiently structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (external network call, alternative handling, duplicate prevention), the description covers all aspects: side effects, alternatives, failure modes, and output semantics (duplicateOf, options). An output schema exists, so return values are covered. No significant gaps remain.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must explain parameters. It implicitly defines threadId as the thread containing the mailing list email. It explicitly explains force:true for a deliberate second attempt. While it doesn't specify threadId's exact format, it adds meaningful context beyond the bare schema. Slight room for more explicit parameter documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action: 'Unsubscribe from the mailing list a thread came from, via the sender's RFC 8058 one-click endpoint.' It clearly distinguishes from sibling tools (e.g., list_unsubscribe) by emphasizing it actually performs the subscription removal. The scope and method are precise.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit guidance is given: 'USE WHEN: the user wants off a newsletter' and 'DO NOT USE: to check whether unsubscribing is possible (use list_unsubscribe — it contacts nobody).' It also suggests pairing with archive/trash or create_filter for post-processing. This fully covers when and when not to use, with alternative tools named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
untrashAIdempotent
Restore a thread from Trash. SIDE EFFECTS: removes the TRASH label; user labels are preserved, but INBOX is NOT re-added — use modify_labels (add INBOX) to return it to the inbox.
| Name | Required | Description | Default |
|---|---|---|---|
| threadId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations (readOnlyHint=false, destructiveHint=false) already indicate mutation and non-destructive nature. Description adds critical context: side effects on labels (TRASH removal, INBOX not re-added). No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences: first provides core action, second details side effects and follow-up. No superfluous words. Efficient and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Output schema exists, so return values are documented externally. Description covers side effects, usage, and follow-up. With one simple param and clear annotations, the description is fully adequate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter 'threadId' with 0% schema description coverage. Description does not explicitly detail the parameter, but context from 'Restore a thread from Trash' implies its purpose. Adequate for a simple string param, but could be more explicit.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states 'Restore a thread from Trash.' verb+resource pair. Distinguishes from sibling tools like 'trash' (opposite action) and 'archive' (different destination).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly notes side effects: removes TRASH label, preserves user labels, but does NOT re-add INBOX. Recommends using 'modify_labels (add INBOX)' to return to inbox. Provides clear when-to-use and follow-up action.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
what_changedARead-only
What happened in the mailbox since a point you already hold: messages that arrived or were removed, and labels applied or taken off — from Gmail's own event log, in one call. Pass the historyId a previous what_changed or get_profile returned; the reply carries the next one to keep. mailwarden stores NOTHING between calls, so the id lives with you. Reports EVENTS, not current state: a message that was marked unread and then read appears under both, and both are true. For how the mailbox looks NOW, use search or get_thread. Gmail keeps roughly a week of history. An id older than that is an ERROR, never an empty result — it means the question can no longer be answered incrementally, so take a fresh historyId from get_profile and re-establish state with search. labelId narrows the feed to events touching one label (e.g. 'INBOX', or a snooze label). Counts are complete even when the id lists are cut at 200 (truncated). USE WHEN: a recurring check — 'what came in since I last looked', watching a label, or confirming a bulk action landed. Far cheaper than re-running a search over the whole slice. DO NOT USE: for a first look at a mailbox (there is no id yet — start with triage_digest or search), or to read content: this returns ids only, no sender, subject or body. SIDE EFFECTS: none.
| Name | Required | Description | Default |
|---|---|---|---|
| max | No | ||
| labelId | No | ||
| sinceHistoryId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| added | Yes | |
| deleted | Yes | |
| records | Yes | |
| historyId | Yes | |
| truncated | Yes | |
| labelsAdded | Yes | |
| labelsRemoved | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes far beyond the annotations: explains events vs current state, history retention limits, error-on-stale-id behavior, truncation semantics, count completeness, and side-effect-free behavior. No contradiction with readOnlyHint=true.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every sentence earns its place: mechanics, semantics, error handling, use cases, and exclusions are all present. Critical context is front-loaded before the extended guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a delta-reporting tool with one required parameter, the description covers return shape, state management, error behavior, filter behavior, and alternatives. Even with an output schema present, nothing important is left ambiguous.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description adds deep semantics for sinceHistoryId (how to obtain, carry, and refresh it) and labelId (narrowing to label events). max is not explicitly described, though its schema constraints make it largely self-explanatory.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states exactly what the tool reports — mailbox changes (arrivals, removals, label changes) from Gmail's event log since a held historyId. It explicitly contrasts with search and get_thread, so an agent can distinguish this from siblings immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit USE WHEN guidance for recurring checks and DO NOT USE guidance for first looks and content reading, naming alternatives like triage_digest, search, and get_thread. This is exemplary routing information.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.21.0- Changed
create_filter2 fields changed- added
Input schema / properties / verifyAdded value: +{ + "default": false, + "type": "boolean" +} - changed
Output schema / properties / applied / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "capped": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "failed": { - "items": { - "additionalProperties": false, - "properties": { - "error": { - "type": "string" - }, - "messageIds": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "messageIds", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "matchedMessages": { - "type": "number" - }, - "query": { - "type": "string" - }, - "submittedMessages": { - "type": "number" - }, - "submittedThreadCount": { - "type": "number" - } - }, - "required": [ - "query", - "matchedMessages", - "submittedMessages", - "submittedThreadCount", - "capped", - "failed" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "capped": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "failed": { + "items": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "messageIds": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "messageIds", + "error" + ], + "type": "object" + }, + "type": "array" + }, + "matchedMessages": { + "type": "number" + }, + "query": { + "type": "string" + }, + "submittedMessages": { + "type": "number" + }, + "submittedThreadCount": { + "type": "number" + }, + "verified": { + "additionalProperties": false, + "properties": { + "applied": { + "type": "number" + }, + "notApplied": { + "items": { + "type": "string" + }, + "type": "array" + }, + "unverifiable": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "applied", + "notApplied", + "unverifiable" + ], + "type": "object" + } + }, + "required": [ + "query", + "matchedMessages", + "submittedMessages", + "submittedThreadCount", + "capped", + "failed" + ], + "type": "object" + }, + { + "type": "null" + } +]
5 tool updates
v0.18.0- Changed
bulk_modify2 fields changed- added
Input schema / properties / crossCheckAdded value: +{ + "default": false, + "type": "boolean" +} - added
Output schema / properties / crossCheckedAdded value: +{ + "additionalProperties": false, + "properties": { + "capped": { + "type": "boolean" + }, + "dropped": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "predicate": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "id", + "threadId", + "predicate" + ], + "type": "object" + }, + "type": "array" + }, + "droppedMessages": { + "type": "number" + }, + "predicates": { + "items": { + "type": "string" + }, + "type": "array" + }, + "skipped": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "predicates", + "droppedMessages", + "dropped", + "capped" + ], + "type": "object" +}
- Changed
create_label3 fields changed- added
Input schema / properties / backgroundColorAdded value: +{ + "type": "string" +} - added
Input schema / properties / textColorAdded value: +{ + "type": "string" +} - added
Output schema / properties / colorAdded value: +{ + "additionalProperties": false, + "properties": { + "backgroundColor": { + "type": "string" + }, + "textColor": { + "type": "string" + } + }, + "required": [ + "backgroundColor", + "textColor" + ], + "type": "object" +}
- Changed
get_profile2 fields changed- added
Output schema / properties / historyIdAdded value: +{ + "type": "string" +} - changed
Output schema / requiredPrevious value: -[ - "emailAddress", - "messagesTotal", - "threadsTotal" -]New value: +[ + "emailAddress", + "messagesTotal", + "threadsTotal", + "historyId" +]
- Changed
list_unsubscribe2 fields changed- added
Output schema / properties / bodyCandidatesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "evidence": { + "enum": [ + "link-text", + "url" + ], + "type": "string" + }, + "text": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "url", + "evidence", + "text" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "threadId", - "messageId", - "from", - "subject", - "hasUnsubscribe", - "oneClick", - "httpsUrls", - "mailtos" -]New value: +[ + "threadId", + "messageId", + "from", + "subject", + "hasUnsubscribe", + "bodyCandidates", + "oneClick", + "httpsUrls", + "mailtos" +]
- Added
what_changed
1 tool update
v0.17.0- Changed
get_thread2 fields changed- added
Output schema / properties / messages / items / properties / authenticationAdded value: +{ + "additionalProperties": false, + "properties": { + "alsoReported": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authservId": { + "type": "string" + }, + "dkim": { + "type": "string" + }, + "dmarc": { + "type": "string" + }, + "headerFrom": { + "type": "string" + }, + "mailedBy": { + "type": "string" + }, + "otherReports": { + "type": "integer" + }, + "returnPath": { + "type": "string" + }, + "signedBy": { + "type": "string" + }, + "spf": { + "type": "string" + }, + "unchecked": { + "const": true, + "type": "boolean" + } + }, + "type": "object" +} - changed
Output schema / properties / messages / items / requiredPrevious value: -[ - "id", - "threadId", - "labelIds", - "from", - "to", - "subject", - "date", - "snippet" -]New value: +[ + "id", + "threadId", + "labelIds", + "from", + "to", + "subject", + "date", + "snippet", + "authentication" +]
3 tool updates
v0.15.1- Changed
bulk_modify9 fields changed- added
Input schema / properties / verifyAdded value: +{ + "default": false, + "type": "boolean" +} - removed
Output schema / properties / modifiedMessagesRemoved value: -{ - "type": "number" -} - removed
Output schema / properties / modifiedThreadCountRemoved value: -{ - "type": "number" -} - removed
Output schema / properties / modifiedThreadsRemoved value: -{ - "items": { - "type": "string" - }, - "type": "array" -} - added
Output schema / properties / submittedMessagesAdded value: +{ + "type": "number" +} - added
Output schema / properties / submittedThreadCountAdded value: +{ + "type": "number" +} - added
Output schema / properties / submittedThreadsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / verifiedAdded value: +{ + "additionalProperties": false, + "properties": { + "applied": { + "type": "number" + }, + "notApplied": { + "items": { + "type": "string" + }, + "type": "array" + }, + "unverifiable": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "applied", + "notApplied", + "unverifiable" + ], + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "dryRun", - "matchedMessages", - "matchedThreadCount", - "matchedThreads", - "modifiedMessages", - "modifiedThreadCount", - "modifiedThreads", - "capped", - "unverifiedPredicates", - "failed" -]New value: +[ + "dryRun", + "matchedMessages", + "matchedThreadCount", + "matchedThreads", + "submittedMessages", + "submittedThreadCount", + "submittedThreads", + "capped", + "unverifiedPredicates", + "failed" +]
- Changed
create_filter1 field changed- changed
Output schema / properties / applied / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "capped": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "failed": { - "items": { - "additionalProperties": false, - "properties": { - "error": { - "type": "string" - }, - "messageIds": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "messageIds", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "matchedMessages": { - "type": "number" - }, - "modifiedMessages": { - "type": "number" - }, - "modifiedThreadCount": { - "type": "number" - }, - "query": { - "type": "string" - } - }, - "required": [ - "query", - "matchedMessages", - "modifiedMessages", - "modifiedThreadCount", - "capped", - "failed" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "capped": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "failed": { + "items": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "messageIds": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "messageIds", + "error" + ], + "type": "object" + }, + "type": "array" + }, + "matchedMessages": { + "type": "number" + }, + "query": { + "type": "string" + }, + "submittedMessages": { + "type": "number" + }, + "submittedThreadCount": { + "type": "number" + } + }, + "required": [ + "query", + "matchedMessages", + "submittedMessages", + "submittedThreadCount", + "capped", + "failed" + ], + "type": "object" + }, + { + "type": "null" + } +]
- Changed
get_thread2 fields changed- changed
Output schema / properties / messages / items / requiredPrevious value: -[ - "id", - "threadId", - "labelIds", - "from", - "to", - "subject", - "date", - "snippet", - "plaintextBody", - "htmlBody", - "attachments" -]New value: +[ + "id", + "threadId", + "labelIds", + "from", + "to", + "subject", + "date", + "snippet" +] - added
Output schema / properties / metadataOnlyAdded value: +{ + "const": true, + "type": "boolean" +}
1 tool update
v0.14.0- Changed
unsubscribe2 fields changed- added
Input schema / properties / forceAdded value: +{ + "default": false, + "type": "boolean" +} - added
Output schema / properties / duplicateOfAdded value: +{ + "type": "string" +}
5 tool updates
v0.11.0- Changed
bulk_modify7 fields changed- added
Input schema / properties / dryRunAdded value: +{ + "default": false, + "type": "boolean" +} - added
Output schema / properties / dryRunAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / labelsToCreateAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / matchedThreadCountAdded value: +{ + "type": "number" +} - added
Output schema / properties / matchedThreadsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / unverifiedPredicatesAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "matchedMessages", - "modifiedMessages", - "modifiedThreadCount", - "modifiedThreads", - "capped", - "failed" -]New value: +[ + "dryRun", + "matchedMessages", + "matchedThreadCount", + "matchedThreads", + "modifiedMessages", + "modifiedThreadCount", + "modifiedThreads", + "capped", + "unverifiedPredicates", + "failed" +]
- Changed
bulk_unsubscribe5 fields changed- added
Input schema / properties / dryRunAdded value: +{ + "default": false, + "type": "boolean" +} - added
Output schema / properties / dryRunAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / requestsAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / wouldCallAdded value: +{ + "type": "string" +} - changed
Output schema / requiredPrevious value: -[ - "requested", - "attempted", - "unsubscribed", - "skippedDuplicates", - "skippedOutOfTime", - "results" -]New value: +[ + "dryRun", + "requested", + "attempted", + "unsubscribed", + "skippedDuplicates", + "skippedOutOfTime", + "requests", + "results" +]
- Changed
search2 fields changed- added
Output schema / properties / threads / items / properties / signalsAdded value: +{ + "items": { + "enum": [ + "newsletter", + "automated", + "calendar", + "replyToMismatch" + ], + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / threads / items / requiredPrevious value: -[ - "threadId", - "messageCount", - "from", - "subject", - "date", - "labelIds", - "snippet", - "hasAttachments" -]New value: +[ + "threadId", + "messageCount", + "from", + "subject", + "date", + "labelIds", + "snippet", + "hasAttachments", + "signals" +]
- Changed
sweep_snoozed8 fields changed- added
Input schema / $schemaAdded value: +"http://json-schema.org/draft-07/schema#" - added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / dryRunAdded value: +{ + "default": false, + "type": "boolean" +} - added
Output schema / properties / dryRunAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / dueLabelsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / dueThreadCountAdded value: +{ + "type": "number" +} - added
Output schema / properties / dueThreadsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "date", - "wokenCount", - "woken", - "failedCount", - "errors" -]New value: +[ + "date", + "dryRun", + "dueLabels", + "dueThreadCount", + "dueThreads", + "wokenCount", + "woken", + "failedCount", + "errors" +]
- Changed
triage_digest4 fields changed- added
Output schema / properties / signalsAdded value: +{ + "additionalProperties": false, + "properties": { + "automated": { + "type": "number" + }, + "calendar": { + "type": "number" + }, + "newsletter": { + "type": "number" + }, + "replyToMismatch": { + "type": "number" + } + }, + "required": [ + "newsletter", + "automated", + "calendar", + "replyToMismatch" + ], + "type": "object" +} - added
Output schema / properties / topSenders / items / properties / signalsAdded value: +{ + "items": { + "enum": [ + "newsletter", + "automated", + "calendar", + "replyToMismatch" + ], + "type": "string" + }, + "type": "array" +} - changed
Output schema / properties / topSenders / items / requiredPrevious value: -[ - "sender", - "name", - "count", - "unread" -]New value: +[ + "sender", + "name", + "count", + "unread", + "signals" +] - changed
Output schema / requiredPrevious value: -[ - "query", - "sampled", - "hasMore", - "unread", - "withAttachments", - "byAge", - "topSenders", - "topLabels" -]New value: +[ + "query", + "sampled", + "hasMore", + "unread", + "withAttachments", + "byAge", + "signals", + "topSenders", + "topLabels" +]
4 tool updates
v0.9.0- Added
bulk_unsubscribe - Added
list_subscriptions - Added
list_unsubscribe - Added
unsubscribe
8 tool updates
v0.6.0- Changed
bulk_modify1 field changed- added
Input schema / properties / query / minLengthAdded value: +1
- Added
create_filter - Added
create_label - Added
delete_filter - Added
get_profile - Added
list_filters - Changed
snooze2 fields changed- added
Input schema / properties / until / minLengthAdded value: +1 - removed
Input schema / properties / until / patternRemoved value: -"^\\d{4}-\\d{2}-\\d{2}$"
- Added
triage_digest
1 tool update
v0.1.9- Changed
bulk_modify2 fields changed- added
Output schema / properties / cappedAdded value: +{ + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "matchedMessages", - "modifiedMessages", - "modifiedThreadCount", - "modifiedThreads", - "failed" -]New value: +[ + "matchedMessages", + "modifiedMessages", + "modifiedThreadCount", + "modifiedThreads", + "capped", + "failed" +]
15 tool updates
v0.1.8- First observed
archive - First observed
bulk_modify - First observed
download_attachment - First observed
get_thread - First observed
list_labels - First observed
list_snoozed - First observed
mark_read - First observed
mark_unread - First observed
modify_labels - First observed
search - First observed
snooze - First observed
sweep_snoozed - First observed
trash - First observed
unsnooze - First observed
untrash
TDQS
Scored across 26 tools
Every tool has a clear, distinct job: archive/trash/snooze are separated, single-thread vs bulk operations are explicitly contrasted, and unsubscribe-related tools are carefully split by purpose (checking, performing, bulk, sender overview). Algorithm solutions that could overlap, like `modify_labels` vs `bulk_modify`, provide strong usage guidance that prevents misselection.
Tool names primarily follow a verb_noun convention (`list_labels`, `create_filter`, `get_thread`, `bulk_modify`) with clear verbs like list/get/fetch/create. Minor deviations exist (`what_changed`, `triage_digest`, `archive`/`trash`/`snooze`), but they are still readable and do not break the overall predictable style.
At 26 tools, the set is slightly above the typical well-scoped range and crosses the 'heavy' threshold, but the domain is broad enough that most tools earn a place due to distinct single-versus-bulk variants and separate list views. Some consolidation would benefit usability, though the count is not egregiously inflated.
The mail domain is comprehensively covered: read state, archive/trash, labels, filters, snooze, search, bulk operations, subscription lists, and attachment downloads. The main gaps are marginal—direct spam-classification and permanent-delete tools are not explicitly present, while sending/forwarding is an intentionally documented exclusion rather than an accidental omission.
Maintenance
Related MCP Connectors
A MCP server for Gmail that lets you search, read, and draft emails and replies.
Your IMAP mailbox as an MCP server: read, search and (if you allow it) organize mail. Open source.
Hosted email MCP for your own Gmail, Outlook.com, Microsoft 365, iCloud or IMAP inbox: read, search, draft, reply in thread, forward and file mail. It moves or flags up to 500 messages in one call, and a send leaves exactly one copy in Sent. A calendar is a separate connection, and connecting one adds diary and scheduling tools.
Shipmail MCP server. Modern email for your business, with an API for your agents.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables Gmail integration, allowing users to manage emails (send, receive, read, trash, mark as read) directly through MCP clients like Claude Desktop.1MIT
- AlicenseBqualityDmaintenanceManage your emails effortlessly with a standardized interface for drafting, sending, retrieving, and organizing messages. Streamline your email workflow with complete Gmail API coverage, including label and thread management.641,075 npm62MIT
- AlicenseNot gradedqualityAmaintenanceGmail MCP server — scope-gated tools (readonly / send / modify), path jails for attachments + downloads, hardened OAuth credentials, Sigstore-signed releases.357 npm13MIT
- AlicenseAqualityFmaintenanceA Gmail MCP server with native multi-account support, enabling management of multiple Gmail accounts from a single server instance.76MIT