MCP server
Connect an agent, configure an app, send notifications, manage recurring schedules, and read delivery traces.
For text, number, boolean and date attributes, SDK methods and audience operators, see Tags.
Set up with your agent
Copy a prompt with the quickstart, SDK guides and MCP connection. Sign in or create your account when the agent opens the browser.
Copy setup promptRead the prompt
Help me send my first mobile push with Carillon in this project.
Read https://app.carillon.dev/docs/llms.txt, then https://app.carillon.dev/docs/quickstart and the SDK guide matching this codebase:
React Native: https://app.carillon.dev/docs/sdks/react-native
Swift: https://app.carillon.dev/docs/sdks/swift
Kotlin: https://app.carillon.dev/docs/sdks/kotlin
Connect the hosted MCP at https://app.carillon.dev/mcp using OAuth. Open the browser so I can sign in or create an account; never ask for my password.
List my organizations and select the appropriate workspace.
Reuse a matching app or create one after checking the existing apps.
Inspect the code and use the installed SDK API. Get an active mobile key with key_list. Never put a secret key in the mobile app.
Check provider credentials and guide me through any Apple/Firebase steps or device actions you cannot perform. Keep credential files and secrets out of chat and version control.
Configure the SDK, native capabilities and notification permission, then rebuild. Verify the device registration, send one notification only to my chosen test device, and inspect its trace. A provider acceptance is not proof of display on the phone. Report what was verified and what still needs my action.
Read the current MCP tool list; use the documented API or dashboard when a required action is not available as a tool.Connect an HTTP MCP client to:
https://app.carillon.dev/mcpFor Claude Code:
claude mcp add --transport http carillon https://app.carillon.dev/mcpSign in or create an account through the browser when prompted. Signup creates a workspace and resumes the MCP authorization flow. The client receives an OAuth access token with your organization memberships. Access tokens last one hour; refresh tokens last one week.
Use device_list to verify registration, then notification_send_test to send one real notification to a device you select. Read its result with message_trace. Use notification_send for campaigns with audiences, localization and scheduling. Backend integrations can use a secret key with POST /v1/messages.
The tools
Start with organization_list, then app_list. Pass the returned organization and app IDs
explicitly to subsequent tools.
| Tool | Arguments | Reads only | Destructive |
|---|---|---|---|
device_delete | app_id, device_id, confirm: true | No | Yes |
device_list | app_id, q?, platform?, status?, cursor? | Yes | No |
device_tags_read | app_id, device_id | Yes | No |
device_tags_patch | app_id, device_id, tag maps | No | No |
user_tags_read | app_id, external_id | Yes | No |
user_tags_patch | app_id, external_id, tag maps | No | No |
notification_tags_list | app_id, mode?, cursor? | Yes | No |
message_trace | app_id, message_id | Yes | No |
notification_send | app_id, audience, payload, confirm: true; optional send API fields and idempotency_key | No | No |
notification_send_test | app_id, device_id, title, body?, data?, tags?, confirm: true | No | No |
recurring_list | app_id | Yes | No |
recurring_create | app_id, complete recurring definition | No | No |
recurring_update | app_id, recurring_id, complete definition, confirm: true | No | Yes |
recurring_activate | app_id, recurring_id, confirm: true | No | No |
recurring_pause | app_id, recurring_id, confirm: true | No | Yes |
recurring_archive | app_id, recurring_id, confirm: true | No | Yes |
recurring_history | app_id, recurring_id, days?, cursor? | Yes | No |
notification_list | app_id, tag?, tags?, without_tag?, include_empty?, mode?, source?, cursor? | Yes | No |
notification_cancel | app_id, message_id, confirm: true | No | Yes |
device_history | app_id, device_id | Yes | No |
delivery_trace | app_id, delivery_id | Yes | No |
audience_update | app_id, audience_id, definition, confirm: true | No | Yes |
audience_suggestions | app_id, field, key?, search?, cursor? | Yes | No |
app_health | app_id | Yes | No |
app_activity | app_id | Yes | No |
notification_statistics | app_id, since, until, source?, cursor? | Yes | No |
organization_list | none | Yes | No |
app_list | organization_id | Yes | No |
app_create | organization_id, name | No | No |
credential_setup | app_id | Yes | No |
credential_list | app_id | Yes | No |
credential_upload | app_id, provider, key_id, team_id?, bundle_id?, material | No | No |
key_list | app_id | Yes | No |
key_create | app_id, type, label? | No | No |
key_revoke | app_id, key_id, confirm | No | Yes |
audience_list | app_id | Yes | No |
audience_preview | app_id, definition | Yes | No |
audience_create | app_id, name, definition | No | No |
audience_delete | app_id, audience_id, confirm | No | Yes |
notification_send_test requires your confirmation of the device and content. It sends in live mode, not simulated test mode, and must not be retried automatically. It is offered when sending is configured.
The credential pair is only offered where key management is configured, so a tool that would have to refuse the call is not listed.
Permissions and confirmation
Destructive tools require an explicit confirmation. device_delete, key_revoke and audience_delete take a
confirm argument and refuse without it, which is where the agent asks you first. Revoking a mobile
key breaks device registration in every installed copy of an app until users take an update.
A secret key is shown once, to the agent too. key_create returns its value in that one answer
and nowhere else, exactly as the dashboard does. A mobile key stays readable, as it does everywhere
else.
An app or organization outside your memberships answers "no such", never "forbidden". The two cases are not distinguishable from the answer.
A refused tool call includes an error and the next action. For example:
No such resource. No app with that id, or you are not a member of the
organization owning it. Call app_list for an organization you belong to.Related pages
Authentication · CLI · Send your first notification
Test application data
notification_send_test accepts optional data with the same JSON values, including nested objects and arrays, as a message
payload. Ask the user to confirm the device and content before sending; this sends a real
notification and a repeated call sends another one.
{
"app_id": "{{app_id}}",
"device_id": "{{device_id}}",
"title": "Open the example conversation",
"data": { "url": "myapp://chat/42", "unread": 3 },
"confirm": true
}Read the returned campaign with message_trace. Poll while status is scheduled
or sending. Stop on sent or canceled. A paused campaign requires
action before resuming. sent means processing is finished, not that every notification
was accepted or displayed; inspect the delivery outcomes for failures. Zero pending
deliveries alone is insufficient when scheduled waves remain.
Upload credentials without sharing the file with the client
- Call
credential_setupwith the app ID. It returns the credential screen on the same dashboard environment as this MCP connection. - Open the returned URL and upload the
.p8or service-account JSON file in the dashboard. Sign in there if needed; the URL grants no access by itself. - Call
credential_listto verify the saved credential'sstatusandvalidated_at.
credential_setup reads no local file and uploads nothing. A filesystem path on your
computer is not accessible to the hosted MCP server. Use credential_upload only when you
intend to provide file contents through your MCP client.
Interpret upload validation
An upload response includes validation with outcome, stage and code:
| Stage | Check |
|---|---|
input | Carillon checked the supplied metadata or parsed the file. |
oauth | Carillon attempted the Google OAuth token exchange. |
provider | Carillon checked authentication with APNs or the FCM messaging endpoint. |
validation | The overall Carillon validation timed out or raised an unexpected error. |
null | No stage was reported. |
outcome: "invalid" records a refusal at that stage. The stage locates the observed
failure, not its root cause: an authentication response does not prove the file is wrong.
"unreachable" means validation could not finish; the credential is saved without being
marked invalid by that attempt. "not_checked" means validation was unavailable.
This diagnostic describes the upload attempt and is not retained by credential_list.
The list's last_error remains the last recorded rejection code. Check validated_at:
a saved credential with no validation time has not yet been confirmed by a provider.
No raw provider response, OAuth token or private key is included in the diagnostic.
Notification tags
Use notification_tags_list with app_id to discover notification labels already used in an app. These are campaign categories, not device or user tags.
{ "app_id": "YOUR_APP_ID", "mode": "live" }The result contains tags and next_cursor. Each page contains up to 100 distinct labels sorted by label. Pass next_cursor as cursor to read the next page until it is null. Omit mode to include both live and test campaigns.
Reuse these labels in the optional top-level tags field of notification_send, for example "tags": ["new_comment", "social"]. Confirm the audience and content first: this tool sends a real notification. Read the stored tags with message_trace.
Tags accept up to 10 strings of 1-64 characters. Prefer a small, stable vocabulary. See notification tags for the send API and naming guidance.
Send to an audience
notification_send accepts the same message fields as POST /v1/messages, plus app_id, confirm: true and optional idempotency_key. It sends in live mode using the signed-in user's access to the app. Confirm the audience and content before calling it.
{
"app_id": "YOUR_APP_ID",
"audience": { "audience_id": "YOUR_SAVED_AUDIENCE_ID" },
"payload": {
"title": "New comments",
"body": "See what is new",
"localizations": { "fr": { "title": "Nouveaux commentaires" } }
},
"tags": ["new_comment"],
"idempotency_key": "comments-campaign-42",
"confirm": true
}Choose one audience: device_ids, external_ids, all: true, or audience_id. Use audience_list, audience_create and audience_preview to prepare a saved audience. The latest saved filters are frozen when recipient resolution begins, then shared by all retries and timezone waves. Devices are resolved at send time.
Optional send_at schedules an instant; local_time with timezone_fallback schedules in recipients' timezones. delivery_rate controls send pacing. dedup_key prevents a repeated business notification for 30 days. The API's validation and billing rules apply.
The result contains the campaign ID. Read it with message_trace. Retry an uncertain request only with the same idempotency_key and identical message fields; without that key, another call can send another notification.
notification_send_test remains available as a shortcut for a single-device integration check. Despite its name, it also sends a real live notification. Both send tools require sending to be configured.
Remove an integration device
Use device_list to identify the exact app and device, then explicitly confirm
device_delete with those IDs and confirm: true. The tool removes the device
registration, including test-mode devices. It preserves delivery history and
usage already counted. A later SDK registration can create the device again;
removing a row is not a permanent opt-out.
Backend integrations can also use DELETE /v1/devices/{id} with a secret key
carrying devices:delete. Mobile keys cannot delete devices.
Schedule recurring notifications
Use the dedicated recurring_* tools for repeated sends. notification_send creates a single campaign.
- Call
recurring_listwithapp_idto read existing definitions, statuses and upcoming dates. - Prepare a saved audience with
audience_list,audience_previewandaudience_create, or useaudience_id: nullfor all eligible live devices. - Call
recurring_createwith the complete definition. It returns a draft and itsid; nothing sends yet. - Check the returned content, audience and upcoming dates with the user, then call
recurring_activatewith thatrecurring_idandconfirm: true. An active recurrence sends real pushes automatically.
Example arguments for recurring_create:
{
"app_id": "YOUR_APP_ID",
"name": "Monday reminder",
"audience_id": null,
"payload": {
"title": "A small step this week",
"localizations": { "fr": { "title": "Un petit pas cette semaine" } }
},
"tags": ["weekly_reminder"],
"frequency_days": 7,
"schedule": {
"frequency": "weekly",
"weekdays": [1],
"month_day": 1,
"local_time": "09:00",
"timezone_mode": "recipient",
"timezone": "Europe/Paris",
"start_date": "2026-11-02",
"end_date": null
}
}Choose a future start_date. frequency is daily, weekly or monthly.
Weekdays use Monday = 1 through Sunday = 7. month_day accepts 1-31 or "last".
Supply both weekdays and month_day; only the field relevant to the frequency is used.
timezone_mode: "fixed" sends in the specified IANA timezone; "recipient" uses
it as the fallback for devices without a timezone. frequency_days is a
per-device cooldown for this recurrence, or null for no cooldown.
recurring_update replaces the complete definition. Read recurring_list first
and preserve fields you are not changing. Active recurrences remain active;
changes apply to future occurrences. Creation has no idempotency key: if the
response is lost, check recurring_list before creating another draft.
Use recurring_pause to stop future occurrences and pending deliveries.
recurring_activate resumes from the next future occurrence without replaying
missed sends. recurring_archive cancels the recurrence permanently and retains
its history. Pushes already in flight can still arrive after pausing or archiving.
Updates, activation, pause and archive require the user's authorization and
confirm: true. Archived recurrences cannot be edited or resumed.
Read recurring_history for the last 1-30 days (default 30). Follow next_cursor
until null to read all occurrences. Pass an occurrence's campaign_id as
message_id to message_trace for its delivery outcomes. See
Recurring messages for schedule rules, frequency
limits and lifecycle behavior.
Find and cancel a campaign
Call notification_list with app_id to find campaigns, newest first. Filter
by mode (live or test), source (api or dashboard), a single tag,
or a tags array. tags matches any listed tag; without_tag: "true" also
includes untagged campaigns. include_empty: "false" hides completed campaigns
without delivery activity. These two flags use string values "true" and "false".
Pass next_cursor back as cursor until null. Use a returned id as
message_id with message_trace to inspect its content and outcomes.
To cancel an identified campaign, authorize the cancellation and call:
{
"app_id": "YOUR_APP_ID",
"message_id": "YOUR_CAMPAIGN_ID",
"confirm": true
}notification_cancel stops queued deliveries and pending timezone waves.
It returns canceled_count and already_sent; the latter also includes sends
already in flight, which cannot be recalled. Repeating cancellation is safe.
Canceling one occurrence does not stop its recurring schedule; use
recurring_pause or recurring_archive to stop the recurrence.
Find a device and inspect its deliveries
device_list accepts q for an external ID or token prefix, platform (ios
or android) and status (reachable, opted_out or invalidated). Combine
filters to locate the device, and follow next_cursor for additional pages.
Tokens remain truncated in results.
{ "app_id": "YOUR_APP_ID", "q": "customer-42", "platform": "ios" }Call device_history with the returned device_id and app_id for its last
50 retained deliveries. Then call delivery_trace with a delivery's id as
delivery_id. The result includes provider errors and timestamps, SDK receipt
confirmations, opens and notification content.
received_at records SDK confirmation, which may occur after the push arrived.
received_reported_at records when Carillon received that report, potentially
later after an offline period. resolved content uses the device's current
locale and can differ from the original send. See tracking receipts and opens.
Discover and update audience filters
Call audience_suggestions to discover existing values among eligible live
devices. Fields include locale, timezone_id, app_version, app_build,
os_version, and typed device or user tags.
For tags, choose field: "tag" without a key to list tag keys, then supply a
key to list its values. Use user_tag for user attributes, or number_tag,
boolean_tag, date_tag and their user_ equivalents for typed attributes.
search narrows suggestions; follow next_cursor until null.
Read the current definition with audience_list, check new filters with
audience_preview, then call audience_update with the complete definition,
app_id, audience_id and confirm: true. It preserves the audience's ID and
name. An empty filter list matches all eligible devices. Updated filters apply
to campaigns whose recipient resolution has not started and future recurring
occurrences.
Read health and statistics
app_healthreturns device counts, failures and invalidations over seven days, and up to five live campaigns paused by the circuit breaker. Investigate those campaign IDs withmessage_trace.app_activityreturns daily live sent, opened and failed counts for 30 UTC days, device activity snapshots for 90 days and registration totals for 30 days. Missing snapshots are omitted.notification_statisticsacceptssinceanduntilas ISO timestamps, covering a positive window of at most 90 days. It returns statistics for live campaigns created in that window, optionally filtered bysource.
Statistics include totals, per-tag rows and without_tag. A campaign with
several tags contributes to each tag, so use totals instead of summing tag
rows. Follow next_cursor for additional tag pages. Delivery counts use retained
history; sent means provider acceptance, not device display.