Send in-app notifications from Claude Code
Three tools over a small API: send a notification to a user id, list what is unread, mark it read. Useful when an agent runs a job on someone's behalf and should tell them when it is done, or when you want to check what a user is seeing without writing a query.
Claude Code connects to remote MCP servers over HTTP directly. One command registers the server under the name nordva.
Setup
You need a secret key (nv_live_…) from Dashboard → API keys.
01Register the server
claude mcp add --transport http nordva https://mcp.nordva.dev \
--header "Authorization: Bearer nv_live_..."The name comes before the URL. By default the server is added for the current project only and stored outside the repository; add --scope user to make it available in every project.
02Or share it with the team without sharing the key
.mcp.json
{
"mcpServers": {
"nordva": {
"type": "http",
"url": "https://mcp.nordva.dev",
"headers": {
"Authorization": "Bearer ${NORDVA_API_KEY}"
}
}
}
}Claude Code expands ${NORDVA_API_KEY} from the environment, so the file can be committed while each developer keeps their own key in their shell profile.
03Check the connection
Run claude mcp list, or type /mcp inside a session. nordva should show as connected. Then ask Claude to run setup_check: it reports the project, the plan and which features are unlocked.
Then ask Claude Code
- Notify user 4812 that their import finished, with a link to /imports/77.
- What unread notifications does user 4812 have?
- Mark all of user 4812's notifications as read.
For “Notify user 4812 that their import finished, with a link to /imports/77.” Claude Code calls send_notification with:
{
"user_id": "4812",
"title": "Import finished",
"body": "1,204 rows imported, 3 skipped.",
"action_url": "https://app.example.com/imports/77"
} Returned: The notification id, the user id and the time it was created. The user's client sees it the next time it polls the unread endpoint.
Notifications tools and arguments
send_notification
Confirms firstBuilderSend an in-app notification to one of your users
| user_id | required | string, max 256 | Developer-supplied identifier of the recipient. |
| title | required | string, max 200 | Notification headline. |
| body | required | string, max 2,000 | Notification body text. |
| action_url | optional | string, max 2,048 | URL the recipient should be sent to on click. |
| icon | optional | "check" | "warning" | "info" | "error" | Visual icon hint. |
get_unread_notifications
Read-onlyBuilderList unread notifications
| user_id | optional | string, max 256 | Filter to a specific recipient. |
| limit | optional | number, 1–100 | Page size. |
| since | optional | string | Return only notifications created strictly after this ISO timestamp. |
mark_notifications_read
BuilderMark notifications as read
| notification_id | optional | string, max 128 | Mark this specific notification id read. |
| user_id | optional | string, max 256 | Restrict bulk mark-read to one recipient. |
Behaviour worth knowing
- All three tools require the Builder plan. Other plans receive PLAN_UPGRADE_REQUIRED.
- send_notification reaches a real end user, so the server marks it destructive and clients ask before calling it.
- Delivery is by polling: your front end reads GET /v1/notifications/unread with a publishable key. Nothing is pushed.
- mark_notifications_read works on one notification id, or on everything for a user id.
- Notifications older than 90 days are pruned.
- Tools appear to the model as mcp__nordva__<tool>, which is the form to use in permission allowlists.
- Read-only tools are marked as such by the server; Claude Code still asks before the first call to each tool unless you allow it.