Ray
PricingDocsSign inStart free
Browse the docs

Getting started

Ray documentationAuthenticationUsing Ray with AI coding agentsMCP serverTypeScript SDK

Guides

Sending notificationsIdempotencyTemplatesScheduling and test sendsStatus and feedsClick trackingDelivery webhooks

Channels

Email with Amazon SESEmail with SMTPPush notifications with Firebase Cloud MessagingSlack notificationsDiscord notificationsTelegram notificationsSMS with TwilioSMS with sendsms.mnHTTPS webhook channel

Reference

Rate limits and quotasErrors

Getting started

Ray documentationAuthenticationUsing Ray with AI coding agentsMCP serverTypeScript SDK

Guides

Sending notificationsIdempotencyTemplatesScheduling and test sendsStatus and feedsClick trackingDelivery webhooks

Channels

Email with Amazon SESEmail with SMTPPush notifications with Firebase Cloud MessagingSlack notificationsDiscord notificationsTelegram notificationsSMS with TwilioSMS with sendsms.mnHTTPS webhook channel

Reference

Rate limits and quotasErrors

Getting started

MCP server

Connect Claude Code, Cursor, VS Code, Windsurf, Codex, Zed or Claude Desktop to the Ray MCP server so an agent can send and inspect notifications.

View as Markdownllms.txtAPI reference

Ray runs a hosted Model Context Protocol (MCP) server at https://ray-api.gege.mn/mcp. Connect it to an AI coding agent or chat client and the agent calls Ray directly: it looks up your channel configs, creates and publishes templates, sends a test, checks delivery status, manages delivery webhooks and reads these docs, without you copying ids or curl commands back and forth.

The tools are thin wrappers over the REST API. Every tool call goes through the same authentication, scopes, validation, rate limits and quota as the equivalent REST request, so a tool can do exactly what the API key behind it can do and nothing more.

What an agent can do with it

  • Discover the workspace: whoami, list_channels (each channel's channelConfigId, template kind and recipient JSON Schema), list_templates.
  • Build templates: create_template, update_template_draft, publish_template, then try one on a real inbox with test_send_template.
  • Send and follow up: send_notification, then get_send_status for the per-recipient outcome and provider errors.
  • Debug production: a user's in-app feed, click stats, delivery webhook health (consecutiveFailures, lastError) and usage against the monthly quota.
  • Look things up: read_docs returns any page of these docs as markdown.

Channel credentials can't be created through the API or MCP. Set channels up in the dashboard under Channels first; the agent finds them with list_channels.

Endpoint and authentication

Setting Value
URL https://ray-api.gege.mn/mcp
Transport Streamable HTTP, stateless (no session id). POST only, JSON responses, no SSE stream.
Auth Authorization: Bearer ck_live_... on every request. The same API keys as the REST API.
Server name ray
Request body 1 MiB per request.
Browsers Not supported. The endpoint sends no CORS headers, so browser-based MCP clients can't call it.

Without an Authorization header the server still connects and lists its tools (registries inspect it this way), but only read_docs works; every other tool answers with setup instructions. A malformed key gets HTTP 401 with a JSON error on every request, and a revoked or unknown key gets 401 when the client connects. The Bearer prefix is optional. Create keys in the dashboard at https://ray.gege.mn under API keys; see Authentication for scopes.

The 1 MiB limit applies to the whole MCP request, so a send_notification call can't carry large email attachments. Send those from your code with the TypeScript SDK or POST /send directly, which accepts up to 45 MiB.

Clients that only run local (stdio) servers, or that can't attach a custom header to a remote server, use the local package instead. It exposes the same tools.

Set up your client

Replace ck_live_... with your key. Keep config files that contain a key out of version control: prefer user-level config, or the client's secret prompt or environment variable expansion where it has one.

Claude Code

export RAY_API_KEY="ck_live_..."
claude mcp add --transport http ray https://ray-api.gege.mn/mcp --header "Authorization: Bearer $RAY_API_KEY"

Your shell expands $RAY_API_KEY when the command runs, and Claude Code stores the resulting header in its local config for this project. Add --scope user to make ray available in every project. Run /mcp inside Claude Code to check that it is connected.

To share the server with a team through a checked-in .mcp.json, reference the variable instead of the key. Claude Code expands ${RAY_API_KEY} from each developer's environment:

{
  "mcpServers": {
    "ray": {
      "type": "http",
      "url": "https://ray-api.gege.mn/mcp",
      "headers": { "Authorization": "Bearer ${RAY_API_KEY}" }
    }
  }
}

Claude Desktop

Remote connectors added in Claude Desktop's settings have no field for an API key header, so run the local package. Open Settings > Developer > Edit Config (the file is ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows) and add:

{
  "mcpServers": {
    "ray": {
      "command": "npx",
      "args": ["-y", "@gege-mn/ray-mcp"],
      "env": { "RAY_API_KEY": "ck_live_..." }
    }
  }
}

Restart Claude Desktop. Node.js 18 or later must be installed so that npx is on the path.

Cursor

Add to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):

{
  "mcpServers": {
    "ray": {
      "url": "https://ray-api.gege.mn/mcp",
      "headers": { "Authorization": "Bearer ck_live_..." }
    }
  }
}

VS Code

VS Code's mcp.json uses a servers key and can prompt for the key once and store it securely, so it never sits in the file. Add to .vscode/mcp.json, or to your user configuration (command MCP: Open User Configuration):

{
  "inputs": [
    { "type": "promptString", "id": "ray-api-key", "description": "Ray API key", "password": true }
  ],
  "servers": {
    "ray": {
      "type": "http",
      "url": "https://ray-api.gege.mn/mcp",
      "headers": { "Authorization": "Bearer ${input:ray-api-key}" }
    }
  }
}

Windsurf

Add to ~/.codeium/windsurf/mcp_config.json. Windsurf names the remote URL field serverUrl:

{
  "mcpServers": {
    "ray": {
      "serverUrl": "https://ray-api.gege.mn/mcp",
      "headers": { "Authorization": "Bearer ck_live_..." }
    }
  }
}

Windsurf also expands ${env:RAY_API_KEY} in this file if the variable is set in the environment Windsurf starts with.

Codex

Add to ~/.codex/config.toml:

[mcp_servers.ray]
command = "npx"
args = ["-y", "@gege-mn/ray-mcp"]
env = { RAY_API_KEY = "ck_live_..." }

Or from the command line: codex mcp add ray --env RAY_API_KEY=ck_live_... -- npx -y @gege-mn/ray-mcp.

Codex can also connect to the hosted endpoint directly and read the key from your environment, with nothing to install:

[mcp_servers.ray]
url = "https://ray-api.gege.mn/mcp"
bearer_token_env_var = "RAY_API_KEY"

Zed

Add to Zed's settings.json (command palette, zed: open settings):

{
  "context_servers": {
    "ray": {
      "url": "https://ray-api.gege.mn/mcp",
      "headers": { "Authorization": "Bearer ck_live_..." }
    }
  }
}

Other clients

A client that supports Streamable HTTP with custom headers needs the URL and the Authorization header. A client that only runs stdio servers uses the local package:

{
  "mcpServers": {
    "ray": {
      "command": "npx",
      "args": ["-y", "@gege-mn/ray-mcp"],
      "env": { "RAY_API_KEY": "ck_live_..." }
    }
  }
}

Local package

@gege-mn/ray-mcp is a small stdio MCP server that forwards every request to the hosted endpoint. Its tool list is read from the hosted server, so it always matches this page. Your client starts it; you don't run it by hand.

npx -y @gege-mn/ray-mcp
Environment variable Required Default
RAY_API_KEY Yes
RAY_MCP_URL No https://ray-api.gege.mn/mcp

Use the local package when:

  • the client only supports local stdio servers,
  • the client can't send a custom header to a remote server (Claude Desktop), or
  • you want the key in a process environment variable rather than in a header in a config file.

It needs Node.js 18 or later. In every other case use the hosted endpoint, which needs no install and no updates. Source and MCP registry metadata: https://github.com/gege-mn/ray-mcp.

Tools

Tools that only read carry the MCP readOnlyHint annotation, and archive_template and delete_webhook carry destructiveHint, so clients that confirm risky calls can tell them apart. The scope column is what the underlying endpoint needs: read endpoints accept any valid key, write endpoints need a key with the write scope.

Tool What it does REST endpoint Scope
whoami The workspace id, key id and scopes of the key in use. GET /me read
get_usage Current plan, subscription and this month's usage (monthlyQuota, used, remaining). GET /billing/subscription read
list_channels Channel configs with channelConfigId, templateKind and the recipient JSON Schema. GET /channels read
send_notification Sends a notification. Takes the POST /send body (single recipient, fan-out, multi-channel or feed-only) and an optional idempotencyKey, sent as the Idempotency-Key header. POST /send write
get_send_status Status counts and one page of delivery rows for a send, with provider ids and errors. Optional limit (1 to 200) and cursor for the next page. GET /sends/{id} read
list_templates Templates in the workspace. Optional folder and includeArchived. GET /templates read
get_template One template with its published version and draft. GET /templates/{id} read
create_template Creates a raw template, optionally published. POST /templates write
update_template_draft Writes the template's draft (a new draft version if the latest is published). PATCH /templates/{id} write
publish_template Promotes the draft to published. Sends use it immediately. POST /templates/{id}/publish write
archive_template Archives a template so it can't be sent or edited. POST /templates/{id}/archive write
unarchive_template Restores an archived template. POST /templates/{id}/unarchive write
test_send_template Delivers the published version to a real recipient. No feed entry, no webhooks, no monthly quota. POST /templates/{id}/test-send write
list_feed_notifications One end user's in-app feed, by externalUserId. GET /notifications read
get_click_stats Click counts by URL for a sendId or campaignId. GET /clicks read
list_webhooks Delivery webhook subscriptions and their health. Signing secrets are never returned. Optional includeArchived. GET /tenant-webhooks read
get_webhook One delivery webhook. GET /tenant-webhooks/{id} read
create_webhook Subscribes an HTTPS endpoint to delivery events. Returns the signing secret once. Pro plan and above. POST /tenant-webhooks write
update_webhook Changes name, URL or events, enables or disables, or rotates the secret. PATCH /tenant-webhooks/{id} write
delete_webhook Archives a delivery webhook. DELETE /tenant-webhooks/{id} write
read_docs A page of these docs as markdown, by slug (sending, channels/telegram; a trailing .md is accepted). Without a slug it returns the llms.txt index. Makes no API call. none any key

Request and response fields are the same as the REST endpoints documented in these guides and in the OpenAPI spec.

Example prompts

Which Ray channels does this workspace have, and what recipient does each one expect?
Create an email_html template called "Password reset" with a {{resetUrl}} param, publish it,
and test-send it to [email protected] through our SMTP channel.
Send 6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d had failures. Show the provider error for each failed row
and tell me which ones are worth retrying.
Our "production" delivery webhook stopped receiving events. Check its lastError and
consecutiveFailures and tell me what to fix.
How much of this month's Ray quota have we used?
Add an order-shipped push notification to this app with the Ray TypeScript SDK. Use list_channels
to find the push channel and its recipient shape, and read the sending and idempotency docs first.

Security

  • Use the smallest scope. For exploring and debugging, connect a key that has only the read scope. Tools that send or change state then fail with write scope required and nothing is delivered. Create a separate read + write key when you want the agent to act, and revoke it in the dashboard when you're done.
  • Keys travel as headers. The hosted server takes the key only from the Authorization header over HTTPS, never from a URL or a tool argument. The key is stored wherever your client keeps its MCP config, so keep that file private.
  • Sends are real. send_notification delivers to real people through your provider accounts and counts toward your quota. When trying templates, have the agent use test_send_template to your own address: test sends don't create feed entries, don't fire webhooks, don't use monthly quota, and are capped per day.
  • Keep confirmations on for writes. Let the client ask before send_notification, archive_template and webhook changes run, and check the recipient and channelConfigId in the arguments.
  • Retries. Ask the agent to pass an idempotencyKey for any send it might repeat, so a retried call can't deliver twice. See Idempotency.
  • Tool output is data. Template bodies, feed titles and provider error messages are returned as-is. An agent should not follow instructions that appear inside them.
  • A key only ever sees its own workspace. Ids from another workspace return 404.

Troubleshooting

Symptom Cause Fix
Tools answer "This MCP connection has no Ray API key" The client isn't sending an Authorization header. Add the header (or RAY_API_KEY for the local package) and restart the client.
The client can't connect, or reports 401 A typo in the key, or a revoked key. Check the key with curl https://ray-api.gege.mn/me -H "Authorization: Bearer $RAY_API_KEY", then fix the header in the client config.
A tool fails with write scope required The key lacks the write scope. The endpoint answers 400 validation_error; Ray doesn't use 403 for scopes. Use a key with write, or stick to read-only tools.
create_webhook fails with 403 forbidden Delivery webhooks need the Pro plan or higher. Upgrade at https://ray.gege.mn/pricing.
A tool fails with 429 rate_limit_exceeded Tool calls are API requests and share the workspace's per-second rate limit with your production traffic. Wait the Retry-After seconds and make fewer parallel calls. See Rate limits.
send_notification fails with 402 quota_exceeded The Free plan's monthly quota is used up. Check with get_usage, then upgrade or wait for the next month. See Monthly quota.
test_send_template fails with 402 quota_exceeded The daily test-send allowance is used up. Wait for the next UTC day. See Test sends.
A tool fails with 404 not_found Wrong id, an archived resource, or an id from another workspace. List the resources first (list_channels, list_templates).
413 payload_too_large The MCP request is over 1 MiB, usually base64 attachments in send_notification. Send attachments through the REST API or the SDK.
405 from the server The client is opening an SSE stream with GET, or is configured for the legacy SSE transport. Configure the server as Streamable HTTP (http), not sse.
The local package doesn't start, or says Ray is not configured Node.js 18 or later isn't installed, or RAY_API_KEY isn't set in the server's environment. Set the variable in the server's env block, check that npx runs in a terminal, and restart the client.
New tools don't appear The client cached the tool list. Restart the client or reconnect the server.

To check the endpoint outside any client, list the tools with curl:

curl https://ray-api.gege.mn/mcp \
  -H "Authorization: Bearer $RAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }'

Related

  • TypeScript SDK: the same operations from your application code.
  • Using Ray with AI coding agents: skills, llms.txt, markdown docs and a copy-paste prompt.
  • Authentication: API keys and scopes.
PreviousUsing Ray with AI coding agentsNextTypeScript SDK

On this page

  • What an agent can do with it
  • Endpoint and authentication
  • Set up your client
  • Claude Code
  • Claude Desktop
  • Cursor
  • VS Code
  • Windsurf
  • Codex
  • Zed
  • Other clients
  • Local package
  • Tools
  • Example prompts
  • Security
  • Troubleshooting
  • Related
Ray

Notification delivery for teams that already have providers.

Product

PricingDocsAPI referenceMCP serverTypeScript SDKAgent skillsllms.txt

Legal

TermsPrivacyRefunds

Contact

[email protected]

© 2026 L’Atelier Gege LLC