agentic-notify
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
agentic-notify
Notifications for humans and agents: in-app inbox, email and webhook delivery, with a CLI, API, MCP server and SDK.
Status: 0.6.3. Breaking for Worker auth, see "Migrating from NOTIFY_TOKENS" below. Breaking for 0.1.0 code and databases, see the CHANGELOG upgrade steps. Works today: the notification model, preferences and mute, digest, a store port from agentic-identity with its memory and D1 adapters, signed webhook and email delivery through a dispatcher, an HTTP API in a Worker, an SDK, a CLI, MCP tools, per-recipient export and erase, a retention sweep, and a React bell and inbox (agentic-notify/ui). Not done: a real email provider adapter, webhook endpoint management, and a full-page inbox app.
Live demo
A demo Worker runs at https://agentic-notify-demo.hello-fd1.workers.dev. GET /health answers 200. The /api/v1 routes need a key, so without one they answer 401. The demo keeps its data in the EU and sweeps notifications after 7 days.
The demo page is in ui/ and the Worker serves it at /ui/ after npm run deploy:demo. Sign in with a key that holds notify:read and notify:write. Add notify-send:write to use the send form.
Redeploy your own copy with wrangler.demo.jsonc (create the D1 database with --location weur, put its id in the file, then add principals and keys, see Identity):
WRANGLER_SEND_METRICS=false npm run deploy:demoRelated MCP server: Channels MCP
Install
npm install agentic-notifyQuick start
Library, with the memory store:
import { createNotifier, createMemoryStore } from "agentic-notify";
const notifier = createNotifier({ store: createMemoryStore() });
await notifier.send({
recipient: { kind: "agent", id: "helper" },
type: "approval_request",
resource: { kind: "doc", id: "d1" },
actor: { kind: "human", id: "ana" },
body: "Please approve the edit",
});
const { items } = await notifier.list({ kind: "agent", id: "helper" });Worker (D1 binding DB):
# Create the database first: npx wrangler d1 create agentic-notify --location weur
npx wrangler deployThe Worker reads keys and principals from the same D1 database, with agentic-identity. Issue a key for each caller with agentic-identity (for example issueCredential, or its Worker), into this database. See Identity below.
Without a D1 binding, every /api/v1 call answers 503. A request without a valid key answers 401, on unknown paths too. Nobody gets in by default.
CLI, SDK and MCP talk to that Worker:
export NOTIFY_URL=https://notify.example.org NOTIFY_TOKEN=...
npx agentic-notify send --to human:ana --type mention --resource doc:d1 --body "Look at this"
npx agentic-notify list --unread
npx agentic-notify mcp # tools: ping, list_notifications, read_notification, mark_read,
# mark_all_read, get_preferences, set_preferences, unread_count, digest,
# export_data, erase_data, send_notificationnpx agentic-notify mcp --help (or -h) prints the usage and exits 0. npx agentic-notify mcp --version (or -V or -v) prints the version and exits 0. Neither starts the server.
The MCP server needs NOTIFY_URL and NOTIFY_TOKEN. Without them only ping works. erase_data needs confirm: true and a key with notify:admin. send_notification needs notify-send:write. A tool that lacks its scope reports 403.
import { createClient } from "agentic-notify/sdk";
const client = createClient({ baseUrl: process.env.NOTIFY_URL!, token: process.env.NOTIFY_TOKEN! });
console.log(await client.unreadCount());HTTP API
All routes sit under /api/v1 and need Authorization: Bearer <key>. GET /health is open. A missing or bad key answers 401. A good key without the scope of the route answers 403. Each route needs one scope:
Scope | Routes |
|
|
|
|
|
|
|
|
A scope with a higher action covers the lower ones, so notify:admin covers notify:write and notify:read. *:admin covers all. notify-send is a separate resource, so a key that can only send cannot read.
Route | Use |
| List yours, newest first |
| Send. Needs the send right. The caller is the actor |
| Read one, mark read |
| Bulk read state, digest |
| Channels per type, mute |
| GDPR export and hard erase |
Identity
The Worker authenticates with agentic-identity. requirePrincipal from agentic-identity/http reads the credential, and the Auth port checks that it is not expired or revoked. The default Auth takes a bearer key, and also a session cookie that agentic-identity sets. A write with a cookie is refused, because no origin is allowed here. Pass your own Auth to accept bearer keys only. The default Auth is createStoreAuth over the same D1 store. Each route checks one scope (see HTTP API). A delegated agent holds only the scopes that its person also holds.
A person, and a user_token of that person, use the inbox of the person. An agent (agent, agent_of_human or agent_of_agent) uses the inbox of its own id. A caller reads and changes only its own notifications.
To use another identity system, call createWorker({ auth: (env, store) => myAuth }), where auth builds your own Auth from the Worker env and the store. Import createWorker from agentic-notify/worker.
Migrating from NOTIFY_TOKENS
Before 0.4.0, the Worker read a static NOTIFY_TOKENS secret: a map of token to { kind, id, canSend }. The tokens had no scopes. The Worker no longer reads it. Steps:
Delete the
NOTIFY_TOKENSsecret (npx wrangler secret delete NOTIFY_TOKENS).Make each caller a principal in the D1 database, and issue it a key with
agentic-identity. The old{ "kind": "human" }becomes kindhuman, and{ "kind": "agent" }becomes kindagent.Give each principal scopes. An old token could read, change and erase its own inbox. To keep that, use
notify:admin. For less, usenotify:write(it coversnotify:read) ornotify:read. For the oldcanSend: true, addnotify-send:write. It is a separate resource, so it does not covernotify:read: a caller that sends and reads needs both.Give the new key to the CLI, SDK and MCP server as
NOTIFY_TOKEN.
Library users: createWorker takes auth in place of resolver. authorizeSend still works as an extra check after the scope. Its first argument is now a PrincipalContext (principalId in place of id, no canSend). Its default changed: it was "only canSend: true", and now every caller with notify-send:write may send unless you pass your own.
Privacy
Export and erase work per recipient.
retentionMsplussweep()(or a cron trigger on the Worker, withNOTIFY_RETENTION_DAYS) deletes old notifications.Erase and sweep delete in batches of 100 inside one transaction each, so a large erase does not cost one query per row.
The Worker reads
NOTIFY_REGION(eu,usorunknown, defaulteu). The D1 adapter refuses a database that does not report a serving region in that area. For local runs without a real D1, setNOTIFY_REGION=unknown.Logs hold ids, channel names and error names only. They never hold bodies or addresses.
Erase removes a recipient's own notifications and preferences. Notifications that name the person only as actor stay with their recipients.
Bell and inbox (React)
agentic-notify/ui is a bell button with an unread count. It opens a popover with the inbox: the list, mark read, mark all read, and an empty state. It refreshes by itself over the HTTP API, so new notifications show without a reload. React 19 is a peer dependency. The package depends on agentic-design for the parts and the tokens.
import { NotificationBell } from "agentic-notify/ui";
import "agentic-design/styles.css";
import "agentic-notify/ui.css";
<NotificationBell baseUrl="https://notify.example.com" getToken={() => session.token} />;The Worker sends no CORS headers. Serve the bell from the Worker's own origin, or put the Worker behind a proxy on your app's origin. A cross-origin page cannot call it, and the popover then shows a load error.
getTokenruns before each request. Return the key of the signed-in person or agent. The server checks every call.baseUrlmust behttps:. Plainhttp:works for localhost only. A badbaseUrl,pollMsorlimitthrows aTypeError.When the signed-in caller changes, give the bell a new
key(React) or callupdate(mountremounts it). Then one caller's list never shows under another's token.A call that takes over 10 seconds ends as a load error. Polling stops after a refused token and when
getTokenthrows, until the person opens the bell or presses Try again. After a rate limit (429) it pauses for a minute.align("start"or"end", default"end") sets which edge of the bell the popover lines up with. Use"start"for a bell near the left edge. On a phone the popover spans the width under the bell.The bell name says "could not load" when a call fails, so a failure never reads as an empty inbox.
mountdoes not change the page theme.pollMssets the refresh interval (default 15000, 0 turns it off). The page also refreshes when the tab becomes visible (not after a refused token).limitsets the list size (default 20).A refused or failed call shows an alert in the popover. The list never shows a failed call as success.
Notifications from an agent carry the words "Sent by an agent".
useNotifications(options)gives the state and the actions without the markup.<Inbox state={...} />renders the list alone from that state.headingIdis optional. SetkeepFocusto move focus into the list after a mark call removes the focused button. The bell sets it.Screenshots of each view, light and dark, are in
docs/screenshots. Refresh them withSCREENSHOTS=docs/screenshots npm run test:ui.Install
agentic-designin your app too, so your bundler can resolveagentic-design/styles.css. Use one copy of it.Without React,
mount(element, options)renders the bell into an element and returnsupdateandunmount.It uses
agd-classes only. Keyboard: Enter opens, Escape closes and returns focus. It passes an axe WCAG 2.2 AA scan in light and dark, and it fits a 390px screen.
Account export and erase
A host that runs account export and erase across many packages calls notifyAccount from agentic-notify/account. It has exportAccount(subject, { notifier }) and eraseAccount(subject, { notifier }), so the host does not read the storage layout. The subject is an id and a kind. A person or a user token is a human recipient. Every agent kind is an agent recipient. The export has plain JSON in notifications and preferences. The erase removes both and returns counts. A repeat of the erase counts zero. Notifications that name the subject only as actor stay with their recipients. The shape is provisional: it is the AccountPackage contract that a later agentic-identity release adds to agentic-identity/account.
Testing
agentic-notify/testing helps you test a store adapter and your own code.
runStoreContract(make) runs the store contract suite. Call it at the top level of a vitest test
file, and install vitest 3 or later. The factory makes a new store for each call. A store made
without shared must start empty. Stores made with the same shared value must see the same data.
So the factory passes options to the store:
import { createMemoryStore } from "agentic-notify";
import { runStoreContract } from "agentic-notify/testing";
runStoreContract((options) => createMemoryStore(options));TestEmailAdapter records each message in memory and can fail a set number of sends.
Limits
The default Worker serves the inbox. To fan out to webhook and email, compose
createAppwithcreateDispatcherin your own Worker.Webhook endpoints and email addresses come from resolvers you provide. The package stores none.
Webhook retries run inside the request. Use a queue for heavy fan-out.
Develop
npm ci
npm run ci:localnpm test skips the ./ui export check until you run npm run build. ci:local builds first and also runs the browser tests (npm run test:ui). Install the browser once with npx playwright install chromium.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Send, search, and manage notifications, accounts, and push preferences
Agent communication platform for agent to agent messaging via MCP. Messages, channels, skills.
Let agents send content-free push notifications to a paired phone via MCP.
Agent-to-agent messaging: directory, public lobby, DMs, channels, search. Stateless MCP + REST.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables 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.101 npm1MIT
- FlicenseNot gradedqualityCmaintenanceEnables agents to send structured notifications to recipients via configurable channels (initial SMTP provider) without exposing delivery addresses or credentials, with tools for listing recipients, listing channels, and sending notifications.-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to send notifications to a unified feed, report activity, request input from humans, and manage notification state through MCP tools.4MIT
- FlicenseNot gradedqualityCmaintenanceLets AI assistants manage subscribers, trigger workflows, inspect notification events, and tune preferences across any MCP-compatible client.-