Skip to main content
Glama

mailwarden

npm license Node Website Smithery Available on CodeGuilds

Un servidor nativo y fiable de Gmail MCP — clasificación completa de la bandeja de entrada para asistentes de IA, con la función que ningún otro servidor MCP de Gmail incluye: posponer del lado del buzón.

Destacados

  • Posponer — el único posponer del lado del buzón en un servidor MCP de Gmail. Archiva un hilo ahora, haz que reaparezca en la bandeja de entrada en una fecha. Construido sobre etiquetas con fecha + una limpieza, por lo que funciona desde cualquier cliente, es visible en el propio Gmail y sobrevive a reinicios. (Donde otro servidor ofrece un "posponer", es una lista de recordatorios local — el correo nunca sale ni vuelve a entrar en la bandeja de entrada).

  • Búsqueda en la que puedes confiar. threads.list de Gmail — la llamada por la que pasa cualquier búsqueda de hilos — puede responder a is:unread desde un estado de lectura a nivel de hilo obsoleto: medido en un buzón real, el 86% de los hilos que devolvió no contenía ningún mensaje no leído; en un segundo buzón, ninguna desviación. No puedes saber en qué buzón estás sin mirar, por lo que search vuelve a verificar cada resultado contra sus etiquetas en vivo. Paginado mediante pageToken/nextPageToken.

  • Operaciones masivas que escalan. bulk_modify archiva/etiqueta todo lo que coincide con una consulta a 1000 mensajes por solicitud de API — con informes de éxito parcial por fragmento en lugar de todo o nada. La limpieza de pospuestos utiliza la misma ruta por lotes.

  • Salidas estructuradas. Cada herramienta declara un outputSchema y devuelve structuredContent validado junto con texto JSON delimitado — sin adivinanzas de análisis para los clientes.

  • Superficie de ataque pequeña. Sin herramientas de envío (sin ruta de exfiltración para correo inyectado por prompt), modo de solo lectura opcional, sin telemetría, sin puertos abiertos por defecto, protección de descarga a prueba de enlaces simbólicos, salida protegida contra inyección. Una excepción deliberada: unsubscribe / bulk_unsubscribe (nivel de gestión) contactan con el punto final de exclusión voluntaria nombrado en el propio encabezado de un mensaje — el único host no Google al que mailwarden llega, y un despliegue de nivel read no realiza ninguna solicitud saliente. Detalles en Seguridad y privacidad y Cancelación de suscripción.

  • Correcto con el correo del mundo real. Encabezados RFC 2047 decodificados (=?UTF-8?B?…?= → texto legible), cuerpos decodificados en su juego de caracteres declarado (sin mojibake para correo ISO-8859-1/Shift_JIS), 429/5xx reintentados con retroceso exponencial.

Related MCP server: Gmail MCP

Por qué

Los conectores que sincronizan o almacenan en caché tu buzón pueden retrasarse con respecto a él — e incluso el propio índice de búsqueda de Gmail a veces es impreciso (ver más abajo). mailwarden habla directamente con la API de Gmail en vivo (sin instantánea en caché) y vuelve a verificar lo que devuelve el índice, por lo que lo que ves es lo que realmente hay. Es una capa de capacidad genérica de Gmail — mantén tus propias reglas/lógica en tu cliente de IA, no en el servidor.

search va un paso más allá de la API sin procesar: el índice threads.list de Gmail puede responder a operadores de estado de lectura desde una copia obsoleta de ese estado, por lo que is:unread devuelve hilos que terminaste de leer hace semanas — en un buzón medido, la gran mayoría de lo que devolvió. Dado que cada resultado se obtiene en vivo de todos modos, search vuelve a comprobar los predicados inequívocos (is:unread/is:read/is:starred/in:inbox/category:…, con negación) contra las etiquetas reales de cada hilo y descarta los falsos positivos del índice.

En comparación con otros servidores MCP de Gmail

La mayoría de los servidores MCP de Gmail cubren la misma superficie de lectura/etiquetado/envío. Dos capacidades siguen siendo exclusivas de mailwarden (posponer del lado del buzón, re-verificación de búsqueda), y una omisión deliberada es una característica de seguridad, no una brecha. El propio servidor de Google también es más limitado de lo que parece: solo borradores, y sin papelera, filtros ni cancelación de suscripción.

Capacidad

mailwarden

Google oficial

taylorwilsdon

a-bonus

klodr

Posponer del lado del buzón — archivar ahora, reaparecer en la bandeja de entrada en una fecha/hora o preajuste

✅

—

—

—

—

Re-verificación de resultados de búsqueda — descarta los falsos positivos del índice de hilos contra etiquetas en vivo

✅

—

—

—

—

Limpieza / masivo sobre una consulta — una acción en cada hilo que devuelve una búsqueda

✅ 1000/solicitud, éxito parcial

—

⚠️ lote por ids explícitos

—

⚠️ lote por ids explícitos

Cancelar suscripción — resumen por remitente + exclusión voluntaria con un clic RFC 8058, sin necesidad de ámbito de envío

✅

—

⚠️ encabezado mostrado, sin acción

—

—

Resumen de clasificación de bandeja de entrada — una llamada que agrupa lo que está esperando

✅ remitente/etiqueta/antigüedad + señales de encabezado

—

—

✅ banderas heurísticas + estadísticas

—

Filtros del lado del servidor — reglas que siguen clasificando sin un asistente en el bucle

✅ nunca reenviando

—

✅

—

✅

Sin herramientas de envío — por diseño — un correo inyectado por prompt no tiene ruta de exfiltración

✅ sin redactar en absoluto

⚠️ solo borradores

❌ envía

❌ envía

❌ envía

Niveles de herramientas de privilegio mínimo — ámbitos OAuth derivados de las herramientas que habilitas

✅

⚠️ división de ámbito

—

—

⚠️ inverso: herramientas controladas por ámbitos concedidos

Cifrado de token en reposo (opcional)

✅ AES-256-GCM

n/a (alojado)

✅

—

—

Sin nube del proveedor — tú operas el servidor

✅

❌ Alojado por Google

✅

✅

✅

Salidas estructuradas — cada herramienta declara un outputSchema

✅

—

—

—

—

Instantánea al 16 de agosto de 2026, a partir de los documentos públicos y el código fuente de cada proyecto; — = no ofrecido / no documentado. Las columnas son los servidores a los que un lector tiene más probabilidades de llegar: el de primera parte de Google, más los dos servidores comunitarios más grandes — y klodr, que es el que más se acerca al diseño de privilegio mínimo de mailwarden. La capacidad de envío se enumera como una propiedad de seguridad: la falta de ella en mailwarden es intencional (ver Seguridad y privacidad). La última fila pregunta quién opera el servidor, no dónde se ejecuta: el autoalojamiento es un terreno común aquí, y cada servidor comunitario en esta tabla ofrece algún despliegue remoto excepto klodr (solo stdio) — mailwarden mediante --http, taylorwilsdon sobre HTTP transmisible con OAuth 2.1, a-bonus en Cloud Run. Ejecutar uno de ellos en tu propio host no es una copia en la nube; ejecutarlo en el del proveedor lo es.

El foso no es ninguna fila individual — es posponer + re-verificación en vivo juntos: una capa real de flujo de trabajo de bandeja de entrada que actúa sobre el estado actual del buzón, no una instantánea en caché. Donde otros se han puesto al día, se señala honestamente arriba: cifrado en reposo (taylorwilsdon), control de herramientas basado en ámbito (klodr), una heurística de clasificación por mensaje más rica (a-bonus), y organización masiva sobre un buzón (el alojado mcpemails.com, que tampoco tiene posponer). Lo que ninguno de ellos hace es actuar sobre una consulta y verificar la respuesta del buzón antes de actuar sobre ella.

Por qué la re-verificación es importante — un caso concreto

Pídele a un asistente que "archive el correo promocional no leído que ya ha saltado mi bandeja de entrada" y recurrirá a la consulta obvia, category:updates is:unread -in:inbox. Un servidor que confía en el índice de Gmail ahora archiva hilos que ya habías leído — correo que nunca quisiste tocar, desaparecido en una acción masiva que no puedes revertir fácilmente.

Medido, no afirmado. Un buzón real (~70,000 mensajes), 15.08.2026, solo lectura:

Consulta (threads.list)

Hilos devueltos

Con un mensaje no leído

Desactualizados

category:updates is:unread

131

17

87%

category:updates is:unread -in:inbox

128

14

89%

is:unread -in:inbox

235

99

58%

El índice no está ignorando el predicado — la misma consulta sin is:unread devuelve más de 800 hilos, por lo que se está aplicando. Se aplica contra un estado de lectura a nivel de hilo que no se ha actualizado: los hilos cuyos mensajes están todos leídos aún cuentan como no leídos allí. Un hilo devuelto llevaba una sola etiqueta, SENT. Y no es una rareza de combinaciones exóticas de operadores: la consulta más simple de las tres también lo muestra — con la proporción más baja (58%) pero la mayor cantidad de hilos incorrectos en términos absolutos (136).

Es específicamente el índice de hilos. La misma consulta, mismo buzón, mismo minuto, solicitada a través de messages.list en su lugar: 19 mensajes, ninguno desactualizado. Así que esto no es "la búsqueda de Gmail no es confiable" — es que la vista de hilo del estado de lectura se retrasa mientras que la vista por mensaje no. search pasa por threads.list, que es exactamente por lo que vuelve a verificar.

Un segundo buzón, medido de la misma manera el mismo día, no se desvió en absoluto — cero aciertos del índice bruto para is:unread, aunque se marca como leído a través de la API muchas veces al día. Así que esto es una propiedad de un buzón, no de Gmail en todas partes. Lo que los separa está abierto: difieren en volumen (aproximadamente tres órdenes de magnitud) y antigüedad, y al segundo le falta algo más básico — ningún hilo en él fue archivado mientras aún estaba no leído, que es la única forma en que puede aparecer un estado de lectura desactualizado. Así que no es un contraejemplo de ninguna causa particular; es un buzón sin el candidato.

Que es el punto central: un servidor no puede saber en qué tipo de buzón se encuentra. La re-verificación no cuesta nada donde nada se desvía, y te salva donde lo hace — en la medición anterior, cada hilo que search descartó estaba genuinamente leído, y descartó ningún correo genuinamente no leído.

Donde no es gratuito: las herramientas masivas. search vuelve a verificar porque obtiene cada acierto de todos modos; bulk_modify (y el barrido applyToExisting de create_filter) está dimensionado en miles de mensajes, donde una obtención por acierto es un orden de costo diferente. Esas actúan sobre lo que devuelve el índice — por lo que ahora reportan unverifiedPredicates, las condiciones de tu consulta que se tomaron según la palabra del índice (+UNREAD, -INBOX, …). Vacío significa que no había nada que desconfiar. ¿No vacío y el resultado debe ser preciso en cuanto al estado de lectura? Resuelve el conjunto con search primero y actúa sobre esos IDs de hilo. Un dryRun no cierra esta brecha: vuelve a leer el mismo índice, por lo que confirma cuán grande es el conjunto, nunca si es correcto.

mailwarden obtiene cada acierto en vivo de todos modos, por lo que search vuelve a verificar los predicados inequívocos (is:unread, is:read, in:inbox, category:…, con negación) contra las etiquetas reales de cada hilo y descarta los falsos positivos del índice antes de que cualquier herramienta los vea. La acción masiva entonces se ejecuta exactamente sobre el conjunto que solicitaste. Esta es la diferencia entre actuar sobre lo que Gmail indexó y actuar sobre lo que realmente está en el buzón ahora mismo — y es por lo que posponer/barrer son seguros para entregar a un asistente: el barrido solo reflota los hilos cuyo posposición está genuinamente vencida, verificados contra etiquetas en vivo en tiempo de ejecución.

Compruébalo tú mismo — no se necesita cuenta de Gmail. Desde un clon del repositorio (la demo es un script de verificación solo del repositorio, no parte del paquete npm):

git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node scripts/demo-reverify.mjs

Hay un segundo script junto a él, node scripts/probe-reverify.mjs, que mide lo mismo en tu buzón en lugar de uno falso — solo lectura, solo metadatos (no se obtiene asunto, remitente ni cuerpo), imprimiendo conteos y nombres de etiquetas. Es cómo se produjeron los números anteriores, y cómo puedes verificar si tu buzón se desvía en absoluto.

La demo ejecuta el search() real contra una API de Gmail falsa cuyo índice está deliberadamente desactualizado (devuelve un hilo leído para una consulta is:unread, exactamente como lo hace Gmail) y muestra a mailwarden descartando el falso positivo. Afirma el resultado, por lo que sale con código distinto de cero si el comportamiento alguna vez retrocede. El mismo caso está asegurado por pruebas unitarias en test/gmail.test.ts ("descarta falsos positivos del índice mediante re-verificación de etiquetas en vivo").

Herramientas

Herramienta

Qué hace

search

Sintaxis de consulta de Gmail → resúmenes de hilos (de/asunto/fecha/etiquetas/fragmento); los predicados de estado de lectura/categoría se vuelven a verificar con las etiquetas en vivo de cada resultado; paginado mediante pageToken/nextPageToken. Cada resultado lleva signals — newsletter (List-Id / List-Unsubscribe / Precedence bulk o list), automated (Auto-Submitted, encabezados de auto-respuesta/supresión, remitentes estilo no-reply), calendar (parte text/calendar o .ics), replyToMismatch (Reply-To en otro dominio que From; un subdominio del mismo dominio cuenta como igual) — leído de los encabezados/MIME del primer mensaje, sin llamada adicional

get_thread

Hilo completo: encabezados, cuerpos en texto plano + HTML, metadatos de adjuntos

list_labels

Todas las etiquetas (del sistema + del usuario)

get_profile

Dirección de la cuenta conectada + total de mensajes/hilos — confirma qué buzón está conectado antes de actuar

triage_digest

Resumen estructurado de una porción del buzón para decisiones: principales remitentes (cada uno con las señales que llevan sus hilos), grupos por etiqueta y antigüedad, recuentos de no leídos + adjuntos, y cuántos hilos son boletines / automatizados / invitaciones de calendario / desajustes de respuesta — en lugar de una lista de hilos en bruto

list_unsubscribe

Qué opciones de exclusión anuncia un hilo (List-Unsubscribe) — no contacta a nadie

list_subscriptions

Una porción del buzón agrupada por remitente: recuentos de hilos/no leídos, el intervalo de fechas en que se vio cada uno, y las opciones de exclusión de cada uno — una obtención de encabezado por remitente, no contacta a nadie. sendersFound informa cuántos remitentes había antes de que topN truncara la lista

create_label

Crear una etiqueta de usuario (idempotente; anidada mediante Parent/Child) y devolver su id

modify_labels

Añadir/eliminar etiquetas por nombre o id — un nombre desconocido en add se crea automáticamente (archivar = eliminar INBOX, leer = eliminar UNREAD)

bulk_modify

Cambios de etiquetas por lotes para cada mensaje que coincida con una consulta — 1000 mensajes por solicitud API, éxito parcial informado por fragmento (lista de thread-id limitada a 500, modifiedThreadCount tiene el total). Actúa sobre el índice sin procesar, por lo que unverifiedPredicates nombra las condiciones que no pudo verificar (ver más abajo). dryRun: true resuelve la consulta e informa los hilos coincidentes y las etiquetas que crearía, sin tocar nada

archive / mark_read / mark_unread

Envoltorios de conveniencia

trash / untrash

Mover a / restaurar desde Papelera

download_attachment

Guardar un adjunto en una ruta local (nunca sobrescribe — las colisiones obtienen un sufijo numérico)

unsubscribe

Exclusión con un clic (RFC 8058) usando el endpoint del propio encabezado del mensaje — la única herramienta que contacta a un host que no es de Google (detalles)

bulk_unsubscribe

Lo mismo para varios hilos, secuencialmente y como máximo una solicitud por remitente; éxito parcial informado por hilo. dryRun: true ejecuta las mismas lecturas de encabezados y deduplicación e informa el endpoint que cada hilo wouldCall — sin contactar a nadie

snooze

Archivar ahora, reaparecer en/después de una fecha (YYYY-MM-DD), una fecha+hora (2026-06-20 9am), o un valor predefinido (tomorrow, tomorrow 9am, weekend, next week, un nombre de día de la semana, in N days, in N hours)

unsnooze

Cancelar un aplazamiento, volver a la bandeja de entrada ahora

list_snoozed

Todos los hilos aplazados + fechas de vencimiento

sweep_snoozed

Reaparecer hilos cuyo aplazamiento ha vencido (ejecutar bajo demanda, mediante cron o el daemon); por lotes, con informe de fallos parciales. dryRun: true responde "¿qué está vencido ahora mismo?" (dueLabels/dueThreads) sin despertar nada

list_filters

Todos los filtros de Gmail (criterios + acciones de etiquetas); muestra cualquier dirección forward en filtros existentes para auditoría

create_filter

Crear una regla de clasificación automática del lado del servidor (solo criterios → acciones de etiquetas; sin reenvío — ver más abajo). Opcionalmente applyToExisting para también barrer el correo coincidente ya en el buzón

delete_filter

Eliminar un filtro por id

Todas las herramientas declaran un outputSchema y devuelven contenido estructurado (validado, legible por máquina) junto con el mismo JSON como texto delimitado; los clientes nunca tienen que analizar prosa.

Cómo funciona snooze (no existe una función snooze en la API de Gmail — la construimos)

snooze elimina INBOX y aplica una etiqueta fechada MCP/Snoozed/<key>, donde la clave es YYYY-MM-DD (pendiente todo el día) o YYYY-MM-DDTHHMM (pendiente en ese minuto local). El argumento until acepta una fecha explícita, una fecha+hora (2026-06-20 9am, …T17:00), o un valor predefinido resuelto en el servidor — today, tomorrow, weekend (próximo sábado), next week (próximo lunes), un nombre de día de la semana (monday–sunday, siguiente ocurrencia), in N days, o in N hours — y un valor predefinido de fecha puede llevar una hora al final (tomorrow 9am, monday 8:30), por lo que el llamante nunca tiene que calcular el momento por sí mismo. sweep_snoozed encuentra las etiquetas pendientes y devuelve esos hilos a la bandeja de entrada (marcados como no leídos); un snooze temporizado se activa en el primer barrido en o después de su minuto, por lo que la latencia de activación equivale a tu intervalo de barrido. Ejecuta el barrido:

  • bajo demanda (herramienta sweep_snoozed),

  • mediante cron: mailwarden --sweep,

  • o automáticamente: configura MAILWARDEN_AUTO_SWEEP=1 (barrido cada hora mientras el servidor se ejecuta).

Filtros (reglas persistentes de autotriage)

create_filter establece una regla del lado del servidor de Gmail: el correo que coincida con los criterios recibe automáticamente las acciones de etiqueta dadas — la bandeja de entrada sigue triándose sola sin la intervención del asistente.

  • Criterios: from, to, subject, query (sintaxis completa de búsqueda de Gmail), negatedQuery, hasAttachment, excludeChats, y size + sizeComparison (smaller/larger, proporcionados juntos). Se requiere al menos uno.

  • Acciones (solo etiqueta): addLabels / removeLabels, por nombre o id (un nombre desconocido en addLabels se crea automáticamente, anidado mediante /). Recetas comunes: omitir la bandeja de entrada → removeLabels: ["INBOX"]; marcar como leído automáticamente → removeLabels: ["UNREAD"]; enviar a la papelera automáticamente → addLabels: ["TRASH"]; destacar → addLabels: ["STARRED"]; nunca spam → removeLabels: ["SPAM"]; archivar bajo una etiqueta → addLabels: ["Receipts"].

  • Correo existente: un filtro solo se aplica a los mensajes que llegan después de crearlo. Pasa applyToExisting: true para aplicar también las mismas acciones una vez al correo ya presente en la bandeja de entrada — mailwarden construye una búsqueda de Gmail a partir de los criterios y realiza una modificación masiva (hasta maxMessages, por defecto 1000; misma advertencia de índice no verificado que bulk_modify, y el pase único excluye Spam/Papelera). Esto requiere al menos un criterio positivo (from/to/subject/query/hasAttachment:true/size): una regla de solo exclusión (negatedQuery o hasAttachment:false) se rechaza para applyToExisting porque coincidiría con casi toda la bandeja de entrada — crea dicho filtro sin la bandeja. El resultado se devuelve bajo applied (la query utilizada, recuentos de matchedMessages/modifiedMessages/modifiedThreadCount, capped cuando el conjunto de coincidencias alcanzó maxMessages, failed por fragmento, y una cadena error si todo el pase falló); es null cuando applyToExisting no se estableció. El filtro se crea primero, por lo que un pase parcial o fallido del historial se reporta en applied, nunca se lanza — la regla sigue vigente.

  • Sin reenvío — ver Seguridad y privacidad.

  • Requiere el ámbito gmail.settings.basic; ejecuta --auth una vez si autorizaste una versión anterior. No disponible en modo de solo lectura.

Cancelar suscripción — la única solicitud saliente

list_unsubscribe (nivel de lectura) informa lo que ofrece el remitente, sin contactar a nadie. Lee el mensaje más reciente que realmente lleva un encabezado List-Unsubscribe — una respuesta enhebrada a un boletín se encuentra al final y no anuncia nada, lo que de otro modo se leería como "esta lista no tiene opción de exclusión". list_subscriptions (nivel de lectura) hace lo mismo en un segmento completo, agrupado por remitente, para que puedas ver quién sigue escribiendo y cuáles de ellos pueden ser realmente abandonados — una obtención de encabezado por remitente en lugar de por hilo. unsubscribe y bulk_unsubscribe (nivel de gestión) actúan sobre ello — y ese es el único lugar donde mailwarden se comunica con un host que no sea Google, por lo que las reglas son estrictas:

  • No hay parámetro de URL. El punto final proviene del propio encabezado del mensaje y de ningún otro lugar. Un argumento de URL permitiría que un correo con inyección de prompt convirtiera la herramienta en un canal de exfiltración (contenido de la bandeja de entrada en una cadena de consulta); el encabezado no puede contener datos que el modelo haya elegido.

  • Solo se realiza un clic según RFC 8058 — el remitente debe haber optado por ello mediante List-Unsubscribe-Post. Un enlace https: simple está destinado a un humano en un navegador y se devuelve, no se obtiene.

  • Las opciones de exclusión mailto: nunca se realizan. Requerirían enviar correo, lo cual mailwarden no puede hacer. La dirección se informa para que puedas actuar tú mismo.

  • Solicitud fija, respuesta descartada. El cuerpo POST siempre es List-Unsubscribe=One-Click y nunca se deriva de nada; el cuerpo de la respuesta se cancela sin leer. Lo que regresa al modelo es el código de estado y la URL realmente llamada — sin contenido del punto final, por lo que no puede responder con instrucciones. (Una redirección 301/302/303 se sigue como GET, es decir, sin cuerpo alguno).

  • Una solicitud por remitente, secuencialmente, dentro de un presupuesto. bulk_unsubscribe toma identificadores de hilo (nunca una consulta — una exclusión masiva basada en consultas dispararía una solicitud por remitente coincidente antes de que nadie hubiera mirado). Los hilos de un remitente cuya solicitud ya se envió se reportan con duplicateOf y no cuestan una segunda solicitud: dos hilos de una lista comparten una exclusión, y llamarla dos veces solo confirma tu dirección dos veces. Un remitente solo se registra una vez que una solicitud realmente alcanzó un punto final, por lo que un rechazo o una conexión caída aún deja al siguiente hilo su propio intento — y si el hilo omitido anuncia un punto final diferente, la razón lo dice, ya que un remitente puede gestionar varias listas. Limitado a 25 hilos y 60 segundos por llamada; lo que el presupuesto no cubra regresa como skippedOutOfTime en lugar de deshacerse silenciosamente. Ninguno de ellos se puede revertir, por lo que existen los tres límites.

  • Protecciones SSRF. Solo https, solo puerto predeterminado, sin credenciales en la URL, y cada salto — incluyendo redirecciones, seguidas como máximo 3 veces — debe resolverse exclusivamente a direcciones globalmente accesibles. La verificación analiza cada dirección a sus bytes y la compara con el registro de propósito especial de la IANA, por lo que cada ortografía de la misma dirección obtiene el mismo veredicto (::1 y 0:0:0:0:0:0:0:1 por igual); una dirección que no se analiza se rechaza. La resolución DNS y todos los saltos comparten un presupuesto de 10 segundos. No es a prueba de rebinding (fetch resuelve de nuevo cuando se conecta) — ver SECURITY.md; lo que sobrevive a esa brecha es un POST ciego cuya respuesta nunca se lee.

Compruébalo con tu propio correo antes de confiar en él. Desde un clon del repositorio (solo repositorio, no en el paquete npm), después de npm run build y mailwarden --auth:

node scripts/probe-unsubscribe.mjs --vet          # category:promotions, 25 threads
node scripts/probe-unsubscribe.mjs "from:substack.com" --max 50 --vet

Imprime cada encabezado List-Unsubscribe real junto a lo que el analizador hizo de él, y --vet también ejecuta el punto final a través de la verificación de URL y la protección de direcciones — así ves tanto si el analizador entendió el encabezado como si las protecciones habrían dejado pasar esa exclusión. Estrictamente de solo lectura: nunca se realiza ninguna solicitud a un remitente, y nada en la bandeja de entrada cambia.

Lo que no se puede deshacer: la solicitud le dice al remitente que tu dirección está activa. Un remitente que ignora su propia exclusión está fuera del alcance de cualquier cliente — combina unsubscribe con create_filter o trash para esos casos. No ofrecer una opción automatizable se reporta como unsubscribed:false con las alternativas, no como un error. Una implementación de solo read obtiene list_unsubscribe y list_subscriptions, y nunca realiza la solicitud.

Seguridad y privacidad

Para el modelo de amenazas completo — límite de confianza, mitigaciones por amenaza, no objetivos explícitos, y cómo reportar una vulnerabilidad — consulta SECURITY.md. Los aspectos destacados:

  • Sin telemetría. No se comunica con el exterior — sin análisis, sin informes de fallos, sin seguimiento.

  • Sin puertos abiertos por defecto. Solo stdio. El listener opcional --http se vincula a 127.0.0.1 (no a la LAN) y se niega a iniciar sin un token portador MAILWARDEN_TOKEN — establezca MAILWARDEN_ALLOW_NO_TOKEN=1 para anularlo en una red aislada y de confianza. En un enlace de loopback también valida el encabezado Host (defensa contra DNS-rebinding). Para alojamiento remoto, configure MAILWARDEN_HOST y protéjalo con TLS.

  • Sin herramientas de envío — por diseño. mailwarden no puede redactar, responder ni reenviar. Una instrucción inyectada por prompt dentro de un correo electrónico no tiene ruta de exfiltración a través de este servidor. create_filter sigue la misma regla: puede etiquetar, archivar, enviar a la papelera, destacar o marcar correos, pero nunca crea un filtro de reenvío (que sería una ruta de exfiltración). list_filters sigue mostrando cualquier filtro de reenvío ya existente en la cuenta, para que pueda detectarlo. Esto se cumple porque no existe tal herramienta y ninguna puede registrarse en tiempo de ejecución; para la variante más fuerte, donde Google se niega a enviar en lugar de que mailwarden lo rechace, consulte Modo de solo lectura más abajo.

  • Un único host de salida, sin URL elegida por el modelo. La herramienta unsubscribe es la única ruta de código que contacta con un host que no sea de Google. Su endpoint se lee del encabezado List-Unsubscribe del mensaje — nunca de un argumento de herramienta — el cuerpo de la solicitud es fijo y el cuerpo de la respuesta se descarta, por lo que no puede convertirse en un canal de datos. Solo https/puerto por defecto, las redirecciones se revalidan, y cualquier salto que resuelva a una dirección privada, de loopback, link-local o de metadatos es rechazado. Consulte Cancelar suscripción.

  • Niveles de herramientas (divulgación progresiva + alcance mínimo). MAILWARDEN_TOOLS anuncia solo los niveles que usted nombre — read (las herramientas de lectura), manage (mutaciones de buzón, posponer, descargas), filters (CRUD de filtros del lado del servidor, el único nivel cuyas herramientas necesitan gmail.settings.basic). El valor predeterminado son los tres; por ejemplo, read,manage proporciona una superficie de triaje completa sin gestión de filtros. Los ámbitos de OAuth solicitados en --auth se derivan de los niveles habilitados — una implementación read solicita solo gmail.readonly, y gmail.settings.basic se solicita solo cuando el nivel filters está activo. Y las herramientas de filtro se ocultan automáticamente cuando el token almacenado no incluye gmail.settings.basic (por ejemplo, un token autorizado antes de habilitar el nivel) — vuelva a ejecutar --auth para concederlo. Los tokens antiguos sin un ámbito registrado se anuncian como antes, con el mensaje de alcance insuficiente en tiempo de ejecución como alternativa.

  • Modo de solo lectura. Establezca MAILWARDEN_READONLY=1 (abreviatura de MAILWARDEN_TOOLS=read) y solo se registran las herramientas de lectura (search, get_thread, list_labels, list_snoozed, get_profile, triage_digest, list_unsubscribe, list_subscriptions) — nada que pueda cambiar el buzón o escribir archivos se anuncia siquiera a los clientes (las herramientas de filtro, que necesitan el ámbito más amplio gmail.settings.basic, también se excluyen). Recomendado para implementaciones compartidas/HTTP que solo hacen triaje. También es el único nivel cuya propiedad de no envío Google hace cumplir: posee un token gmail.readonly, que los endpoints de envío de Gmail rechazan de plano. manage necesita gmail.modify, y Gmail sí acepta ese ámbito para enviar — mailwarden simplemente no expone ninguna herramienta que lo haga. Por lo tanto, una implementación read no podría enviar incluso si este binario fuera reemplazado; una implementación manage no puede enviar porque no hay nada que llamar. (No hay un ámbito de escritura sin envío al que cambiar — consulte SECURITY.md, amenaza 1.)

  • Descargas delimitadas. Con MAILWARDEN_DOWNLOAD_DIR establecido, las escrituras de archivos adjuntos se limitan a ese directorio (canonicalizado con realpath, consciente de enlaces simbólicos) y nunca sobrescriben un archivo existente.

  • Delimitación de contenido no confiable. Cada resultado de herramienta se envuelve en marcadores <untrusted-tool-output> y se eliminan los caracteres invisibles/de anulación bidireccional, para que los clientes puedan distinguir el contenido de correo citado de las instrucciones.

  • API en vivo, sin copia. No se almacena ningún espejo de buzón ni índice de búsqueda en ningún lugar. El único estado local es su token OAuth en ~/.mailwarden/.

  • Cifrado opcional del token en reposo. token.json contiene un token de actualización; en disco está protegido solo por mode 0o600 (sin efecto en Windows). Establezca MAILWARDEN_TOKEN_PASSPHRASE en una frase de contraseña y el token se almacena cifrado con AES-256-GCM (clave derivada de scrypt), por lo que una copia del archivo — una copia de seguridad, una carpeta sincronizada, otra máquina — es inútil sin la frase de contraseña. Vuelva a ejecutar mailwarden --auth una vez después de configurarlo para cifrar el token existente. Tenga en cuenta el límite: esto defiende contra el robo de archivos, no contra malware que se ejecuta como su usuario (que también puede leer la frase de contraseña del entorno).

Quick start

claude mcp add mailwarden -- npx -y mailwarden

Esa es toda la instalación — npx obtiene y ejecuta el paquete publicado, sin clonación ni paso de compilación. Solo necesita las credenciales de OAuth de Google una vez (abajo).

Setup

¿Es la primera vez que configura una aplicación OAuth de Google? Siga la guía de configuración paso a paso — recorre la consola de Google Cloud con rutas de clic exactas, explica la pantalla de "aplicación no verificada" y cubre la trampa que hace que los tokens caduquen después de 7 días. La versión corta:

  1. Google Cloud: cree un proyecto → habilite la API de Gmail → configure la pantalla de consentimiento OAuth y publíquela en Producción (en estado Prueba, Google caduca los tokens de actualización después de 7 días) → cree un ID de cliente OAuth de tipo Aplicación de escritorio → descárguelo como credentials.json.

  2. Coloque credentials.json en ~/.mailwarden/ (o establezca MAILWARDEN_CREDENTIALS=/ruta/a/credentials.json).

  3. Autorice una vez — abre un navegador, almacena un token de actualización en ~/.mailwarden/token.json:

    npx -y mailwarden --auth

    Ámbitos solicitados: gmail.modify (lectura + etiquetar/archivar/papelera) y gmail.settings.basic (solo gestión de filtros). Si autorizó una versión anterior a que existieran los filtros, vuelva a ejecutar --auth una vez para conceder el ámbito añadido. Para tener un token con el que el propio Gmail se niegue a enviar, autorice con MAILWARDEN_TOOLS=read — consulte Modo de solo lectura más arriba.

  4. Verifique la configuración en cualquier momento con el doctor incorporado:

    npx -y mailwarden --check

    Verifica credentials.json, si existe un token (y si está cifrado), si los ámbitos concedidos cubren sus niveles habilitados, y realiza una llamada en vivo a Gmail para demostrar que el token aún funciona — imprime una solución concreta para cualquier cosa que esté mal, y sale con código distinto de cero si es así (útil en verificaciones de CI/salud). Diagnostica las trampas comunes: archivo de credenciales inexistente/incorrecto, nunca autorizado, un token cifrado sin MAILWARDEN_TOKEN_PASSPHRASE, un ámbito faltante, o la caducidad del token de consentimiento de "Prueba" de 7 días.

Connect

Claude Code (stdio local):

claude mcp add mailwarden -- npx -y mailwarden

Plugin de Claude Code — el mismo servidor más una habilidad /mailwarden:setup que le guía a través de la configuración OAuth y diagnostica una configuración rota. La raíz del repositorio es el plugin (.claude-plugin/plugin.json), así que desde un clon:

claude --plugin-dir /path/to/mailwarden

Está enviado al mercado comunitario de Anthropic; una vez listado, /plugin marketplace add anthropics/claude-plugins-community luego /plugin install mailwarden@claude-community hace lo mismo sin un clon. El plugin ejecuta toda la superficie de herramientas — para un nivel más restringido (MAILWARDEN_TOOLS=read) o una segunda cuenta, use claude mcp add con el entorno que desee (consulte Config y Varias cuentas).

Claude Desktop — añada a claude_desktop_config.json:

{
  "mcpServers": {
    "mailwarden": { "command": "npx", "args": ["-y", "mailwarden"] }
  }
}

O instale el paquete MCPB (mailwarden-<version>.mcpb, adjunto a los lanzamientos de GitHub desde 0.10.0) como una extensión de escritorio — Configuración → Extensiones → Instalar extensión… — el mismo servidor, autocontenido en tiempo de ejecución (sin npx; Claude Desktop trae el runtime de Node), con los niveles de herramientas como configuración. El paquete se construye a partir del paquete npm empaquetado (mismo conjunto de archivos que el publicado; npm run mcpb, verificado en CI: validado, desempaquetado e iniciado) y es el mismo conjunto de archivos que distribuye Smithery. El npx -y mailwarden --auth único aún aplica (Node necesario una vez para eso) — el paquete lee el mismo token ~/.mailwarden/.

Smithery — listado como csitte/mailwarden, que sirve ese paquete:

npx -y @smithery/cli install csitte/mailwarden --client claude   # local stdio entry in the client's config

Tenga en cuenta cuál de los dos caminos de Smithery toma. La instalación anterior escribe una entrada de servidor local simple: el proceso, su token y su correo permanecen en su máquina, exactamente como con npx. Agregarlo a la caja de herramientas de Smithery en su lugar (smithery mcp add) también ejecuta el paquete localmente, pero retransmite el tráfico de herramientas a través de la puerta de enlace de Smithery para que un cliente remoto pueda alcanzarlo — el contenido del buzón en esas respuestas pasa entonces a través de un tercero. Eso es una propiedad de la puerta de enlace, no de mailwarden; si desea la garantía de sin terceros, use la instalación local, el paquete npm o el .mcpb de la página de lanzamiento.

Remoto (HTTP transmisible) — para un VPS / conector personalizado de 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

Luego en claude.ai: Configuración → Conectores → Añadir conector personalizado → su URL https://your-host/mcp. En Claude Code: claude mcp add --transport http mailwarden https://your-host/mcp.

Multiple accounts

Una aplicación OAuth (un credentials.json) puede autorizar varias cuentas de Gmail. Cada cuenta mantiene su propio token de actualización en un archivo separado, seleccionado por MAILWARDEN_ACCOUNT:

mailwarden --auth --account work        # stores token.work.json
mailwarden --auth --account personal    # stores token.personal.json

Ejecútelos uno al lado del otro registrando el servidor una vez por cuenta, cada una con su propio MAILWARDEN_ACCOUNT. Cada instancia está completamente aislada — su propio token, sus propios ámbitos concedidos, su propia superficie de herramientas — por lo que nada puede actuar en el buzón equivocado:

{
  "mcpServers": {
    "gmail-work":     { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "work" } },
    "gmail-personal": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "personal" } }
  }
}

Los nombres de cuenta no distinguen entre mayúsculas y minúsculas — se convierten en nombres de archivo, por lo que Work y work serían el mismo archivo en Windows/macOS. mailwarden los convierte a minúsculas (--account Work → token.work.json) para que un nombre siempre se asigne exactamente a un buzón.

El archivo que escribe --auth depende solo de --account / MAILWARDEN_ACCOUNT — nunca de la cuenta que elija en el navegador. Autorizar un segundo buzón sin --account apuntaría directamente al archivo de token del primero, por lo que --auth verifica primero y se niega en lugar de reemplazar el token de otro buzón; --force lo anula deliberadamente. Los dos controles no son intercambiables: MAILWARDEN_ACCOUNT es para varias cuentas desde un directorio de configuración (elige token.<nombre>.json), mientras que MAILWARDEN_DIR mueve el directorio completo — útil para mantener configuraciones completamente separadas, pero no le da una segunda cuenta dentro de una. npm run auth desde un clon del repositorio no pasa ninguno, es decir, siempre sirve la cuenta predeterminada.

mailwarden --check muestra la cuenta activa y enumera las otras que encuentra. Sin MAILWARDEN_ACCOUNT establecido, todo usa el token.json predeterminado exactamente como antes — esto es totalmente compatible hacia atrás.

From source

git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node dist/index.js --auth

Config (env)

Var

Significado

MAILWARDEN_DIR

directorio de configuración (por defecto ~/.mailwarden)

MAILWARDEN_CREDENTIALS

ruta a credentials.json

MAILWARDEN_ACCOUNT

seleccionar una cuenta con nombre (su token es token.<nombre>.json; los nombres están en minúsculas); sin definir = el token.json por defecto. Ver Múltiples cuentas

MAILWARDEN_TOKEN_PASSPHRASE

frase de contraseña → cifrar token.json en reposo (AES-256-GCM); volver a ejecutar --auth después de configurar

MAILWARDEN_AUTO_SWEEP

1 → barrido de pospuestos al inicio + cada hora mientras se ejecuta (escribe etiquetas — necesita el ámbito manage/gmail.modify; una concesión de solo lectura no puede barrer)

MAILWARDEN_DOWNLOAD_DIR

restringir download_attachment a este directorio (muy recomendado para alojamiento HTTP)

MAILWARDEN_READONLY

1 → registrar solo las herramientas de lectura (search/get_thread/list_labels/list_snoozed/get_profile/triage_digest/list_unsubscribe/list_subscriptions). Abreviatura de MAILWARDEN_TOOLS=read

MAILWARDEN_TOOLS

niveles de herramientas separados por comas para anunciar: read, manage, filters (por defecto: todos). También deriva los ámbitos OAuth solicitados en --auth. Por ejemplo, read,manage elimina las herramientas de filtro y su ámbito gmail.settings.basic

MAILWARDEN_DEBUG

1 → imprimir errores completos con trazas de pila en lugar de un mensaje de una línea (para informes de errores)

PORT

puerto HTTP (por defecto 8787)

MAILWARDEN_HOST

dirección de enlace HTTP (por defecto 127.0.0.1; establecer por ejemplo 0.0.0.0 para alojamiento remoto)

MAILWARDEN_TOKEN

token de portador para el punto final HTTP — requerido para --http a menos que se anule

MAILWARDEN_ALLOW_NO_TOKEN

1 → permitir --http sin token (solo redes de confianza/aisladas)

MAILWARDEN_ALLOWED_HOSTS

valores adicionales host:puerto separados por comas aceptados por la lista blanca de Host de bucle invertido

Estado

En funcionamiento y utilizado en la automatización diaria del buzón. Herramientas principales de Gmail + pospuestos implementados contra googleapis, cubiertos por un conjunto de pruebas vitest (789 pruebas — npm run coverage). Versión actual: ver la insignia npm arriba, el registro de cambios o versiones. Se aceptan PRs.

Licencia

MIT © C.Sitte Softwaretechnik

Available Tools

26 tools
archiveA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_modifyA
DestructiveIdempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
addNo
queryYes
dryRunNo
removeNo
verifyNo
crossCheckNo
maxMessagesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
cappedYes
dryRunYes
failedYes
verifiedNo
crossCheckedNo
labelsToCreateNo
matchedThreadsYes
matchedMessagesYes
submittedThreadsYes
submittedMessagesYes
matchedThreadCountYes
submittedThreadCountYes
unverifiedPredicatesYes

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_unsubscribeA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
dryRunNo
threadIdsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
dryRunYes
resultsYes
requestsYes
attemptedYes
requestedYes
unsubscribedYes
skippedOutOfTimeYes
skippedDuplicatesYes

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_filterA
Destructive

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNo
fromNo
sizeNo
queryNo
verifyNo
subjectNo
addLabelsNo
maxMessagesNo
excludeChatsNo
negatedQueryNo
removeLabelsNo
hasAttachmentNo
sizeComparisonNo
applyToExistingNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
appliedYes
forwardNo
criteriaYes
addLabelIdsYes
removeLabelIdsYes

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_labelA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYes
textColorNo
backgroundColorNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
nameYes
colorNo

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes

TDQS

A3.8/5.0
Behavior1/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
destPathYes
messageIdYes
attachmentIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
savedYes

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_profileA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
historyIdYes
emailAddressYes
threadsTotalYes
messagesTotalYes

TDQS

A4.7/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_threadA
Read-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).

ParametersJSON Schema
NameRequiredDescriptionDefault
fullNo
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
messagesYes
threadIdYes
metadataOnlyNo

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_filtersA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
filtersYes

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_labelsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
labelsYes

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_snoozedA
Read-only

List all snoozed threads with their due dates. SIDE EFFECTS: none.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
snoozedYes

TDQS

A4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines2/5

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_subscriptionsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
maxNo
topNNo
queryNocategory:promotions

Output Schema

ParametersJSON Schema
NameRequiredDescription
queryYes
hasMoreYes
sampledYes
sendersFoundYes
subscriptionsYes

TDQS

A4.3/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines5/5

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_unsubscribeA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
fromYes
mailtosYes
subjectYes
oneClickYes
threadIdYes
httpsUrlsYes
messageIdYes
bodyCandidatesYes
hasUnsubscribeYes

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_readA
Idempotent

Mark a thread as read. SIDE EFFECTS: removes UNREAD; reversible via mark_unread.

ParametersJSON Schema
NameRequiredDescriptionDefault
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes

TDQS

A4.5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_unreadA
Idempotent

Mark a thread as unread. SIDE EFFECTS: adds UNREAD; reversible via mark_read.

ParametersJSON Schema
NameRequiredDescriptionDefault
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_labelsA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
addNo
removeNo
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

snoozeA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
untilYes
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
threadIdYes
snoozedUntilYes

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_snoozedA
Idempotent

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

ParametersJSON Schema
NameRequiredDescriptionDefault
dryRunNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
dateYes
wokenYes
dryRunYes
errorsYes
dueLabelsYes
dueThreadsYes
wokenCountYes
failedCountYes
dueThreadCountYes

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

trashA
DestructiveIdempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_digestA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
maxNo
topNNo
queryNoin:inbox

Output Schema

ParametersJSON Schema
NameRequiredDescription
byAgeYes
queryYes
unreadYes
hasMoreYes
sampledYes
signalsYes
topLabelsYes
topSendersYes
withAttachmentsYes

TDQS

A4.4/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

unsnoozeA
Idempotent

Cancel a snooze: return the thread to the inbox now. SIDE EFFECTS: removes the snooze label, restores INBOX.

ParametersJSON Schema
NameRequiredDescriptionDefault
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
threadIdYes
unsnoozedYes

TDQS

A4.1/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters1/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

unsubscribeA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
forceNo
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
urlNo
fromYes
reasonNo
statusNo
optionsYes
threadIdYes
messageIdYes
duplicateOfNo
unsubscribedYes

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

untrashA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
threadIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYes

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_changedA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
maxNo
labelIdNo
sinceHistoryIdYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
addedYes
deletedYes
recordsYes
historyIdYes
truncatedYes
labelsAddedYes
labelsRemovedYes

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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. 1 tool updatev0.21.0
    • Changedcreate_filter2 fields changed
      • addedInput schema / properties / verify
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • changedOutput schema / properties / applied / anyOf
        Previous 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"
        +  }
        +]
  2. 5 tool updatesv0.18.0
    • Changedbulk_modify2 fields changed
      • addedInput schema / properties / crossCheck
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / crossChecked
        Added 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"
        +}
    • Changedcreate_label3 fields changed
      • addedInput schema / properties / backgroundColor
        Added value: +{
        +  "type": "string"
        +}
      • addedInput schema / properties / textColor
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / color
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "backgroundColor": {
        +      "type": "string"
        +    },
        +    "textColor": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "backgroundColor",
        +    "textColor"
        +  ],
        +  "type": "object"
        +}
    • Changedget_profile2 fields changed
      • addedOutput schema / properties / historyId
        Added value: +{
        +  "type": "string"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "emailAddress",
        -  "messagesTotal",
        -  "threadsTotal"
        -]New value: +[
        +  "emailAddress",
        +  "messagesTotal",
        +  "threadsTotal",
        +  "historyId"
        +]
    • Changedlist_unsubscribe2 fields changed
      • addedOutput schema / properties / bodyCandidates
        Added 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"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "threadId",
        -  "messageId",
        -  "from",
        -  "subject",
        -  "hasUnsubscribe",
        -  "oneClick",
        -  "httpsUrls",
        -  "mailtos"
        -]New value: +[
        +  "threadId",
        +  "messageId",
        +  "from",
        +  "subject",
        +  "hasUnsubscribe",
        +  "bodyCandidates",
        +  "oneClick",
        +  "httpsUrls",
        +  "mailtos"
        +]
    • Addedwhat_changed
  3. 1 tool updatev0.17.0
    • Changedget_thread2 fields changed
      • addedOutput schema / properties / messages / items / properties / authentication
        Added 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"
        +}
      • changedOutput schema / properties / messages / items / required
        Previous value: -[
        -  "id",
        -  "threadId",
        -  "labelIds",
        -  "from",
        -  "to",
        -  "subject",
        -  "date",
        -  "snippet"
        -]New value: +[
        +  "id",
        +  "threadId",
        +  "labelIds",
        +  "from",
        +  "to",
        +  "subject",
        +  "date",
        +  "snippet",
        +  "authentication"
        +]
  4. 3 tool updatesv0.15.1
    • Changedbulk_modify9 fields changed
      • addedInput schema / properties / verify
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • removedOutput schema / properties / modifiedMessages
        Removed value: -{
        -  "type": "number"
        -}
      • removedOutput schema / properties / modifiedThreadCount
        Removed value: -{
        -  "type": "number"
        -}
      • removedOutput schema / properties / modifiedThreads
        Removed value: -{
        -  "items": {
        -    "type": "string"
        -  },
        -  "type": "array"
        -}
      • addedOutput schema / properties / submittedMessages
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / submittedThreadCount
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / submittedThreads
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / verified
        Added 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"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "dryRun",
        -  "matchedMessages",
        -  "matchedThreadCount",
        -  "matchedThreads",
        -  "modifiedMessages",
        -  "modifiedThreadCount",
        -  "modifiedThreads",
        -  "capped",
        -  "unverifiedPredicates",
        -  "failed"
        -]New value: +[
        +  "dryRun",
        +  "matchedMessages",
        +  "matchedThreadCount",
        +  "matchedThreads",
        +  "submittedMessages",
        +  "submittedThreadCount",
        +  "submittedThreads",
        +  "capped",
        +  "unverifiedPredicates",
        +  "failed"
        +]
    • Changedcreate_filter1 field changed
      • changedOutput schema / properties / applied / anyOf
        Previous 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"
        +  }
        +]
    • Changedget_thread2 fields changed
      • changedOutput schema / properties / messages / items / required
        Previous value: -[
        -  "id",
        -  "threadId",
        -  "labelIds",
        -  "from",
        -  "to",
        -  "subject",
        -  "date",
        -  "snippet",
        -  "plaintextBody",
        -  "htmlBody",
        -  "attachments"
        -]New value: +[
        +  "id",
        +  "threadId",
        +  "labelIds",
        +  "from",
        +  "to",
        +  "subject",
        +  "date",
        +  "snippet"
        +]
      • addedOutput schema / properties / metadataOnly
        Added value: +{
        +  "const": true,
        +  "type": "boolean"
        +}
  5. 1 tool updatev0.14.0
    • Changedunsubscribe2 fields changed
      • addedInput schema / properties / force
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / duplicateOf
        Added value: +{
        +  "type": "string"
        +}
  6. 5 tool updatesv0.11.0
    • Changedbulk_modify7 fields changed
      • addedInput schema / properties / dryRun
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / dryRun
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / labelsToCreate
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / matchedThreadCount
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / matchedThreads
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / unverifiedPredicates
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "matchedMessages",
        -  "modifiedMessages",
        -  "modifiedThreadCount",
        -  "modifiedThreads",
        -  "capped",
        -  "failed"
        -]New value: +[
        +  "dryRun",
        +  "matchedMessages",
        +  "matchedThreadCount",
        +  "matchedThreads",
        +  "modifiedMessages",
        +  "modifiedThreadCount",
        +  "modifiedThreads",
        +  "capped",
        +  "unverifiedPredicates",
        +  "failed"
        +]
    • Changedbulk_unsubscribe5 fields changed
      • addedInput schema / properties / dryRun
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / dryRun
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / requests
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / wouldCall
        Added value: +{
        +  "type": "string"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "requested",
        -  "attempted",
        -  "unsubscribed",
        -  "skippedDuplicates",
        -  "skippedOutOfTime",
        -  "results"
        -]New value: +[
        +  "dryRun",
        +  "requested",
        +  "attempted",
        +  "unsubscribed",
        +  "skippedDuplicates",
        +  "skippedOutOfTime",
        +  "requests",
        +  "results"
        +]
    • Changedsearch2 fields changed
      • addedOutput schema / properties / threads / items / properties / signals
        Added value: +{
        +  "items": {
        +    "enum": [
        +      "newsletter",
        +      "automated",
        +      "calendar",
        +      "replyToMismatch"
        +    ],
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / threads / items / required
        Previous value: -[
        -  "threadId",
        -  "messageCount",
        -  "from",
        -  "subject",
        -  "date",
        -  "labelIds",
        -  "snippet",
        -  "hasAttachments"
        -]New value: +[
        +  "threadId",
        +  "messageCount",
        +  "from",
        +  "subject",
        +  "date",
        +  "labelIds",
        +  "snippet",
        +  "hasAttachments",
        +  "signals"
        +]
    • Changedsweep_snoozed8 fields changed
      • addedInput schema / $schema
        Added value: +"http://json-schema.org/draft-07/schema#"
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / dryRun
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / dryRun
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / dueLabels
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / dueThreadCount
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / dueThreads
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "date",
        -  "wokenCount",
        -  "woken",
        -  "failedCount",
        -  "errors"
        -]New value: +[
        +  "date",
        +  "dryRun",
        +  "dueLabels",
        +  "dueThreadCount",
        +  "dueThreads",
        +  "wokenCount",
        +  "woken",
        +  "failedCount",
        +  "errors"
        +]
    • Changedtriage_digest4 fields changed
      • addedOutput schema / properties / signals
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "automated": {
        +      "type": "number"
        +    },
        +    "calendar": {
        +      "type": "number"
        +    },
        +    "newsletter": {
        +      "type": "number"
        +    },
        +    "replyToMismatch": {
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "newsletter",
        +    "automated",
        +    "calendar",
        +    "replyToMismatch"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / topSenders / items / properties / signals
        Added value: +{
        +  "items": {
        +    "enum": [
        +      "newsletter",
        +      "automated",
        +      "calendar",
        +      "replyToMismatch"
        +    ],
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / topSenders / items / required
        Previous value: -[
        -  "sender",
        -  "name",
        -  "count",
        -  "unread"
        -]New value: +[
        +  "sender",
        +  "name",
        +  "count",
        +  "unread",
        +  "signals"
        +]
      • changedOutput schema / required
        Previous value: -[
        -  "query",
        -  "sampled",
        -  "hasMore",
        -  "unread",
        -  "withAttachments",
        -  "byAge",
        -  "topSenders",
        -  "topLabels"
        -]New value: +[
        +  "query",
        +  "sampled",
        +  "hasMore",
        +  "unread",
        +  "withAttachments",
        +  "byAge",
        +  "signals",
        +  "topSenders",
        +  "topLabels"
        +]
  7. 4 tool updatesv0.9.0
    • Addedbulk_unsubscribe
    • Addedlist_subscriptions
    • Addedlist_unsubscribe
    • Addedunsubscribe
  8. 8 tool updatesv0.6.0
    • Changedbulk_modify1 field changed
      • addedInput schema / properties / query / minLength
        Added value: +1
    • Addedcreate_filter
    • Addedcreate_label
    • Addeddelete_filter
    • Addedget_profile
    • Addedlist_filters
    • Changedsnooze2 fields changed
      • addedInput schema / properties / until / minLength
        Added value: +1
      • removedInput schema / properties / until / pattern
        Removed value: -"^\\d{4}-\\d{2}-\\d{2}$"
    • Addedtriage_digest
  9. 1 tool updatev0.1.9
    • Changedbulk_modify2 fields changed
      • addedOutput schema / properties / capped
        Added value: +{
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "matchedMessages",
        -  "modifiedMessages",
        -  "modifiedThreadCount",
        -  "modifiedThreads",
        -  "failed"
        -]New value: +[
        +  "matchedMessages",
        +  "modifiedMessages",
        +  "modifiedThreadCount",
        +  "modifiedThreads",
        +  "capped",
        +  "failed"
        +]
  10. 15 tool updatesv0.1.8
    • First observedarchive
    • First observedbulk_modify
    • First observeddownload_attachment
    • First observedget_thread
    • First observedlist_labels
    • First observedlist_snoozed
    • First observedmark_read
    • First observedmark_unread
    • First observedmodify_labels
    • First observedsearch
    • First observedsnooze
    • First observedsweep_snoozed
    • First observedtrash
    • First observedunsnooze
    • First observeduntrash

TDQS

A4.2/5.0

Scored across 26 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count3/5

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.

Completeness4/5

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

ActivityActive
ResponsivenessSlow

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An 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.
    1
    MIT
  • A
    license
    B
    quality
    D
    maintenance
    Manage 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.
    64
    1,075 npm
    62
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Gmail MCP server — scope-gated tools (readonly / send / modify), path jails for attachments + downloads, hardened OAuth credentials, Sigstore-signed releases.
    357 npm
    13
    MIT
  • A
    license
    A
    quality
    F
    maintenance
    A Gmail MCP server with native multi-account support, enabling management of multiple Gmail accounts from a single server instance.
    7
    6
    MIT