Skip to main content
Glama
JohnGilligan2

notify-mcp

{"type": "text"}# notify-mcp

Correo electrónico y SMS de entrega a uno mismo para el personal de Example Corp, como servidor MCP remoto autenticado con Entra. Esta es la ruta de envío para el despliegue de jefe de personal de claude.ai: los agentes pueden enviar por correo o mensaje de texto el propio contenido del usuario autenticado, y estructuralmente nada más.

El invariante (por qué existe este servidor)

Los agentes que leen contenido no confiable (correo entrante, tickets, chats) nunca deben tener una capacidad de envío sin restricciones: una inyección de prompt podría convertirlo en un canal de exfiltración. Este servidor lo resuelve por construcción:

  • Los destinatarios no son parámetros de la herramienta. El correo se envía al UPN del llamante, tomado del token de Entra validado. El SMS se envía al móvil registrado del llamante. Un modelo completamente comprometido puede cambiar qué se envía, nunca dónde.

  • El remitente (FROM) está bloqueado a <agent>approver@example.com — el servidor no puede enviar como humano, por lo que no puede suplantar a nadie.

  • Las claves de Resend e Inteliquent viven solo en este contenedor. Los usuarios nunca ven una clave; se autentican con su propio inicio de sesión de M365.

  • Presupuestos por hora por usuario + una línea de registro de auditoría por envío.

Related MCP server: Mailbuttons MCP Server

Herramientas

Tool

Qué hace

whoami

Devuelve el correo electrónico y el móvil enmascarado a los que se enviarán los envíos. Prueba la conexión con esto primero.

send_email_to_me(subject, body_markdown, agent_name?)

Envía un correo al llamante. agent_name personaliza el remitente (alerts → approver@example.com).

send_sms_to_me(message)

Envía un mensaje de texto al móvil del llamante desde el DID de la empresa 13105550100. ≤640 caracteres.

register_my_mobile(mobile_number)

Inicia la configuración de SMS: envía un código de 6 dígitos al número.

confirm_my_mobile(code)

Completa la configuración: verifica el código, guarda el número y envía un aviso de cambio por correo al usuario.

Cómo sabe el servidor quién eres

Claude → inicio de sesión de Entra (el propio inicio de sesión del usuario) → cada llamada de herramienta lleva un JWT de portador → AzureJWTVerifier valida firma/emisor/audiencia → la reclamación preferred_username = el destinatario. Sin variables de entorno por usuario, sin configuración; la identidad es criptográfica.

Los números de móvil no están en el token, por lo que el SMS utiliza auto-registro verificado (diseñado para inquilinos que no completan el campo mobilePhone de Entra — el nuestro no lo hace):

  1. El usuario (normalmente durante la entrevista de configuración del CoS): "registra mi móvil 310-555-1212" → el servidor envía un código de 6 dígitos a ese número.

  2. El usuario escribe el código → confirm_my_mobile → el número se guarda en /data/sms_directory.json vinculado a su UPN + se envía un correo de confirmación.

  3. Los cambios quedan entonces bloqueados (NOTIFY_ALLOW_MOBILE_CHANGE=false): un número confirmado solo puede ser cambiado por el administrador editando el registro. La verificación del código demuestra la posesión; el bloqueo de cambios + el aviso por correo eliminan el ataque de re-vinculación por inyección (contenido hostil que redirige los mensajes a otro lugar).

Orden de resolución: archivo de registro (las entradas manuales son cadenas simples, las entradas auto-registradas son objetos) → mobilePhone opcional de Entra a través de Graph (NOTIFY_GRAPH_LOOKUP_ENABLED=true + -GrantGraphUserRead; innecesario si se usa el auto-registro).

Duración de la sesión del conector (la solución a la desconexión)

Tres cosas evitan que el conector muestre "desconectado":

  1. offline_access se anuncia en los metadatos del recurso protegido (NOTIFY_ADVERTISE_OFFLINE_ACCESS=true), por lo que el inicio de sesión de Claude obtiene un token de actualización y renueva silenciosamente los tokens de acceso de ~60–90 min.

  2. stateless_http=True — las sesiones sobreviven a los redespliegues del contenedor.

  3. El host proxy NPM debe tener las anulaciones de tiempo de espera del playbook (ver PORTAINER_DEPLOY.md) para que las transmisiones inactivas no se corten a los 60 s.

También verifica que ninguna política de acceso condicional de frecuencia de inicio de sesión cubra esta aplicación. Prueba de aceptación: conéctate, espera 2+ horas inactivo, redespliega el contenedor y luego llama a whoami — no debería aparecer ninguna solicitud de re-autenticación.

Configuración (orden del playbook)

  1. scripts/setup_entra_app.ps1 -TenantId <tenant> (opcionalmente -GrantGraphUserRead) — imprime los valores de entorno del servidor + el secreto del conector (va en la configuración avanzada del conector de Claude, nunca en el servidor).

  2. Despliega mediante la pila Git de Portainer — PORTAINER_DEPLOY.md. Puerto de host sugerido 8093 (prueba primero; registrado en el registro del playbook).

  3. Host proxy NPM notify-mcp.example.com → el contenedor; tiempos de espera del playbook + lista de permitidos de IP de Anthropic; certificado LE antes de habilitar la lista de permitidos.

  4. Añádelo como conector personalizado de la organización en claude.ai; los usuarios se conectan una vez con su propio inicio de sesión de M365.

Pruebas de aceptación

curl http://<docker-host>:8093/healthz                       # {"status":"ok",...}
curl -s https://notify-mcp.example.com/.well-known/oauth-protected-resource/mcp | jq
#   scopes_supported MUST include the full https://.../mcp/access_as_user scope
#   AND "offline_access"
curl -i https://notify-mcp.example.com/mcp                  # 401 + WWW-Authenticate

Luego, en Claude, como miembro del grupo:

  1. whoami → tu propio correo electrónico, móvil "en registro (…1234)" o un fallo claro.

  2. send_email_to_me → llega a TU bandeja de entrada desde notify_ai@.

  3. Pide al modelo que "envíe esto a <alguien más>" → debe negarse / no tener forma de cumplirlo. Si esto falla es un P1 — no desplegar.

  4. register_my_mobile con tu número → llega el código → confirm_my_mobile → llega el correo de confirmación → send_sms_to_me funciona. Luego intenta registrar un número DIFERENTE → debe ser rechazado (bloqueo de cambios).

  5. La prueba de 2 horas inactivo + redespliegue anterior.

  6. Un no miembro se conecta → rechazado en el inicio de sesión (AADSTS50105).

Desarrollo local

pip install -r requirements.txt
MCP_AUTH_ENABLED=false python -m notify_mcp   # Inspector only — tools will
                                              # refuse to send without a token
                                              # identity; NEVER expose auth-off

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to send and receive email with enforced security policies, scoped mailboxes, and human approval for external sending.
    264 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Sovereign autonomous mailbox and identity layer for AI agents, providing persistent email identities, object-level security, OTP/2FA capture, link safety analysis, and event-driven email handling via MCP.
    3 npm
    3
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to send, receive, and reply to email through dedicated thread-aware mailboxes, with sandbox testing and compatibility across MCP clients.
    9
    38 npm
    MIT