Skip to main content
Glama
jiridudekusy

signal-cli-mcp

by jiridudekusy
README.md
# signal-cli-mcp

An MCP server that lets AI assistants (Claude Code) propose sending Signal messages — but **nothing is ever sent without explicit human approval**. The security guarantee is hard-wired into the architecture: the only path to sending a message goes through `signal-client.ts`, which is called only after a `Decision` is approved in `approval-core.ts`. No tool can bypass this mechanism — if the user rejects or the timeout expires, the message is never sent.

## Supported client

- **Claude Code** — supported. You must set `"timeout": 900000` in the configuration; otherwise Claude Code will cut off the call during long approvals.
- **Claude Desktop** — **not supported**. It has a hard-coded 60 s tool timeout that cannot be reconfigured.

## Tools

| Tool | Description |
|---|---|
| `proposeSignalMessage` | Proposes sending a message to an individual |
| `proposeSignalGroupMessage` | Proposes sending a message to a group |
| `proposeSignalGroupCreation` | Proposes creating a new group |

Recipients, group targets, and group members may be given **either as a contact/group
name or as an E.164 number / `group.<id>`**. The server resolves names against signal-cli's
contacts and groups (exact, case-insensitive match); if nothing matches — or several do — the
call fails with the candidates so the assistant can disambiguate. The approval GUI always shows
the resolved `Name (number)` so you verify the real recipient before approving.

## Setup

### 1. Start signal-cli-rest-api

```bash
docker compose up -d
```

The Docker Compose file is included in the repository and starts [signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) on port 8080.

### 2. Link a Signal account

Open in your browser:

```
http://localhost:8080/v1/qrcodelink?device_name=signal-cli-mcp
```

Scan the displayed QR code in the Signal app (Settings → Linked Devices). Verify the link:

```bash
curl http://localhost:8080/v1/accounts
```

The output should contain your phone number.

### 3. Build the project

```bash
npm install && npm run build
```

### 4. Configure Claude Code

Copy `.mcp.json.example` to `.mcp.json` in your project folder (Claude Code loads it from there) and edit:

- `/ABSOLUTE/PATH/signal-cli-mcp/dist/index.js` → the real absolute path on your machine
- `SIGNAL_ACCOUNT` → your phone number in E.164 format (e.g. `+1234567890`)

Leave the other variables as-is or adjust as needed. **`timeout: 900000` is required** — without it Claude Code will cut off the call if approval takes longer than 60 seconds.

### 5. Open the approval GUI

Keep a browser tab open at `http://localhost:8088`. Proposed messages appear there for approval. Without the tab open approval still works, but you won't see a live preview (you'll get the result when you visit the URL).

## Usage

In Claude Code, type something like: "Send John a message that I'll arrive at 6 PM."

Claude Code will call `proposeSignalMessage`. The proposal appears at `http://localhost:8088`. Click **Approve** to send, **Reject** to decline (with an optional reason), or **Edit + Approve** to modify the text before sending.

For a detailed walkthrough see [`docs/MANUAL.md`](docs/MANUAL.md).

## Push notifications (optional)

So you don't have to watch the GUI tab, the server can send an [ntfy](https://ntfy.sh/) push
(macOS + iOS apps) whenever a proposal arrives. Set these env variables:

- `NTFY_TOPIC` — the ntfy topic to publish to (subscribe to it in the ntfy app). Enables notifications.
- `NTFY_BASE_URL` — ntfy server (default `https://ntfy.sh`; point this at a self-hosted server later).
- `GUI_PUBLIC_URL` — where you reach the GUI; used for the tap-through and the action buttons (see below).

With `NTFY_TOPIC` set, every new proposal triggers a push with the recipient and a text preview.
When `GUI_PUBLIC_URL` is also set, the notification is actionable:

- **tapping the notification opens the GUI directly** (ntfy `Click`),
- **Approve / Reject buttons** appear in the notification and POST straight to the GUI's `/action`
  endpoint — one-tap decisions without opening anything; **Open GUI** is offered for edits.

Delivery is best-effort — a failed or unconfigured push never blocks approval. Notifications go to
ntfy only; they never touch signal-cli.

> **Privacy note:** on the public `ntfy.sh`, the topic name is a shared secret and the notification
> content (recipient, text preview) transits ntfy.sh. For a private setup, self-host ntfy and point
> `NTFY_BASE_URL` at it — planned as a follow-up.

## Remote deployment (Docker MCP gateway + Tailscale)

When the server runs remotely (e.g. inside a Docker MCP gateway rather than launched locally by
Claude Code):

- **Transport:** the MCP gateway exposes the server over HTTP/SSE; the server itself stays stdio —
  no code change. Point your MCP client at the gateway.
- **Reaching the GUI:** expose the GUI port over your Tailscale network, e.g.
  `tailscale serve --bg <GUI_PORT>`, and set `GUI_PUBLIC_URL` to the resulting MagicDNS URL
  (e.g. `https://signal-mcp.<tailnet>.ts.net/`). Tailscale is the access boundary — only your
  devices can reach it, so the GUI needs no extra login. Because your phone is on the tailnet too,
  tapping the notification (or its Approve/Reject buttons) reaches the GUI straight from the iPhone.
- **Egress:** the container needs outbound access to the ntfy server and to signal-cli-rest-api.

## Releasing (npm)

The package is published to npm automatically by GitHub Actions
([`.github/workflows/publish.yml`](.github/workflows/publish.yml)) when a version tag is pushed,
using npm **Trusted Publishing (OIDC)** — no token secret, and provenance is generated automatically.

One-time setup on npmjs.com (package → Settings → Trusted Publisher): add a GitHub Actions publisher
with organization/user `jiridudekusy`, repository `signal-cli-mcp`, and workflow filename
`publish.yml`. The workflow already requests `id-token: write` and upgrades npm to ≥ 11.5.1.

To cut a release:

```bash
npm version patch    # or minor / major — bumps package.json, commits, and tags vX.Y.Z
git push --follow-tags
```

The workflow checks out the tag, runs `npm ci`, builds, runs the tests, verifies the tag matches
`package.json`, and publishes. Once published, install with `npm install -g signal-cli-mcp` (the
`signal-cli-mcp` binary can then be used as the `command` in `.mcp.json`).

TDQS

A4.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: sending to an individual, sending to a group, and creating a group. The recipient type and action are explicit in the names, leaving no ambiguity.

Naming Consistency5/5

All tools follow a consistent 'propose[Action]' pattern with clear noun phrases (SignalMessage, SignalGroupMessage, SignalGroupCreation). The naming is uniform and predictable.

Tool Count3/5

At 3 tools, the count is at the low end of the typical range. While each tool earns its place, the server feels minimal and could benefit from a few more operations to feel well-rounded.

Completeness2/5

The server covers sending messages and creating groups but lacks any receive, list, or management capabilities. For a Signal client, this is a significant gap that would prevent agents from performing common workflows.

Maintenance

ActivityInactive
ResponsivenessNo issues