Skip to content

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);
  • set up heartbeats, monitors and port scans, and read the check history;
  • administer server users — only with an administrator’s device token (see Users).

Five steps to your first call:

  1. Pick your client. Ready-made configs for Claude Desktop, Claude Code and Cursor are below; VS Code, Windsurf, Codex, Zed and Continue are covered in the Connecting to … sections further down.
  2. Build the server. notifly-mcp is a single executable, and there are no downloadable builds yet: it is built from source (see Installation) and placed somewhere like /usr/local/bin/notifly-mcp.
  3. Grab your tokens in the admin panel: an API token (M…, the API tokens page) — for working with channels, monitors and messages, and a channel app token (A…, the Channels section) — so the assistant can send notifications.
  4. Paste the config for your client and substitute your own values for MXXXXXXXXXXXXXXXXXXXXXX (API token) and AXXXXXXXXXXXXXXXXXXXXXX (app token). NOTIFLY_URL for cloud Notifly is https://api.notifly.ru; for self-hosted — your server address.
  5. Check it with a first call — see below.

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": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_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=AXXXXXXXXXXXXXXXXXXXXXX \
-e NOTIFLY_API_TOKEN=MXXXXXXXXXXXXXXXXXXXXXX

Cursor — 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": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}

Restart the client (Cursor picks the config up by itself) and ask your assistant:

  1. “Show my Notifly channels” — the assistant calls list_applications and returns a JSON list of your channels. This checks the address and the API token.
  2. “Send a Notifly notification saying ‘MCP connected’” — the send_message call returns the created message with its id, the message appears in the admin feed and — if a receiving device is connected — arrives as a notification.

If a tool returns HTTP 401, check the token; if the client does not see any Notifly tools at all, check the path to notifly-mcp in the config. get_health checks the server address without any tokens.


  • Go 1.27 or newer

There is no downloadable binary yet, and the source repository is private: the MCP server is built from source. If you don’t have access to the repository, write to support@notifly.ru. If you do have the sources, the build goes like this:

Окно терминала
git clone https://github.com/truboter/notifly.ru.git
cd notifly.ru/mcp
go mod tidy
go build -o notifly-mcp .

The resulting binary notifly-mcp runs over a stdio transport — the MCP client launches it as a child process.


The server is configured exclusively via environment variables:

VariableRequiredDescription
NOTIFLY_URL✓Base URL of the Notifly server: https://api.notifly.ru for the cloud, or your own server’s address
NOTIFLY_APP_TOKENChannel token (prefix A) — for send_message and the question tools: ask_question, get_ask_answer, extend_ask_question, list_pending_answers, ack_answers
NOTIFLY_API_TOKENAPI token (prefix M) — for everything else
NOTIFLY_USERUsername (fallback instead of an API token)
NOTIFLY_PASSIts password

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://api.notifly.ru",
"NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}

After restarting Claude Desktop, the Notifly tools will appear in the interface.


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://api.notifly.ru",
"NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}

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://api.notifly.ru",
"NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}

After saving, Cursor will pick up the server automatically. Make sure notifly-mcp is available in PATH.


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://api.notifly.ru",
"NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}

After saving, restart Windsurf — the Notifly tools will appear in Cascade.


Use the claude mcp add command to register the server:

Окно терминала
claude mcp add notifly /usr/local/bin/notifly-mcp \
-e NOTIFLY_URL=https://api.notifly.ru \
-e NOTIFLY_APP_TOKEN=AXXXXXXXXXXXXXXXXXXXXXX \
-e NOTIFLY_API_TOKEN=MXXXXXXXXXXXXXXXXXXXXXX

The --scope user flag adds the server globally for all projects (by default — project-scope). To list added servers: claude mcp list.


Open or create ~/.codex/config.toml and add the section:

[mcp_servers.notifly]
command = "/usr/local/bin/notifly-mcp"
env = { NOTIFLY_URL = "https://api.notifly.ru", NOTIFLY_APP_TOKEN = "AXXXXXXXXXXXXXXXXXXXXXX", NOTIFLY_API_TOKEN = "MXXXXXXXXXXXXXXXXXXXXXX" }

After saving, Codex will pick up the server on next start. Make sure notifly-mcp is available in PATH.


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://api.notifly.ru",
"NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}
}

Zed will pick up the server without a restart.


Add a mcpServers section to the Continue config (~/.continue/config.json):

{
"mcpServers": [
{
"name": "notifly",
"command": "/usr/local/bin/notifly-mcp",
"env": {
"NOTIFLY_URL": "https://api.notifly.ru",
"NOTIFLY_APP_TOKEN": "AXXXXXXXXXXXXXXXXXXXXXX",
"NOTIFLY_API_TOKEN": "MXXXXXXXXXXXXXXXXXXXXXX"
}
}
]
}

Continue is supported as an extension for VS Code and JetBrains IDEs.


The server registers 117 tools in total. A few of them cover most needs:

TaskTools
Check the connectionget_health, list_applications
Send a notificationsend_message
Ask a human and wait for the answerask_question → get_ask_answer (wait_sec)
Read and search messageslist_messages, search_messages
Create a channelcreate_application

The full catalog below is grouped by section.

ToolDescription
get_healthCheck server and database health
get_versionGet version, commit and build date
get_server_infoGet server flags (registration, OIDC)
ToolParametersDescription
send_messagemessage*, title, priority, markdown, extras, app_tokenSend a notification (requires NOTIFLY_APP_TOKEN or app_token). priority 0–10, 0 — the channel’s default priority; markdown: true — render the text as Markdown; extras — a string holding a JSON object of extras, merged with markdown; app_token — another channel’s token (A…) instead of NOTIFLY_APP_TOKEN
list_messageslimit, sinceList all messages with pagination
list_application_messagesapp_id*, limit, sinceMessages of a specific channel
delete_messageid*Delete a message by ID
delete_all_messages—Delete all messages
delete_application_messagesapp_id*Delete all messages of a channel
search_messagesq*, app_id, recursive, limit, sinceSearch messages by text (min. 2 characters); across all channels or in one. recursive: true together with app_id — search child channels too
mark_messages_readids*Mark messages as read (JSON array of IDs, e.g. [1,2,3])
mark_application_readapp_id*, recursiveMark all of a channel’s messages as read (recursive — including child channels). Reading stops delivery escalation for those messages
ToolParametersDescription
ask_questionquestion*, options, fields, next, title, timeout_sec, default_answer, allow_multi, json_format, client_message_id, app_tokenAsk a human a question (push/Telegram/email with buttons); returns the question id. fields — a form of up to 10 fields, the answer comes as a {name: value} object (incompatible with options); next — branching for a closed question {"<option>": {…question…}}: only with options and without allow_multi, chain depth up to 5, the answer carries nextQuestionId. Requires NOTIFLY_APP_TOKEN or app_token. See Questions (Ask)
get_ask_answerid*, wait_sec, app_tokenFetch the answer; with wait_sec > 0 — wait for it: the answer arrives as a WebSocket push, falling back to polling every 3 s, up to 300 s. Returns answered+answer (for a form — a {name: value} object, with branching — also nextQuestionId), pending, expired or cancelled. Requires NOTIFLY_APP_TOKEN or app_token
extend_ask_questionid*, timeout_sec*, app_tokenExtend a question: for a pending one the deadline moves to timeout_sec (1–86400) from now; an expired one that expired less than 24 hours ago goes back to pending; for answered/cancelled — a conflict. Requires NOTIFLY_APP_TOKEN or app_token
cancel_ask_questionid*Cancel a pending question
answer_questionid*, answer*Answer a question on the user’s behalf — when the agent makes the decision itself rather than a human
list_pending_answersapp_tokenThe channel’s questions that have been answered but whose answers have not been collected yet. Handy when there are many questions: get the list, process the answers, then confirm with ack_answers. Requires NOTIFLY_APP_TOKEN or app_token
ack_answersids*, app_tokenConfirm the answers are processed — list_pending_answers will stop showing them. ids is an array of question IDs, e.g. [1,2,3]. Requires NOTIFLY_APP_TOKEN or app_token

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.

ToolParametersDescription
list_applications—List all channels
create_applicationname*, description, default_priority, tagsCreate a channel. tags — up to 10 tags of [a-z0-9_-]; used by the tag scope of API tokens
update_applicationid*, name, description, default_priority, tagsUpdate a channel; tags replace the current ones (omitted — unchanged)
delete_applicationid*Delete a channel
get_application_statusapp_id*, recursiveThe channel’s aggregate status from its heartbeats: how many in total, how many are alerting, when the last signal came; recursive — include child channels
list_event_sourcesapp_id*What sends events to the channel: monitors, heartbeats and other sources
test_deliveryapp_id*, adapter*Send a test notification through a configured delivery method. adapter — email, sms, call, slack, telegram, webhook, discord, teams, mattermost, pagerduty or opsgenie
ToolParametersDescription
list_clients—List all clients (devices/tokens)
create_clientname*Create a device and get its token (C…)
update_clientid*, name*Rename a client
delete_clientid*Delete a client (revoke token)

create_client, update_client and delete_client answer 403 with an API token: an API token cannot manage devices, a device token (C…) is required.

ToolParametersDescription
get_current_user—Information about the current user
list_users—List all users (admin)
get_userid*Get a user by ID (admin). The current server version has no GET /user/:id route — the call returns an error
create_username*, pass*, adminCreate a user. Without administrator rights the call is treated as a regular sign-up: it succeeds only if registration is open, and without admin
delete_userid*Delete a user (admin)
request_password_resetemail*Request a password reset — a link with a token will be sent to the email (public, no auth)
confirm_password_resettoken*, new_password*Confirm reset: set a new password using the token from the email

An API token never gets administrator rights, even if it belongs to an administrator: list_users and delete_user answer 403 with it. These tools need an administrator’s device token (C…) in NOTIFLY_API_TOKEN and, on top of that, a fresh sudo confirmation on that device (a 15-minute window); otherwise they also answer 403.

More about heartbeats — see the Heartbeat page.

ToolParametersDescription
list_heartbeats—List heartbeat notifications (dead-man switch)
create_heartbeatappid*, name*, intervalSec*, alertMessage*, graceSec, alertTitle, alertPriority, recoveryTitle, recoveryMessageCreate a heartbeat. intervalSec ≥ 30; an alert is sent if a ping hasn’t arrived within intervalSec+graceSec seconds; alertPriority defaults to 8
update_heartbeatid*, name*, intervalSec*, alertMessage*, graceSec, alertTitle, alertPriority, recoveryTitle, recoveryMessageUpdate heartbeat settings
delete_heartbeatid*Delete a heartbeat
pause_heartbeatid*Pause (alerts will not be sent until resume_heartbeat)
resume_heartbeatid*Resume a paused heartbeat
ping_heartbeatping_token*Send a ping using a public ping token (as an app would from cron)
test_heartbeat_alertid*Send the heartbeat’s alert notification right now — to check the channel and delivery. Creates a real message in the channel
test_heartbeat_recoveryid*Send the heartbeat’s recovery notification right now. Creates a real message in the channel

More about active checks — see the Monitors page.

ToolParametersDescription
list_monitors—List active monitors (HTTP, TCP and SSL)
create_monitorappid*, name*, kind*, target*, intervalSec*, alertMessage*, timeoutSec, expectedStatus, certMinDays, consecutiveFails, alertTitle, alertPriority, recoveryTitle, recoveryMessageCreate a monitor. kind = http (then target is a URL), tcp (target is host:port) or ssl — TLS certificate expiry and validity (target is host or host:port, port 443 by default). intervalSec 30–86400; timeoutSec 1–60, defaults to 10; expectedStatus (http only) 0 = “any 2xx”; certMinDays (ssl only) 1–365, defaults to 14 — an alert fires when fewer days are left before the certificate expires; consecutiveFails defaults to 1 (max. 20); alertPriority defaults to 8
update_monitorid*, name*, kind*, target*, intervalSec*, alertMessage*, timeoutSec, expectedStatus, certMinDays, consecutiveFails, alertTitle, alertPriority, recoveryTitle, recoveryMessageUpdate monitor settings (fields as in create_monitor)
delete_monitorid*Delete a monitor
pause_monitorid*Pause (checks will not run until resume_monitor)
resume_monitorid*Resume a paused monitor
test_monitorkind*, target*, timeoutSec, expectedStatus, certMinDaysProbe a target once without creating a monitor — the same probe an active monitor runs. An unreachable target returns ok: false in the body rather than a tool error

The five families work the same way: list_*, create_*, update_*, delete_*, pause_*, resume_*. An update replaces the settings entirely (omitted fields are reset), and the channel (appid) of an existing monitor cannot be changed.

FamilyToolsPurpose
http_monitorlist_http_monitors, create_http_monitor, update_http_monitor, delete/pause/resume_http_monitorA request with the response checked by filters: status, substring, regular expression, JSONPath, duration. intervalSec 30–86400
port_monitorlist_port_monitors, create_port_monitor, update_port_monitor, delete/pause/resume_port_monitorWatches that the required TCP ports are open (alertMode: closed) or, conversely, closed (open). intervalSec 60–86400
content_monitorlist_content_monitors, create_content_monitor, update_content_monitor, delete/pause/resume_content_monitorA value on a page: mode = hash, json, text, css or xpath; useBrowser renders JS. intervalSec 60–86400
workflow_monitorlist_workflow_monitors, create_workflow_monitor, update_workflow_monitor, delete/pause/resume_workflow_monitorA chain of HTTP requests (log in, then call) with {{name}} substitution from extracted values. intervalSec 60–86400
browser_workflowlist_browser_workflows, create_browser_workflow, update_browser_workflow, delete/pause/resume_browser_workflowA real browser walks through the page steps. intervalSec from 300 — a higher minimum than the rest; pause answers with no body

Trial runs without creating a monitor: test_http_monitor, test_port_monitor, test_content_rule, test_workflow (browser_workflow has no trial run; for a regular monitor — test_monitor above). An unreachable target returns ok: false in the body rather than a tool error.

ToolParametersDescription
get_monitor_historykind*, id*, bucket, from, toCheck statistics by bucket: total, successful, durations. bucket = 1m, 1h (default) or 1d; from/to are Unix seconds
get_monitor_logkind*, id*, limitThe latest individual checks with each one’s error text; up to 200, kept for 7 days

kind is written in snake_case and does not match the monitor paths: monitor, heartbeat, http_monitor, content_monitor, port_monitor, workflow_monitor, browser_workflow_monitor.

ToolParametersDescription
list_port_scans—List scans
get_port_scanid*A scan with its progress and the ports found
create_port_scanappid*, name*, host*, profile, rangeFrom, rangeTo, timeoutSec, repeatIntervalSec, alertTitle, alertMessage, alertPriorityStart a scan. profile = quick, standard, full or custom; except for custom, the profile overwrites the range. A private host is allowed. alertTitle/alertMessage/alertPriority — the result notification (the text is optional here)
update_port_scanid*, the rest optionalA true patch: only the passed fields change (the same as in create_port_scan, except profile). A running scan cannot be edited; appid can be changed here
delete_port_scanid*Delete a scan
cancel_port_scanid*Stop a queued or running scan
restart_port_scanid*Restart a finished scan
port_scan_to_monitorid*, name, intervalSec, timeoutSec, alertTitle, alertMessage, alertPriorityTurn the open ports found into a monitor that watches for them closing
list_verified_hosts—Verified hosts (a verification lives 365 days)
start_host_verificationhost*, method*Start verification: dns or http. A private address is verified immediately, a public one returns a token to publish
check_host_verificationhost*, method*, token*Confirm a published challenge. While the record is not visible yet — 422, worth retrying
delete_verified_hostid*Revoke a verification
ToolParametersDescription
push_metricmetric_token*, name + value or pointsWrite values to a metric source. points — a JSON array of up to 500 points with an optional ts in Unix milliseconds. A 204 response comes back even for an unknown token

More — see the Channel sharing page.

ToolParametersDescription
list_channel_sharesapp_id*List share tokens (delegations) for a channel; available to the owner or a user with send permission
create_channel_shareapp_id*, permission*, recipient_email, noteCreate a delegation. permission = view (read) or send (read + send); an empty recipient_email = an open token
update_channel_shareid*, permission*, recipient_email, noteChange recipient email, access level or note (channel owner only)
delete_channel_shareid*Revoke a share token (delete the delegation)
list_incoming_shares—Channels the current user has been granted access to (incoming shares by email)
accept_shareid*Accept an incoming delegation (status → active)
decline_shareid*Decline an incoming delegation
join_share_by_tokentoken*Join a channel by share token (prefix S)
ToolParametersDescription
get_client_subscriptionsclient_id*Channels the device is subscribed to by client ID
set_client_subscriptionsclient_id*, app_ids*Set the full list of device subscriptions; app_ids is an array of IDs, e.g. [1,2,3] (replaces the current ones; an empty one removes all subscriptions)
get_my_subscriptions—Subscriptions of the current device
subscribe_to_channelapp_id*Subscribe the current device to a channel
unsubscribe_from_channelchannel_id*Unsubscribe the current device from a channel

get_my_subscriptions, subscribe_to_channel and unsubscribe_from_channel work with the current device: they need a device token (C…) in NOTIFLY_API_TOKEN; an API token has no device, and the server answers 401.

ToolParametersDescription
get_quota_breakdowndayDetailed breakdown of event quota usage for a day (YYYY-MM-DD, default — today in MSK)
topup_balanceamount_rubles*Top up balance (integer ≥ 1; admin only, others will receive payment details)

* — required parameter


Send a notification to Notifly with the title "Deploy finished"
and the text "Version 2.1.0 was deployed to prod successfully" with priority 7.

Claude will call the send_message tool automatically.

Show the list of all channels in Notifly and the last 10 messages
from the channel with ID 3.

Claude will call list_applications and then list_application_messages sequentially.

Create a new user in Notifly named "devops"
with the password "securepass", without administrator rights.

Claude will call create_user with the required parameters. Creating a user as an administrator works with an administrator’s device token in NOTIFLY_API_TOKEN; without administrator rights the server treats the call as a regular sign-up (see Users).


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.

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_id is 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.

{
"tool": "get_ask_answer",
"arguments": {"id": 8260183728527568, "wait_sec": 300}
}

get_ask_answer with wait_sec > 0 blocks until the answer arrives and returns a terminal status. The answer comes as a WebSocket push, so the tool returns the moment someone presses the button; when the socket is unavailable it falls back to polling every 3 seconds:

{"id": 8260183728527568, "status": "answered", "answer": "yes"}

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 token

Section titled “Security: a tag scope and TTL for the agent’s token”

The agent does not need access to every channel in the account. Restrict its API token:

  1. Put an ai tag on the “agent” channels (the channel’s “Tags” field or create_application/update_application with tags: ["ai"]).
  2. Issue an API token with a tag scope and an expiry:
POST /api-token
{
"name": "deploy-agent",
"access": "write",
"appTags": ["ai"],
"expiresAt": "2026-08-01T00:00:00Z"
}

Such a token sees only channels tagged ai — including ones created later (a dynamic scope, no token edits needed), and past expiresAt it stops working by itself: a token forgotten in an agent’s config does not remain a permanent hole. More about the scope — on the API tokens page.


  • The MCP server runs locally over stdio — no network port is opened.
  • Tokens are stored only in the process environment variables.
  • The delete_all_messages and delete_user tools perform irreversible operations — the AI assistant should request confirmation before calling them.