Skip to main content
Glama

claude-notify

A remote MCP server exposing exactly one tool, notify_me, that sends a push notification to your phone through Pushover.

Write "notify me when the migration finishes" or "ping me if the test suite fails" in a Claude Code prompt, and the session calls the tool when it gets there. Sessions run for hours on several machines; this is how they reach you.

It is a remote server rather than a local one so that adding a machine is one claude mcp add line with nothing to install, and so the Pushover credentials live in exactly one place.

Claude Code (any machine)
  │  remote MCP over HTTPS, Authorization: Bearer <per-machine token>
  ▼
Cloudflare Worker ──► auth → rate limit → resolve label → truncate → POST ──► Pushover → iPhone
  │
  └─ KV: token hashes, rate counters, quota state

The tool

Param

Type

Required

Notes

message

string

yes

The notification body

urgent

boolean

no, default false

Pushover priority 1 — bypasses quiet hours

There is deliberately no source or machine parameter. The label in the title comes from the bearer token, resolved server-side, so a session cannot mislabel itself and the model has one less argument to get wrong. Notifications arrive titled [vps-01] Claude Code.

Related MCP server: pushover-mcp

First-time setup

You need a Cloudflare account, a Pushover account, and a Healthchecks.io check.

1. Pushover

  1. Install Pushover on your phone and sign in. Your user key is on the dashboard.

  2. Register an application at https://pushover.net/apps/build to get an API token.

Since 1 May 2026 the monthly allowance is per account, not per application — every app you register shares one pool of 10,000 messages a month.

2. Cloudflare

git clone <this repo> && cd claude-notify
npm install

cp wrangler.toml.example wrangler.toml
wrangler kv namespace create NOTIFY_KV

Put your account_id (from wrangler whoami) and the namespace id printed by that command into wrangler.toml. That file is gitignored.

3. Healthchecks.io

Create a check at https://healthchecks.io with a period of 1 hour and a grace of 20 minutes, and copy its ping URL.

Treat that URL as a credential. Anyone holding it can ping your dead-man's switch and permanently suppress the alert that tells you the Worker died.

4. Secrets and deploy

wrangler secret put PUSHOVER_TOKEN     # the application API token
wrangler secret put PUSHOVER_USER      # your user key
wrangler secret put HEALTHCHECKS_URL   # https://hc-ping.com/<uuid>

wrangler deploy

Secrets are only ever set this way. They are never reachable by any client and never appear in a file in this repo.

Adding a machine

One token per machine, so a leak costs you one machine rather than all of them.

node scripts/token.mjs add vps-01

The token is printed once and cannot be recovered — only its SHA-256 hash is stored. The command prints the registration line to run on that machine:

claude mcp add --transport http --scope user claude-notify \
  https://YOUR-WORKER.workers.dev/mcp \
  --header "Authorization: Bearer <the token printed by the CLI>"

Then check it:

claude mcp list        # claude-notify ... ✔ Connected

Managing machines:

node scripts/token.mjs list             # labels and status, never the tokens
node scripts/token.mjs revoke laptop    # that machine gets 401 on its next call

Add --local to any of these to work against the wrangler dev KV simulation instead of production.

Only the hash is stored, so hash the token before writing it — a mistake here produces a token that silently never authenticates.

TOKEN=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
HASH=$(printf '%s' "$TOKEN" | sha256sum | cut -d' ' -f1)

wrangler kv key put "token:$HASH" '{"label":"ci-runner","enabled":true}' \
  --binding NOTIFY_KV --remote

echo "$TOKEN"   # shown once

# Revoke:
wrangler kv key delete "token:$HASH" --binding NOTIFY_KV --remote

Using it

Just say so in the prompt:

Run the full test suite and notify me if anything fails.

This migration will take a while — ping me when it's done.

The session decides when to call the tool. It reports the real outcome back: on success, how many Pushover messages remain this month; on failure, the actual HTTP status and Pushover's own error text. It never retries and never reports success it did not get, so if the pipe is broken the session says so.

Health monitoring

A system cannot report its own death, so detection comes from outside.

Hourly the Worker reads KV and then pings Healthchecks.io. If the Worker dies, Healthchecks notices the silence and emails you over a path that touches neither Cloudflare nor Pushover. If KV is unreachable it pings /fail instead, so a storage outage is reported rather than papered over.

Daily at 08:00 Europe/Athens it sends a real notification: Pipe OK, N remaining. This is the only thing that exercises the entire chain, including APNs and your iOS notification settings — which can break silently after an OS update, in a way no server-side check can detect.

Cloudflare cron triggers are UTC-only, so the daily job is registered at both 05:00 and 06:00 UTC and the Worker checks the wall-clock hour in DAILY_PING_TZ. One of the two is 08:00 in Athens whether or not daylight saving is in effect, so exactly one firing per day gets through and the notification lands at 08:00 local year-round.

Rate limits and quota

The only power this endpoint has is making your phone buzz, so spam is the threat and rate limiting is the control. Both limits are [vars] in wrangler.toml:

  • RATE_LIMIT_PER_TOKEN — default 60/hour per machine

  • RATE_LIMIT_GLOBAL — default 200/hour across all machines, so one compromised machine cannot burn the whole Pushover quota

A breach comes back as a tool error naming the limit and when it resets, so the calling session sees it.

Counters live in KV, which is eventually consistent and last-write-wins. Under a concurrent burst some increments are lost and a few extra messages get through. That is a deliberate trade: this is a spam guard on a doorbell, not an exact quota.

Every Pushover response carries the account's remaining monthly allowance, which is persisted. When it drops below QUOTA_WARN_THRESHOLD (default 500) a warning is appended to the next notification — reserved before the message is truncated, so a long message cannot push the warning off the end.

Pushover caps messages at 1024 characters and titles at 250. Both are truncated, never rejected: a truncated notification beats a silent failure.

Development

npm run typecheck

# Order matters. Local KV is a single SQLite database that only one Wrangler
# process may hold open, and each process carries its own workerd runtime — so
# create the token *before* starting the dev server, never alongside it.
# `token.mjs --local` refuses if it sees a dev server on port 8787.
node scripts/token.mjs add laptop --local
npm run dev                       # wrangler dev, with .dev.vars for secrets

NOTIFY_URL=http://localhost:8787/mcp \
NOTIFY_TOKEN=<the token printed above> \
  ./test/smoke.sh

Point NOTIFY_URL at https://YOUR-WORKER.workers.dev/mcp instead to smoke-test the deployed Worker; a --local token will not authenticate against it, so use one issued without --local.

Budget roughly 550 MB of RAM per wrangler dev (a Node parent plus two workerd processes). Two Wrangler processes at once need over a gigabyte, which is enough to livelock a small VM that has no swap.

The smoke test drives the whole path — tools/list, a real tools/call, an unknown token, and a browser-origin request — and reads its credentials from the environment rather than from the file.

For local development put secrets in .dev.vars (gitignored):

PUSHOVER_TOKEN=...
PUSHOVER_USER=...
HEALTHCHECKS_URL=...

Cron jobs can be triggered by hand against wrangler dev:

curl "http://localhost:8787/cdn-cgi/local/scheduled?cron=0+*+*+*+*"    # Healthchecks ping
curl "http://localhost:8787/cdn-cgi/local/scheduled?cron=0+5,6+*+*+*"  # daily pipe test

Protocol notes

MCP revision 2026-07-28 made the protocol stateless: no initialize handshake, no sessions, no Mcp-Session-Id, no GET stream. Each request declares its own protocol version and capabilities in a _meta envelope, mirrored into MCP-Protocol-Version, Mcp-Method and Mcp-Name headers.

This server is dual-era: it serves both the stateless revision and 2025-era clients that still open with initialize, on the same /mcp endpoint. Claude Code's rollout of the stateless revision is still in progress across versions, so serving only one era would strand clients on the other.

Requests carrying an Origin header are refused with 403. Nothing legitimate calls this from a browser — Claude Code sends no Origin at all — and this is the transport spec's required DNS-rebinding protection.

Troubleshooting

✘ Failed to connect in claude mcp list — usually a bad or revoked token. The server returns 401 with no detail, deliberately; check with node scripts/token.mjs list and reissue if needed.

Connected, but tool calls return Unauthorized — there was a Claude Code bug where the configured --header was attached on connect but dropped on tool calls (#50464, fixed June 2026). If you see this, upgrade Claude Code first.

Notifications stop arriving on the 1st of a month — the Pushover account quota is exhausted; it resets monthly. The daily pipe test reports the remaining count, which is the warning you get before this happens.

The daily test stops but the hourly check stays green — the Worker and KV are fine, so suspect the chain beyond them: Pushover credentials, or iOS notification settings after an update.

Security

  • Tokens are stored only as SHA-256 hashes, keyed by the hash. Nothing compares secrets, so there is no timing-attack surface.

  • Raw tokens are never logged. What reaches the request context is the hash.

  • Unknown and disabled tokens are indistinguishable to the client: same status, same empty body.

  • PUSHOVER_TOKEN, PUSHOVER_USER and HEALTHCHECKS_URL are Worker secrets and are never reachable by any client.

  • If a credential leaks, rotate it. Rewriting git history is not sufficient.

.gitignore excludes wrangler.toml (it carries the account id and deployed URL), .dev.vars, and .env. Every example in this file uses placeholders, and every machine label is a generic one — vps-01, laptop, ci-runner. Real labels live in KV, not in the repository.

Non-goals

Deliberately not built, and not to be added: a second transport (Telegram, ntfy); two-way messaging; Claude Code hooks integration; IP allowlisting or Tailscale; scheduled or interval status pings; a local MCP server variant.

License

MIT

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Push notifications for AI agents - send instant iPhone notifications from any MCP client.

  • Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.

  • A very simple remote MCP server that greets you, with a custom icon.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/slamprou/claude-notify'

If you have feedback or need assistance with the MCP directory API, please join our Discord server