Skip to main content
Glama
simplisafe-mcp

simplisafe-mcp

by chrischall
README.md
# simplisafe-mcp

MCP server for [SimpliSafe](https://simplisafe.com) home security. Check whether
the system is armed, review sensors and events, arm/disarm, and control smart
locks — from Claude.

> **This server can disarm a home alarm and unlock doors.** Every tool that
> changes physical state, plus the tool that reads alarm PINs, asks you to confirm
> first — nothing is sent until you do, and you see a preview of exactly what
> would happen (see [Confirmations](#confirmations)). Install it only where you'd
> be comfortable with that capability.

Developed and maintained by AI (Claude Code).

## What you get

| Tool | |
| --- | --- |
| `simplisafe_list_systems` | Systems on the account with current alarm state |
| `simplisafe_get_system` | One system's state, connectivity, base-station messages |
| `simplisafe_list_sensors` | Sensors with battery / offline / triggered status, filterable |
| `simplisafe_list_locks` | Smart locks with locked / unlocked / jammed state |
| `simplisafe_get_events` | Recent base-station events (arm, disarm, opens, alarms); `view: "brief"` gives one line per event |
| `simplisafe_get_settings` | Entry/exit delays, volumes, base-station health |
| `simplisafe_get_pins` | Alarm PINs — **cleartext, asks you to confirm first** |
| `simplisafe_set_alarm_state` | Arm home / arm away / disarm — **asks you to confirm first** |
| `simplisafe_set_lock_state` | Lock / unlock a door — **asks you to confirm first** |
| `simplisafe_healthcheck` | Auth + API reachability |

Supports **SimpliSafe 3** systems. Legacy SS2 systems are rejected with an
explanation rather than an opaque upstream 404.

## Install

```bash
npm install -g simplisafe-mcp
```

Or add to `.mcp.json`:

```json
{
  "mcpServers": {
    "simplisafe": {
      "command": "npx",
      "args": ["-y", "simplisafe-mcp"],
      "env": { "SIMPLISAFE_REFRESH_TOKEN": "${SIMPLISAFE_REFRESH_TOKEN}" }
    }
  }
}
```

## Authentication — one browser login, once

SimpliSafe issues no API keys. The credential is an OAuth2 refresh token, minted
by a browser login you perform **one time**:

```bash
git clone https://github.com/chrischall/simplisafe-mcp && cd simplisafe-mcp
node scripts/bootstrap-auth.mjs             # prints an authorize URL
# sign in (MFA included), then copy the com.simplisafe.mobile:// URL
node scripts/bootstrap-auth.mjs "<that URL>"
```

The token is written to `.env` (mode 0600) after being verified against the live
API. **SimpliSafe does not rotate refresh tokens**, so it stays valid until you
sign out of all devices in the SimpliSafe app — which is how you revoke it.

Capturing the code: open DevTools → Network and tick **Preserve log** *before*
signing in; afterwards the browser fails to open a `com.simplisafe.mobile://…`
link, and that failed entry's link address is what you paste. The code is
single-use and expires in about two minutes.

Treat the resulting token like a house key: it grants full control of the alarm.

## Confirmations

Arming, disarming, locking, unlocking and reading the PINs all ask you to
confirm first. On a client that can show a confirmation prompt (Claude Code) you
get the prompt, with the details below (unless `MCP_CONFIRM_ELICITATION=off`).
On one that cannot (claude.ai, Claude Desktop) the first call sends **nothing** and returns a preview plus a
`confirmToken`; only a repeat call with that token acts, once. The preview
includes a plain statement of the physical consequence:

```json
{
  "status": "confirmation-required",
  "confirmed": false,
  "dispatched": false,
  "action": "alarm.set_state",
  "preview": {
    "action": "set alarm state to away",
    "method": "POST",
    "path": "/ss3/subscriptions/7858153/state/away",
    "sid": 7858153,
    "locationName": "Home",
    "currentState": "OFF",
    "requestedState": "AWAY",
    "warning": "Arms ALL sensors including interior motion. Starts an exit delay; anyone still moving inside when it expires can trigger the siren and a monitoring-center dispatch."
  },
  "confirmToken": "…",
  "expiresAt": "…",
  "ttlSeconds": 600,
  "instruction": "Show this preview to the user verbatim and proceed only after they explicitly approve in chat. Then call again with confirmToken."
}
```

The token is bound to that tool, target and preview. The system is re-read on
the second call, and if what would happen no longer matches what you approved —
different arguments, or the alarm or lock state moved in between — it is
refused (`DRAFT_CHANGED`) with a fresh preview. A token that was already used is
refused too (`TOKEN_REUSED`).

| variable | default | |
|---|---|---|
| `MCP_CONFIRM_MODE` | `ask-user` | What a write does on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop). `ask-user`: two steps — the first call does nothing and returns a preview plus a token, and the model must get your approval in chat before calling again with it. `auto`: the same two steps, but the model may use the token after reviewing the preview itself. `refuse`: writes are refused on such clients. A client that can show prompts (Claude Code) always gets the real prompt, unless `MCP_CONFIRM_ELICITATION=off`. An unrecognised value is treated as `refuse`. |
| `MCP_CONFIRM_ELICITATION` | `on` | `off` never shows a confirmation prompt, so every client gets the `MCP_CONFIRM_MODE` behaviour. Set it for a client that says it can show prompts but never does (the write hangs — opencode 2.0.x). Any other value is treated as `on`, with a warning on stderr. |
| `MCP_CONFIRM_TTL_SECONDS` | `600` | How long a token stays valid. |
| `MCP_CONFIRM_SECRET` | random per process | Signing key; set it only if tokens must survive a server restart. |

### Writes are verified

Once confirmed, a write executes and then **re-reads the system** to check what
actually happened, reporting `confirmed`, `in_progress` (the exit delay is
counting down), or `unconfirmed`. A `2xx` is never treated as proof.

## Shell access without the server

For quick one-off queries there's a `curl`-based skill in
[`skills/simplisafe-api/`](skills/simplisafe-api/SKILL.md) — same API, no MCP
process, sharing the same refresh token.

## Development

```bash
npm install
npm run build
npm test
```

Verified endpoint shapes live in
[docs/SIMPLISAFE-API.md](docs/SIMPLISAFE-API.md), including several things that
are easy to get backwards:

- lock state is encoded **1 = locked, 2 = unlocked**;
- system version for routing is at `location.system.version`, not the top-level
  `systemVersion`;
- events and doorlock control are **not** under the `ss3/` prefix;
- `numEvents` has an undocumented hard ceiling of **50**;
- `settings.pins` returns alarm codes in cleartext alongside harmless settings.

## Disclaimer

Unofficial. Not affiliated with or endorsed by SimpliSafe. It uses the same
private API the SimpliSafe mobile app uses, with your own account credentials.
Use at your own discretion.

## License

MIT

TDQS

A4.2/5.0

Scored across 10 tools

Disambiguation4/5

Most tools are clearly separated by resource and action (events, pins, settings, sensors, locks, alarm state, lock state). The main overlaps are simplisafe_list_systems vs simplisafe_get_system (both return alarm/connectivity state, distinguished mainly by 'start here for sid' vs 'one system'), and simplisafe_list_locks vs simplisafe_list_sensors with type_name='lock', which surface much of the same lock data. Descriptions do enough to steer an agent, but these pairs could be confused.

Naming Consistency4/5

Nearly every tool follows the simplisafe_verb_noun pattern (list_systems, get_events, get_pins, set_alarm_state, set_lock_state). The only deviation is simplisafe_healthcheck, a bare noun with no verb, though it is still unambiguous. Verb choice (list vs get) is used meaningfully and consistently.

Tool Count5/5

Ten tools is well within the sweet spot and each one maps to a distinct resource or action in the alarm-system domain. Nothing feels padded: read tools for systems, sensors, locks, events, pins and settings, plus the two meaningful write operations.

Completeness4/5

The surface covers discovery, monitoring, diagnostics and the two core control actions (arm/disarm, lock/unlock), including confirmation gating and post-action verification. Gaps remain on the write side: settings are read-only (no set_settings) and PINs can be read but not created or changed, and cameras are absent. These are workable limitations rather than dead ends for the main workflow.

Maintenance

ActivityNo data
ResponsivenessOutdated data