MCP Server
Notifly MCP Server — this is an implementation of the Model Context Protocol (MCP) that allows AI assistants (Claude, GitHub Copilot and others) to control Notifly via a set of ready-made tools.
With the MCP server an AI assistant can:
- send notifications to channels;
- read, filter and delete messages;
- create and manage channels and clients;
- ask a human questions and wait for the answer (human-in-the-loop);
- administer server users.
Quick start: copy and go
Section titled “Quick start: copy and go”Three steps and your assistant is sending notifications:
- Build the
notifly-mcpbinary (see Installation) and place it somewhere like/usr/local/bin/notifly-mcp. - Grab your tokens in the admin panel: a channel
app token (
A…, channels tab) and an MCP code (M…, the MCP page). - Copy the config for your client and substitute your own values for
AXXXXXXXXXXXXXX(app token) andMXXXXXXXXXXXXXXXXXXXXXX(MCP code).NOTIFLY_URLfor cloud Notifly ishttps://api.notifly.ru; for self-hosted — your server address.
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json
(macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{ "mcpServers": { "notifly": { "command": "/usr/local/bin/notifly-mcp", "env": { "NOTIFLY_URL": "https://api.notifly.ru", "NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXX", "NOTIFLY_CLIENT_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX" } } }}Claude Code — a single terminal command:
claude mcp add notifly /usr/local/bin/notifly-mcp \ -e NOTIFLY_URL=https://api.notifly.ru \ -e NOTIFLY_APP_TOKEN=AXXXXXXXXXXXXXX \ -e NOTIFLY_CLIENT_TOKEN=MXXXXXXXXXXXXXXXXXXXXXXCursor — a .cursor/mcp.json file in the project root (or ~/.cursor/mcp.json globally):
{ "mcpServers": { "notifly": { "command": "/usr/local/bin/notifly-mcp", "env": { "NOTIFLY_URL": "https://api.notifly.ru", "NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXX", "NOTIFLY_CLIENT_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX" } } }}Restart the client (Cursor picks the config up by itself) — and ask your assistant: “Send a Notifly notification saying ‘MCP connected’”. Other clients (VS Code, Windsurf, Codex, Zed, Continue) are covered in the sections below.
Installation
Section titled “Installation”Requirements
Section titled “Requirements”- Go 1.21 or newer
Build from source
Section titled “Build from source”git clone https://github.com/notifly/mcp.gitcd mcpgo mod tidygo build -o notifly-mcp .The resulting binary notifly-mcp runs over a stdio transport — the MCP client launches it as a child process.
Configuration
Section titled “Configuration”The server is configured exclusively via environment variables:
| Переменная | Обязательная | Описание |
|---|---|---|
NOTIFLY_URL | ✓ | Base URL of the Notifly server, e.g. https://your-domain.com |
NOTIFLY_APP_TOKEN | App token (prefix A) — for the send_message tool | |
NOTIFLY_CLIENT_TOKEN | An MCP code (prefix M) or a client token (prefix C) — for managing resources | |
NOTIFLY_USER | Login for Basic Auth (alternative to MCP code) | |
NOTIFLY_PASS | Password for Basic Auth |
Connecting to Claude Desktop
Section titled “Connecting to Claude Desktop”Open the Claude Desktop configuration file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add a mcpServers section:
{ "mcpServers": { "notifly": { "command": "/usr/local/bin/notifly-mcp", "env": { "NOTIFLY_URL": "https://your-notifly-domain.com", "NOTIFLY_APP_TOKEN": "AGdjfk_L.dKe8q", "NOTIFLY_CLIENT_TOKEN": "CaQw5lL_L.yiRbN" } } }}After restarting Claude Desktop, the Notifly tools will appear in the interface.
Connecting to VS Code (GitHub Copilot)
Section titled “Connecting to VS Code (GitHub Copilot)”Add to .vscode/mcp.json in the project root or to your VS Code user settings:
{ "servers": { "notifly": { "type": "stdio", "command": "/usr/local/bin/notifly-mcp", "env": { "NOTIFLY_URL": "https://your-notifly-domain.com", "NOTIFLY_APP_TOKEN": "AGdjfk_L.dKe8q", "NOTIFLY_CLIENT_TOKEN": "CaQw5lL_L.yiRbN" } } }}Connecting to Cursor
Section titled “Connecting to Cursor”Add .cursor/mcp.json in the project root (project-scope) or ~/.cursor/mcp.json globally:
{ "mcpServers": { "notifly": { "command": "/usr/local/bin/notifly-mcp", "env": { "NOTIFLY_URL": "https://your-notifly-domain.com", "NOTIFLY_APP_TOKEN": "AGdjfk_L.dKe8q", "NOTIFLY_CLIENT_TOKEN": "CaQw5lL_L.yiRbN" } } }}After saving, Cursor will pick up the server automatically. Make sure notifly-mcp is available in PATH.
Connecting to Windsurf
Section titled “Connecting to Windsurf”Open or create ~/.codeium/windsurf/mcp_config.json and add a mcpServers section:
{ "mcpServers": { "notifly": { "command": "/usr/local/bin/notifly-mcp", "env": { "NOTIFLY_URL": "https://your-notifly-domain.com", "NOTIFLY_APP_TOKEN": "AGdjfk_L.dKe8q", "NOTIFLY_CLIENT_TOKEN": "CaQw5lL_L.yiRbN" } } }}After saving, restart Windsurf — the Notifly tools will appear in Cascade.
Connecting to Claude Code (CLI)
Section titled “Connecting to Claude Code (CLI)”Use the claude mcp add command to register the server:
claude mcp add notifly /usr/local/bin/notifly-mcp \ -e NOTIFLY_URL=https://your-notifly-domain.com \ -e NOTIFLY_CLIENT_TOKEN=CaQw5lL_L.yiRbNThe --scope user flag adds the server globally for all projects (by default — project-scope). To list added servers: claude mcp list.
Connecting to Codex CLI
Section titled “Connecting to Codex CLI”Open or create ~/.codex/config.toml and add the section:
[[mcp_servers]]name = "notifly"command = "/usr/local/bin/notifly-mcp"env = { NOTIFLY_URL = "https://your-notifly-domain.com", NOTIFLY_CLIENT_TOKEN = "CaQw5lL_L.yiRbN" }After saving, Codex will pick up the server on next start. Make sure notifly-mcp is available in PATH.
Connecting to Zed
Section titled “Connecting to Zed”Open the Zed settings file (~/.config/zed/settings.json) and add a context_servers section:
{ "context_servers": { "notifly": { "command": { "path": "/usr/local/bin/notifly-mcp", "args": [], "env": { "NOTIFLY_URL": "https://your-notifly-domain.com", "NOTIFLY_CLIENT_TOKEN": "CaQw5lL_L.yiRbN" } } } }}Zed will pick up the server without a restart.
Connecting to Continue
Section titled “Connecting to Continue”Add a mcpServers section to the Continue config (~/.continue/config.json):
{ "mcpServers": [ { "name": "notifly", "command": "/usr/local/bin/notifly-mcp", "env": { "NOTIFLY_URL": "https://your-notifly-domain.com", "NOTIFLY_CLIENT_TOKEN": "CaQw5lL_L.yiRbN" } } ]}Continue is supported as an extension for VS Code and JetBrains IDEs.
Available tools
Section titled “Available tools”Server information
Section titled “Server information”| Tool | Description |
|---|---|
get_health | Check server and database health |
get_version | Get version, commit and build date |
get_server_info | Get server flags (registration, OIDC) |
Messages
Section titled “Messages”| Tool | Parameters | Description |
|---|---|---|
send_message | message*, title, priority | Send a notification (requires NOTIFLY_APP_TOKEN) |
list_messages | limit, since | List all messages with pagination |
list_application_messages | app_id*, limit, since | Messages of a specific channel |
delete_message | id* | Delete a message by ID |
delete_all_messages | — | Delete all messages |
delete_application_messages | app_id* | Delete all messages of a channel |
search_messages | q*, app_id, limit, since | Search messages by text (min. 2 characters); across all channels or in one |
mark_messages_read | ids* | Mark messages as read (JSON array of IDs, e.g. [1,2,3]) |
Questions (ask) — human-in-the-loop
Section titled “Questions (ask) — human-in-the-loop”| Tool | Parameters | Description |
|---|---|---|
ask_question | question*, options, title, timeout_sec, default_answer, allow_multi, json_format, client_message_id | Ask a human a question (push/Telegram/email with buttons); returns the question id. Requires NOTIFLY_APP_TOKEN. See Questions (Ask) |
get_ask_answer | id*, wait_sec | Fetch the answer; with wait_sec > 0 — wait for it (polls every 3 s, up to 300 s). Returns answered+answer, pending, expired or cancelled. Requires NOTIFLY_APP_TOKEN |
cancel_ask_question | id* | Cancel a pending question |
The ask_question → get_ask_answer(wait_sec) pair is a ready-made approval
gate for an AI agent: ask a human for permission and continue based on the answer.
Channels
Section titled “Channels”| Tool | Parameters | Description |
|---|---|---|
list_applications | — | List all channels |
create_application | name*, description, default_priority, tags | Create a channel. tags — up to 10 tags of [a-z0-9_-]; used by the tag scope of MCP codes |
update_application | id*, name, description, default_priority, tags | Update a channel; tags replace the current ones (omitted — unchanged) |
delete_application | id* | Delete a channel |
Clients
Section titled “Clients”| Tool | Parameters | Description |
|---|---|---|
list_clients | — | List all clients (devices/tokens) |
create_client | name* | Create a client and obtain a client token |
update_client | id, name | Rename a client |
delete_client | id* | Delete a client (revoke token) |
| Tool | Parameters | Description |
|---|---|---|
get_current_user | — | Information about the current user |
list_users | — | List all users (requires admin) |
get_user | id* | Get a user by ID (admin) |
create_user | name, pass, admin | Create a user (admin) |
delete_user | id* | Delete a user (admin) |
request_password_reset | email* | Request a password reset — a link with a token will be sent to the email (public, no auth) |
confirm_password_reset | token, new_password | Confirm reset: set a new password using the token from the email |
Heartbeats (control signals)
Section titled “Heartbeats (control signals)”More about heartbeats — see the Heartbeat page.
| Tool | Parameters | Description |
|---|---|---|
list_heartbeats | — | List heartbeat notifications (dead-man switch) |
create_heartbeat | appid, name, intervalSec, alertMessage, graceSec, alertTitle, alertPriority, recoveryTitle, recoveryMessage | Create a heartbeat. intervalSec ≥ 30; an alert is sent if a ping hasn’t arrived within intervalSec+graceSec seconds; alertPriority defaults to 8 |
update_heartbeat | id, name, intervalSec, alertMessage, graceSec, alertTitle, alertPriority, recoveryTitle, recoveryMessage | Update heartbeat settings |
delete_heartbeat | id* | Delete a heartbeat |
pause_heartbeat | id* | Pause (alerts will not be sent until resume_heartbeat) |
resume_heartbeat | id* | Resume a paused heartbeat |
ping_heartbeat | ping_token* | Send a ping using a public ping token (as an app would from cron) |
Monitors
Section titled “Monitors”More about active checks — see the Monitors page.
| Tool | Parameters | Description |
|---|---|---|
list_monitors | — | List active monitors (HTTP URLs and TCP ports) |
create_monitor | appid, name, kind, target, intervalSec, alertMessage, timeoutSec, expectedStatus, consecutiveFails, alertTitle, alertPriority, recoveryTitle, recoveryMessage | Create a monitor. kind = http (then target is a URL) or tcp (target is host:port). intervalSec 30–86400; timeoutSec defaults to 10; expectedStatus 0 = “any 2xx”; consecutiveFails defaults to 1 (max. 20); alertPriority defaults to 8 |
update_monitor | id, name, kind, target, intervalSec, alertMessage, timeoutSec, expectedStatus, consecutiveFails, alertTitle, alertPriority, recoveryTitle, recoveryMessage | Update monitor settings |
delete_monitor | id* | Delete a monitor |
pause_monitor | id* | Pause (checks will not run until resume_monitor) |
resume_monitor | id* | Resume a paused monitor |
Channel sharing (shares)
Section titled “Channel sharing (shares)”More — see the Channel sharing page.
| Tool | Parameters | Description |
|---|---|---|
list_channel_shares | app_id* | List share tokens (delegations) for a channel; available to the owner or a user with send permission |
create_channel_share | app_id, permission, recipient_email, note | Create a delegation. permission = view (read) or send (read + send); an empty recipient_email = an open token |
update_channel_share | id, permission, recipient_email, note | Change recipient email, access level or note (owner only) |
delete_channel_share | id* | Revoke a share token (delete the delegation) |
list_incoming_shares | — | Channels the current user has been granted access to (incoming shares by email) |
accept_share | id* | Accept an incoming delegation (status → active) |
decline_share | id* | Decline an incoming delegation |
join_share_by_token | token* | Join a channel by share token (prefix S) |
Device subscriptions
Section titled “Device subscriptions”| Tool | Parameters | Description |
|---|---|---|
get_client_subscriptions | client_id* | Channels the device is subscribed to by client ID |
set_client_subscriptions | client_id, app_ids | Set the full list of device subscriptions; app_ids is a JSON array of IDs, e.g. [1,2,3] (replaces current) |
get_my_subscriptions | — | Subscriptions of the current device (authorize with device token via NOTIFLY_CLIENT_TOKEN) |
subscribe_to_channel | app_id* | Subscribe the current device to a channel |
unsubscribe_from_channel | channel_id* | Unsubscribe the current device from a channel |
Account and billing
Section titled “Account and billing”| Tool | Parameters | Description |
|---|---|---|
get_quota_breakdown | day | Detailed breakdown of event quota usage for a day (YYYY-MM-DD, default — today in MSK) |
topup_balance | amount_rubles* | Top up balance (integer ≥ 1; admin only, others will receive payment details) |
* — required parameter
Examples
Section titled “Examples”Send a notification via Claude
Section titled “Send a notification via Claude”Отправь уведомление в Notifly с заголовком "Деплой завершён"и текстом "Версия 2.1.0 успешно развёрнута на prod" с приоритетом 7.Claude will call the send_message tool automatically.
Channel management
Section titled “Channel management”Покажи список всех каналов в Notifly и последние 10 сообщенийиз канала с ID 3.Claude will call list_applications and then list_application_messages sequentially.
Administration
Section titled “Administration”Создай нового пользователя в Notifly с именем "devops"и паролем "securepass", без прав администратора.Claude will call create_user with the required parameters.
Recipe: an approval gate for an AI agent (human-in-the-loop)
Section titled “Recipe: an approval gate for an AI agent (human-in-the-loop)”An autonomous agent should not decide on its own whether to apply a migration
to prod — it should ask. The ask_question → get_ask_answer tool pair turns
Notifly into a ready-made approval gate: the question arrives as a push
notification (and in Telegram, if connected), the human answers with a
button right in the notification, the agent receives the answer and acts.
Step 1. The agent asks the question
Section titled “Step 1. The agent asks the question”The agent calls ask_question with button options, a timeout and a safe
default answer:
{ "tool": "ask_question", "arguments": { "question": "Apply the migration to prod?", "options": ["yes", "no"], "timeout_sec": 300, "default_answer": "no", "client_message_id": "deploy-2026-07-13-migration" }}The tool result is the id of the created question:
{"id": 8260183728527568, "status": "pending"}timeout_sec: 300+default_answer: "no"— if the human hasn’t answered within 5 minutes, the question closes with a safe “no”: silence is not consent.client_message_idis an idempotency key: if the agent repeats the call (a retry after a dropped connection), no duplicate question is created — the existing one is returned.
Step 2. The human answers from the notification
Section titled “Step 2. The human answers from the notification”A push arrives on the phone — “Apply the migration to prod?” with yes / no buttons; the same question with buttons lands in Telegram if it is connected to the channel. One tap and the answer is recorded — no need to open the app.
Step 3. The agent waits for the answer
Section titled “Step 3. The agent waits for the answer”{ "tool": "get_ask_answer", "arguments": {"id": 8260183728527568, "wait_sec": 300}}get_ask_answer with wait_sec > 0 blocks until the answer arrives (polling
every 3 seconds) and returns a terminal status:
{"id": 8260183728527568, "status": "answered", "answer": "yes"}Step 4. The agent acts on the answer
Section titled “Step 4. The agent acts on the answer”The full pseudo-dialog:
Agent: about to apply migration 0042 to prod. Asking for permission. → ask_question("Apply the migration to prod?", options=["yes","no"], timeout_sec=300, default_answer="no") ← {"id": 8260…, "status": "pending"} → get_ask_answer(id=8260…, wait_sec=300)
Human: [push on the phone] "Apply the migration to prod?" [yes] [no] — taps "yes"
Agent: ← {"status": "answered", "answer": "yes"} applying the migration… done, sending send_message("Migration 0042 applied").If the status is expired, the timeout fired and the default_answer (“no”)
was recorded; if cancelled, the question was cancelled. In both cases the
agent does not perform the action. More about the questions mechanics — on the
Questions (Ask) page.
Security: a tag scope and TTL for the agent’s MCP code
Section titled “Security: a tag scope and TTL for the agent’s MCP code”The agent does not need access to every channel in the account. Restrict its MCP code:
- Put an
aitag on the “agent” channels (the channel’s “Tags” field orcreate_application/update_applicationwithtags: ["ai"]). - Create an MCP code with a tag scope and an expiry:
POST /mcp/token{ "name": "deploy-agent", "access": "write", "appTags": ["ai"], "expiresAt": "2026-08-01T00:00:00Z"}Such a code sees only channels tagged ai — including ones created later
(a dynamic scope, no code edits needed), and past expiresAt it stops working
by itself: a token forgotten in an agent’s config does not remain a permanent
hole. The scope can be combined with a static channel list (appIds) — access
is granted if the channel is in the list or carries a matching tag; empty
appIds and appTags mean “all channels”.
Security
Section titled “Security”- The MCP server runs locally over stdio — no network port is opened.
- Tokens are stored only in the process environment variables.
- The
delete_all_messagesanddelete_usertools perform irreversible operations — the AI assistant should request confirmation before calling them.