BullseyeNotify
Your AI agent reaches you on desktop, Telegram, Slack, email, SMS, ntfy, Discord or
Teams — and you answer back. Configure all of it from a panel inside VS Code.
What it does
An agent that works for ten minutes and then sits waiting for you is wasting both of
you. BullseyeNotify gives it a way out: an MCP server that delivers a message on
whatever channel you actually read, waits for your reply when it needs a decision, and
shuts up when you are sitting at the keyboard.
The extension is the editor-side face of it. The notification server itself is the
omni-notify-mcp npm package, shared by every window and every agent on the machine.
What the extension adds
- A config panel in the activity bar. The full web UI, framed in a webview — every
channel card, every test button, the live activity log and the connected-clients tab,
without leaving the editor.
- Server lifecycle. On activation it checks
http://localhost:3737/v1/health, and
if nothing answers it spawns the server detached (with ENABLE_MCP=1 so the MCP
endpoint is live, and with the browser pop suppressed since the UI is right here). It
waits up to 15 s for the port. It never kills the server on deactivate — other windows
and the MCP bridge share it.
- A status-bar bell that reflects real state, refreshed every 10 s: server down,
master mute on, this window muted, or normal. Click it to focus the panel.
- Per-window mute. One command toggles every agent client belonging to this
editor window, so a noisy workspace goes quiet without silencing the others. Requires
the host to expose
CLAUDE_CODE_SESSION_ID.
- Readable client names. The extension knows this window's real workspace — the MCP
bridge cannot — so it registers
session id → workspace name with the server. Two
windows on two projects then show up as two named clients instead of two
indistinguishable sessions.
- First-run MCP wiring. Once, silently, it adds
notify to mcpServers in
~/.claude.json as npx -y omni-notify-mcp. An existing custom notify entry is
left untouched.
Commands are contributed under the omniNotifyMcp. prefix: refresh the panel, open the
UI in an external browser, start the server, open the setup help page, re-run the Claude
MCP wiring, and mute/unmute this window.
Settings
| Setting |
What |
omniNotifyMcp.uiPort |
Port the config UI and MCP endpoint listen on. Falls back to 3737. |
omniNotifyMcp.autoStartUi |
When off, the extension will not spawn the server; the panel offers a Start config server button instead. |
Wiring an agent to it
The extension does this automatically for Claude Code. For anything else, register the
stdio bridge:
{
"servers": {
"notify": { "type": "stdio", "command": "npx", "args": ["-y", "omni-notify-mcp"] }
}
}
That is the .vscode/mcp.json shape for VS Code and Copilot. Claude Desktop, Cursor,
Windsurf and Zed each want the same command under their own key — the bundled help page
at http://localhost:3737/help.html has the exact snippet for each, ready to copy.
What the agent gets
Nine tools over the stdio bridge, eight over HTTP. The agent never picks a channel —
routing is entirely server-side.
| Tool |
What it does |
notify |
Send a message at low, normal or high priority. |
ask |
Send a question and block until you answer — reply in Telegram, or click the button in the email. |
poll |
Drain messages you sent while it was busy. |
wait_for_inbox |
Block up to 55 s and return the moment you type something. The delivery path that works in every MCP client, because the message comes back as a tool result. |
get_idle_seconds |
Seconds since your last keypress. Piggy-backs any pending messages. |
get_idle_config |
The idle-gating policy. Piggy-backs messages. |
get_dnd_status |
Whether quiet mode, master mute or a per-client disable is suppressing right now. Piggy-backs messages. |
update_instructions |
Persist a behavioural rule into CLAUDE.md so it survives restarts and compaction. |
reply |
The Claude Code Channels return path, routed back through notify. |
Messages over 500 characters are split into numbered (1/N) chunks and delivered in
order, up to 5000 — the agent sends the whole thing in one call and never truncates.
How delivery is decided
Three switches suppress first: the master mute kills everything including urgent
messages; a per-client disable kills one agent; Do Not Disturb — manual or scheduled
quiet hours, per day of the week — kills everything below high.
Then idle gating: if you are at the keyboard, or the config UI is open in front of you,
a normal message degrades to a desktop notification only rather than lighting up
your phone. Slack is deliberately exempt, so a channel log stays complete. SMS fires
only on high. high bypasses DND and idle gating entirely.
Cross-platform idle detection: Windows via GetLastInputInfo, macOS via ioreg, Linux
via xprintidle.
Channels
- Desktop — native toast; on Windows a system sound is fired alongside it because
the toast's own sound is routinely muted. Optional text-to-speech reads the message
out loud in a neural voice, picked from a list in the UI, no API key.
- Telegram — two-way. The bot answers in-thread, tells you whether your message was
routed, broadcast or queued, and your replies land straight in the agent.
- Slack — bot token to any number of channels, or a single incoming webhook.
One-click OAuth in the UI. A shared channel also works as a cross-machine bus: address
one agent with
@<name>, ask clients to see who is connected, or leave it untagged
to broadcast.
- Email — Gmail App Password, Gmail OAuth, or any SMTP host.
ask over email sends
a reply button that opens a one-shot web page.
- SMS — AWS End User Messaging, on
high priority only.
- ntfy — the server is the ntfy server: point the ntfy mobile app at this machine
and get push notifications with nothing in between.
- Discord — channel webhook, obtainable through one-click OAuth.
- Teams — Adaptive Card into a Workflows webhook.
Multiple agents, one server
Every session declares a tag — host plus project, or an explicit NOTIFY_MCP_TAG.
Untagged messages broadcast to all of them; @<tag> … goes to exactly one. Subagents
fold into their parent's panel instead of cluttering the list. The Clients tab shows
who is connected, and lets you rename, disable or force-reconnect any of them.
License
MIT. Source and issues: https://github.com/menih/BullseyeNotif