Skip to main content
Glama
tmgr-notify

tmgr-notify

Official
by tmgr-dev

@tmgr/notify

MCP server and Claude Code / Codex hook CLI that lets an AI agent reach you on your phone.

TMGR is a task manager with an iOS app. With @tmgr/notify (command tmgr-notify) an agent (Claude Code, Codex, or any MCP client) gets three tools:

  • notify_user sends a push notification.

  • alarm rings you for an urgent incident: an alarm in the app, then a voice call if you do not acknowledge it.

  • alarm_status re-checks an alarm.

The package also ships hooks that notify you automatically when Claude Code needs input or finishes a long turn, and when a Codex turn completes.

Since 1.0 the package is a thin launcher for the tmgr command line tool, which implements the MCP server, the hooks and the commands. Commands, tool names, tool parameters and configuration stay the same as in 0.2.0. See How the tmgr binary is found.

Requirements

  • Node 22 or newer

  • macOS (Apple silicon or Intel) or Linux (arm64 or x64). Windows is not supported natively; use WSL2.

  • A TMGR account

  • The TMGR mobile app, for push notifications and for alarms (AlarmKit)

Related MCP server: blipr-mcp

Get a token

In TMGR open Settings → Agent notifications → Create token. The token (tmgrn_...) is shown once, so copy it right away. You can revoke it from the same screen.

After you create a token, TMGR shows ready-to-paste setup for Claude Code, Codex, Cursor and hooks with the token filled in. The same steps are in the agent setup guide.

Configuration

Configuration is read from environment variables first, then from a fallback file. Environment variables always win.

Variable

Required

Description

TMGR_URL

no

API base URL, default https://api.tmgr.dev. Override it for a self-hosted or local TMGR. A base that already ends in /api is normalized.

TMGR_NOTIFY_TOKEN

yes

The tmgrn_... token. Sent as Authorization: Bearer <token>.

TMGR_NOTIFY_STOP_MIN_MINUTES

no

Minimum turn duration in minutes before hook stop sends a "finished" push. Default 5.

If a variable is not set, tmgr-notify reads it from ~/.config/tmgr-notify/env, one KEY=VALUE per line, # comments allowed:

TMGR_NOTIFY_TOKEN=<TMGR_NOTIFY_TOKEN>

The file holds a bearer token, so restrict it: chmod 600 ~/.config/tmgr-notify/env. Since 1.0 this is required: the tmgr CLI ignores the file unless it is a regular file owned by you with mode 600, and tmgr-notify prints a chmod 600 hint on stderr when it sees a looser file.

If neither the env var nor the file provides a token, the CLI falls back to the account you signed in with tmgr login.

How the tmgr binary is found

tmgr-notify runs the first match:

  1. TMGR_BIN: an absolute path to a tmgr binary. Used as is.

  2. tmgr on PATH, when tmgr --version reports 1.0.1 or newer within 1.x.

  3. A prebuilt binary in the user cache, $XDG_CACHE_HOME/tmgr-notify/cli/1.0.1/<target>/tmgr (default ~/.cache/...). On the first run tmgr-notify downloads it from the cli-v1.0.1 release of tmgr-dev/frontend, for macOS (arm64, x64) or Linux (arm64, x64; static musl builds that run on any distribution).

How the download is checked:

  • HTTPS only, on every redirect, from the exact cli-v1.0.1 tag, never "latest".

  • The archive must match the SHA-256 pinned in cli-release.json, which ships inside this package. The release's SHA256SUMS must agree with that pin.

  • The binary is taken from the archive in memory and must match its own pinned SHA-256. No other archive entry is written.

  • It is installed atomically (temporary file, fsync, mode 755, rename), so two agents starting at once are safe.

  • Before every start tmgr-notify hashes the binary again and refuses to run it if the hash differs from the pin. A cached file that fails the check is replaced by a fresh verified download.

  • The cache directory must be a real directory owned by you and not writable by others, and the cached binary must be a regular file, not a symlink. Otherwise tmgr-notify refuses to start.

There is no install script, so npm install --ignore-scripts, pnpm and npx -y all work. The first start needs network access to github.com and takes a few seconds; later starts are offline. Without network, or behind a proxy (Node's built-in fetch ignores HTTPS_PROXY), install the tmgr CLI from the release page and put it on PATH, or set TMGR_BIN.

Claude Code

MCP server

Register it once at user scope so it is available in every project:

claude mcp add -s user tmgr-notify \
  -e TMGR_NOTIFY_TOKEN=<TMGR_NOTIFY_TOKEN> \
  -- npx -y @tmgr/notify mcp

To keep the token out of ~/.claude.json, skip the -e flag and rely on the env file above:

claude mcp add -s user tmgr-notify -- npx -y @tmgr/notify mcp

Or add it to a project's .mcp.json:

{
  "mcpServers": {
    "tmgr-notify": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@tmgr/notify", "mcp"]
    }
  }
}

MCP servers belong in .mcp.json or ~/.claude.json, not in settings.json. Use claude mcp list to check the connection.

Hooks

Hooks live in ~/.claude/settings.json (user scope shown, project scope works the same way). Add all three:

{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          { "type": "command", "command": "npx -y @tmgr/notify hook notification" }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          { "type": "command", "command": "npx -y @tmgr/notify hook prompt" }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "npx -y @tmgr/notify hook stop" }
        ]
      }
    ]
  }
}

npx adds startup latency to every hook run. For faster hooks install the package globally with npm i -g @tmgr/notify and use tmgr-notify hook ... as the command.

UserPromptSubmit and Stop do not support a matcher. They fire on every prompt and turn, which is what the hook prompt / hook stop pair needs to measure turn duration.

What each hook does:

  • Notification (needs your input, permission, or idle prompt): sends a high-priority push titled Claude Code · <project> · needs you, with the first line of the message as the body. This event fires for more than permission prompts, so the wording is generic ("needs your attention"). The types auth_success, elicitation_complete and elicitation_response are skipped.

  • UserPromptSubmit: records the turn start time in a per-session state file. No push is sent.

  • Stop: does nothing if stop_hook_active is true. Otherwise it reads the recorded turn start; if that is missing it falls back to the last user prompt timestamp in the transcript (best effort). If the turn lasted at least TMGR_NOTIFY_STOP_MIN_MINUTES (default 5) it sends a normal-priority push titled Claude Code · <project> · done, with the first line of the last assistant message as the body.

<project> is the basename of the working directory.

Every hook subcommand always exits 0 and writes nothing to stdout, so a misconfigured or failing hook never changes the behaviour of Claude Code or Codex. Diagnostics go to stderr only, and the token is never logged.

Codex

Codex's notify hook is user-level only (~/.codex/config.toml); a project's .codex/config.toml cannot set it. Codex runs the program with the event JSON as the final argv argument (not stdin), for the agent-turn-complete event:

notify = ["npx", "-y", "@tmgr/notify", "hook", "codex"]

To also let Codex call the tools directly:

[mcp_servers.tmgr-notify]
command = "npx"
args = ["-y", "@tmgr/notify", "mcp"]
tool_timeout_sec = 660

[mcp_servers.tmgr-notify.env]
TMGR_NOTIFY_TOKEN = "<TMGR_NOTIFY_TOKEN>"

Codex cancels MCP tool calls after 60 s by default, while alarm waits up to 600 s; tool_timeout_sec = 660 keeps the result. Omit the env table to rely on ~/.config/tmgr-notify/env instead of putting the token in config.toml.

hook codex sends a normal-priority push titled Codex · <project> with the first line of last-assistant-message as the body, only when type is agent-turn-complete.

Alarm setup

notify_user is a soft channel. alarm is for incidents that need you now (production down, data loss, security). A silent push triggers an alarm in the mobile app. If you do not acknowledge it in time, or the app never receives it, TMGR places a voice call where pressing 1 acknowledges. One alarm call is one escalation; the calling agent decides whether to retry. Prefer notify_user for everything else.

To make alarms reliable:

  1. In TMGR Settings, set and verify an alarm phone. Without it the voice-call fallback is unavailable and the alarm is push-only; it ends as call_unavailable if the app does not acknowledge it.

  2. Add the TMGR caller number to your contacts or Favorites so Focus / Do Not Disturb lets the call through.

  3. On iOS enable Repeated Calls (Settings → Focus → Do Not Disturb → Allow Calls From) so a second call within 3 minutes gets through.

  4. Install the mobile app and allow alarms (AlarmKit) when prompted.

The voice call repeats until acknowledged: up to callAttempts calls (default 3, the server clamps it to 1-5). Each attempt rings for about 55 s, with about 20 s between attempts. Answering machines are detected and hung up, so voicemail never counts as an acknowledgement.

Tools reference

notify_user

Parameter

Type

Description

title

string, required

1-120 characters.

body

string

Up to 1000 characters.

priority

low | normal | high

Default normal.

link

string

Absolute http(s) URL to open on tap.

Returns the status on success. A rate limit is returned as a tool error with the retry-after seconds.

alarm

Parameter

Type

Description

title

string, required

1-120 characters, read aloud on the call.

message

string, required

1-500 characters, read aloud on the call.

ackTimeoutSeconds

integer

Seconds to wait for an app acknowledgement before calling. The server clamps it to 15-900.

deliveryTimeoutSeconds

integer

Seconds to wait for the app to receive the alarm before calling. The server clamps it to 10-300.

call

boolean

Voice-call fallback, default true. With false the alarm ends expired if not acknowledged in the app.

callAttempts

integer 1-5

Voice-call attempts, default 3. The result includes attempts: made/max.

waitForResult

boolean

Default true. With false it returns {id, status} immediately.

maxWaitSeconds

number, max 3600

Wait cap, default 600. On timeout the last status is returned with timedOut: true.

With waitForResult it long-polls until a final status or maxWaitSeconds, and returns the status, ack channel, call status and alarm id. While waiting, transient network errors, 5xx and 429 responses are retried up to 3 times (1 s, 2 s, 4 s backoff; Retry-After honored, capped at 10 s); 401 and 404 fail at once. If a call times out client-side the alarm keeps running on the server; re-check it with alarm_status.

alarm_status

Parameter

Type

Description

id

string, required

Alarm id returned by alarm.

waitSeconds

integer 0-50

Long-poll seconds, default 0.

Use it after a timeout or after waitForResult: false.

Alarm statuses

Non-final: pending, delivered, calling. Final: acknowledged (channel app or call), no_answer, busy, failed, call_unavailable, expired.

Client tool-call timeouts

A blocking wait can be long, so the client's MCP tool-call timeout matters.

  • Claude Code CLI: MCP_TOOL_TIMEOUT defaults to about 28 hours, so the 600 s default wait fits. A per-server timeout in .mcp.json overrides it as a hard limit that progress notifications do not extend. A stdio call that sends no response and no progress for 30 minutes is aborted.

  • Claude desktop app and Cowork: reported to cancel tool calls at about 60 s regardless of MCP_TOOL_TIMEOUT.

  • While waiting, the server sends notifications/progress after every poll when the client supplies a progressToken. For clients with a short hard cap, pass a small maxWaitSeconds (for example 45) or waitForResult: false, then call alarm_status.

A full default escalation (3 attempts) takes about 6 minutes; 5 attempts take about 9 minutes, so with long timeouts pass a larger maxWaitSeconds.

CLI

npx -y @tmgr/notify send --title "Deploy finished" --body "v1.2.3 is live" --priority high [--link https://example.com]

send runs tmgr notify. Without --body the title is also used as the text, because a push needs a text. Exits non-zero and prints an error on failure (auth, validation, rate limit, timeout, network).

npx -y @tmgr/notify alarm "prod DB is down" --title "Prod down" [--no-wait] [--ack-timeout 90] [--delivery-timeout 30] [--no-call] [--call-attempts 3]

alarm runs tmgr alarm with the same flags. The default title is Alarm. --no-call disables the voice-call fallback. It prints each status change and the final status. The exit code is 0 only when the alarm was acknowledged (with --no-wait, 0 once the alarm is created).

npx -y @tmgr/notify mcp                       # MCP server over stdio (also the default with no arguments)
npx -y @tmgr/notify hook notification|prompt|stop   # Claude Code hooks, payload on stdin
npx -y @tmgr/notify hook codex '<event json>'       # Codex notify, payload as last argument
npx -y @tmgr/notify --help | --version

If you install the tmgr CLI itself you can also call it directly: tmgr mcp, tmgr notify, tmgr alarm, tmgr hook claude <event>, tmgr hook codex.

To test the backend independently of this CLI:

curl -i -X POST https://api.tmgr.dev/api/notifications/push \
  -H "Authorization: Bearer <TMGR_NOTIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"title":"Test","body":"Hello from curl","priority":"normal"}'

Security

  • The token is a bearer secret. Keep it out of repositories, prefer the ~/.config/tmgr-notify/env file (mode 600), and revoke it in TMGR if it leaks.

  • Agents cannot acknowledge alarms. Only you can, in the app or by pressing 1 on the call.

  • Hooks and tools never print the token.

See SECURITY.md for how to report a vulnerability.

Troubleshooting

  • No push arrives and no error is shown: hooks swallow all errors to stderr and always exit 0 by design. Check that your env vars or ~/.config/tmgr-notify/env are visible to the shell that Claude Code or Codex spawns (a login shell profile may not be sourced). Run the matching hook subcommand by hand with sample stdin or argv to see stderr.

  • hook stop never fires: short turns stay silent below TMGR_NOTIFY_STOP_MIN_MINUTES (default 5). Also confirm the UserPromptSubmit hook is configured; without it hook stop only has the transcript fallback, which can be inaccurate.

  • 401 from the API: the token was revoked, or another auth header was sent alongside it (the API allows exactly one auth method).

  • 429 from the API: rate limited per user, 20 per minute by default; retry after the Retry-After value.

  • MCP tool not showing up in Claude Code: check claude mcp list, and remember mcpServers does not go in settings.json.

  • Codex notify does nothing: notify is ignored in a project-local .codex/config.toml; set it in ~/.codex/config.toml. Codex passes the JSON as an argv string, not on stdin.

Development

npm ci
npm test

npm test compiles src/, tools/ and test/ to dist/ and runs the node:test suite without network calls; the end-to-end tests use a fake tmgr. node dist/tools/smoke.js packs the package, installs it with npm and pnpm (--ignore-scripts) into temporary projects, and runs two concurrent first starts that download the real CLI release into an empty cache, then an MCP handshake.

License

The @tmgr/notify package is MIT and contains no tmgr binary. The tmgr CLI is AGPL-3.0-only; tmgr-notify downloads it at run time from the CLI project's own GitHub release, which ships the license and points to the source.

Available Tools

3 tools
alarmA

Only for incidents that need the human NOW (production down, data loss, security). Rings the owner's phone: silent push to the app alarm, then a voice call if not acknowledged. Prefer notify_user for everything else. Calls repeat up to callAttempts times (default 3) until acknowledged. You decide when/whether to retry; one call = one escalation.

Inputs: title (required), message (required), call, callAttempts, +4 more.

alarm_statusA

Check an alarm created by the alarm tool. With waitSeconds (max 50) it long-polls until the status changes. Use it to re-check after the alarm call timed out or was started with waitForResult=false.

Inputs: id (required), waitSeconds.

notify_userC

Send a phone push notification to the user via TMGR agent notifications.

Inputs: title (required), body, link, priority.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 3 tools

    Disambiguation4/5

    notify_user and alarm both push to the user's phone, so there is some surface overlap, but the descriptions sharply delineate when to use each (routine vs. urgent incident escalation). alarm_status is clearly a distinct read/check operation tied to the alarm tool.

    Naming Consistency3/5

    notify_user follows a verb_noun pattern, but alarm is a bare noun and alarm_status is noun_noun. The convention is mixed, though all names remain readable and inferable from their descriptions.

    Tool Count4/5

    Three tools is a tight, well-scoped set for a notification/escalation service: one soft notify, one hard escalation, one status check. It is slightly lean but each tool clearly earns its place.

    Completeness3/5

    The core lifecycle (notify, escalate, check status) is covered, but there is no tool to cancel/resolve an alarm, explicitly acknowledge it, or list past notifications/alarms. Agents must rely on out-of-band phone interaction to close out an escalation.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Human-in-the-loop approvals and notifications for AI agents via WhatsApp. Enables Cursor, Claude Code, and autonomous AI agents to reach users away from their computers.
      196 npm
      ISC
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables voice-driven control of coding agents via Claude on your phone, allowing status checks, stand-ups, sending prompts, and receiving push notifications when agents finish or need decisions.
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI agents to ring or text a user's actual iPhone, read a question or update aloud, and return the spoken answer back to the agent as text. Calls and messages land in a single titled thread per agent, with tools for polling results, waiting for replies, and labeling conversations.
      42
      MIT