Ray notifications
by gege-mn · remote · local (npm) · no sign-in to list tools · v0.1.0 · published by GitHub user gege-mn
Send email, push, Slack, Discord, Telegram and webhook notifications and manage templates with Ray.
21 tools: 2 can delete or cancel things, 3 do something that can't be undone, 5 change things and 11 only read.
Scanned Oct 21, 2026
- Deletes or cancels2Claude Code: deny: see these tools
- Can't be undone3Claude Code: ask: see these tools
- Changes things5Claude Code: ask: see these tools
- Only reads11Claude Code: allow: see these tools
1 check matched something in the tool definitions. No finding is not a safety guarantee. How the checks work.
claude mcp add --transport http ray https://ray-api.gege.mn/mcp --header 'Authorization: <your-authorization>'Findings
Matches of fixed checks, with the text they matched.
- Infolist_channels
The description mentions "credentials".
What this check looks forMatched text
description: "...it expects. Channels and their provider credentials are set up in the Ray dashboard, not th..."
Tools
Open a tool for its reasons and full description. Descriptions are the publisher's own words, shown as they are.
21 tools
Deletes or cancels 2Claude Code: deny
archive_templateArchive a template (POST /templates/{id}/archive; write scope).No findings
Why it is in this group
- The name "archive_template" says "archive": a word for removing or ending something.
- The server marks it destructive (destructiveHint: true).
Description, as the server wrote it Archive a template (POST /templates/{id}/archive; write scope). Sends that reference it fail until it is restored with unarchive_template; its name stays reserved meanwhile. Confirm with the user first if it may be in production use.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: true
- idempotentHint: true
delete_webhookDelete (archive) a webhook so it receives no more events (DELETE /tenant-webhooks/{id}; write scope).No findings
Why it is in this group
- The name "delete_webhook" says "delete": a word for removing or ending something.
- The description says "cannot be restored".
- The server marks it destructive (destructiveHint: true).
Description, as the server wrote it Delete (archive) a webhook so it receives no more events (DELETE /tenant-webhooks/{id}; write scope). It cannot be restored through the API; confirm with the user first.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: true
- idempotentHint: true
Can't be undone 3Claude Code: ask
publish_templatePublish a template's current draft as its new live version (POST /templates/{id}/publish; write scope).No findings
Why it is in this group
- The name "publish_template" says "publish": a word for a change that another call does not undo.
Description, as the server wrote it Publish a template's current draft as its new live version (POST /templates/{id}/publish; write scope). Sends by templateId use it immediately. Fails if there is no draft or the template is archived.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: false
send_notificationSend a notification (POST /send; needs a `write`-scoped key).No findings
Why it is in this group
- The name "send_notification" says "send": a word for sending, and what is sent cannot be taken back.
- The input addresses people (input.recipient): what it sends cannot be taken back.
Description, as the server wrote it Send a notification (POST /send; needs a `write`-scoped key). Returns `{ sendId }` with HTTP 202: delivery is asynchronous, so check the outcome with get_send_status. Use exactly one mode: 1. Single: `channelConfigId` + `recipient`. 2. Fan-out: `channelConfigId` + `targets` (1-1000 `{ recipient, externalUserId? }`), one channel, one sendId. 3. Multi-channel: `deliveries` (1-10, each with its own `channelConfigId`, `recipient` and `templateId` or `content`) for ONE person over several channels; add `feed` + `externalUserId` for a single in-app feed entry. 4. Feed-only: `feed` (with `title`) + `externalUserId` and no channel: an in-app notification only. In modes 1-2 give exactly one of `templateId` (a published template, plus `params`) or inline `content`; in mode 3 that choice is made per delivery. Get channel ids and each recipient shape from list_channels first. Pass `idempotencyKey` whenever a retry must not deliver twice.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: false
- openWorldHint: true
test_send_templateSend a real test of a template's PUBLISHED version to one recipient (POST /templates/{id}/test-send; write scope).No findings
Why it is in this group
- The name "test_send_template" says "send": a word for sending, and what is sent cannot be taken back.
- The input addresses people (input.recipient): what it sends cannot be taken back.
Description, as the server wrote it Send a real test of a template's PUBLISHED version to one recipient (POST /templates/{id}/test-send; write scope). It goes through the provider for real, so use a recipient the user controls. Test sends don't count toward the monthly quota (a small daily allowance applies instead) and never appear in feeds. Returns `{ sendId }` for get_send_status.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: false
- openWorldHint: true
Changes things 5Claude Code: ask
create_templateCreate a template (POST /templates; write scope).No findings
Why it is in this group
- The name "create_template" says "create": a word for a change.
Description, as the server wrote it Create a template (POST /templates; write scope). `channelKind` is permanent and must match the channels you'll send through (list_channels `templateKind`). It is saved as an unpublished draft unless `publish: true`; sending by templateId needs a published version (publish_template). Names are unique per workspace including archived templates: a collision returns 409, so unarchive_template the old one instead. Returns `{ id, requiredParams }`. Designed (drag-and-drop) email templates can only be made in the dashboard.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: false
create_webhookCreate an outbound webhook that Ray POSTs signed event payloads to (POST /tenant-webhooks; write scope; Pro plan or higher, otherwise 403).No findings
Why it is in this group
- The name "create_webhook" says "create": a word for a change.
Description, as the server wrote it Create an outbound webhook that Ray POSTs signed event payloads to (POST /tenant-webhooks; write scope; Pro plan or higher, otherwise 403). The URL must be public HTTPS. The response contains the signing `secret` exactly once: show it to the user to store (e.g. as an env var), since it can't be read again, only rotated with update_webhook. Read the `webhooks` docs page for the signature scheme.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: false
unarchive_templateRestore an archived template (POST /templates/{id}/unarchive; write scope).No findings
Why it is in this group
- Nothing in its name or description says what it does, so it is counted as a write.
Description, as the server wrote it Restore an archived template (POST /templates/{id}/unarchive; write scope). This is how to reclaim a name held by an archived template instead of creating a new one.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: true
update_template_draftReplace a template's draft (PATCH /templates/{id}; write scope).No findings
Why it is in this group
- The name "update_template_draft" says "update": a word for a change.
Description, as the server wrote it Replace a template's draft (PATCH /templates/{id}; write scope). This is a full replacement, not a partial patch: pass content, logTitle, logDescription and paramOverrides as they should end up (get_template first). `name`, `folder` and `channelKind` are required for validation but not changed (channelKind must equal the original). Live sends keep using the published version until you publish, either with `publish: true` here or publish_template. Fails on archived templates.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: false
update_webhookChange a webhook (PATCH /tenant-webhooks/{id}; write scope).No findings
Why it is in this group
- The name "update_webhook" says "update": a word for a change.
Description, as the server wrote it Change a webhook (PATCH /tenant-webhooks/{id}; write scope). Only the fields you pass change; pass at least one. `rotateSecret: true` issues a new signing secret, returned once in the response, and the old secret stops verifying immediately, so the receiving service must be updated at the same time.
Annotations the server declares
- readOnlyHint: false
- destructiveHint: false
- idempotentHint: false
Only reads 11Claude Code: allow
get_click_statsEmail link click counts grouped by destination URL (GET /clicks) for a `sendId`, a `campaignId`, or both (at least one is required).No findings
Why it is in this group
- The name "get_click_stats" says "get": a word for reading.
Description, as the server wrote it Email link click counts grouped by destination URL (GET /clicks) for a `sendId`, a `campaignId`, or both (at least one is required). Only links from email sends made with `trackClicks: true` are counted.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
get_send_statusDelivery status of a send (GET /sends/{id}): `aggregate` counts per status over the whole send (pending, claimed, delivered, failed_retryable, failed_terminal, suppressed) plus one…No findings
Why it is in this group
- The name "get_send_status" says "get": a word for reading.
Description, as the server wrote it Delivery status of a send (GET /sends/{id}): `aggregate` counts per status over the whole send (pending, claimed, delivered, failed_retryable, failed_terminal, suppressed) plus one page of per-recipient delivery rows including provider errors. Right after sending, rows are usually `pending`; check again after a few seconds. Feed-only sends have no rows. For more rows, pass the returned `nextCursor` as `cursor`.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
get_templateGet one template with its published version and its current draft (GET /templates/{id}): content, logTitle/logDescription, `requiredParams` a send must supply, and paramOverrides.No findings
Why it is in this group
- The name "get_template" says "get": a word for reading.
Description, as the server wrote it Get one template with its published version and its current draft (GET /templates/{id}): content, logTitle/logDescription, `requiredParams` a send must supply, and paramOverrides. Read this before update_template_draft, which needs the full body.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
get_usageThe workspace's plan, subscription status and this calendar month's usage against the monthly quota (GET /billing/subscription).No findings
Why it is in this group
- The name "get_usage" says "get": a word for reading.
Description, as the server wrote it The workspace's plan, subscription status and this calendar month's usage against the monthly quota (GET /billing/subscription). `used`/`remaining` are null on unmetered plans. On a hard-capped plan, sends beyond the quota fail with HTTP 402 quota_exceeded.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
get_webhookGet one outbound webhook (GET /tenant-webhooks/{id}) including its delivery health (consecutiveFailures, lastDeliveryAt, lastFailureAt, lastError).No findings
Why it is in this group
- The name "get_webhook" says "get": a word for reading.
Description, as the server wrote it Get one outbound webhook (GET /tenant-webhooks/{id}) including its delivery health (consecutiveFailures, lastDeliveryAt, lastFailureAt, lastError).
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
list_channelsList the delivery channels configured in this workspace (GET /channels).1 finding
Why it is in this group
- The name "list_channels" says "list": a word for reading.
Findings
- Info
The description mentions "credentials".
What this check looks forMatched text
description: "...it expects. Channels and their provider credentials are set up in the Ray dashboard, not th..."
Description, as the server wrote it List the delivery channels configured in this workspace (GET /channels). Call this FIRST before sending: each entry's `id` is the `channelConfigId` for send_notification and test_send_template, `kind` is the channel type (ses_email, smtp_email, fcm_push, slack_webhook, discord_webhook, telegram_bot, twilio_sms, sendsms_mn, generic_webhook), `templateKind` is the template channelKind it accepts, and `recipientSchema` is the exact JSON Schema of the `recipient` it expects. Channels and their provider credentials are set up in the Ray dashboard, not through this API; if the list is empty, ask the user to add one there.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
list_feed_notificationsRead one end user's in-app notification feed (GET /notifications): the entries created by sends with `feed` or `showInFeed`.No findings
Why it is in this group
- The name "list_feed_notifications" says "list": a word for reading.
Description, as the server wrote it Read one end user's in-app notification feed (GET /notifications): the entries created by sends with `feed` or `showInFeed`. This is what an in-app inbox would show, not delivery status (use get_send_status for that). Paginate by passing `nextCursor` back as `cursor`.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
list_templatesList message templates (GET /templates): id, name, folder, channelKind, and `publishedVersionId` (null = never published, so it cannot be sent by templateId yet).No findings
Why it is in this group
- The name "list_templates" says "list": a word for reading.
Description, as the server wrote it List message templates (GET /templates): id, name, folder, channelKind, and `publishedVersionId` (null = never published, so it cannot be sent by templateId yet). Archived templates are hidden unless `includeArchived` is true. Use get_template for content and required params.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
list_webhooksList the workspace's outbound event webhooks (GET /tenant-webhooks): url, subscribed events, enabled flag and delivery health (consecutiveFailures, lastError).No findings
Why it is in this group
- The name "list_webhooks" says "list": a word for reading.
Description, as the server wrote it List the workspace's outbound event webhooks (GET /tenant-webhooks): url, subscribed events, enabled flag and delivery health (consecutiveFailures, lastError). Signing secrets are never returned.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
read_docsRead Ray's documentation as markdown.No findings
Why it is in this group
- The name "read_docs" says "read": a word for reading.
Description, as the server wrote it Read Ray's documentation as markdown. With no `slug` it returns the index (llms.txt) listing every page; then pass a page slug, i.e. the path after /docs/ without `.md`, such as `sending`, `templates`, `idempotency`, `errors`, `rate-limits`, `webhooks`, `status-and-feeds` or `channels/telegram`. Use it for anything the tool descriptions don't cover.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
whoamiIdentify the API key this connection uses (GET /me): `tenantId` (the workspace), `apiKeyId` and `scopes`.No findings
Why it is in this group
- The name "whoami" says "whoami": a word for reading.
Description, as the server wrote it Identify the API key this connection uses (GET /me): `tenantId` (the workspace), `apiKeyId` and `scopes`. Write tools need the `write` scope; call this to check before attempting them.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
This is not a safety rating, and no finding is not a safety guarantee. The groups and findings come from fixed checks of the tool definitions the server returned on the date shown, not from its code or what it does when called. How the checks work.
Install
Pinned to version 0.1.0. Values in angle brackets are yours to fill in.
claude mcp add --transport http ray https://ray-api.gege.mn/mcp --header 'Authorization: <your-authorization>'Run in a terminal.
Claude Code permissions
Allow the reads, ask before a change, deny the deletes. Paste into .claude/settings.json.
{
"permissions": {
"allow": [
"mcp__ray__whoami",
"mcp__ray__get_usage",
"mcp__ray__list_channels",
"mcp__ray__get_send_status",
"mcp__ray__list_templates",
"mcp__ray__get_template",
"mcp__ray__list_feed_notifications",
"mcp__ray__get_click_stats",
"mcp__ray__list_webhooks",
"mcp__ray__get_webhook",
"mcp__ray__read_docs"
],
"ask": [
"mcp__ray__send_notification",
"mcp__ray__create_template",
"mcp__ray__update_template_draft",
"mcp__ray__publish_template",
"mcp__ray__unarchive_template",
"mcp__ray__test_send_template",
"mcp__ray__create_webhook",
"mcp__ray__update_webhook"
],
"deny": [
"mcp__ray__archive_template",
"mcp__ray__delete_webhook"
]
}
}Tools that only read are allowed, tools that change things ask first, tools that delete or cancel are denied. It assumes the server is installed as ray. Review it before you use it.
