Signal Messaging (via signal-cli) について
既存の signal-cli デーモンを介して Signal メッセージを送受信する非公式 MCP サーバーです。エージェントからの状況通知や質問、許可リクエストを送り、別のサブスクライバーで返信を収集します。
主な機能
- 01メッセージ、質問、許可リクエストの送信
- 02テキスト返信の収集と絞り込み
- 03送受信状態の確認
- 04既存の signal-cli デーモンの利用
活用シーン
- 01タスク完了の通知
- 02作業が止まった際の質問
- 03取り消せない操作の前の承認依頼
MCP 対応
stdio 経由で MCP サーバーを提供し、Signal メッセージの送信、返信確認、ヘルスチェック用のツールを公開します。オプションのサブスクライバーが既存の signal-cli デーモンから通知を受信します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm -e SIGNAL_SENDER -e SIGNAL_DEFAULT_RECIPIENT -e SIGNAL_RPC_URL -e SIGNAL_TCP_HOST -e SIGNAL_TCP_PORT -e SIGNAL_INBOX_PATH -e SIGNAL_POLL_INTERVAL -e SIGNAL_INBOX_RETENTION_DAYS ghcr.io/joshuawowk/signal-mcp:1.0.1SIGNAL_SENDERE.164 number of the account registered in signal-cli (e.g. +15555550100)SIGNAL_DEFAULT_RECIPIENTE.164 number messaged by default; tools accept a per-call recipient overrideSIGNAL_RPC_URLsignal-cli HTTP JSON-RPC endpoint as seen from the container. The server's built-in default is http://127.0.0.1:8080/api/v1/rpc (use with --network host on Linux); this listing defaults to the Docker host alias.SIGNAL_TCP_HOSTsignal-cli daemon TCP host, used only for the receive-path health checkSIGNAL_TCP_PORTsignal-cli daemon TCP port (daemon started with --tcp), used only for the receive-path health checkSIGNAL_INBOX_PATHPath of the replies inbox file inside the container (populated only by the subscriber process)SIGNAL_POLL_INTERVALSubscriber poll cadence in secondsSIGNAL_INBOX_RETENTION_DAYSDays of replies kept in the inbox fileローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/joshuawowk/signal-mcp:1.0.1stdioツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
signal-mcp
signal-cli を介して Signal と連携する、非公式の MCP 統合です。MCP クライアントから Signal メッセージ(タスク完了通知、作業を妨げる質問、権限リクエスト)を送信し、返信を読み取ることができます。
要件: 稼働中の signal-cli JSON-RPC デーモン(signal-cli daemon --http ...。返信も受信する場合は --tcp ... も指定)と、signal-cli に登録済みの Signal 電話番号が必要です。このプロジェクトは電話番号の登録、デーモンの起動、Signal のサーバーとの直接通信は行いません。デーモンのクライアントとしてのみ動作します。Signal Messenger または Signal Foundation とは提携しておらず、承認も受けていません。
MCP サーバー(app/server.py)と、既存の signal-cli デーモンの前段として動作する任意の返信サブスクライバー(app/subscriber.py)で構成されています。再登録やデータディレクトリのバインドマウントは必要ありません。
インストール
イメージは ghcr.io/joshuawowk/signal-mcp で公開されており、io.github.joshuawowk/signal-mcp として MCP Registry に掲載されています。引数なしで実行すると、コンテナは返信サブスクライバーを起動します(以下の Compose デプロイで使用する長時間実行モードです)。MCP サーバーは明示的に起動します。
stdio 経由の MCP クライアント(送信のみ)
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
注意事項:
- Linux では、代わりに
--network hostを使用し、--add-hostとSIGNAL_RPC_URLの設定を省略できます(デフォルトの127.0.0.1:8080からデーモンにアクセスできます)。 - ブリッジネットワークを使う場合、デーモンはコンテナからアクセス可能でなければなりません。
127.0.0.1のみにバインドされたデーモンにはアクセスできません。signal-cli の HTTP エンドポイントには認証がありません。信頼できるインターフェース以外には公開しないでください。 - 単独の stdio コンテナにはサブスクライバーがないため、返信は収集されません。
signal_check_repliesは何も返さず、signal_healthは受信経路がダウンしていると報告します。返信を受信するには、以下の Compose デプロイを使用してください。
Compose デプロイ(送信と返信の受信)
セットアップを参照してください。
アーキテクチャ
MCP client ──stdio──> docker exec ──> python3 /app/server.py ─┐
│ HTTP JSON-RPC
host: 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
- signal-cli のデータはバインドマウントしません。 コンテナはホストのデーモンのクライアントとしてのみ動作します。Signal の ID キーがコンテナに入ることはありません。
- 再登録は不要です。 Signal の ID はホスト上に保持されます。
- 2 チャネルのデーモンです。 同期呼び出し(送信、バージョン、ヘルスチェック)は
:8080の HTTP 経由で行います。受信メッセージの通知は、永続的な TCP ソケット(:7583)を介してストリーミングされます。HTTP デーモンのreceiveメソッドは、signal-cli の内部自動受信ループによって恒久的にロックされるため、返信に使える経路は TCP のみです。 - デーモンに追加するフラグは 1 つです。 既存の
signal-cli-daemon.serviceの ExecStart に--tcp 127.0.0.1:7583引数を追加します(下記の「デーモンの設定」を参照)。他の利用者に対する HTTP の動作は変わりません。
セットアップ
git clone https://github.com/joshuawowk/signal-mcp && cd signal-mcp
cp .env.example .env # set SIGNAL_SENDER and SIGNAL_DEFAULT_RECIPIENT
docker compose build
docker compose up -d
docker compose logs -f signal-mcp # watch the subscriber start up
コンテナからデーモンにアクセスできることを確認します。
docker exec signal-mcp python3 -c \
"import rpc; print(rpc.ping())"
# expected: 0.14.3
Claude Desktop に MCP サーバーを登録する
claude_desktop_config.snippet.json の内容を ~/.config/Claude/claude_desktop_config.json の mcpServers の下にマージします。
"signal": {
"command": "sg",
"args": ["docker", "-c", "exec docker exec -i signal-mcp python3 /app/server.py"]
}
Claude Desktop を再起動します。
sg ラッパーを使う理由
sg docker -c "…" は、docker 補助グループの権限でコマンドを実行します。Claude Desktop プロセスの起動後にユーザーを docker グループに追加した場合は、これが必要です。既存のセッションは古い補助グループの一覧を引き継ぐため、docker exec は permission denied while trying to connect to the docker API at unix:///var/run/docker.sock を返します。このエラーは Claude Desktop のログでは write EPIPE / Server transport closed unexpectedly として表示されます。
-c 文字列内の exec によって、sg が起動した一時的な bash が docker exec プロセスに置き換わります。そのため、接続が閉じたときに Claude Desktop から送られる SIGTERM/SIGPIPE が MCP サーバーに正しく届きます。
ログアウトして再度ログインする(または再起動する)と、新しいプロセスはすべて docker グループを引き継ぐため、ラッパーは必須ではありません。ただし、残しておいても問題はなく、グループの設定が異なる可能性のあるホスト間で設定を移植可能にできます。signal サーバーが MCP サーバーの一覧に表示され、そのツール(signal_send_message、signal_send_question、signal_send_permission_request、signal_check_replies、signal_health)をどのセッションでも利用できるようになります。
コンパニオンスキルをインストールする
スキルの例は skill/SKILL.md にあります(1 人の環境向けに書かれているため、名前や文言を調整してください)。エージェントのスキルディレクトリにコピーまたはシンボリックリンクを作成します。例:
mkdir -p <your-skills-dir>/signal
ln -sf "$PWD/skill/SKILL.md" <your-skills-dir>/signal/SKILL.md
Claude セッションからのエンドツーエンドテスト
Use signal_health to confirm the daemon is up, then signal_send_message
with text="signal-mcp end-to-end test from session XYZ" and
session_id="signal-mcp-test-001". Then wait 30 seconds and call
signal_check_replies with session_id="signal-mcp-test-001".
スマートフォンから何か返信してください。次の signal_check_replies 呼び出しで、メッセージが返されるはずです。
ツール
| ツール | 呼び出すタイミング |
|---|---|
signal_send_message(text, recipient?, session_id?) | タスク完了の通知や参考情報の共有に使います。返信は不要です。 |
signal_send_question(question, options?, recipient?, session_id?) | 作業がブロックされ、回答が必要なときに使います。options はリスト ["A","B"]、JSON 配列の文字列 '["A","B"]'、または改行/カンマ区切りのテキストを受け付けます。いずれもサーバー側で正規化されます。応答には options_parsed が含まれます。 |
signal_send_permission_request(action, context, recipient?, session_id?) | 取り消せない操作を実行する前に使います。明示的な YES を待ってください。 |
signal_check_replies(since?, from_recipient?, session_id?, limit?) | 返信をポーリングします。session_id で絞り込めます。 |
signal_health() | 完全なヘルスチェックを実行します。送信経路(デーモン HTTP)、受信経路(TCP ポートとサブスクライバープロセスの稼働状況)、受信トレイの状態を確認します。ok が true になるのは、送信と受信の両方が正常な場合のみです。受信トレイが空でも問題ありません。何か失敗した場合は、まず実行してください。 |
信頼性に関する注意事項(2026年6月)
- 受信トレイのファイル(
/data/inbox.jsonl)はサブスクライバーの起動時に作成され、保持期間に基づく削除では消去されません。受信トレイが空の場合も、0 バイトのファイルとして残ります。以前は、すべてのレコードが期限切れになる(保持期間は 14 日)と削除処理によってファイル自体が削除され、サブスクライバーが停止しているようにsignal_check_replies/signal_healthに表示されていました。signal_check_repliesとsignal_healthは、呼び出しのたびにファイルが存在することも確認します。 signal_healthとsignal_check_repliesは、subscriber_running(/procをスキャンしてsubscriber.pyを確認)とreceive_channel_ok(デーモンの:7583への TCP 接続)を報告します。これにより、新しいセッションでも「まだ返信がない」のか「受信経路に問題がある」のかを区別できます。signal_send_questionは、optionsリストを文字列にシリアライズするクライアントにも対応します(「ツール」表を参照)。
設定(.env)
| 変数 | 意味 |
|---|---|
SIGNAL_SENDER | 登録済み signal-cli アカウントの E.164 番号です。必須です。 |
SIGNAL_DEFAULT_RECIPIENT | デフォルトで通知を送る E.164 番号です。必須です。 |
SIGNAL_RPC_URL | コンテナから見たデーモンのエンドポイントです。デフォルトは http://127.0.0.1:8080/api/v1/rpc です。network_mode: host の場合は、そのまま使用できます。 |
SIGNAL_TCP_HOST / SIGNAL_TCP_PORT | 受信通知用のデーモン TCP ストリームです。デフォルトはそれぞれ 127.0.0.1/7583 です。 |
SIGNAL_POLL_INTERVAL | サブスクライバーがポーリングする間隔(秒)です。 |
SIGNAL_INBOX_RETENTION_DAYS | inbox.jsonl に返信を保持する日数です。 |
SIGNAL_INBOX_PATH | コンテナ内の受信トレイファイルのパスです。 |
運用
docker compose ps # status
docker compose logs -f signal-mcp # subscriber output
docker exec signal-mcp tail -f /data/inbox.jsonl # raw replies
docker compose restart signal-mcp # bounce the container
docker compose down # stop
ホストのデーモンが再起動すると、サブスクライバーは次のポーリング時に再接続します(エラーが発生した場合、最大60秒まで待機時間が延長されます)。
注意事項と制限
- Linux ホストのみ対応しています。
network_mode: hostは、macOS/Windows の Docker Desktop では何も行いません。移行するには、ブリッジネットワークに切り替え、SIGNAL_RPC_URL=http://host.docker.internal:8080/api/v1/rpcを設定し、extra_hosts: ["host.docker.internal:host-gateway"]を追加してください。 - HTTP デーモンのポーリング。 signal-cli の HTTP モードでは受信イベントをストリーミングできないため、ポーリングを行います。WebSocket サブスクリプションに対応した TCP モードのデーモンなら遅延を抑えられますが、既存のデーモンサービスを変更する必要があります。
- 添付ファイルはスキップされます。 サブスクライバーが保存するのはテキスト本文のみです。リアクション、入力中インジケーター、既読通知は無視されます。
- 電話番号は記憶されません。 電話番号は
.envと登録用スニペットにのみ保存され、どちらもこのプロジェクト内にあります。
デーモンの設定
このプロジェクトでは、ホストの signal-cli-daemon.service が、同期呼び出し用の HTTP とストリーミング受信通知用の TCP の両方を公開している必要があります。
~/.config/systemd/user/signal-cli-daemon.service にあるユニットファイルには、次の設定が必要です。
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
変更を適用するには、次の手順を実行してください。
systemctl --user daemon-reload
systemctl --user restart signal-cli-daemon.service
signal-mcp を初めてセットアップすると、元のユニットファイルのバックアップが、稼働中のファイルと同じ場所に .bak.YYYYMMDD-HHMMSS というサフィックスを付けて作成されます。
受信に HTTP を使わない理由
signal-cli の HTTP デーモンモードは同期処理専用です。HTTP 経由の receive は常に "Receive command cannot be used if messages are already being received" というエラーになります。これは、デーモン内部の受信ループがキューを管理しており、HTTP にはプッシュイベントを配信する方法がないためです。TCP モードでは、改行区切りの JSON-RPC ストリームを常時接続で開き、到着した receive 通知をプッシュします。subscriber.py が使用するのはこの方法です。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。