mailwarden
mailwarden
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.listde Gmail — la llamada por la que pasa cualquier búsqueda de hilos — puede responder ais:unreaddesde 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 quesearchvuelve a verificar cada resultado contra sus etiquetas en vivo. Paginado mediantepageToken/nextPageToken.Operaciones masivas que escalan.
bulk_modifyarchiva/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
outputSchemay devuelvestructuredContentvalidado 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 nivelreadno 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 | ||||
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 | ✅ | — | — | — | — |
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 ( | Hilos devueltos | Con un mensaje no leído | Desactualizados |
| 131 | 17 | 87% |
| 128 | 14 | 89% |
| 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.mjsHay 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 |
| 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 |
| Hilo completo: encabezados, cuerpos en texto plano + HTML, metadatos de adjuntos |
| Todas las etiquetas (del sistema + del usuario) |
| Dirección de la cuenta conectada + total de mensajes/hilos — confirma qué buzón está conectado antes de actuar |
| 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 |
| Qué opciones de exclusión anuncia un hilo ( |
| 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. |
| Crear una etiqueta de usuario (idempotente; anidada mediante |
| Añadir/eliminar etiquetas por nombre o id — un nombre desconocido en |
| 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, |
| Envoltorios de conveniencia |
| Mover a / restaurar desde Papelera |
| Guardar un adjunto en una ruta local (nunca sobrescribe — las colisiones obtienen un sufijo numérico) |
| 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) |
| Lo mismo para varios hilos, secuencialmente y como máximo una solicitud por remitente; éxito parcial informado por hilo. |
| Archivar ahora, reaparecer en/después de una fecha ( |
| Cancelar un aplazamiento, volver a la bandeja de entrada ahora |
| Todos los hilos aplazados + fechas de vencimiento |
| Reaparecer hilos cuyo aplazamiento ha vencido (ejecutar bajo demanda, mediante cron o el daemon); por lotes, con informe de fallos parciales. |
| Todos los filtros de Gmail (criterios + acciones de etiquetas); muestra cualquier dirección |
| 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 |
| 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, ysize+sizeComparison(smaller/larger, proporcionados juntos). Se requiere al menos uno.Acciones (solo etiqueta):
addLabels/removeLabels, por nombre o id (un nombre desconocido enaddLabelsse 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: truepara 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 (hastamaxMessages, por defecto 1000; misma advertencia de índice no verificado quebulk_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 (negatedQueryohasAttachment:false) se rechaza paraapplyToExistingporque coincidiría con casi toda la bandeja de entrada — crea dicho filtro sin la bandeja. El resultado se devuelve bajoapplied(laqueryutilizada, recuentos dematchedMessages/modifiedMessages/modifiedThreadCount,cappedcuando el conjunto de coincidencias alcanzómaxMessages,failedpor fragmento, y una cadenaerrorsi todo el pase falló); esnullcuandoapplyToExistingno se estableció. El filtro se crea primero, por lo que un pase parcial o fallido del historial se reporta enapplied, nunca se lanza — la regla sigue vigente.Sin reenvío — ver Seguridad y privacidad.
Requiere el ámbito
gmail.settings.basic; ejecuta--authuna 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 enlacehttps: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-Clicky 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_unsubscribetoma 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 conduplicateOfy 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 comoskippedOutOfTimeen 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 (
::1y0:0:0:0:0:0:0:1por 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 (fetchresuelve 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 --vetImprime 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
--httpse vincula a127.0.0.1(no a la LAN) y se niega a iniciar sin un token portadorMAILWARDEN_TOKEN— establezcaMAILWARDEN_ALLOW_NO_TOKEN=1para anularlo en una red aislada y de confianza. En un enlace de loopback también valida el encabezadoHost(defensa contra DNS-rebinding). Para alojamiento remoto, configureMAILWARDEN_HOSTy 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_filtersigue 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_filterssigue 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
unsubscribees la única ruta de código que contacta con un host que no sea de Google. Su endpoint se lee del encabezadoList-Unsubscribedel 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_TOOLSanuncia 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 necesitangmail.settings.basic). El valor predeterminado son los tres; por ejemplo,read,manageproporciona una superficie de triaje completa sin gestión de filtros. Los ámbitos de OAuth solicitados en--authse derivan de los niveles habilitados — una implementaciónreadsolicita sologmail.readonly, ygmail.settings.basicse solicita solo cuando el nivelfiltersestá activo. Y las herramientas de filtro se ocultan automáticamente cuando el token almacenado no incluyegmail.settings.basic(por ejemplo, un token autorizado antes de habilitar el nivel) — vuelva a ejecutar--authpara 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 deMAILWARDEN_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 ampliogmail.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 tokengmail.readonly, que los endpoints de envío de Gmail rechazan de plano.managenecesitagmail.modify, y Gmail sí acepta ese ámbito para enviar — mailwarden simplemente no expone ninguna herramienta que lo haga. Por lo tanto, una implementaciónreadno podría enviar incluso si este binario fuera reemplazado; una implementaciónmanageno 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_DIRestablecido, 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.jsoncontiene un token de actualización; en disco está protegido solo pormode 0o600(sin efecto en Windows). EstablezcaMAILWARDEN_TOKEN_PASSPHRASEen 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 ejecutarmailwarden --authuna 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 mailwardenEsa 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:
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.Coloque
credentials.jsonen~/.mailwarden/(o establezcaMAILWARDEN_CREDENTIALS=/ruta/a/credentials.json).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) ygmail.settings.basic(solo gestión de filtros). Si autorizó una versión anterior a que existieran los filtros, vuelva a ejecutar--authuna vez para conceder el ámbito añadido. Para tener un token con el que el propio Gmail se niegue a enviar, autorice conMAILWARDEN_TOOLS=read— consulte Modo de solo lectura más arriba.Verifique la configuración en cualquier momento con el doctor incorporado:
npx -y mailwarden --checkVerifica
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 sinMAILWARDEN_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 mailwardenPlugin 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/mailwardenEstá 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 configTenga 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/mcpLuego 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.jsonEjecú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 --authConfig (env)
Var | Significado |
| directorio de configuración (por defecto |
| ruta a |
| seleccionar una cuenta con nombre (su token es |
| frase de contraseña → cifrar |
|
|
| restringir |
|
|
| niveles de herramientas separados por comas para anunciar: |
|
|
| puerto HTTP (por defecto 8787) |
| dirección de enlace HTTP (por defecto |
| token de portador para el punto final HTTP — requerido para |
|
|
| valores adicionales |
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
Maintenance
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables Gmail integration, allowing users to manage emails (send, receive, read, trash, mark as read) directly through MCP clients like Claude Desktop.1MIT
- AlicenseBqualityDmaintenanceManage your emails effortlessly with a standardized interface for drafting, sending, retrieving, and organizing messages. Streamline your email workflow with complete Gmail API coverage, including label and thread management.641,39856MIT
- AlicenseNot gradedqualityAmaintenanceGmail MCP server — scope-gated tools (readonly / send / modify), path jails for attachments + downloads, hardened OAuth credentials, Sigstore-signed releases.20711MIT
- AlicenseAqualityFmaintenanceA Gmail MCP server with native multi-account support, enabling management of multiple Gmail accounts from a single server instance.75MIT
Related MCP Connectors
Read, search, send, organize, draft and schedule email across your inboxes from any MCP client.
Shipmail MCP server for AI agent custom-domain email inboxes with REST API and webhooks.
Fully-managed email as MCP tools - register domains, real mailboxes, send and receive mail.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/csitte/mailwarden'
If you have feedback or need assistance with the MCP directory API, please join our Discord server