MCP server
Notifyzr ships a Model Context Protocol server at mcp.<DOMAIN>, so an AI assistant can read your contacts, segments, templates and delivery history — and send an email — through the same API and the same permissions as any other client.
Connecting
The server speaks Streamable HTTP at the origin root. Authenticate with the same X-API-Key you use for the REST API.
{
"mcpServers": {
"notifyzr": {
"url": "http://mcp.localhost/",
"headers": { "X-API-Key": "your-api-key" }
}
}
}A request without the header is rejected with 401 before the session is established, rather than failing later on the first tool call.
How permissions work
The server holds no credentials of its own. Your key is relayed verbatim to the public API, which decides what it may see — so organization scoping, quotas and rate limits apply exactly as they would to a direct API call.
It follows that the server can grant no access your key does not already have, and that a key scoped to one organization exposes exactly that organization through MCP.
Tools
Everything is read-only except three writes: send_message, send_channel_message and create_template.
| Tool | Reads |
|---|---|
list_contacts, get_contact | Contacts, with search and subscription filtering |
list_unsubscribes | The suppression list, filterable by scope |
list_contact_properties | Property definitions |
list_segments, get_segment, list_segment_contacts | Segments and their live membership |
list_templates, get_template | Templates and their draft/published state |
list_forms, list_form_submissions | Forms and what they captured |
list_messages, get_message | Delivery history |
list_broadcasts, get_broadcast | Broadcast progress |
list_automations, get_automation, list_automation_runs, get_automation_metrics | Automations and why a run did what it did |
list_domains | Sender domains and verification state |
list_channels | Connected Discord/Telegram channels |
get_usage | Plan, quota limits and current usage |
Writes
| Tool | Writes |
|---|---|
send_message | Sends one email |
send_channel_message | Posts to one Discord/Telegram channel |
create_template | Creates an email template |
All three are annotated as not read-only and not idempotent, so a well-behaved client prompts before calling them and does not silently retry.
send_message sends real email
It spends quota, refuses an unverified sender domain, refuses a draft template, and drops opted-out recipients — the same rules as the REST endpoint. A retry is a second real email.
send_channel_message posts to a room
It reaches everyone in the channel rather than recipients you named, so the suppression list does not apply and there is nothing to un-send. It spends the same message quota as an email. Take the channel id from list_channels — ids are not guessable, and there is no "default" channel.
create_template is the mildest of the three: it mails nobody, and the template is created as a draft unless you pass status: "published", which every send path already refuses to use. Pass status: "published" and you must also supply at least one of body_html / body_text. Template names are not unique, so calling it twice leaves two templates. It does not accept editor_state — that is the dashboard editor's own block document, and a template created here simply opens in the editor as a single raw-HTML block.
Deliberately not exposed: updating or deleting records, and POST /v1/broadcasts/{id}/send. Mailing an entire segment is irreversible and its blast radius is however many contacts the filter matches at that moment; that belongs behind a human in the dashboard, not behind a tool annotation.
Notes
- Ids are UUIDs and are validated as such before any call is made upstream, so an assistant cannot guess or construct one.
- Sessions are held in Redis, so they survive across workers and across replicas.
- An upstream rejection comes back as a readable tool error — including the API's own error code — rather than a protocol failure, so the assistant can correct itself and try again.