Skip to main content
Glama
mcp-signal

mcp-signal

mcp-signal

Python uv MCP License: MIT GitHub issues GitHub stars Sealjay/mcp-signal MCP server

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-cli installed 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 only

Tested 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.local

Variable

Purpose

SIGNAL_ACCOUNT

Linked signal-cli account used for sends

SIGNAL_CLI_PATH

Override the signal-cli binary path

SIGNAL_DATA_DIR

Override the Signal Desktop data directory

SIGNAL_DB_PASSWORD / SIGNAL_DB_KEY

Password or raw key for an encrypted desktop DB

SIGNAL_JSONRPC_TIMEOUT_SECONDS

signal-cli JSON-RPC timeout. Default 30, max 300

MCP_AUTH_TOKEN

Bearer token for HTTP mode (all routes except /health). Unset means no auth

MCP_LISTEN_ADDR

host:port for HTTP mode; setting it enables HTTP mode. Default 0.0.0.0:8765

Shell variables override .env.local. .env.local only loads SIGNAL_* keys, so set MCP_* in the shell.

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 smoke

signal-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:8765

Streamable 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 mcp add --transport stdio signal --scope user -- uv run --directory /absolute/path/to/mcp-signal signal-mcp serve, or the block above in .mcp.json

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json

Cursor

~/.cursor/mcp.json

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_chats

List direct and group chats from Signal Desktop

read_messages

Read messages from a specific chat

search_messages

Search messages within one chat or across all chats

list_groups

List groups from signal-cli, including group IDs

chat_activity

Rank chats by recent activity, with last-message/last-reply dates and unanswered-inbound counts

decrypt_attachment

Decrypt a locally stored attachment and return the decrypted file's path

send_message

Send text to a phone number, group ID or exact chat name. chat_name resolves to the contact's service ID when their number is hidden

get_status

Show desktop DB / signal-cli / account readiness

pairing_status

Report signal-cli link state and surface the live link QR

Prompts: summarise_chat and discover_topics.

Architecture

Component

Description

MCP server

Python/FastMCP 4, stdio or stateless streamable HTTP

Read path

signal-export against the local Signal Desktop database

Send path

signal-cli -a ACCOUNT jsonRpc, launched on demand

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_TOKEN is set (the default bind is 0.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 flagged is_voice_note from Desktop's attachment flag.

Report vulnerabilities via SECURITY.md.

Troubleshooting

  • signal-cli not found: put it on PATH or set SIGNAL_CLI_PATH.

  • Reads work, sends fail: signal-cli is not linked or SIGNAL_ACCOUNT is unset. Check signal-cli listAccounts.

  • [403] Authorization failed: the linked device was removed or expired. Re-link with signal-cli link -n "signal-mcp".

  • signal-cli link hangs: don't pass -a/--account to link.

  • Native signal-cli crashes 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 smoke

CI runs the same on every pull request. See CLAUDE.md for structure and constraints.

Licence

MIT. See LICENSE.

Available Tools

9 tools
chat_activityChat ActivityA
Read-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 StatusA
Read-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 ChatsA
Read-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 GroupsA
Read-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 MessagesA
Read-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 MessagesA
Read-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

    A4.5/5.0

    Scored across 9 tools

    Disambiguation5/5

    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.

    Naming Consistency4/5

    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.

    Tool Count5/5

    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.

    Completeness4/5

    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

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      MCP server for Signal via signal-cli that enables sending and receiving messages, managing contacts and groups, and reacting over stdio.
      8 npm
      -
    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP 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
      -