Email Agent MCP
Conectividad de correo electrónico local para agentes de IA: leer, redactar, enviar y organizar correos de Outlook a través de MCP. Licencia Apache-2.0.
Documentación
Agent Email
English | Español | 简体中文 | Português (Brasil) | Deutsch
email-agent-mcp por UseJunior -- conectividad de correo local para agentes de IA.
Agent Email es un servidor MCP de TypeScript de código abierto que permite a Claude Code, Cursor, Gemini CLI, OpenClaw y otros entornos compatibles con MCP leer correos, buscar hilos, redactar respuestas, etiquetar mensajes, cambiar el estado de lectura, mover mensajes y enviar correos a través de tu propia bandeja de entrada. Microsoft 365 / Outlook y Gmail son compatibles hoy en día. Los valores predeterminados de seguridad primero significan que los agentes no pueden enviar correos hasta que configures explícitamente una lista de permitidos.
Inicio rápido
npx -y email-agent-mcp
El asistente de configuración interactivo te guía a través de la configuración de OAuth y la selección de la bandeja de entrada.
Qué funciona hoy
- Acceso a la bandeja de entrada de Microsoft 365 / Outlook a través de MCP stdio
list_emails,read_email,search_emailsyget_threadcreate_draft,update_draft,send_draft,send_emailyreply_to_emaillabel_email,mark_readymove_to_folder- listas de permitidos de envío, eliminación deshabilitada por defecto y errores saneados
La pasada actual de preparación para el lanzamiento se validó contra una bandeja de entrada real de Outlook para los flujos de lectura, borrador, envío, categorización, movimiento y estado de lectura.
Por qué existe esto
Los agentes de IA necesitan leer, responder y actuar sobre el correo electrónico, pero las API de correo son complejas. Flujos de OAuth, consultas delta de Graph, suscripciones push de Gmail, conversión de HTML a markdown, semántica de hilos: cada proveedor tiene sus propias peculiaridades.
Agent Email envuelve esta complejidad en herramientas MCP deterministas con barreras de seguridad:
- listas de permitidos de envío y recepción que controlan a quién pueden contactar los agentes
- eliminación deshabilitada por defecto (requiere aceptación explícita)
- saneamiento de errores que elimina claves de API, rutas de archivos y trazas de pila
- sandboxing de archivos de cuerpo con protección contra traversal de rutas
Uso con Claude Code
Añade a ~/.claude/settings.json o a tu proyecto .claude/settings.json:
{
"mcpServers": {
"email-agent-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "email-agent-mcp"]
}
}
}
Uso con Cursor
// .cursor/mcp.json
{
"mcpServers": {
"email-agent-mcp": {
"command": "npx",
"args": ["-y", "email-agent-mcp"]
}
}
}
Uso con Gemini CLI
gemini extensions install https://github.com/UseJunior/email-agent-mcp
Uso con OpenClaw
Añade un bloque mcp a ~/.openclaw/openclaw.json:
{
// ... existing config ...
mcp: {
servers: {
email: {
command: "npx",
args: ["tsx", "/path/to/email-agent-mcp/packages/email-mcp/src/serve-entry.ts"],
transport: "stdio"
}
}
}
}
Nota de versión: La clave de configuración
mcprequiere OpenClaw app >= 2026.3.24. Si el CLI es más antiguo que la app, puede rechazar esta clave durante la validación aunque la puerta de enlace la acepte. Actualiza el CLI connpm install openclaw@latesten tu directorio NemoClaw, o reinicia la puerta de enlace directamente conlaunchctl kickstart -k gui/501/ai.openclaw.gateway.
Vigilante de correo
El vigilante consulta tu bandeja de entrada y envía señales de activación a OpenClaw cuando llega correo nuevo:
# Set the hooks token (must match hooks.token in openclaw.json)
export OPENCLAW_HOOKS_TOKEN="your-hooks-token"
# Start the watcher (defaults to http://localhost:18789/hooks/wake)
npm run dev:watch
El vigilante requiere al menos una bandeja de entrada configurada. Ejecuta npx email-agent-mcp o npm run dev:configure primero para completar el flujo de OAuth.
Prueba de humo de preparación para el lanzamiento
Antes de grabar una demostración, ejecuta el script de humo en vivo contra una bandeja de entrada real y una lista de permitidos de envío segura. El script ejercita:
get_mailbox_statuslist_emails+read_emailmark_readno leído -> leído -> no leídolabel_emailen un candidato seguro de la bandeja de entradacreate_draftreply_to_emailsolo borradorsend_emailopcional
Ejemplo:
EMAIL_AGENT_MCP_HOME=/tmp/email-agent-mcp-live \
AGENT_EMAIL_SEND_ALLOWLIST=/tmp/email-agent-mcp-live/send-allowlist.json \
npm run launch:prep:smoke -- --live-write --send-to beta@usejunior.com
La selección predeterminada de candidatos seguros busca notifications@github.com en la bandeja de entrada para que puedas ensayar el flujo de grabación en un mensaje seguro para el público en lugar de correo de clientes.
Si el nombre de estado de tu bandeja de entrada no es una dirección de correo electrónico, pasa --reply-sender <email> o establece EMAIL_AGENT_MCP_REPLY_SENDER para que el script pueda encontrar un mensaje autoenviado para la verificación de respuesta de borrador.
Referencia de herramientas
Perfiles de alcance
Establece EMAIL_AGENT_MCP_SCOPE_PROFILE antes de configurar e iniciar el servidor para
elegir los permisos expuestos a un agente. El valor predeterminado es full para
compatibilidad hacia atrás.
| Perfil | Alcances delegados de Microsoft | Herramientas expuestas |
|---|---|---|
observe | Mail.Read, User.Read, offline_access | Herramientas de correo de solo lectura, menos list_inbox_rules (ver abajo) |
full (predeterminado) | Mail.Read, Mail.ReadWrite, Mail.Send, MailboxSettings.ReadWrite, User.Read, offline_access | Todas las herramientas |
Para una implementación de solo observación, establece el perfil tanto para la configuración como para el tiempo de ejecución para que el consentimiento de OAuth y la lista de herramientas MCP coincidan:
export EMAIL_AGENT_MCP_SCOPE_PROFILE=observe
npx email-agent-mcp configure
npx email-agent-mcp serve
Los alcances de observe son un subconjunto estricto deliberado de los de full. Eso es lo que
hace adoptable el perfil: un inquilino que ya ha consentido el conjunto completo
otorga observe silenciosamente, por lo que cambiar full → observe no necesita nuevo consentimiento.
Cambiar observe → full sí requiere un nuevo consentimiento interactivo, porque
pide alcances que nunca fueron otorgados.
Por la misma razón, observe no expone list_inbox_rules. Graph limita
/mailFolders/inbox/messageRules detrás de MailboxSettings, y Entra trata
MailboxSettings.Read como un alcance distinto del
MailboxSettings.ReadWrite de full en lugar de implicado por él. Solicitarlo rompería
la propiedad de subconjunto y forzaría un nuevo consentimiento en cada implementación de observe — una
solicitud de aprobación de administrador en inquilinos que restringen el consentimiento del usuario. Usa full si
necesitas visibilidad de reglas de bandeja de entrada.
Un valor de perfil inválido detiene el inicio en lugar de otorgar silenciosamente un acceso más amplio. Si las credenciales en caché no cubren los alcances del perfil, el servidor falla rápidamente con un error procesable en lugar de bloquearse en un inicio de sesión interactivo.
Qué garantiza y qué no garantiza observe
observe siempre elimina las herramientas de escritura de la superficie de herramientas MCP, por lo que un agente
no puede invocarlas. Esa parte se cumple en todas partes.
Solo reduce el token de OAuth en una bandeja de entrada que no haya consentido ya
los alcances de escritura. Entra emite un token de acceso que lleva cada alcance que el usuario
o inquilino ya ha consentido para esa aplicación — no solo el subconjunto
solicitado en el momento de la adquisición del token. Entonces, si apuntas observe a una bandeja de entrada
configurada previamente como full, el token subyacente aún lleva
Mail.ReadWrite y Mail.Send; solo se reduce la superficie de herramientas.
Para un token genuino de privilegio mínimo, consiente observe desde una bandeja de entrada que
nunca haya recibido los alcances de escritura — un configure fresco contra un
registro de aplicación cuyos permisos delegados se detienen en Mail.Read/User.Read. Trata
la reducción de la superficie de herramientas como defensa en profundidad, no como un límite de OAuth, a menos que
controles la concesión.
Herramientas
El perfil full expone 26 herramientas MCP; observe omite toda herramienta cuya acción
esté marcada como mutación de bandeja de entrada:
| Herramienta | Descripción | Tipo |
|---|---|---|
list_emails | Lista correos recientes con filtrado | lectura |
read_email | Lee el contenido completo del correo como markdown, o HTML sin procesar con format: "html" | lectura |
search_emails | Búsqueda de texto completo en las bandejas de entrada | lectura |
list_mailboxes | Enumera las bandejas de entrada configuradas, su estado y la predeterminada | lectura |
get_mailbox_status | Estado de conexión y advertencias | lectura |
get_thread | Contexto completo de la conversación | lectura |
list_attachments | Lista metadatos de adjuntos de un correo | lectura |
download_attachment | Descarga un adjunto de archivo como base64 | lectura |
send_email | Envía correo nuevo (restringido por lista de permitidos) | escritura |
reply_to_email | Responde dentro del hilo (restringido por lista de permitidos al enviar) | escritura |
create_draft | Crea un borrador de correo | escritura |
update_draft | Actualiza el contenido del borrador | escritura |
send_draft | Envía un borrador guardado | escritura |
list_scheduled_sends | Lista envíos programados pendientes retenidos por el proveedor (Microsoft 365) | lectura |
cancel_scheduled_send | Cancela un envío programado pendiente (Microsoft 365) | destructiva |
label_email | Aplica etiquetas/categorías | escritura |
flag_email | Marca/desmarca correos con bandera | escritura |
mark_read | Marca como leído/no leído | escritura |
move_to_folder | Mueve entre carpetas | escritura |
delete_email | Elimina (requiere entorno de operador + indicador de llamante) | destructiva |
list_folders | Lista carpetas recursivamente y rutas calculadas (Microsoft 365) | lectura |
create_folder | Crea una carpeta hija personalizada (Microsoft 365) | escritura |
delete_folder | Elimina una carpeta personalizada (requiere entorno de operador + indicador de llamante); las carpetas del sistema están protegidas (Microsoft 365) | destructiva |
list_inbox_rules | Lista reglas de bandeja de entrada del lado del servidor (Microsoft 365) | lectura |
create_inbox_rule | Crea una regla de bandeja de entrada segura persistente; el reenvío, la redirección, la eliminación y el descarte a Elementos eliminados están bloqueados (Microsoft 365) | escritura |
delete_inbox_rule | Elimina una regla de bandeja de entrada del lado del servidor (requiere entorno de operador + indicador de llamante) (Microsoft 365) | destructiva |
Esta es una superficie de herramientas compartida entre proveedores. Aunque label_email,
flag_email, mark_read, move_to_folder y delete_email se anuncian,
el adaptador de Gmail intencionalmente no implementa esas capacidades de mutación.
Fracasan de forma cerrada antes de cualquier solicitud de mutación de Gmail: las acciones no compatibles devuelven
NOT_SUPPORTED, mientras que la eliminación puede detenerse primero en la puerta de operador
predeterminada DELETE_DISABLED. El gmail.readonly predeterminado más la concesión gmail.compose no
autorizarían esas mutaciones tampoco; no agregues gmail.modify para intentar hacer que las
herramientas estén disponibles. Siguen disponibles solo para proveedores cuyos adaptadores y
concesiones los admiten.
Cada fila devuelta por list_emails, search_emails y get_thread, y la
respuesta de read_email, lleva un booleano isDraft siempre presente. isDraft: true
significa que el mensaje es un borrador no enviado: no ha sido enviado, y su receivedAt
es metadatos proporcionados por el proveedor en lugar de evidencia de entrega. El campo nunca se
omite, por lo que un agente consumidor puede distinguir "no es un borrador" de "estado de borrador no
informado" — pero ten en cuenta que false solo afirma que el proveedor no marcó el mensaje
como borrador no enviado, no que el propietario de la bandeja de entrada lo envió (el correo recibido también es
false). Los borradores aparecen en listados y resultados de búsqueda como antes.
La gestión de carpetas y reglas de bandeja de entrada requiere el consentimiento de Microsoft Graph MailboxSettings.ReadWrite. Las conexiones existentes de bandejas de entrada de Microsoft deben volver a consentir después de la actualización. Gmail usa etiquetas en lugar de carpetas jerárquicas/reglas de Exchange del lado del servidor, por lo que estas seis herramientas devuelven NOT_SUPPORTED para bandejas de entrada de Gmail.
Formatos de cuerpo
Los cuerpos cruzan el cable como markdown por defecto en ambas direcciones. Ese valor predeterminado es deliberado — el markdown es eficiente en tokens, y una lectura rutinaria debería mantenerse económica. Ambas direcciones pueden optar por no usarlo.
Escritura — send_email, reply_to_email, create_draft y update_draft
aceptan un format opcional:
format | Comportamiento |
|---|---|
markdown (predeterminado) | Renderizado a HTML a través de marked (GFM, breaks: true). El HTML sin procesar incrustado en el markdown se conserva. |
html | Paso directo — tu HTML se envía tal cual. |
text | Sin renderizado; se envía como texto plano. |
html es paso directo sin saneamiento: sin saneador, sin lista de permitidos, sin reescritura de
tu marcado. CSS en línea y etiquetas arbitrarias sobreviven hasta el cable — eso es lo que
hace posible el correo con estilo, y significa que eres dueño de lo que envías. La única
modificación predeterminada es el envoltorio negro forzado a continuación; con force_black: false el cuerpo pasa byte por byte.
Para markdown y html el HTML renderizado se envuelve en un
<div style="color: #000000;"> para que el modo oscuro de Outlook no convierta el texto
en blanco sobre blanco.
Archivos de cuerpo — send_email, create_draft y update_draft también aceptan
body_file: una ruta a un archivo .md, .html o .txt (leído relativo a
EMAIL_MCP_SAFE_DIR, por defecto el directorio de trabajo del proceso, más cualquier
raíz de AGENT_EMAIL_ALLOWED_DIRS) utilizado como cuerpo
en lugar de body. Un archivo de cuerpo .md puede
abrir con un bloque de frontmatter YAML:
Para update_draft, body y body_file solo se permiten en un
borrador que no sea de respuesta y requieren replace_body: true; el cuerpo almacenado se reemplaza entonces
por completo. Los cuerpos de borradores de respuesta no se pueden editar de forma segura porque incluyen
historial citado ensamblado por el proveedor, así que crea un nuevo borrador en su lugar. Asunto,
destinatario y actualizaciones de adjuntos siguen disponibles en borradores de respuesta.
---
to: alex@example.com
subject: Quarterly summary
format: html
force_black: false
---
<p>The body starts after the closing delimiter.</p>
Las claves reconocidas son to, cc, subject, reply_to, draft, format y
force_black (draft: true convierte una llamada a send_email en un guardado de borrador). Un
valor de frontmatter anula el parámetro de herramienta del mismo nombre. El frontmatter se
analiza solo desde archivos .md, y la extensión del archivo nunca selecciona el formato —
sin un parámetro de herramienta format explícito, incluso un archivo de cuerpo .html recibe el
renderizado markdown por defecto; pasa format: "html" para paso directo.
Lectura — read_email acepta un format opcional de markdown (por defecto)
o html:
{ "id": "AAMkAD...", "format": "html" }
format: "html" devuelve el HTML del cuerpo del mensaje sin modificar. Úsalo cuando
necesites estilos que markdown no puede transportar — color, background-color,
text-decoration, <u> — por ejemplo, para cambiar una frase de un cuerpo formateado
y dejar el resto intacto. A través de la ruta markdown, ese estilo se
destruye silenciosamente al salir.
Dos campos vuelven con él:
bodyFormat— siempre presente:markdown,htmlotext. Obtienestextcuando pedistehtmlpero el mensaje no tiene parte HTML, en cuyo caso se devuelve el cuerpo de texto plano. Verifica esto antes de escribir un cuerpo de vuelta.bodyTruncated— presente ytruesolo si el cuerpo superó el presupuesto de respuesta de 256 KB paraformat: "html"(la ruta markdown no tiene límite, como antes). No escribas un cuerpo truncado de vuelta a un borrador.
El HTML sin procesar cuesta muchos más tokens que su reducción markdown, así que deja el valor por defecto
a menos que necesites el estilo. strip_quoted_history y strip_signatures
son transformaciones de texto con forma markdown y no se aplican cuando format es html —
el HTML sin procesar se devuelve sin tocar.
Escribirlo de vuelta. Pasa format: "html" y force_black: false:
{ "draft_id": "AAMkAD...", "body": "<edited html>", "format": "html", "force_black": false }
force_black por defecto es true, que envuelve cualquier HTML que envíes en un
<div style="color: #000000;">. Eso es correcto para HTML que hayas creado tú mismo,
pero en un cuerpo que acabas de leer, anida un envoltorio más en cada ciclo —
después de quince revisiones tienes quince divs anidados. Con force_black: false los
bytes sobreviven al viaje de ida y vuelta sin cambios.
Envío programado
send_email y send_draft aceptan scheduled_send_at, una marca de tiempo ISO 8601 futura
con una zona horaria explícita. Microsoft 365 mantiene el mensaje
en el servidor, por lo que la entrega sobrevive a la salida de este proceso. Usa el
messageId devuelto con cancel_scheduled_send mientras esté pendiente, o inspecciona los elementos
pendientes con list_scheduled_sends. Esa lista cubre mensajes que programaste
desde Outlook mismo, no solo los programados a través de este servidor — Outlook aparca
el mensaje retenido donde le plazca (a menudo en Elementos eliminados), por lo que el descubrimiento escanea el
buzón en lugar de una carpeta, y reporta solo envíos cuya hora aún está por llegar.
Microsoft Graph cambia el ID del mensaje cuando el borrador retenido se mueve a Elementos enviados,
por lo que el ID devuelto es un identificador de gestión previo a la entrega, no un ID de mensaje enviado
permanente. La API pública de Gmail no expone el envío programado; todas las superficies
de envío programado devuelven NOT_SUPPORTED para Gmail mientras los envíos inmediatos permanecen
sin cambios.
Adjuntos salientes
send_email, reply_to_email, create_draft y update_draft aceptan un
array attachments opcional. Cada entrada toma una ruta de archivo path en sandbox (leída
relativa a EMAIL_MCP_SAFE_DIR, por defecto el directorio de trabajo del proceso, más
cualquier raíz de AGENT_EMAIL_ALLOWED_DIRS — ver abajo) o
base64 en línea, más anulaciones opcionales de filename / mimeType:
{
"to": "alice@example.com",
"subject": "Signed agreement",
"body": "Attached as requested.",
"attachments": [
{ "path": "./out/agreement.pdf" },
{ "base64": "iVBORw0KGgo...", "filename": "screenshot.png" }
]
}
Los archivos están limitados a 25MB cada uno; Microsoft Graph además limita los envíos en línea
a ~3MB en total (archivos más grandes necesitan una sesión de carga — aún no soportada). Para
update_draft, omitir attachments conserva los archivos existentes del borrador;
pasar un array (incluso vacío) los reemplaza.
Adjuntar archivos desde fuera del directorio de trabajo — establece
AGENT_EMAIL_ALLOWED_DIRS a una lista separada por delimitadores de directorios
absolutos (: en macOS/Linux, ; en Windows; un ~ inicial se expande) que
attachments[].path y body_file también pueden leer:
{
"mcpServers": {
"email-agent-mcp": {
"command": "npx",
"args": ["-y", "@usejunior/email-agent-mcp"],
"env": { "AGENT_EMAIL_ALLOWED_DIRS": "~/Downloads:/Volumes/Shared/Contracts" }
}
}
}
Cómo se resuelve la ruta de un llamador — las dos reglas siguientes aparecen ambas como
FILE_NOT_FOUND, que es fácil de malinterpretar como un archivo faltante:
| Ruta que pasas | Se resuelve contra | ¿Encuentra un archivo en una raíz permitida? |
|---|---|---|
contract.pdf | EMAIL_MCP_SAFE_DIR (por defecto: cwd) | ❌ las rutas relativas nunca buscan en las raíces adicionales |
~/Downloads/contract.pdf | EMAIL_MCP_SAFE_DIR — el ~ no se expande | ❌ |
/Users/you/Downloads/contract.pdf | cada raíz por turno | ✅ |
La abreviatura ~ se expande en AGENT_EMAIL_ALLOWED_DIRS (la configuración del
operador) pero no en attachments[].path o body_file (el argumento del
llamador) — pásalas como rutas absolutas. Y una ruta relativa está deliberadamente
confinada al directorio seguro: buscar en cada raíz permitida un
contract.pdf desnudo adjuntaría silenciosamente la copia que existiera primero.
Cada raíz se canoniza con realpath antes de la verificación de contención: una
raíz en la lista permitida que es en sí misma un enlace simbólico se resuelve a su ubicación real, una raíz
que no se puede canonizar no autoriza nada, y un enlace simbólico que escapa de cada
raíz aún se rechaza. Sin establecer (el valor por defecto) mantiene el directorio de trabajo como la
única raíz — por eso un agente que no puede alcanzar un archivo debería pedirte que
incluyas su directorio en la lista permitida en lugar de copiar documentos confidenciales en un árbol de trabajo git.
Incluir un directorio en la lista permitida confía en todos los que pueden escribir en él. La validación y la
apertura son operaciones separadas en una ruta, por lo que un principal que puede reemplazar un
directorio dentro de una raíz permitida entre las dos aún puede redirigir la lectura;
solo incluye raíces cuyos ancestros no sean escribibles por usuarios no confiables. El
archivo final se abre con O_NOFOLLOW, por lo que el archivo en sí no se puede intercambiar por un
enlace simbólico después de la validación.
Soporte de proveedores
| Proveedor | Estado | Paquete |
|---|---|---|
| Microsoft 365 (Graph API) | Totalmente soportado | @usejunior/provider-microsoft |
| Gmail | Soportado vía OAuth CLI interactivo (cliente por defecto o el tuyo propio) o configuración manual de token de actualización | @usejunior/provider-gmail |
Usa email-agent-mcp configure --provider gmail para ejecutar el flujo OAuth del navegador local, o agrega un archivo de token de buzón manual bajo ~/.email-agent-mcp/tokens/. Consulta Configuración de Gmail abajo y packages/provider-gmail/README.md.
Configuración de Gmail
Gmail tiene dos rutas OAuth soportadas. Ambas terminan con el mismo resultado: un token de actualización en tu máquina, y llamadas a la API de Gmail yendo directamente desde tu máquina a Google.
| Ruta | Comando | ¿Se necesita proyecto de Google Cloud? |
|---|---|---|
| Cliente OAuth por defecto | email-agent-mcp configure --provider gmail | No |
| Trae tu propia clave (BYOK) | mismo comando más --client-id / --client-secret | Sí, el tuyo |
Recomendado por ahora: BYOK. Nuestro cliente OAuth por defecto aún está en el estado de publicación "Pruebas" de Google mientras la verificación está en progreso, lo que lo limita a 100 usuarios de prueba registrados y muestra el aviso "Google no ha verificado esta aplicación" durante el consentimiento. La verificación para el alcance restringido de Gmail requiere una evaluación de seguridad CASA y toma varias semanas; el progreso se rastrea en issue #112. BYOK evita el límite compartido de 100 usuarios y pone el estado de publicación de la aplicación bajo tu control; tu propia aplicación aún tiene las restricciones de Pruebas de Google hasta que la publiques.
Otras razones para elegir BYOK: cuota de API dedicada, tu propia política de privacidad y
estado de verificación, y sin dependencia del broker alojado en
https://oauth.usejunior.com.
Alcances solicitados
Agent Email solicita exactamente estos dos alcances de Gmail por defecto:
https://www.googleapis.com/auth/gmail.readonly
https://www.googleapis.com/auth/gmail.compose
gmail.readonly es requerido para leer mensajes e hilos;
gmail.compose gestiona borradores y envío pero no autoriza
users.threads.get o users.messages.get. La concesión por defecto no incluye
gmail.modify. Por separado, el adaptador de Gmail deja intencionalmente los cambios de etiquetas,
cambios de estado de lectura, movimientos, papelera y operaciones de eliminación sin soporte,
por lo que esas herramientas fallan cerradas con NOT_SUPPORTED. Agrega ambos alcances a tu pantalla
de consentimiento OAuth.
Las implementaciones que establecen explícitamente GMAIL_OAUTH_SCOPES deben actualizarlo a los dos
alcances separados por espacios anteriores. Las nuevas autorizaciones y las autorizaciones repetidas
deben volver a consentir el nuevo par de alcances. Los archivos de buzón existentes y sus metadatos
de token de actualización almacenados permanecen sin cambios.
BYOK: crea tu propio cliente OAuth de Google
- Crea un proyecto en la Consola de Google Cloud, o selecciona uno existente.
- Habilita la API de Gmail bajo APIs y servicios → Biblioteca → API de Gmail → Habilitar.
- Configura la pantalla de consentimiento OAuth bajo APIs y servicios → Pantalla de consentimiento OAuth:
- Tipo de usuario Externo para una cuenta personal de
@gmail.com, o Interno si estás en Google Workspace y solo tu propia organización necesita acceso. - Agrega tanto
https://www.googleapis.com/auth/gmail.readonlycomohttps://www.googleapis.com/auth/gmail.compose. - Mientras la aplicación esté en Pruebas, agrega tu propia dirección de Gmail bajo Usuarios de prueba, o el consentimiento será rechazado.
- Tipo de usuario Externo para una cuenta personal de
- Crea el cliente OAuth bajo APIs y servicios → Credenciales → Crear
credenciales → ID de cliente OAuth. Elige el tipo de aplicación Aplicación de escritorio.
Escritorio es requerido:
configureinicia un listener desechable en un puerto loopback efímero (http://127.0.0.1:<port>/oauth2callback), y solo los clientes de Escritorio permiten que Google acepte un puerto loopback arbitrario sin pre-registrar la URI de redirección exacta. Un cliente de aplicación web fallará conredirect_uri_mismatch. - Copia el ID de cliente y el secreto de cliente.
BYOK: entrega las credenciales a email-agent-mcp
Pasa ambas mitades como banderas:
npx email-agent-mcp configure \
--provider gmail \
--mailbox personal \
--client-id YOUR_GOOGLE_CLIENT_ID \
--client-secret YOUR_GOOGLE_CLIENT_SECRET
O establece las dos variables de entorno con espacio de nombres y omite las banderas:
export AGENT_EMAIL_GMAIL_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
export AGENT_EMAIL_GMAIL_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
npx email-agent-mcp configure --provider gmail --mailbox personal
Ambas mitades son requeridas. Proporcionar solo una sale con un error en lugar de volver silenciosamente al cliente por defecto. No proporcionar ninguna selecciona el cliente OAuth por defecto.
Tu navegador abre la pantalla de consentimiento de Google, el CLI captura la devolución de llamada en
loopback, intercambia el código con PKCE y escribe el buzón en
~/.email-agent-mcp/tokens/<safe-key>.json con "source": "byok" junto a
tu clientId, clientSecret y el refreshToken resultante. Los refrescos de
token luego van directamente al endpoint de tokens de Google; no hay broker involucrado.
Volver a ejecutar configure para un buzón que ya está guardado reutiliza las credenciales
guardadas, por lo que solo pasas las banderas una vez. Pasar --client-id y
--client-secret para un buzón previamente configurado contra el cliente
por defecto lo migra a BYOK.
BYOK: evita que la concesión expire después de 7 días
Google caduca los tokens de actualización después de 7 días mientras tu aplicación OAuth esté en estado Testing (Pruebas), lo que se manifiesta como un aviso de re-autenticación aproximadamente una vez por semana. email-agent-mcp status advierte cuando un buzón se acerca a esa ventana. Publicar tu aplicación (Pantalla de consentimiento de OAuth → Publicar aplicación) elimina la caducidad de 7 días. Dado que eres dueño de la aplicación y eres su único usuario, el límite de 100 usuarios de prueba no aplica para ti de todos modos.
Desconectar Gmail y revocar el acceso
Desconectar tiene dos partes independientes:
- Detén cualquier proceso
email-agent-mcpen ejecución. En Finder, abre~/.email-agent-mcp/tokens/, identifica el único archivo JSON correspondiente al buzón que deseas desconectar y mueve ese archivo exacto a la Papelera. No abras, pegues ni compartas su contenido, y no elimines todo el directorio~/.email-agent-mcpsi hay otros buzones configurados. Si configurasteEMAIL_AGENT_MCP_HOME, usa la carpetatokens/de ese directorio en su lugar. - Abre https://myaccount.google.com/connections, selecciona Email Agent MCP (o el nombre de tu aplicación BYOK) y elimina su acceso.
Eliminar el archivo local evita que esta instalación use la credencial guardada. Eliminar la conexión de la cuenta de Google revoca la concesión en Google. Ejecuta email-agent-mcp status después para confirmar que el buzón ya no está configurado.
Valores de seguridad predeterminados
Agent Email incluye valores restrictivos predeterminados que puedes flexibilizar según sea necesario:
- Lista de permitidos de envío: vacía por defecto — los agentes no pueden enviar correos hasta que agregues destinatarios
- Lista de permitidos de recepción: acepta todo por defecto — controla qué remitentes activan el vigilante
- Eliminación deshabilitada: los agentes no pueden eliminar correos por defecto. Deben cumplirse dos condiciones:
- El operador establece
AGENT_EMAIL_DELETE_ENABLED=trueen el entorno del proceso email-agent-mcp (yAGENT_EMAIL_HARD_DELETE_ENABLED=truepara eliminación permanente). Se requiere reinicio después del cambio. - El llamador pasa
user_explicitly_requested_deletion: trueen la llamada a la herramienta.
- El operador establece
- Sanitización de errores: las claves API, rutas de archivos y trazas de pila se redactan de las respuestas de error
- Aislamiento de archivos de cuerpo: sin recorrido
../, sin enlaces simbólicos, detección binaria - Protección contra envíos duplicados: activada por defecto — un envío, respuesta o borrador idéntico repetido dentro de 15 minutos se rechaza en lugar de entregarse dos veces
Protección contra envíos duplicados
Los endpoints de envío de los proveedores (Graph POST /sendMail, Gmail users.messages.send) no aceptan clave de idempotencia, por lo que Agent Email despacha cada entrega exactamente una vez y nunca reintenta automáticamente. Eso evita que la biblioteca duplique un mensaje. No evita que el llamador lo haga: un agente cuyo resultado de herramienta se perdió — contexto compactado, transporte caído, supervisor reiniciando el turno, o una persona reintentando algo que parecía atascado — reproduce la misma instrucción aprobada, y la segunda llamada se ve exactamente como la primera.
send_email, reply_to_email y send_draft por lo tanto registran cada despacho en un registro en memoria, claveado por un resumen de las entradas efectivas de la acción — buzón, destinatarios, asunto, cuerpo renderizado, contenido de adjuntos y el ID del padre de respuesta o borrador. (Entradas efectivas, no bytes finales en el cable: un proveedor puede transformar un mensaje en el camino, y Graph por ejemplo trunca un asunto a 255 caracteres, por lo que dos entradas diferentes pueden salir como correo idéntico. La protección detecta una llamada de herramienta reproducida, que es lo que realmente ocurre.) Una repetición dentro de la ventana se rechaza antes de que se despache la entrega:
| Código | Significado | Qué hacer |
|---|---|---|
DUPLICATE_SEND_IN_FLIGHT | Una entrega idéntica aún no ha regresado | Espera la primera llamada |
DUPLICATE_SEND_BLOCKED | Una entrega idéntica ya tuvo éxito | Detente — la respuesta lleva el messageId original |
DUPLICATE_SEND_UNRESOLVED | Una entrega idéntica terminó de forma ambigua | Revisa Elementos enviados antes de reenviar |
Un fallo que demuestra que nada se entregó — cualquier código derivado de 4xx, un 429 o un fallo de conexión — libera el registro inmediatamente, por lo que reenviar después de un rechazo nunca se bloquea. Cualquier otra cosa, incluido un código de un proveedor que no hemos visto, se mantiene como no resuelto: un mensaje retenido incorrectamente cuesta un reenvío bloqueado; uno liberado incorrectamente cuesta un duplicado en la bandeja de entrada de alguien.
Para enviar el mismo mensaje de nuevo a propósito, pasa allow_duplicate: true. Es un bypass incondicional por solicitud — una reproducción que también lleva la bandera se despacha de nuevo, porque la bandera afirma que una persona decidió este envío. El intento forzado se registra junto al anterior en lugar de reemplazarlo, por lo que si se rechaza la anulación, la entrega original aún rechaza una reproducción ordinaria.
Los registros están limitados a un buzón: mailbox cuando el llamador proporciona uno, de lo contrario la instancia del proveedor. Los operadores pueden cambiar la ventana con AGENT_EMAIL_DUPLICATE_SEND_WINDOW_MS (por defecto 900000); 0 desactiva la protección por completo.
El registro vive en el proceso del servidor y en memoria. Cierra la ventana de reproducción que realmente ocurre — un reintento dentro de un servidor en ejecución — y no sobrevive al reinicio de ese servidor.
Paquetes
| Paquete | Descripción |
|---|---|
@usejunior/email-core | Acciones de correo principales, motor de contenido, seguridad e interfaces de proveedor |
@usejunior/email-mcp | Adaptador de servidor MCP, CLI y vigilante |
@usejunior/provider-microsoft | Proveedor de correo de Microsoft Graph API |
@usejunior/provider-gmail | Proveedor de correo de Gmail API |
email-agent-mcp | Envoltorio de distribución (npx email-agent-mcp) |
@usejunior/email-agent-mcp | Envoltorio de compatibilidad, publicado en sincronía; usa email-agent-mcp para nuevas instalaciones |
Señales de calidad y confianza
- CI se ejecuta en cada pull request y push a main (lint, typecheck, pruebas en Node 20 + 22)
- Escaneo de seguridad CodeQL y Semgrep
- Cobertura publicada en Codecov
- Cumplimiento de trazabilidad OpenSpec mediante
npm run check:spec-coverage - Más de 300 pruebas en el conjunto
- Mantenedor: Steven Obiajulu
Arquitectura
email-agent-mcp/
├── packages/
│ ├── email-core Core actions, content engine, security
│ ├── email-mcp MCP server adapter, CLI, watcher
│ ├── provider-microsoft Microsoft Graph provider
│ ├── provider-gmail Gmail API provider
│ └── email-agent-mcp Distribution wrapper (npx entry point)
├── openspec/ Spec-driven development
└── scripts/ CI and validation scripts
Publicación
Publicación impulsada por etiquetas mediante GitHub Actions con publicación confiable npm OIDC. Los 6 paquetes se publican en orden de dependencia con --provenance, luego server.json se publica en el Registro MCP oficial con mcp-publisher.
Preguntas frecuentes
¿Funciona esto con Claude Code?
Sí. Ejecuta npx email-agent-mcp para iniciar el servidor MCP, luego configúralo en la configuración de Claude Code.
¿Pueden los agentes enviar correos sin mi permiso?
No. La lista de permitidos de envío está vacía por defecto. Los agentes no pueden enviar ningún correo hasta que configures explícitamente los destinatarios permitidos.
¿Esto almacena mis credenciales de correo?
Los tokens OAuth son gestionados por MSAL (Microsoft) y se almacenan en tu llavero del sistema operativo o archivos de configuración locales bajo ~/.email-agent-mcp/. Agent Email nunca almacena contraseñas en texto plano.
¿Puedo conectar múltiples buzones?
Sí. Puedes configurar Microsoft 365 y Gmail simultáneamente. Las acciones de lectura usan tu buzón principal por defecto; las acciones de escritura requieren especificar un buzón cuando hay múltiples configurados.
El CLI de OpenClaw rechaza mi configuración con "Unrecognized key: mcp"
El CLI de OpenClaw y la aplicación macOS pueden ser versiones diferentes. La aplicación (que ejecuta la puerta de enlace) puede admitir claves de configuración que el CLI aún no reconoce. Actualiza el CLI: cd ~/Projects/NemoClaw && npm install openclaw@latest. Alternativamente, reinicia la puerta de enlace directamente: launchctl kickstart -k gui/501/ai.openclaw.gateway.
El vigilante inicia pero encuentra cero buzones
Las credenciales de buzón se almacenan en ~/.email-agent-mcp/tokens/. Si este directorio está vacío, ejecuta npx email-agent-mcp o npm run dev:configure para autenticarte mediante OAuth. El vigilante saldrá sin buzones que consultar hasta que al menos uno esté configurado.
OpenClaw dice "Demo mode -- run email-agent-mcp configure to connect"
El servidor MCP está en ejecución pero no tiene credenciales de buzón reales. Ejecuta npx email-agent-mcp para completar la configuración OAuth interactiva, luego reinicia la puerta de enlace de OpenClaw para que el servidor MCP se reconecte con tokens válidos.
Token caducado después de una semana aunque acabo de autenticarme
Los tokens de actualización de Microsoft suelen durar 90 días, pero tu inquilino de Azure AD puede imponer duraciones más cortas. El código usa MSAL con persistencia en el llavero del sistema operativo (@azure/identity-cache-persistence), que maneja la renovación silenciosa de tokens automáticamente. Si MSAL informa interaction_required o invalid_grant, vuelve a ejecutar npx email-agent-mcp para re-autenticarte. Causas comunes: políticas de acceso condicional, requisitos de re-verificación MFA o políticas de duración de tokens configuradas por el administrador.
El bot de Telegram de OpenClaw recibe mensajes pero no responde
Verifica que el canal de Telegram esté saludable con openclaw status. Si el canal muestra OK pero no llegan respuestas, verifica que: (1) tu ID de usuario de Telegram esté en channels.telegram.allowFrom en openclaw.json, (2) exista un enlace que coincida con channel: "telegram", y (3) la puerta de enlace se haya reiniciado después de los cambios de configuración. Para bots de un solo propietario, usa dmPolicy: "allowlist" con IDs explícitos de allowFrom en lugar de depender de aprobaciones de emparejamiento.
Desarrollo
npm ci
npm run build
npm run lint --workspaces --if-present
npm run test:run
npm run check:spec-coverage
Ver también
- Safe DOCX Suite — edición quirúrgica de documentos de Word con agentes de codificación
- Open Agreements — completa plantillas legales estándar con agentes de codificación
Privacidad
Agent Email se ejecuta completamente en tu máquina local. Las credenciales de correo se almacenan en tu llavero del sistema operativo (MSAL) y archivos de configuración locales. Agent Email no envía contenido de correo a servidores externos por sí mismo.