Send in-app notifications in VS Code
Every notification operation your secret key can do, as tools: send to a user id or broadcast to everyone, list what was sent with how many people read it, see what one user's bell shows, mark it read, and create the signing secret that lets your pages prove which user is asking. Useful when an agent runs a job on someone's behalf and should tell them when it is done, or when you ship something everyone should hear about.
VS Code's agent mode (GitHub Copilot Chat) loads MCP servers from .vscode/mcp.json. Inputs keep the key out of the file.
Setup
You need a secret key (nv_live_…) from Dashboard → API keys.
01Add the server to the workspace
.vscode/mcp.json
{
"inputs": [
{
"type": "promptString",
"id": "nordva-key",
"description": "Nordva Launch secret key",
"password": true
}
],
"servers": {
"nordva": {
"type": "http",
"url": "https://mcp.nordva.dev",
"headers": {
"Authorization": "Bearer ${input:nordva-key}"
}
}
}
}VS Code prompts for the key the first time the server starts and stores it in its secret storage, so this file is safe to commit. Note that the top-level property is servers, not mcpServers.
02Check the connection
Run MCP: List Servers from the Command Palette and start nordva. In the Chat view, switch to Agent mode and check that the nordva tools are listed under the tools button. Ask it to run setup_check.
Then ask VS Code
- Notify user 4812 that their import finished, with a link to /imports/77.
- Tell everyone about tonight's planned downtime, 22:00 to 22:30 UTC.
- How many people read last week's broadcast?
- Set up identity verification for the notifications bell.
For “Notify user 4812 that their import finished, with a link to /imports/77.” VS Code calls send_notification with:
{
"user_id": "4812",
"title": "Import finished",
"body": "1,204 rows imported, 3 skipped.",
"action_url": "/imports/77"
} Returned: The notification id, the user id, the time it was created and this month's usage against the cap. The user's bell shows it the next time it checks.
Notifications tools and arguments
send_notification
Confirms firstSend an in-app notification to one user or to everyone
| user_id | optional | string, max 256 | The developer's own id for one recipient. |
| audience | optional | "user" | "users" | "all" | "all" for a broadcast to every signed-in user; "users" is implied by the group fields. |
| user_ids | optional | array | The developer's own user ids. |
| emails | optional | array | People by email; each is reached through their known user id. |
| contact_ids | optional | array | Contacts by id (from list_contacts). |
| contacts | optional | object | A Contacts filter, values as strings: { "stage": "customer" }, { "consent": "email_marketing" }, { "detail": "plan=pro" }, { "paying": "true" }. |
| title | required | string, max 200 | Notification headline. |
| body | required | string, max 2,000 | Notification body, plain text. |
| action_url | optional | string, max 2,048 | A full http(s) URL, or a path such as /billing. |
| icon | optional | "check" | "warning" | "info" | "error" | Visual icon hint. |
| category | optional | string | A short lower-case tab name, such as billing. |
| actions | optional | array | Up to two buttons. |
| data | optional | object | A small JSON object for the developer's page. |
| send_at | optional | string | ISO time to show it, at most 60 days ahead. |
| pinned | optional | boolean | Keep it at the top of the bell until each person archives it. |
| reactions | optional | union | true: the project's changelog reaction set; or the names in order. |
preview_notification_audience
Read-onlySee who a group notification would reach
| user_ids | optional | array | The developer's own user ids. |
| emails | optional | array | People by email; each is reached through their known user id. |
| contact_ids | optional | array | Contacts by id (from list_contacts). |
| contacts | optional | object | A Contacts filter, values as strings: { "stage": "customer" }, { "consent": "email_marketing" }, { "detail": "plan=pro" }, { "paying": "true" }. |
list_notifications
Read-onlyList sent notifications with reach and reads
| user_id | optional | string, max 256 | |
| audience | optional | "user" | "users" | "all" | |
| status | optional | "read" | "unread" | "all" | "scheduled" | |
| category | optional | string, max 24 | |
| limit | optional | number, 1–100 | |
| cursor | optional | string, max 64 |
get_notification
Read-onlyRead one sent notification
| notification_id | required | string, max 64 | The ntf_... id. |
delete_notification
Confirms firstDelete a sent notification
| notification_id | required | string, max 64 | The ntf_... id. |
get_unread_notifications
Read-onlySee what a user's bell shows
| user_id | optional | string, max 256 | The person whose bell to read. |
| include_read | optional | boolean | true for read and unread (the inbox). |
| limit | optional | number, 1–100 | Page size. |
| since | optional | string | Only notifications created after this ISO timestamp. |
| category | optional | string | With include_read: one category (a tab in the bell). |
| archived | optional | boolean | With include_read: true for only the archived ones. |
mark_notifications_read
Mark notifications read for a user
| notification_id | optional | string, max 128 | Mark this one read. |
| user_id | optional | string, max 256 | The person who read it. Required for a broadcast or a group. |
| category | optional | string | Without notification_id: only this category. |
archive_notification
Archive a notification for one user
| notification_id | required | string, max 64 | The ntf_... id. |
| user_id | required | string, max 256 | |
| archived | optional | boolean | false brings it back. |
record_notification_click
Record a click on a notification
| notification_id | required | string, max 64 | The ntf_... id. |
| user_id | required | string, max 256 |
set_notification_reaction
Set a user's reaction to a notification
| notification_id | required | string, max 64 | The ntf_... id. |
| user_id | required | string, max 256 | |
| reaction | required | "like" | "celebrate" | "love" | "laugh" | "fire" | "eyes" | "confused" | "dislike" | One the notification offers, or null to take it back. |
get_notification_settings
Read-onlyNotification settings, usage and warnings
No arguments.
update_notification_settings
Change notification settings
| require_user_hash | optional | boolean | Browser calls must prove their user with user_hash. |
| changelog_to_bell_default | optional | boolean | New changelog entries post to the bell unless they say otherwise. |
create_notifications_signing_secret
Create the notifications signing secret (shown once)
No arguments.
rotate_notifications_signing_secret
Confirms firstRotate the notifications signing secret (new one shown once)
No arguments.
Behaviour worth knowing
- Every plan can send notifications, capped per project per calendar month: Free 1,000, Indie 20,000, Builder 200,000. A broadcast counts once.
- send_notification reaches real people, so the server marks it destructive and clients ask before calling it. With audience "all" it is one broadcast that shows in every signed-in user's bell for 30 days.
- list_notifications shows, for a broadcast, how many people it reached (their bell checked after it was sent) and how many read it.
- Your pages read the bell with the publishable key, the user's id and a user_hash your server computes with the signing secret, so one browser cannot read another user's notifications.
- create_notifications_signing_secret and rotate_notifications_signing_secret return the secret once, in the text only; the agent is told to write it to your server's env file and never to the browser.
- publish_changelog_entry with notify_in_app posts the entry to the bell once when it publishes.
- Notifications older than 90 days are pruned.
- MCP tools are only available in Agent mode, not in Ask or Edit mode.