Signal Messaging (via signal-cli)

io.github.joshuawowk由 GitHub 帳號 joshuawowk 發布v1.0.1更新於 Oct 26, 2026

Send Signal messages and read replies through your own signal-cli JSON-RPC daemon (unofficial).

已驗證STDIO僅桌面效率與工作流溝通與協作

概覽

AI 產生的概覽

非官方 MCP 伺服器,透過自建的 signal-cli 守護程序傳送 Signal 訊息並讀取回覆,讓助理能通知你或向你提問。

可以做什麼
  • 向指定收件人傳送 Signal 訊息,可帶工作階段標記用於任務完成通知與進度更新
  • 傳送附選項的問題並等待回覆
  • 在執行不可逆操作前傳送授權請求,等待明確同意
  • 依工作階段、寄件者或時間篩選並輪詢回覆收件匣
  • 檢查傳送通道、接收通道與收件匣的健康狀態
適用情境
適合希望在本機執行的助理透過 Signal 主動聯繫你的情境:長任務完成時通知、遇到阻塞時提問,或在執行不可逆操作前請求核准。適合已用 signal-cli 註冊號碼的使用者。
執行需求
需要本機 Docker 容器(映像發佈於 ghcr.io),以及已執行且已註冊號碼的 signal-cli JSON-RPC 守護程序;本專案不負責註冊號碼或啟動守護程序。必填環境變數 SIGNAL_SENDER 與 SIGNAL_DEFAULT_RECIPIENT;SIGNAL_RPC_URL、SIGNAL_TCP_HOST、SIGNAL_TCP_PORT 用於指向守護程序。接收回覆需使用含訂閱程序的 Compose 部署;README 說明主要針對 Linux 主機網路。
安裝前請注意
signal-cli 的 HTTP 端點沒有驗證,不應暴露在可信任介面之外。伺服器會以你的號碼傳送真實 Signal 訊息並可請求授權,收件人或核准出錯會有實際影響。電話號碼保存在專案的 .env 與設定片段中。附件、表情回應與已讀回條會被忽略,僅傳送的 stdio 模式不會收集回覆。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Signal Messaging (via signal-cli),將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

signal-mcp

[Release] [License: MIT]

An unofficial MCP integration for Signal via signal-cli. It lets an MCP client send you Signal messages (task-done pings, blocking questions, permission requests) and read your replies.

Requirements: a running signal-cli JSON-RPC daemon (signal-cli daemon --http ..., plus --tcp ... if you want replies) and a Signal phone number already registered with signal-cli. This project does not register numbers, run the daemon, or talk to Signal's servers itself; it is only a client of your daemon. It is not affiliated with or endorsed by Signal Messenger or the Signal Foundation.

It consists of an MCP server (app/server.py) and an optional reply subscriber (app/subscriber.py) that fronts the existing signal-cli daemon without re-registering or bind-mounting its data directory.

Install

The image is published at ghcr.io/joshuawowk/signal-mcp and listed in the MCP Registry as io.github.joshuawowk/signal-mcp. Run with no arguments, the container starts the reply subscriber (the long-running mode used by the Compose deployment below); the MCP server is started explicitly.

MCP client over stdio (send-only)

bash
docker run -i --rm \  --add-host host.docker.internal:host-gateway \  -e SIGNAL_SENDER=+15555550100 \  -e SIGNAL_DEFAULT_RECIPIENT=+15555550101 \  -e SIGNAL_RPC_URL=http://host.docker.internal:8080/api/v1/rpc \  ghcr.io/joshuawowk/signal-mcp:latest python3 /app/server.py --stdio

Notes:

  • On Linux you can instead use --network host and drop the --add-host and SIGNAL_RPC_URL settings (the default 127.0.0.1:8080 then reaches your daemon).
  • With a bridge network the daemon must be reachable from the container. A daemon bound only to 127.0.0.1 is not. The signal-cli HTTP endpoint is unauthenticated: do not expose it beyond a trusted interface.
  • A standalone stdio container has no subscriber, so replies are not collected: signal_check_replies returns nothing and signal_health reports the receive path as down. For replies, use the Compose deployment below.

Compose deployment (send and receive replies)

See Setup.

Architecture

MCP client ──stdio──> docker exec ──> python3 /app/server.py ─┐                                                                  │ HTTP JSON-RPChost: signal-cli-daemon.service  ◀── 127.0.0.1:8080 (synchronous)─┤  (send, version)                                  ◀── 127.0.0.1:7583 (TCP stream)─┤  (receive notifications)                                                                  │       docker container (signal-mcp, restart=unless-stopped)      │       PID 1: subscriber.py ── persistent TCP socket ─────────────┘                            └─> append /data/inbox.jsonl  ◀── signal_check_replies reads
  • No bind-mount of signal-cli data. The container is purely a client of the host daemon. Signal identity keys never enter the container.
  • No re-registration. The Signal identity stays on the host.
  • Two-channel daemon. Synchronous calls (send, version, health) go over HTTP on :8080. Incoming-message notifications stream over a persistent TCP socket on :7583. The HTTP daemon's receive method is permanently locked by signal-cli's internal auto-receive loop, so TCP is the only path that works for replies.
  • One daemon flag added. The --tcp 127.0.0.1:7583 arg was added to the existing signal-cli-daemon.service ExecStart (see "Daemon configuration" below). HTTP behavior for any other consumer is unchanged.

Setup

bash
git clone https://github.com/joshuawowk/signal-mcp && cd signal-mcpcp .env.example .env       # set SIGNAL_SENDER and SIGNAL_DEFAULT_RECIPIENTdocker compose builddocker compose up -ddocker compose logs -f signal-mcp   # watch the subscriber start up

Verify the daemon is reachable from the container:

bash
docker exec signal-mcp python3 -c \  "import rpc; print(rpc.ping())"# expected: 0.14.3

Register the MCP server with Claude Desktop

Merge claude_desktop_config.snippet.json into ~/.config/Claude/claude_desktop_config.json under mcpServers:

json
"signal": {  "command": "sg",  "args": ["docker", "-c", "exec docker exec -i signal-mcp python3 /app/server.py"]}

Restart Claude Desktop.

Why the sg wrapper

sg docker -c "…" runs the command under the docker supplementary group. This is required when the Claude Desktop process was started before your user was added to the docker group — existing sessions inherit a stale supplementary-group list and docker exec returns permission denied while trying to connect to the docker API at unix:///var/run/docker.sock, which surfaces in Claude Desktop's logs as write EPIPE / Server transport closed unexpectedly.

The exec inside the -c string replaces the temporary bash spawned by sg with the docker exec process, so SIGTERM/SIGPIPE from Claude Desktop reach the MCP server cleanly when the connection closes.

If you log out + back in (or reboot), all new processes inherit the docker group and the wrapper isn't strictly needed — but it's harmless to keep, and it makes the config portable across hosts where the group situation may vary. The signal server will appear in the MCP server list and its tools (signal_send_message, signal_send_question, signal_send_permission_request, signal_check_replies, signal_health) become available to any session.

Install the companion skill

An example agent skill lives in skill/SKILL.md (written for one person's setup; adapt the name and wording). Copy or symlink it into your agent's skills directory, for example:

bash
mkdir -p <your-skills-dir>/signalln -sf "$PWD/skill/SKILL.md" <your-skills-dir>/signal/SKILL.md

End-to-end test from a Claude session

Use signal_health to confirm the daemon is up, then signal_send_messagewith text="signal-mcp end-to-end test from session XYZ" andsession_id="signal-mcp-test-001". Then wait 30 seconds and callsignal_check_replies with session_id="signal-mcp-test-001".

Reply with anything from your phone; the next signal_check_replies call should return your message.

Tools

ToolWhen to call
signal_send_message(text, recipient?, session_id?)Task-done pings, FYI updates. No reply expected.
signal_send_question(question, options?, recipient?, session_id?)Blocked, need an answer. options accepts a list ["A","B"], a JSON-array string '["A","B"]', or newline/comma-separated text — all normalized server-side. Response echoes options_parsed.
signal_send_permission_request(action, context, recipient?, session_id?)About to do something irreversible. Wait for explicit YES.
signal_check_replies(since?, from_recipient?, session_id?, limit?)Poll for replies. Filter by session_id.
signal_health()Full health: send path (daemon HTTP), receive path (TCP port + subscriber process liveness), and inbox status. ok is true only when send AND receive are healthy. An empty inbox is normal. Run first if anything fails.

Reliability notes (Jun 2026)

  • The inbox file (/data/inbox.jsonl) is created at subscriber startup and is never deleted by retention pruning — an empty inbox stays as a 0-byte file. Previously, pruning removed the file entirely once all records aged out (14-day retention), which made signal_check_replies / signal_health look like the subscriber was down. signal_check_replies and signal_health also ensure the file exists on every call.
  • signal_health and signal_check_replies report subscriber_running (scans /proc for subscriber.py) and receive_channel_ok (TCP connect to the daemon's :7583), so a fresh session can distinguish "no replies yet" from "receive path broken".
  • signal_send_question tolerates clients that serialize the options list into a string (see tools table).

Config (.env)

VariableMeaning
SIGNAL_SENDERE.164 of the registered signal-cli account. Required.
SIGNAL_DEFAULT_RECIPIENTE.164 to ping by default. Required.
SIGNAL_RPC_URLDaemon endpoint as seen from the container. Default http://127.0.0.1:8080/api/v1/rpc; with network_mode: host, that works.
SIGNAL_TCP_HOST / SIGNAL_TCP_PORTDaemon TCP stream for receive notifications. Defaults 127.0.0.1 / 7583.
SIGNAL_POLL_INTERVALSubscriber poll cadence in seconds.
SIGNAL_INBOX_RETENTION_DAYSDays of replies kept in inbox.jsonl.
SIGNAL_INBOX_PATHInbox file path inside the container.

Operational

bash
docker compose ps                       # statusdocker compose logs -f signal-mcp       # subscriber outputdocker exec signal-mcp tail -f /data/inbox.jsonl   # raw repliesdocker compose restart signal-mcp       # bounce the containerdocker compose down                     # stop

If the host daemon is restarted, the subscriber will reconnect on its next poll (errors back off up to 60 s).

Notes & limitations

  • Linux host only. network_mode: host is a no-op on Docker Desktop for macOS/Windows. To move it: switch to a bridge network, set SIGNAL_RPC_URL=http://host.docker.internal:8080/api/v1/rpc, and add extra_hosts: ["host.docker.internal:host-gateway"].
  • HTTP daemon polling. signal-cli's HTTP mode does not stream receive events; we poll. A TCP-mode daemon with WebSocket subscription would be lower-latency, but would require changing your existing daemon service.
  • Attachments are skipped. The subscriber only persists text bodies. Reactions, typing indicators, and read receipts are ignored.
  • The phone number is not memorized. It lives only in .env and the registration snippet, both inside this project.

Daemon configuration

This project requires the host's signal-cli-daemon.service to expose both HTTP (for synchronous calls) and TCP (for streaming receive notifications). The unit file at ~/.config/systemd/user/signal-cli-daemon.service should have:

ExecStart=/usr/local/bin/signal-cli -a +15555550100 daemon --http 127.0.0.1:8080 --tcp 127.0.0.1:7583 --receive-mode on-start

To apply changes:

bash
systemctl --user daemon-reloadsystemctl --user restart signal-cli-daemon.service

A backup of the original unit file is written next to the live one with a .bak.YYYYMMDD-HHMMSS suffix the first time signal-mcp is set up.

Why not just use HTTP for receive?

signal-cli's HTTP daemon mode is synchronous-only. receive over HTTP always errors with "Receive command cannot be used if messages are already being received" because the daemon's internal receive loop owns the queue and HTTP has no way to deliver push events. TCP mode opens a persistent newline-delimited JSON-RPC stream and pushes receive notifications as they arrive — that's the path subscriber.py uses.

來源:README.md,提交 7a77027

工具

0
SourceWeft 曾在隔離沙箱中以無憑證方式啟動該軟體包一次,以列出其工具,但未能完成:該軟體包類型無法在沙箱中執行。

版本歷史

1
  1. v1.0.1最新Oct 20, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分