Messaging Channels
Reactive Agents can send and receive messages on Signal and Telegram using MCP servers. No custom adapter code needed — the framework’s built-in .withMCP() and .withGateway() capabilities handle everything.
How It Works
Section titled “How It Works”Gateway heartbeat fires every N seconds → Agent calls receive_message MCP tool → Processes new messages (with guardrails) → Responds via send_message MCP toolThe Signal MCP server is a custom TypeScript implementation (docker/signal-mcp/server/) that spawns signal-cli in persistent jsonRpc mode — a single JVM boot with instant command execution (no cold starts per message). The Telegram MCP server uses chigwell/telegram-mcp. Both run in hardened Docker containers. The gateway heartbeat polls for messages; the agent uses MCP tools to read and respond.
Signal Setup
Section titled “Signal Setup”1. Build the Docker Image
Section titled “1. Build the Docker Image”docker build -t signal-mcp:local docker/signal-mcp/2. Register a Phone Number
Section titled “2. Register a Phone Number”Signal requires a real phone number and a captcha. Run the registration helper:
./scripts/signal-register.sh +1234567890This will:
- Ask you to solve a captcha at https://signalcaptchas.org/registration/generate
- Send a verification code to your phone
- Store encrypted auth keys in
./signal-data/
The data directory is volume-mounted into Docker on subsequent runs.
3. Configure the Agent
Section titled “3. Configure the Agent”const agent = await ReactiveAgents.create() .withName("signal-agent") .withProvider("anthropic") .withReasoning() .withTools() .withGuardrails() .withKillSwitch() .withMCP([{ name: "signal", transport: "stdio", command: "docker", args: [ "run", "-i", "--rm", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--memory", "512m", "-v", "./signal-data:/data:rw", "-e", `SIGNAL_USER_ID=${process.env.SIGNAL_PHONE_NUMBER}`, "signal-mcp:local", ], }]) .withGateway({ heartbeat: { intervalMs: 15_000, policy: "adaptive", instruction: "Check Signal for new messages using signal/receive_message. Respond to any that need attention.", }, policies: { dailyTokenBudget: 50_000, maxActionsPerHour: 30 }, }) .build();Available Signal Tools
Section titled “Available Signal Tools”| Tool | Description |
|---|---|
signal/send_message_to_user | Send a direct message to a Signal user |
signal/send_message_to_group | Send a message to a Signal group |
signal/receive_message | Receive pending messages (with timeout) |
signal/list_groups | List all Signal groups the account belongs to |
Telegram Setup
Section titled “Telegram Setup”1. Generate a Session String
Section titled “1. Generate a Session String”Get API credentials from my.telegram.org/apps, then run:
./scripts/telegram-session.shSave the output to .env.telegram:
TELEGRAM_API_ID=12345678TELEGRAM_API_HASH=abc123...TELEGRAM_SESSION_STRING=1BVtsO...2. Configure the Agent
Section titled “2. Configure the Agent”const agent = await ReactiveAgents.create() .withName("telegram-agent") .withProvider("anthropic") .withReasoning() .withTools() .withGuardrails() .withKillSwitch() .withMCP([{ name: "telegram", transport: "stdio", command: "docker", args: [ "run", "-i", "--rm", "--cap-drop", "ALL", "--no-new-privileges", "--memory", "128m", "--user", "1000:1000", "--env-file", ".env.telegram", "ghcr.io/reactive-agents/telegram-mcp", ], }]) .withGateway({ heartbeat: { intervalMs: 15_000, policy: "adaptive", instruction: "Check Telegram for unread messages using telegram/get_chats. Respond to conversations that need attention.", }, policies: { dailyTokenBudget: 50_000, maxActionsPerHour: 30 }, }) .build();Available Telegram Tools
Section titled “Available Telegram Tools”The Telegram MCP server exposes 70+ tools. Key ones for messaging:
| Tool | Description |
|---|---|
telegram/send_message | Send a text message to a chat |
telegram/get_chats | List chats with unread counts |
telegram/search_messages | Search messages in a chat |
telegram/send_file | Send a file or document |
telegram/forward_message | Forward a message between chats |
Security Best Practices
Section titled “Security Best Practices”Container Hardening
Section titled “Container Hardening”All Docker flags in the examples enforce strict isolation:
| Flag | Purpose |
|---|---|
--cap-drop ALL | Remove all Linux capabilities |
--no-new-privileges | Prevent privilege escalation |
--memory 512m | Hard memory limit (Signal needs 512m for JVM) |
--pids-limit 30 | Prevent fork bombs |
--user 1000:1000 | Run as non-root |
--read-only | Immutable root filesystem |
Secret Management
Section titled “Secret Management”- Never pass secrets as MCP tool arguments — they’d appear in agent context
- Use
--env-filefor Telegram credentials - Use Docker volumes for Signal auth keys (
./signal-data/) - Add
.env.telegramandsignal-data/to.gitignore
Guardrails
Section titled “Guardrails”Always enable .withGuardrails() for messaging agents. Inbound messages from external users can contain prompt injection attempts. Guardrails check for injection, PII, and toxicity before the LLM processes the message.
Kill Switch
Section titled “Kill Switch”Always enable .withKillSwitch() for autonomous messaging agents. This provides:
agent.stop(reason)— graceful shutdown at next phase boundaryagent.terminate(reason)— immediate halt
Troubleshooting
Section titled “Troubleshooting”Signal registration fails
Section titled “Signal registration fails”- Ensure Docker is running
- Signal requires a CAPTCHA — see the registration script
- The Docker image requires glibc (not Alpine) for signal-cli’s native library
Telegram session expired
Section titled “Telegram session expired”- Re-run
./scripts/telegram-session.sh - Update
.env.telegramwith new session string
Agent not responding to messages
Section titled “Agent not responding to messages”- Check heartbeat interval (default: 15s)
- Verify daily token budget isn’t exhausted
- Check
ProactiveActionSuppressedevents for policy blocks - Ensure MCP containers are running:
docker ps