agent-notify

agent-notify

Enables AI agents and applications to send audio notifications with text-to-speech, message streaming, agent-to-agent conversations, and web push notifications through a persistent message store and MCP integration.

Category
Visit Server

README

<p align="center"> <img src="logo.png" alt="Agent Notify" width="600"> </p>

<p align="center"> πŸ”” Audio notifications Β Β·Β  πŸ—£οΈ Text-to-speech with per-agent voices Β Β·Β  πŸ”Œ MCP integration<br> πŸ’¬ Message stream Β Β·Β  🀝 Agent-to-agent conversations Β Β·Β  πŸ‘οΈ Multi-window watch mode<br> πŸ“¦ App & CI notifications Β Β·Β  πŸ”„ Sequential queue Β Β·Β  ⌨️ Remote keyboard controls </p>

πŸ“‘ Table of Contents

<a id="features"></a>

✨ Features

  • 🎡 Audio Notifications - Plays distinct sounds for different notification types
  • πŸ—£οΈ Text-to-Speech - Vocalizes notification messages using macOS say command
  • πŸŽ™οΈ Multi-Agent Voice System - Distinct TTS voices per agent role or number
  • πŸ“‚ Project Identification - Identifies which project/workspace a notification came from
  • 🎨 Visual Feedback - Clean console output with emoji-led metadata and dim message text
  • πŸ”Œ MCP Integration - Works seamlessly with Cursor AI and other MCP-compatible tools
  • πŸ“¦ App Notifications - Build tools, CI scripts, and deploy pipelines can fire notifications
  • πŸ”„ Notification Queue - Sequential playback β€” notifications never overlap
  • πŸ’¬ Message Stream - Persistent message store with incremental polling and playback tracking
  • 🀝 Agent Conversations - Orchestrator-driven agent-to-agent audio conversations with turn-taking
  • 🌐 Web UI - Phone-friendly dashboard at localhost:8881 with dark/light theme toggle
  • πŸ“² Web Push Notifications - Native OS notifications via service worker, even with browser closed (requires localhost or HTTPS)
  • πŸ§‘β€πŸ’» Operator Messaging - Human-in-the-loop messages to agents via web UI or API
  • πŸ“Š Log Levels - Configurable audio thresholds for app notifications (console always shows all)
  • ⌨️ Keyboard Control - Spacebar to stop all, S to skip current, M to mute all audio
  • πŸ‘οΈ Watch Mode - Display-only panels that mirror notifications without playing audio
  • πŸ”— Synced Controls - Mute, stop, and skip sync across all panels via remote control endpoints
  • 🌐 HTTP API - RESTful endpoints for external integrations
  • πŸ’Ύ Disk Persistence - Message store survives server restarts

<a id="architecture"></a>

πŸ—οΈ Architecture

Agent (MCP)      ──▢  MCP tool "notify"  ──▢  HTTP /notify/agent  ──┐
                                                                     β”œβ”€β”€β–Ά  message store  ──▢  notification queue  ──▢  sequential playback
Agent (HTTP/CLI) ──▢  HTTP /notify/agent  ───────────────────────────
                                                                     β”‚
App (HTTP/CLI)   ──▢  HTTP /notify/app  β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Agent (MCP)      ──▢  MCP tool "get_messages"                ──▢  HTTP /messages                       ──▢  message store (read)
Agent (MCP)      ──▢  MCP tool "check_message_status"       ──▢  HTTP /messages/status                ──▢  counters only (read)
Agent (MCP)      ──▢  MCP tool "check_responses_available"  ──▢  HTTP /responses/available/for/id/:id  ──▢  message store (count)
Agent (MCP)      ──▢  MCP tool "check_responses_observed"   ──▢  HTTP /responses/observed/for/id/:id   ──▢  message store (count)
  • /notify/agent β€” for all AI agent notifications (MCP, HTTP, or CLI). Always plays audio and logs to console.
  • /notify/app β€” for all application notifications (HTTP or CLI). Subject to log level thresholds.
  • /messages β€” query the persistent message stream. Supports incremental polling and playback tracking.
  • /responses/available/for/id/:id β€” count responses to a message (bus mode, ~5 tokens)
  • /responses/observed/for/id/:id β€” count observed responses (conversational mode, ~5 tokens)
  • Five MCP tools β€” notify, get_messages, check_message_status, check_responses_available, check_responses_observed.
  • One CLI β€” notify command. If --app flag is present β†’ /notify/app; otherwise β†’ /notify/agent.
  • One queue β€” both endpoints feed into the same FIFO queue. Sequential playback, no overlap.
  • One message store β€” every notification is persisted. Survives server restarts.

<a id="notification-types"></a>

πŸ”” Notification Types

πŸ€– Agent Types

Type Emoji Description Use Case
done βœ… Task completion Successful operations
error ❌ Error occurred Failed operations
question ❓ Need user input Waiting for decisions
permission πŸ” Need authorization Requiring user approval
status πŸ“‘ Progress update Ongoing operations
waiting ⏳ Processing Long-running tasks
review πŸ‘οΈ Code review needed File changes ready
message πŸ’¬ Agent conversation Agent-to-agent dialogue

πŸ“¦ App Log Levels

Level Emoji Sound Use Case
debug πŸ› (none) Verbose debug info
info ℹ️ status.mp3 General information, progress updates
warn ⚠️ waiting.mp3 Warnings, deprecations, non-critical issues
error ❌ error.mp3 Failures, crashes, critical issues
success βœ… done.mp3 Build complete, tests passed, deploy finished

<a id="installation"></a>

πŸ“₯ Installation

# Clone the repository
git clone <repository-url>
cd agent-notify

# Install globally
npm install -g

# Link globally for customization
npm link

<a id="configuration"></a>

βš™οΈ Configuration

<a id="server-connection-url"></a>

🌐 Server Connection URL

By default, the notification clients (CLI and MCP) connect to http://localhost:8881. To use a different server address, set the AGENT_NOTIFY_URL environment variable.

For CLI Usage

# Set for current shell session
export AGENT_NOTIFY_URL="http://192.168.0.6:8881"
notify done "Task complete"

# Set for single command
AGENT_NOTIFY_URL="http://192.168.0.6:8881" notify done "Task complete"

# Add to ~/.bashrc or ~/.zshrc for persistence
echo 'export AGENT_NOTIFY_URL="http://192.168.0.6:8881"' >> ~/.bashrc

For MCP (Cursor) Usage

Add the env block to your Cursor settings.json:

{
  "mcpServers": {
    "agent-notify": {
      "command": "notify-mcp",
      "env": {
        "AGENT_NOTIFY_URL": "http://192.168.0.6:8881"
      }
    }
  }
}

Finding Your Server's IP Address

# macOS
ipconfig getifaddr en0    # WiFi
ipconfig getifaddr en1    # Ethernet

# Linux
hostname -I

# The server prints its address on startup:
# πŸ“‘ Listening on http://0.0.0.0:8881

Troubleshooting

Issue Solution
Connection refused Check that the server is running (npm start) and the URL is correct
Wrong IP address Use the commands above to find your server's IP, then set AGENT_NOTIFY_URL
Port already in use The server auto-switches to watch mode. Or use a different port: node lib/server.mjs --address 0.0.0.0:9000
Cross-machine access Ensure the server uses 0.0.0.0 (default) not localhost

<a id="notification-links-app-only"></a>

πŸ”— Notification Links (App Only)

App notifications can include an optional clickable link (e.g., to a CI build, deploy dashboard, or health check endpoint). The link appears as a third line in the server terminal output and is not spoken via TTS.

Security Note: Links are deliberately excluded from agent notifications. AI agents are untrusted URL sources β€” allowing models to inject arbitrary clickable URLs creates a phishing/malicious link surface. Links are only available for app notifications, which come from user-controlled code.

CLI Usage

# Attach a dashboard link
notify success "Deploy complete" --app my-api --link https://my-api.example.com/health

# Attach a CI build link
notify error "Build failed" --app github-actions --link https://github.com/user/repo/actions/runs/12345

# Links are optional
notify info "Starting deploy..." --app deploy

HTTP API Usage

curl "http://localhost:8881/notify/app?type=success&message=Deploy%20complete&app=my-api&url=https://my-api.example.com/health"

Server Terminal Output

βœ… SUCCESS πŸ“¦ my-api
"Deploy complete"
πŸ”— https://my-api.example.com/health

The URL is automatically clickable in most terminals (iTerm2, VS Code terminal, Hyper, etc.).

<a id="usage"></a>

πŸš€ Usage

<a id="command-line-interface"></a>

πŸ’» Command Line Interface

# Agent notification (type and message only)
notify done "Task completed successfully"
notify error "Something went wrong"
notify question "Do you want to continue?"

# Agent with project identification
notify done "Build complete" --workspace-dir /Users/user/repos/my-app

# Agent multi-agent (orchestrator)
notify done "All tasks complete" --workspace-dir /Users/user/repos/my-app --agent-role Orchestrator --agent-number 0

# Agent subagent with full context
notify done "Build complete" --workspace-dir /Users/user/repos/my-app --agent-role Coder --agent-number 2 --model claude-4.6-sonnet

# Agent override TTS voice
notify status "Processing..." --voice Nathan

# App notification
notify success "Build complete" --app webpack
notify error "3 tests failed" --app jest
notify info "Starting deploy..." --app deploy
notify debug "Cache hit ratio 95%" --app webpack

🏁 CLI Flags

Flag HTTP Query Param Description
(positional 1) type Notification type or app log level (required)
(positional 2) message Message text (required)
--workspace-dir workspaceDir Full workspace path β€” project name derived from last segment (agent notifications only)
--agent-role agentRole Agent role name (e.g., "Coder", "Orchestrator") (agent notifications only)
--agent-number agentNumber Agent number (Orchestrator = 0, subagents = 1, 2, 3...) (agent notifications only)
--voice voice TTS voice override
--model model Your exact model identifier (e.g., "claude-4.6-opus-high") (agent notifications only)
--app app App name β€” routes to /notify/app endpoint
--project project Project name (app notifications only)
--detail detail Short context info, e.g. file path or count (app notifications only)
--link url Attach a clickable link to app notification (app notifications only, not spoken)

<a id="mcp-integration-cursor-ai"></a>

πŸ”Œ MCP Integration (Cursor AI)

Add to your Cursor settings (settings.json):

{
  "mcpServers": {
    "agent-notify": {
      "command": "notify-mcp"
    }
  }
}

Then configure the notification rules:

Option 1: Project-specific - Copy the rules from .cursorrules to your project's .cursorrules file

Option 2: Global - Add the rules from .cursorrules globally in: Settings > Rules & Commands > Add to use across all projects

πŸ“ MCP Tool Schema

mcp_agent-notify_notify({
  type: "done",                               // Required: notification type
  message: "Build complete",                  // Required: message text
  workspaceDir: "/Users/user/repos/my-app",   // Optional: Workspace Path from <user_info>
  agentRole: "Coder",                         // Optional: agent role name
  agentNumber: 2,                             // Optional: agent number (0 = orchestrator)
  voice: "Nathan",                            // Optional: TTS voice override
  model: "claude-4.6-sonnet",                  // Required: exact model identifier (console log only)
  to: "Reviewer",                             // Optional: recipient for agent conversations
  response_to: 225                            // Optional: message ID this is a reply to
})

Note: The MCP tool is exclusively for agents. App notifications should use the CLI (--app flag) or HTTP API (/notify/app) directly.

πŸ“‹ MCP Parameter Descriptions

Parameter Type Required Description
type string Yes Notification type: question, permission, done, error, status, waiting, review, message
message string Yes Message to vocalize
workspaceDir string No The Workspace Path from <user_info>. Used to identify which project this notification is from.
agentRole string No Agent role name assigned by orchestrator (e.g., "Coder", "Reviewer"). The orchestrator itself should use "Orchestrator".
agentNumber integer No Agent number assigned by orchestrator. Orchestrator = 0, subagents = 1, 2, 3, etc.
voice string No Override the TTS voice for this notification. If omitted, the server selects a voice based on agentRole or agentNumber.
model string Yes Your exact model identifier as shown in system info (e.g., "claude-4.6-opus-high", "gpt-4o-2025-03"). Console log only.
to string No Agent role or name this message is directed to (e.g., "Reviewer", "Coder"). Used for agent-to-agent conversations. Display/filtering only β€” does not route messages.
response_to integer No Message ID this is a reply to. Enables lightweight response polling via check_responses_available and check_responses_observed.

πŸ“¬ MCP get_messages Tool

Poll the persistent message stream for notifications. Supports incremental polling via since_id.

mcp_agent-notify_get_messages({
  since_id: 42,          // Optional: only messages after this ID (0 for initial fetch)
  limit: 50,             // Optional: max messages to return (default 50, max 200)
  type: "message",       // Optional: filter by notification type
  to: "Coder",           // Optional: filter by recipient
  project: "my-app",     // Optional: filter by project name
  source: "agent",       // Optional: filter by source ("agent" or "app")
  agentRole: "Reviewer", // Optional: filter by agent role
  agentNumber: 2,        // Optional: filter by agent number
  model: "claude-opus",  // Optional: filter by model
  voice: "Samantha",     // Optional: filter by TTS voice
  app: "webpack"         // Optional: filter by app name
})

Response:

{
  "messages": [
    {
      "id": 47,
      "timestamp": "2025-03-01T04:40:07.000Z",
      "prevHash": "a3f2c1e809b7d4f2",
      "source": "agent",
      "type": "message",
      "message": "Build complete",
      "project": "my-app",
      "agentRole": "Coder",
      "agentNumber": 1,
      "model": "claude-opus-4-6",
      "voice": "Nathan",
      "to": "Reviewer"
    }
  ],
  "latest_id": 47,
  "last_played_id": 47
}
  • latest_id β€” highest message ID in the store (use as since_id for next poll)
  • last_played_id β€” highest message ID whose audio has finished playing (derived from internal played events)
  • Played events (type: "played") are filtered out of results β€” they are internal bookkeeping

⚑ MCP check_message_status Tool

ALWAYS use check_message_status for turn-taking and playback polling β€” NEVER use get_messages for this. Returns ~30 tokens instead of ~400-600, saving thousands of tokens over a conversation.

mcp_agent-notify_check_message_status({
  since_id: 46   // Optional: check for messages newer than this ID
})

Response:

{
  "latest_id": 47,
  "last_played_id": 45,
  "muted": false,
  "has_new": true,
  "queue_length": 2,
  "agents": [
    {
      "project": "my-app",
      "agentRole": "Coder",
      "agentNumber": 1,
      "model": "claude-opus-4-6",
      "voice": "Nathan",
      "to": "Reviewer",
      "latestId": 47,
      "played": false
    }
  ]
}
  • latest_id β€” highest message ID in the store
  • last_played_id β€” highest message ID whose audio has finished playing
  • has_new β€” true if latest_id > since_id
  • queue_length β€” number of notifications waiting in the audio queue
  • agents β€” deduplicated array of agents that have posted since since_id. Each agent is identified by the composite key project + agentRole + agentNumber. Fields model, voice, and to reflect the agent's most recent message. latestId is that message's ID, and played is true if its audio has finished.

Only use get_messages when you need actual message content.

⚑ MCP check_responses_available Tool

Check if any responses have been sent to a specific message. Returns a count. Use for bus-mode polling β€” you only need to know if a reply exists, regardless of whether the human has heard it.

mcp_agent-notify_check_responses_available({
  id: 225   // Required: the message ID to check for responses to
})

Response: {"n":1} (~5 tokens)

⚑ MCP check_responses_observed Tool

Check if any responses to a specific message have been heard by the human (audio played). Returns a count. Use for conversational-mode polling β€” wait for the human to actually hear the reply before proceeding.

mcp_agent-notify_check_responses_observed({
  id: 225   // Required: the message ID to check for observed responses to
})

Response: {"n":0} (~5 tokens)

<a id="http-api"></a>

🌐 HTTP API

Start the notification server:

# Default (listens on 0.0.0.0:8881 - accessible from network)
npm start

# With custom log levels for app notifications
node lib/server.mjs --log-level debug --log-level-audio warn

# Cross-network access (recommended for SSH/remote projects)
node lib/server.mjs --address 0.0.0.0:8881

# Custom IP and port
node lib/server.mjs --address 192.168.1.100:8881

# Custom port only (uses 0.0.0.0 as host)
node lib/server.mjs --address 9000

# Localhost only (NOT accessible from other machines)
node lib/server.mjs --address localhost:8881

# Watch mode β€” display only, no audio (auto-detects or explicit)
node lib/server.mjs --watch

# Custom store directory
node lib/server.mjs --store /path/to/store-dir

# Skip startup confirmation prompt
node lib/server.mjs --yes

# Clear message history and start fresh
node lib/server.mjs --clear

🌍 Network Access:

  • 0.0.0.0 - 🌐 Accessible from any machine on your network (recommended)
  • localhost/127.0.0.1 - 🏠 Only accessible from the same machine
  • Specific IP - 🎯 Only accessible via that network interface

Send notifications via HTTP:

# Agent notification
curl "http://localhost:8881/notify/agent?type=done&message=Build%20complete&model=claude-4.6-opus-high"

# Agent with full context
curl "http://localhost:8881/notify/agent?type=done&message=Build%20complete&workspaceDir=/Users/user/repos/my-app&agentRole=Coder&agentNumber=2&model=claude-4.6-sonnet"

# App notification
curl "http://localhost:8881/notify/app?type=success&message=Build%20complete&app=webpack"

# App notification with link
curl "http://localhost:8881/notify/app?type=success&message=Deploy%20complete&app=my-api&url=https://my-api.example.com/health"

# App debug (only shown if --log-level allows it)
curl "http://localhost:8881/notify/app?type=debug&message=Cache%20hit%20ratio%2095%25&app=webpack"

πŸ€– /notify/agent Parameters

Parameter Required Description
type Yes Notification type (question, permission, done, error, status, waiting, review, message)
message Yes Message text
model Yes Exact model identifier (e.g., "claude-4.6-opus-high")
workspaceDir No Full workspace path (project name derived from last segment)
agentRole No Agent role name
agentNumber No Agent number
voice No TTS voice override
to No Recipient agent role/name (for agent conversations, display/filtering only)
response_to No Message ID this is a reply to. Enables lightweight response polling.

πŸ“¦ /notify/app Parameters

Parameter Required Description
type Yes Log level (trace, debug, info, warn, error, success)
message Yes Message text
app Yes App name (e.g., "webpack", "jest", "github-actions")
project No Project name (e.g., "my-app")
detail No Short context that doesn't belong in the message (e.g., "src/auth", "3 files")
voice No TTS voice override
url No URL to attach as clickable link (not spoken, visual only)

πŸ§‘β€πŸ’» POST /notify/operator (JSON body)

Field Required Description
message Yes Message text
to No Target agent role (e.g., "Coder")
project No Target project name
voice No TTS voice override (default: Daniel)
curl -X POST http://localhost:8881/notify/operator \
  -H 'Content-Type: application/json' \
  -d '{"message":"Focus on auth","to":"Coder"}'

πŸ“¬ /messages Parameters

Parameter Required Description
since_id No Return messages with ID greater than this (0 for initial fetch)
limit No Max messages to return (default 50, max 2000)
type No Filter by notification type
to No Filter by recipient agent role/name
project No Filter by project name
source No Filter by source ("agent", "app", or "operator")
agentRole No Filter by agent role
agentNumber No Filter by agent number
model No Filter by model identifier
voice No Filter by TTS voice
app No Filter by app name
response_to No Filter to messages that are replies to this message ID
# Get all recent messages
curl "http://localhost:8881/messages"

# Incremental poll (only new messages since ID 42)
curl "http://localhost:8881/messages?since_id=42"

# Filter by type and recipient
curl "http://localhost:8881/messages?type=message&to=Coder"

πŸ”„ /responses/available/for/id/:id

Count responses to a message (bus mode β€” all sent, regardless of playback).

Parameter Required Description
:id (path) Yes Message ID to check for responses to
curl "http://localhost:8881/responses/available/for/id/225"
# β†’ {"n":1}

πŸ”„ /responses/observed/for/id/:id

Count observed responses to a message (conversational mode β€” only those whose audio has been played).

Parameter Required Description
:id (path) Yes Message ID to check for observed responses to
curl "http://localhost:8881/responses/observed/for/id/225"
# β†’ {"n":0}

<a id="programmatic-usage"></a>

βš™οΈ Programmatic Usage

import { execSync } from 'child_process';

// Agent notification
execSync('notify done "Operation completed" --model claude-4.6-opus-high');

// Agent with workspace context
execSync('notify done "Build finished" --workspace-dir /Users/user/repos/my-app --model claude-4.6-opus-high');

// App notification
execSync('notify success "Build complete" --app webpack');

<a id="app-notifications"></a>

πŸ“¦ App Notifications

App notifications allow build tools, CI scripts, deploy pipelines, test runners, and any other application to fire notifications alongside agent notifications.

<a id="app-log-levels"></a>

πŸ“Š App Log Levels

Apps use logger-style levels instead of agent notification types:

Level Sound Emoji Use Case
trace (none) πŸ”¬ Fine-grained tracing, function entry/exit
debug (none) πŸ› Verbose debug info
info status.mp3 ℹ️ General information, progress updates
warn waiting.mp3 ⚠️ Warnings, deprecations, non-critical issues
error error.mp3 ❌ Failures, crashes, critical issues
success done.mp3 βœ… Build complete, tests passed, deploy finished

Hierarchy (lowest to highest): trace < debug < info < warn < error < success

<a id="log-level-configuration"></a>

🎚️ Log Level Configuration

Two server flags control which app notifications get audio (sound + TTS). All app messages are always shown in the terminal console and the web UI regardless of these flags.

Flag Default Description
--log-level info Minimum level for audio playback. Below this, the notification is logged and stored but silent.
--log-level-audio info Secondary audio threshold. Both flags must be met for audio to play.

πŸ’‘ Examples:

# Default: all levels visible, info+ gets audio
node lib/server.mjs

# Only hear warnings and above
node lib/server.mjs --log-level warn

# Hear everything including trace and debug
node lib/server.mjs --log-level trace --log-level-audio trace

# Only hear errors and successes
node lib/server.mjs --log-level error

⚠️ Important: Log level flags only control audio for app notifications. Console output and web UI always show all levels. Agent notifications always play audio regardless of these settings.

πŸ”— Example Integrations

πŸ“¦ npm scripts (package.json)

{
  "scripts": {
    "build": "webpack --mode production",
    "postbuild": "notify success 'Build complete' --app webpack",
    "test": "jest",
    "posttest": "notify success 'Tests passed' --app jest"
  }
}

🐚 Shell script

#!/bin/bash
notify info "Starting deploy..." --app deploy
npm run build
if [ $? -eq 0 ]; then
  notify success "Deploy successful" --app deploy --link https://my-api.example.com/health
else
  notify error "Deploy failed" --app deploy --link https://github.com/user/repo/actions/runs/12345
fi

🌐 curl (HTTP)

# App notification with project, detail, and link
curl "http://localhost:8881/notify/app?\
type=success&message=Pipeline%20complete\
&app=github-actions\
&project=my-app\
&detail=deploy-prod\
&url=https://github.com/user/repo/actions/runs/12345"

# App notification without project
curl "http://localhost:8881/notify/app?\
type=success&message=Pipeline%20complete\
&app=github-actions"

# Agent notification (links not supported for security)
curl "http://localhost:8881/notify/agent?type=done&message=Build%20complete&model=claude-4.6-opus-high"

<a id="notification-queue"></a>

πŸ”„ Notification Queue

When multiple notifications arrive simultaneously (from parallel agents, apps, or a mix), a server-side FIFO queue ensures they play sequentially β€” one at a time, never overlapping. All callers receive an immediate response.

βš™οΈ Queue Behavior

  1. Every notification is queued β€” when a request arrives, it's added to the end of the queue
  2. Sequential playback β€” only one notification plays at a time (sound + TTS). The next one starts only after the previous one completes
  3. Immediate response β€” the server always responds immediately with { success: true, queued: true, position: N }
  4. Log level filtering β€” app notifications below the --log-level-audio threshold are logged to console but not enqueued (no audio)

<a id="multi-window--multi-agent-support"></a>

πŸͺŸ Multi-Window & Multi-Agent Support

When running multiple Cursor windows and parallel agents, the notification system identifies the source of each notification through project name, agent role, and agent number.

<a id="console-log-format"></a>

πŸ“‹ Console Log Format

πŸ€– Agent Notifications

Emoji-led format with notification type capitalized. Message displayed in dim text on its own line, followed by a blank line separator. Optional fields omitted when not provided:

# Orchestrator (full):
βœ… DONE πŸ“‚ my-app πŸ€– Orchestrator #0 🧠 claude-4.6-opus-high
"All tasks complete"

# Subagent (full):
βœ… DONE πŸ“‚ my-app πŸ€– Coder #2 🧠 claude-4.6-sonnet
"Build complete"

# Solo agent (with workspaceDir):
βœ… DONE πŸ“‚ my-app 🧠 claude-4.6-opus-high
"Build complete"

# Solo agent (no workspaceDir):
βœ… DONE 🧠 claude-4.6-opus-high
"Build complete"

πŸ“¦ App Notifications

ℹ️ INFO πŸ“¦ webpack πŸ“‚ my-app βš™οΈ src/index.ts
"Build started"

βœ… SUCCESS πŸ“¦ webpack πŸ“‚ my-app
"Build complete in 4.2s"

❌ ERROR πŸ“¦ jest πŸ“‚ my-app βš™οΈ auth.test.ts
"3 tests failed"

⚠️ WARN πŸ“¦ eslint
"12 warnings found"

πŸ› DEBUG πŸ“¦ webpack
"Module resolution: ./src/index.ts β†’ ./dist/index.js"

# With optional link (3-line format):
βœ… SUCCESS πŸ“¦ my-api πŸ“‚ my-api
"Deploy complete"
πŸ”— https://my-api.example.com/health
  • πŸ“¦ app name always shown
  • πŸ“‚ project folder shown when workspaceDir is provided
  • βš™οΈ detail shown when detail is provided
  • No model field (apps don't have models)

<a id="tts-spoken-order"></a>

πŸ—£οΈ TTS Spoken Order

The notification sound and TTS speech run independently and in parallel. The sound fires immediately, and TTS begins after a 500ms delay. The spoken order matches the screen reading order β€” parts are omitted when not provided:

πŸ€– Agent Spoken Order

  1. Message type (always included, e.g., "done", "question")
  2. Project name (from workspaceDir last segment β€” omitted if not provided)
  3. Agent role (if provided, e.g., "Coder")
  4. Agent number (if provided, e.g., "Agent 2" or "Agent Zero" for orchestrator)
  5. Message text (always included)

Examples:

  • "done, my-app, Coder, Agent 2, Build complete" β€” full context
  • "done, my-app, Build complete" β€” solo agent with workspaceDir
  • "done, Build complete" β€” solo agent, no workspaceDir

πŸ“¦ App Spoken Order

  1. Log level (e.g., "success", "error")
  2. App name (e.g., "webpack")
  3. Project name (omitted if not provided)
  4. Detail (omitted if not provided)
  5. Message text

Examples:

  • "success, webpack, my-app, src/auth, Build complete" β€” full context
  • "success, webpack, my-app, Build complete" β€” with project, no detail
  • "success, webpack, Build complete" β€” minimal

Note: The optional url parameter is not spoken via TTS. URLs are visual-only in the console output.

Examples:

  • "success, webpack, Build complete in 4.2 seconds"
  • "error, jest, 3 tests failed"

<a id="agent-zero-convention"></a>

πŸ€– Agent Zero Convention

When using an orchestrator with multiple subagents:

  • Orchestrator = agentRole="Orchestrator", agentNumber=0 β†’ spoken as "Orchestrator, Agent Zero"
  • Subagent 1 = agentRole="Coder", agentNumber=1 β†’ spoken as "Coder, Agent 1"
  • Subagent 2 = agentRole="Reviewer", agentNumber=2 β†’ spoken as "Reviewer, Agent 2"

<a id="message-stream"></a>

πŸ’¬ Message Stream

Every notification sent via notify is stored in a persistent message stream. Use the /messages endpoint (or get_messages MCP tool) to query the stream for monitoring, polling, or reading conversation history.

<a id="incremental-polling"></a>

πŸ”„ Incremental Polling

Use since_id for efficient incremental polling:

  1. First fetch β€” pass since_id=0 to get recent messages
  2. Note latest_id from the response
  3. Subsequent polls β€” pass since_id=<latest_id> to get only new messages
# Initial fetch
curl "http://localhost:8881/messages?since_id=0"
# β†’ { "messages": [...], "latest_id": 42, "last_played_id": 42 }

# Next poll β€” only new messages
curl "http://localhost:8881/messages?since_id=42"

<a id="playback-tracking"></a>

🎧 Playback Tracking

The message stream tracks audio playback state:

  • last_played_id β€” the highest message ID whose audio has finished playing (derived from internal played events)

Use this to know when a message has been heard before sending the next one. This is the foundation for the turn-taking protocol.

<a id="agent-to-agent-conversations"></a>

🀝 Agent-to-Agent Conversations

The orchestrator creates audio conversations by sending notify on behalf of different agents. The user hears each agent in a distinct TTS voice β€” the conversation unfolds live through audio.

The orchestrator drives the conversation. Individual agents don't need to independently poll the stream β€” the orchestrator:

  • Decides what each agent says and when
  • Sends notify using each agent's agentRole and agentNumber
  • Waits for each message to finish playing before sending the next

Agents can independently poll get_messages for cross-tool scenarios (e.g., bridging Cursor and Claude Code agents via the shared message stream).

<a id="turn-taking-protocol"></a>

⏳ Turn-Taking Protocol

The orchestrator must wait for each message to finish playing before sending the next. Without this, messages queue up faster than audio can play and the conversation loses its natural pacing.

Flow:

  1. Send on behalf of an agent β€” note the returned id:

    notify(type="message", to="Reviewer", message="...", agentRole="Coder", agentNumber=1) β†’ id: 47
    
  2. Wait for audio to finish β€” poll check_message_status until last_played_id >= 47:

    check_message_status(since_id=46) β†’ { last_played_id: 46, has_new: true }  # still playing
    check_message_status(since_id=46) β†’ { last_played_id: 47, has_new: true }  # done β€” send next turn
    
  3. Send the next turn on behalf of the other agent, only after the previous message has been played.

Key details:

  • The orchestrator waits for each message's id, not for the queue to be empty. Multiple conversations can run simultaneously without blocking each other.
  • When the user skips audio (spacebar), all queued messages are marked as played immediately, so the orchestrator proceeds without getting stuck.
  • Use type="message" for conversation turns; reserve other types for their intended purpose.
  • The to parameter indicates who the message is addressed to (for display/filtering) β€” it does not route or deliver messages.

Lightweight Turn-Taking (response polling)

For agent-to-agent conversations where each agent independently polls for replies:

  1. Agent A sends with response_to pointing at the message it's replying to:

    notify(type="message", message="...", response_to=225) β†’ id: 226
    
  2. Agent B polls for available or observed responses:

    check_responses_observed(id=226) β†’ {"n":0}  # not heard yet
    check_responses_observed(id=226) β†’ {"n":1}  # reply heard β€” proceed
    
  3. Agent B reads the reply (if content needed):

    get_messages(response_to=226)
    

This uses ~5 tokens per poll instead of ~50-100 with check_message_status.

<a id="voice-system"></a>

πŸŽ™οΈ Voice System

The server selects a TTS voice using a triple fallback strategy:

  1. Voice override β€” If voice param is provided, use it directly (highest priority)
  2. Role-based map β€” If agentRole matches a role in the map, use that voice
  3. Index-based map β€” If agentNumber matches an index in the map, use that voice
  4. System default β€” Use macOS default voice

<a id="voice-maps"></a>

πŸ—ΊοΈ Voice Maps

Agent Role Voice Region
Orchestrator System default -
Coder Nathan America
Reviewer Samantha America
Tester Karen Australia
Designer Zoe America
Researcher Serena America
Debugger Lee America
DevOps Evan America
Writer Matilda America
Planner Catherine Australia
Security Ava America
Refactorer Siri 1 America
Analyst Siri 2 America
Migrator Siri 3 America
Agent Number Voice Region
0 System default -
1 Nathan America
2 Samantha America
3 Karen Australia
4 Zoe America
5 Serena America
6 Lee America
7 Evan America
8 Matilda America
9 Catherine Australia
10 Ava America
11 Siri 1 America
12 Siri 2 America
13 Siri 3 America

Voice maps are configured server-side in lib/server.mjs for centralized management.

App notifications use Lee (Australian male) as the default voice to distinguish them from the predominantly American agent voices. This can be overridden with the voice parameter.

<a id="watch-mode"></a>

πŸ‘οΈ Watch Mode

Watch mode lets you open additional terminal panels that mirror all notifications without playing audio. Useful for monitoring from multiple windows or screens.

Starting Watch Mode

Watch mode activates automatically or explicitly:

# Auto-detect β€” if port is already in use, switches to watch mode
npm start

# Explicit β€” skip port binding, go straight to watch mode
node lib/server.mjs --watch

When auto-detected, you'll see:

⚠️  Port 8881 already in use β€” switching to watch mode

Watch mode polls the primary server's /messages endpoint every second and renders new notifications with the same colored formatting.

Synced Controls

Keyboard controls work from any panel β€” watch mode sends commands to the primary server via POST /controls/* endpoints, and the action is broadcast to all panels through the message stream:

Endpoint Action
POST /controls/stop Stop all audio and clear the queue
POST /controls/skip Skip the current notification
POST /controls/mute Toggle mute for all audio

The /messages response includes a muted field so all panels stay in sync with the current mute state.

What Watch Mode Does NOT Do

  • No audio playback β€” display only
  • No notification queue β€” read-only polling
  • Never loads, reads, or writes the message store β€” completely passive
  • Never shows store confirmation prompts β€” store decisions are server-only
  • Never writes to /notify/* β€” read-only

<a id="keyboard-controls"></a>

⌨️ Keyboard Controls

These controls work on both the primary server and any watch mode panel. In watch mode, keypresses are forwarded to the primary server and the resulting action syncs to all connected panels.

Key Action
Spacebar Stop current audio AND clear the entire queue (discard all pending notifications)
S Skip current notification, move to the next one in the queue
M Toggle mute for all audio (agents and apps). Notifications still logged to console.
Ctrl+C Exit the server (or watch mode panel)

<a id="sound-files"></a>

🎡 Sound Files

The system uses predefined sound files located in the sounds/ directory:

  • 🎡 done.mp3 - Success sound (also used for app success)
  • πŸ”” error.mp3 - Error alert (also used for app error)
  • ❓ question.mp3 - Question prompt
  • πŸ” permission.mp3 - Authorization request
  • πŸ“‘ status.mp3 - Status update (also used for app info)
  • ⏳ waiting.mp3 - Processing sound (also used for app warn)

<a id="message-persistence"></a>

πŸ’Ύ Message Persistence

The message store lives in a .agent-notify/ directory as an append-only JSONL file (messages.jsonl). Each message is written immediately β€” zero data loss on crash.

  • Messages survive server restarts β€” the last 10,000 lines are loaded into memory on startup
  • Hash chain integrity β€” each message stores a prevHash linking to the previous message; verified on startup to detect corruption
  • Playback as events β€” playback state is tracked via append-only played events (no mutations)
  • Startup safety β€” timestamped backup created on each startup; CLI confirmation prompt before accepting the store (--yes to skip)
  • Crash-safe β€” no periodic flush, no full-file rewrites; every message is appended immediately
  • Store directory β€” defaults to .agent-notify/ in project root; configurable via --store <dir> or $AGENT_NOTIFY_STORE env var
  • Clear history β€” --clear flag deletes the store after confirmation (--yes --clear to skip prompt)
  • Auto-migration β€” both .message-store.json (old blob format) and .message-store.jsonl (old flat file) are migrated into .agent-notify/ automatically on first run
  • Watch mode safety β€” only the primary server (port-bound process) loads or writes the store; watch mode and EADDRINUSE fallbacks never touch the store, never show prompts
  • Sticky store link β€” clickable OSC8 link to the store file always visible at the bottom of the terminal

<a id="development"></a>

πŸ› οΈ Development

πŸ“ Project Structure

agent-notify/
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ notify.mjs      # CLI interface
β”‚   β”œβ”€β”€ mcp.mjs         # MCP server (notify, get_messages, check_message_status, check_responses_available, check_responses_observed)
β”‚   └── server.mjs      # HTTP server (queue, endpoints, message store, TTS)
β”œβ”€β”€ sounds/             # Audio files
β”œβ”€β”€ .agent-notify/            # Store directory (auto-generated, gitignored)
β”‚   β”œβ”€β”€ messages.jsonl        # Append-only message stream
β”‚   └── messages.jsonl.meta   # Sidecar metadata
β”œβ”€β”€ package.json
└── README.md

πŸš€ Running the Server

# Start the notification server (default settings)
npm start

# Server runs on http://0.0.0.0:8881

# With custom log levels
node lib/server.mjs --log-level debug --log-level-audio warn

πŸ§ͺ Testing

# Test agent notification
notify done "Test complete" --model claude-4.6-opus-high

# Test agent with project context
notify done "Test complete" --workspace-dir /Users/user/repos/test-project --model claude-4.6-opus-high

# Test agent multi-agent
notify done "Task finished" --workspace-dir /Users/user/repos/test-project --agent-role Coder --agent-number 1 --model claude-4.6-sonnet

# Test app notification
notify success "Build complete" --app webpack
notify error "Tests failed" --app jest
notify info "Deploying..." --app deploy
notify warn "Deprecation warning" --app eslint
notify debug "Verbose output" --app webpack

# Test all agent notification types
notify done "Test complete" --model claude-4.6-opus-high
notify error "Test error" --model claude-4.6-opus-high
notify question "Test question" --model claude-4.6-opus-high
notify permission "Test permission" --model claude-4.6-opus-high
notify status "Test status" --model claude-4.6-opus-high
notify waiting "Test waiting" --model claude-4.6-opus-high

# Test via HTTP
curl "http://localhost:8881/notify/agent?type=done&message=Test&model=test"
curl "http://localhost:8881/notify/app?type=success&message=Test&app=test"

<a id="requirements"></a>

πŸ“‹ Requirements

  • 🍎 macOS (uses afplay and say commands)
  • 🟒 Node.js 18+
  • πŸ”Š Audio output capability

<a id="license"></a>

πŸ“„ License

See LICENSE.md for details.

<a id="author"></a>

πŸ‘€ Author

F1LT3R

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured