mcp-os-notifications
# mcp-os-notifications
An [MCP](https://modelcontextprotocol.io) server that lets LLM agents dispatch
native OS notifications when a task finishes. If you tell your agent, *"do this and
notify me when done"*, it will.
Works with **Claude Code**, **Claude Desktop**, **OpenCode**, or any other MCP client.
---
## What it does
- Exposes a single MCP tool: `dispatch_os_notification`.
- Detects when you ask to be notified (`"notify me when done"`, `"ping me when
finished"`, etc.).
- Sends a native desktop notification once the agent finishes its work.
- **Task tokens**: reuse a token to update an existing notification instead of
spamming new ones. Great for progress updates.
- **Mobile bridge**: optionally push to `ntfy.sh`, Telegram, or Pushover when you are
away from your desk.
- **Quiet hours**: suppress non-critical desktop notifications during configured
hours; critical ones still come through.
- Cross-platform: Linux (DBus/FreeDesktop notifications), macOS, Windows.
---
## How it works
1. The MCP server runs locally as a `stdio` subprocess of your agent.
2. Project-level agent instructions (`.claude/CLAUDE.md`, `skills/notify-when-done.md`)
teach the agent when to call the tool.
3. When the work is done, the agent calls `dispatch_os_notification(title, message)`.
4. The server uses the native notification backend on your OS to show the toast/banner.
---
## Requirements
- Python 3.10+
- [uv](https://docs.astral.sh/uv/) (recommended for development) or `pip`
- A notification daemon on Linux (e.g., `mako`, `dunst`, GNOME/KDE built-in)
---
## Install on a new machine
### Option A: clone + `uv` (recommended for development)
```bash
git clone https://github.com/ekomac/mcp-os-notifications.git
cd mcp-os-notifications
uv sync
uv run mcp-os-notifications
```
### Option B: clone + `pip`
```bash
git clone https://github.com/ekomac/mcp-os-notifications.git
cd mcp-os-notifications
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
mcp-os-notifications
```
### Option C: install from GitHub without cloning
```bash
python3 -m pip install git+https://github.com/ekomac/mcp-os-notifications.git
mcp-os-notifications
```
> **Note:** If you install from PyPI in the future (once published), the command is
> simply `pip install mcp-os-notifications`.
### Verify the server starts
The server runs over stdio and waits for MCP messages. Press `Ctrl+C` to stop.
```bash
# If using uv in the repo
uv run mcp-os-notifications
# If installed globally with pip
mcp-os-notifications
```
---
## Add to Claude Code
Claude Code reads `.mcp.json` at project scope or `~/.claude.json` at user scope.
### Project scope (recommended per repo)
If you open this repository directly in Claude Code, the bundled `.mcp.json` already
registers the server. Just ask:
```
Build a quick Python script that prints the current time and notify me when done.
```
To use it in a **different project** while keeping the source in this repo, copy the
snippet below into that project's `.mcp.json` and replace `/ABSOLUTE/PATH/TO/mcp-os-notifications`:
```json
{
"mcpServers": {
"os-notifications": {
"type": "stdio",
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/mcp-os-notifications",
"run",
"mcp-os-notifications"
]
}
}
}
```
### User scope (global for all projects)
If you installed globally with pip or uv tool, add the command directly:
```bash
claude mcp add --transport stdio os-notifications mcp-os-notifications
```
Or if you keep the cloned repo:
```bash
claude mcp add --transport stdio os-notifications uv --directory /ABSOLUTE/PATH/TO/mcp-os-notifications run mcp-os-notifications
```
### Check that it loaded
```bash
claude mcp list
```
You should see `os-notifications` with the `dispatch_os_notification` and `notify_user`
tools.
---
## Add to Claude Desktop
Add the server to `claude_desktop_config.json`:
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows:** `%AppData%\Claude\claude_desktop_config.json`
- **Linux:** `~/.config/Claude/claude_desktop_config.json` (or
`$XDG_CONFIG_HOME/Claude/claude_desktop_config.json`)
If installed globally with pip:
```json
{
"mcpServers": {
"os-notifications": {
"command": "mcp-os-notifications"
}
}
}
```
If using the cloned repo with uv:
```json
{
"mcpServers": {
"os-notifications": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/mcp-os-notifications",
"run",
"mcp-os-notifications"
]
}
}
}
```
---
## Add to OpenCode
OpenCode supports MCP servers via stdio. Add this to your OpenCode MCP configuration:
```json
{
"mcpServers": {
"os-notifications": {
"command": "mcp-os-notifications"
}
}
}
```
If you run from the cloned repo instead:
```json
{
"mcpServers": {
"os-notifications": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/mcp-os-notifications",
"run",
"mcp-os-notifications"
]
}
}
}
```
Then copy the skill reference so OpenCode knows when to use the tool:
```bash
mkdir -p ~/.config/opencode/skills/notify-when-done
cp skills/notify-when-done.md ~/.config/opencode/skills/notify-when-done/SKILL.md
```
---
## Other MCP clients
Any client that supports stdio MCP servers can use one of these commands:
```bash
# Installed globally
mcp-os-notifications
# From the cloned repo with uv
uv --directory /ABSOLUTE/PATH/TO/mcp-os-notifications run mcp-os-notifications
# From the cloned repo with pip venv
.venv/bin/mcp-os-notifications
```
---
## Tool schema
The server exposes two identical tools; `notify_user` is a convenience alias for
`dispatch_os_notification`.
### `dispatch_os_notification`
| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | string | yes | — | Short notification title |
| `message` | string | yes | — | Notification body |
| `urgency` | string | no | `"normal"` | `"low"`, `"normal"`, or `"critical"` |
| `sound` | boolean | no | `false` | Play an audible alert |
| `timeout` | integer | no | `-1` | Auto-dismiss after N seconds; `-1` uses OS default |
| `token` | string | no | `null` | Stable id; reusing it replaces the previous notification |
| `mobile` | boolean | no | `false` | Also push to configured mobile backends |
### Example tool calls
#### Simple completion notification
```json
{
"title": "Build finished",
"message": "All 42 tests passed.",
"urgency": "normal",
"sound": true
}
```
#### Progress updates with a token
```json
{
"title": "Syncing files...",
"message": "25 of 100 files processed",
"token": "file-sync-job"
}
```
Then later with the same token:
```json
{
"title": "Sync complete",
"message": "All 100 files uploaded",
"token": "file-sync-job"
}
```
#### Critical + mobile push
```json
{
"title": "Build failed",
"message": "Production deployment failed on the test stage.",
"urgency": "critical",
"mobile": true
}
```
---
## Mobile bridge
The server can push notifications to mobile/remote backends alongside the desktop toast.
This is useful when you are away from the computer. Configure one or more backends via
environment variables.
### ntfy.sh
```bash
export MCP_OS_NOTIFICATIONS_NTFY_URL="https://ntfy.sh/my-secret-topic"
```
You can also use a self-hosted ntfy instance:
```bash
export MCP_OS_NOTIFICATIONS_NTFY_URL="https://ntfy.example.com/alerts"
```
### Telegram
```bash
export MCP_OS_NOTIFICATIONS_TELEGRAM_BOT_TOKEN="123456:ABC..."
export MCP_OS_NOTIFICATIONS_TELEGRAM_CHAT_ID="123456789"
```
### Pushover
```bash
export MCP_OS_NOTIFICATIONS_PUSHOVER_USER_KEY="uQiRz..."
export MCP_OS_NOTIFICATIONS_PUSHOVER_APP_TOKEN="azGDO..."
```
### Usage
Once a backend is configured, set `mobile: true` in the tool call. The server will
notify every configured backend in parallel.
### Passing environment variables to the server
Mobile and quiet-hours settings are read from the environment of the MCP server process.
How you set them depends on the client:
- **Claude Code:** environment variables are inherited from the shell where you launched
`claude`, or you can set them in the `.mcp.json` `env` block:
```json
{
"mcpServers": {
"os-notifications": {
"type": "stdio",
"command": "mcp-os-notifications",
"env": {
"MCP_OS_NOTIFICATIONS_NTFY_URL": "https://ntfy.sh/my-topic"
}
}
}
}
```
- **Claude Desktop:** use the `env` block in `claude_desktop_config.json`:
```json
{
"mcpServers": {
"os-notifications": {
"command": "mcp-os-notifications",
"env": {
"MCP_OS_NOTIFICATIONS_NTFY_URL": "https://ntfy.sh/my-topic"
}
}
}
}
```
- **OpenCode / other clients:** set the variables in the shell before launching the
client, or use the client's equivalent MCP environment settings.
---
## Quiet hours
Suppress non-critical desktop notifications during a configured time window. Critical
notifications still come through.
```bash
export MCP_OS_NOTIFICATIONS_QUIET_START="22:00"
export MCP_OS_NOTIFICATIONS_QUIET_END="07:00"
```
The window can span midnight (e.g., 22:00 to 07:00). Mobile pushes are not affected by
quiet hours.
See [Passing environment variables to the server](#passing-environment-variables-to-the-server)
above for how to expose these variables to the MCP server process.
---
## Project structure
```
.
├── src/mcp_os_notifications/
│ ├── server.py # MCP server and tools
│ ├── mobile.py # Mobile/remote notification backends
│ └── quiet_hours.py # Do-not-disturb logic
├── skills/notify-when-done.md # Generic agent skill reference
├── .claude/CLAUDE.md # Claude Code project instructions
├── .mcp.json # Project-scoped Claude Code MCP config
├── .github/workflows/ci.yml # GitHub Actions CI
├── Makefile # Common dev commands
├── pyproject.toml
├── README.md
└── LICENSE
```
---
## Development
A `Makefile` is provided for convenience:
```bash
make install # uv sync
make test # uv run pytest
make lint # uv run ruff check .
make run # uv run mcp-os-notifications
make clean # remove caches, venv, build artifacts
```
CI runs on GitHub Actions for Python 3.10–3.14.
---
## Troubleshooting
### Linux: "DBUS_SESSION_BUS_ADDRESS not set"
The server tries to auto-detect the session bus, but if that fails, make sure your
notification daemon is running (e.g., `mako`, `dunst`, `swaync`). You can also set:
```bash
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id - u)/bus"
```
### No notification appears
1. Check that your OS has a notification service enabled.
2. Try running the server directly with verbose logging:
```bash
MCP_OS_NOTIFICATIONS_LOG_LEVEL=DEBUG uv run mcp-os-notifications
```
3. Check your agent's tool call output for the `success` flag.
---
## Publishing / adding to the MCP ecosystem
There is no "Anthropic marketplace" for MCP servers, but there are official and
community registries:
| Registry | How to submit |
| --- | --- |
| [MCP Registry](https://registry.modelcontextprotocol.io) | Official registry. Use `mcp-publisher` CLI to publish `server.json` metadata. |
| [Claude Connectors Directory](https://claude.ai/directory) | Anthropic-reviewed connectors. Submit via the directory form for remote servers or desktop extensions. |
| [Smithery](https://smithery.ai) | Third-party MCP registry/marketplace. |
| [Glama](https://glama.ai/mcp/servers) | Indexes the official MCP Registry automatically. |
`modelcontextprotocol/servers` no longer accepts third-party listings.
---
## Prior art
This is not the first MCP notification server. Similar projects exist, e.g.:
- [Cactusinhand/mcp_server_notify](https://github.com/Cactusinhand/mcp_server_notify)
- [pinkpixel-dev/notification-mcp](https://github.com/pinkpixel-dev/notification-mcp)
- [devizor/macOS-Notification-MCP](https://github.com/devizor/macOS-Notification-MCP)
This repo focuses on:
- A clean, minimal Python implementation using `desktop-notifier`.
- Ready-to-use agent skill references for Claude Code and OpenCode.
- A project-scoped `.mcp.json` so Claude Code works out of the box.
- **Task tokens** to replace notifications instead of spamming.
- **Mobile bridge** to ntfy.sh, Telegram, and Pushover.
- **Quiet hours** to respect do-not-disturb time windows.
---
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 2 tools
The two tools are actually the same; notify_user is explicitly an alias for dispatch_os_notification. This creates complete overlap and makes it impossible for an agent to distinguish between them.
Both tool names follow a verb_noun pattern (dispatch_os_notification, notify_user), so the naming convention is consistent. However, the presence of a duplicate alias reduces the overall pattern's clarity.
With only 2 tools (one of which is a duplicate), the count is minimal but still reasonable for a focused notification dispatch server. It is not excessive, but could be consolidated into a single tool.
The server covers the core functionality of dispatching OS notifications with configurable options (urgency, sound, timeout, mobile). However, it lacks any management or querying capabilities, which could be considered a minor gap.