mcp-signal
A local MCP server for Signal that reads, searches and sends messages from your machine over stdio or optional HTTP.
List direct and group chats from the local Signal Desktop database, filterable by name or number.
Read messages from a chat, newest-first, with date filters and pagination.
Search message bodies for keywords within one chat or across all chats.
List groups with group IDs, members and admins (via
signal-cli, with desktop fallback).Rank chats by recent activity, last reply and unanswered inbound counts.
Decrypt locally stored attachments and get the decrypted file's path.
Send text messages to a phone number, group ID or exact chat name (requires
signal-cli), rate-limited.Check readiness of the desktop DB,
signal-cliand account withget_status.Report
signal-clilink state and surface the live pairing QR withpairing_status.Use prompts
summarise_chatanddiscover_topics.
Provides tools to read, search, and send messages via Signal, listing chats and groups from the local Signal Desktop database and sending outbound messages through signal-cli.
mcp-signal
A local MCP server for Signal. It reads and searches your Signal Desktop history via signal-export and sends messages via signal-cli. Everything runs on your machine over stdio, with an optional HTTP mode.
Mixed backend. Reads come from the local Signal Desktop database. Sends need
signal-cliinstalled and linked separately; without it, read and search still work.
Setup
Prerequisites: Python 3.12+, uv, Signal Desktop with local history, and (for sends) signal-cli.
git clone https://github.com/Sealjay/mcp-signal.git
cd mcp-signal
uv sync
brew install signal-cli # optional, sends onlyTested with signal-cli 0.14.8; 0.14.9 changes nothing this server uses.
Configure sends
Put machine-local config in .env.local at the repo root (gitignored):
echo 'SIGNAL_ACCOUNT="+441234567890"' > .env.localVariable | Purpose |
| Linked |
| Override the |
| Override the Signal Desktop data directory |
| Password or raw key for an encrypted desktop DB |
|
|
| Bearer token for HTTP mode (all routes except |
|
|
Shell variables override .env.local. .env.local only loads SIGNAL_* keys, so set MCP_* in the shell.
Link signal-cli (first run)
signal-cli link -n "signal-mcp" # scan the QR: Signal app > Settings > Linked Devices
signal-cli listAccounts # should match SIGNAL_ACCOUNT
uv run signal-mcp smokesignal-cli keeps its linked-device state in its own data directory (usually ~/.local/share/signal-cli/data), outside this repo.
HTTP mode (optional)
uv run signal-mcp serve --http --listen-addr 0.0.0.0:8765Streamable HTTP at /mcp. It is stateless (no Mcp-Session-Id, and GET /mcp returns 405 because there is no standalone SSE stream) and serves MCP 2026-07-28 as well as older revisions.
OpenAI Secure MCP Tunnel (optional)
.tunnel-client/ runs the stdio server behind OpenAI's outbound tunnel client for ChatGPT. See .tunnel-client/README.md. Copy .env.example to .env.local (gitignored) and keep the tunnel key as a 1Password op:// reference; never commit real IDs or keys.
Related MCP server: Signal MCP
MCP client configuration
Every client launches the server over stdio with the same block. Use an absolute repo path.
{
"mcpServers": {
"signal": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/mcp-signal", "signal-mcp", "serve"]
}
}
}Client | Where |
Claude Code |
|
Claude Desktop |
|
Cursor |
|
Restart the client afterwards. If a GUI app reports spawn uv ENOENT, set command to the absolute path from which uv (Homebrew: /opt/homebrew/bin/uv).
Tools
Tool | Purpose |
| List direct and group chats from Signal Desktop |
| Read messages from a specific chat |
| Search messages within one chat or across all chats |
| List groups from |
| Rank chats by recent activity, with last-message/last-reply dates and unanswered-inbound counts |
| Decrypt a locally stored attachment and return the decrypted file's path |
| Send text to a phone number, group ID or exact chat name. |
| Show desktop DB / |
| Report |
Prompts: summarise_chat and discover_topics.
Architecture
Component | Description |
MCP server | Python/FastMCP 4, stdio or stateless streamable HTTP |
Read path |
|
Send path |
|
State | No cache; reads Signal Desktop data directly |
Privacy and limitations
No cloud relay; data stays on your machine. HTTP mode is opt-in, and bearer auth applies only when
MCP_AUTH_TOKENis set (the default bind is0.0.0.0).Prompt injection: this server has the lethal trifecta. A malicious incoming message could try to make an agent exfiltrate others, so review outbound actions before approving them.
Text-only sends; no real-time notifications; one account per instance.
Group sends need
signal-cli; the desktop DB alone cannot address groups safely.Read metadata comes from Signal Desktop, not
signal-cli. Voice notes are flaggedis_voice_notefrom Desktop's attachment flag.
Report vulnerabilities via SECURITY.md.
Troubleshooting
signal-clinot found: put it onPATHor setSIGNAL_CLI_PATH.Reads work, sends fail:
signal-cliis not linked orSIGNAL_ACCOUNTis unset. Checksignal-cli listAccounts.[403] Authorization failed: the linked device was removed or expired. Re-link withsignal-cli link -n "signal-mcp".signal-cli linkhangs: don't pass-a/--accounttolink.Native
signal-clicrashes after a successful RPC: the server preserves the matching success response and logs a shutdown warning. Do not retry a confirmed send; this can duplicate a message.No messages returned: confirm Signal Desktop is installed and has history.
Development
uv sync
uv run ruff check .
uv run pytest
uv run signal-mcp smokeCI runs the same on every pull request. See CLAUDE.md for structure and constraints.
Licence
MIT. See LICENSE.
Available Tools
9 toolschat_activityChat ActivityARead-onlyIdempotent
List Signal chats ranked by recent activity, showing last message date, last reply date, and count of unanswered inbound messages.
Read-only with no side effects. Use this to identify chats that need a response. Use list_chats instead for a general chat directory with message previews.
Inputs: limit. Returns: result
decrypt_attachmentDecrypt AttachmentA
Decrypt a locally stored Signal attachment and return the path to the decrypted file.
The encrypted_path and local_key values come from attachment metadata in read_messages or search_messages results. Read-only on Signal data; writes a decrypted copy to a temporary directory (the temp file is not auto-cleaned). Returns the absolute path to the decrypted file as a string on success, or a string starting with 'Error:' on failure (missing file, wrong key length, or HMAC mismatch). Use this after reading messages that contain attachments.
Inputs: local_key (required), encrypted_path (required). Returns: result
get_statusGet StatusARead-onlyIdempotent
Check whether the Signal MCP server can read local messages and send outbound messages.
Returns boolean fields: source_dir_exists, signal_cli_available, signal_account_configured, read_available, send_available. Read-only with no side effects. Call this before read or send operations to verify configuration; call pairing_status instead to check first-run device-linking state.
Inputs: no named parameters. Returns: object
list_chatsList ChatsARead-onlyIdempotent
List direct and group Signal chats from the local desktop database, sorted by most recent message.
Each result includes name, phone number, message count, and a preview of the last message. Read-only with no side effects. Use this to discover exact chat names before calling read_messages or search_messages. Use list_groups instead when you need group_id values for send_message.
Inputs: limit, query. Returns: result
list_groupsList GroupsARead-onlyIdempotent
List Signal groups with group_id, name, description, members, and admin lists, sorted alphabetically.
Read-only with no side effects. Queries signal-cli when available; falls back to the local desktop database (without group_id). Use this to obtain group_id values needed by send_message. Use list_chats instead for a combined view of both direct and group chats.
Inputs: limit, query. Returns: result
pairing_statusPairing StatusA
Report the Signal device-linking setup state for first-run pairing, so a client can surface the live link QR.
Returns a setup_state envelope: state 'ready' with the linked account once signal-cli is linked; 'awaiting_qr' with a 'qr_payload' sgnl://linkdevice URI to scan in Signal (Settings → Linked devices → Link new device) while a link is in progress (no payload yet on the first call, until the URI is captured); or 'error' when signal-cli is unavailable. Read-only from the caller's view, but the first call on an unlinked device starts a background signal-cli link process as a side effect. Poll this to drive a pairing UI; call get_status instead to check read/send availability once linked.
Inputs: no named parameters. Returns: object
read_messagesRead MessagesARead-onlyIdempotent
Read messages from a single Signal chat, returned newest-first.
Each message includes sender, date, body text, reactions, and attachment metadata. Read-only with no side effects. Requires an exact chat name from list_chats. Use search_messages instead to find messages by keyword across chats.
Inputs: chat_name (required), after, limit, before, +1 more. Returns: result
search_messagesSearch MessagesARead-onlyIdempotent
Search Signal message bodies for a keyword, within one chat or across all chats, returned newest-first.
Each result includes sender, date, chat name, body text, reactions, and attachment metadata. Read-only with no side effects. Use this to find messages by content. Use read_messages instead to browse a specific chat chronologically.
Inputs: query (required), limit, chat_name. Returns: result
send_messageSend MessageA
Send a text message via Signal to a direct recipient or group.
This is a write operation that delivers a real message through signal-cli. Sends are not reversible from this server — Signal supports user-initiated message deletion only via the official clients. Exactly one of phone_number, group_id, or chat_name must be supplied; providing zero or more than one raises an error. Rate-limited to 1 message per recipient per second and 10 messages per 60-second window globally. Requires signal-cli and SIGNAL_ACCOUNT to be configured — call get_status first to verify send_available is true. Returns target_type, target identifier, and timestamp on success.
Inputs: message (required), group_id, chat_name, phone_number. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Each tool has a narrow, distinct purpose, and descriptions explicitly route the agent elsewhere when overlap exists (e.g. chat_activity vs list_chats, read_messages vs search_messages, get_status vs pairing_status). The only natural overlap is list_chats also covering groups, but list_groups is clearly framed as the source of group_id values. An agent should never misselect here.
Most tools follow a clean verb_noun pattern (list_chats, read_messages, search_messages, send_message, decrypt_attachment). Two names deviate by dropping the verb (chat_activity, pairing_status), where get_ is implied by analogy with get_status. Minor and still readable.
Nine tools is well-scoped for a Signal read/send bridge: discovery, reading, search, activity triage, attachment decryption, diagnostic/pairing state, and one write path. No obviously redundant or filler tools.
Core lifecycle is covered: discover chats/groups, read and search messages, decrypt attachments, check configuration/pairing, and send text messages. Gaps remain on the write side — no sending attachments, no group creation/management, no message deletion — though these are reasonable scope limits rather than dead ends for the stated read-focused purpose.
Maintenance
Related MCP Connectors
Your own WhatsApp as an MCP server: read, search and send from any MCP client.
MCP connector for iMessage & Contacts via a local Mac agent + Vercel relay
Unified messaging MCP server: WhatsApp, Instagram, Telegram, SMS, Messenger & email support inbox
Drive WhatsApp from any MCP client: pair devices, send text and media, manage contacts and groups.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceMCP Server for retrieving Signal messages using signal-export logic.8-
- AlicenseAqualityBmaintenanceThe most complete Signal MCP server and CLI.81168 PyPI12MIT
- FlicenseNot gradedqualityDmaintenanceMCP server for Signal via signal-cli that enables sending and receiving messages, managing contacts and groups, and reacting over stdio.8 npm-
- AlicenseNot gradedqualityBmaintenanceMCP server for a local-first messaging workspace that integrates Google Messages, WhatsApp, and Signal. It enables reading, sending, searching messages, and managing conversations through MCP tools.72-