MCP Server
NorthRelay MCP Server for AI agent integration with Claude Code, Claude Desktop, Codex, Cursor, Windsurf, and other MCP-compatible clients
MCP Server
The NorthRelay MCP Server (@northrelay/mcp-server 1.9.0) exposes the full NorthRelay API as tools that AI agents can use directly. It implements the Model Context Protocol (MCP) standard, allowing AI assistants to send emails, manage templates, check delivery status, and more through natural language.
What is MCP?
The Model Context Protocol (MCP) is an open standard that lets AI applications interact with external services through structured tool calls. Instead of writing code, you describe what you want in plain language, and the AI agent calls the appropriate NorthRelay tools on your behalf.
Supported AI clients:
- Claude Code
- Claude Desktop
- Codex (CLI, IDE extension and desktop app)
- Cursor
- Windsurf
- Any MCP-compatible client
The server runs in one of two modes. Over stdio the client starts it locally and gives it your NorthRelay API key. In hosted (HTTP) mode one running instance serves a team over the network, callers present a bearer token, and the API key stays on the host. See Hosted mode.
Installation
No separate installation is needed. The server runs via npx:
NORTHRELAY_API_KEY=nr_live_xxx npx @northrelay/mcp-serverOr install globally:
npm install -g @northrelay/mcp-serverConfiguration
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
NORTHRELAY_API_KEY | Yes | — | Your NorthRelay API key |
NORTHRELAY_BASE_URL | No | https://app.northrelay.ca | Custom API base URL |
NORTHRELAY_MCP_AUTH_TOKEN | Hosted mode only | — | Bearer token(s) callers must present; comma-separate several to rotate without a gap |
NORTHRELAY_MCP_PORT | No | 3010 | Port for hosted mode (--port wins) |
NORTHRELAY_MCP_BIND | No | 127.0.0.1 | Interface for hosted mode (--bind wins) |
NORTHRELAY_MCP_SESSION_TTL_MS | No | 600000 | Close a hosted session left idle this long |
Keep the API key and the bearer token out of files you commit or text you paste. Where a client can read a secret from the environment, the examples below do that.
Claude Code
Over stdio, with the API key passed through --env:
claude mcp add northrelay --env NORTHRELAY_API_KEY=nr_live_your_api_key_here \
-- npx -y @northrelay/mcp-serverAgainst a hosted endpoint, with the bearer as a header (--scope user makes
it available in every project):
claude mcp add --transport http northrelay https://mcp.example.com/mcp \
--header "Authorization: Bearer $NORTHRELAY_MCP_AUTH_TOKEN" \
--scope userIn a project's checked-in .mcp.json, write the placeholder and Claude Code
expands it from the environment when it loads:
{
"mcpServers": {
"northrelay": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer ${NORTHRELAY_MCP_AUTH_TOKEN}"
}
}
}
}claude mcp list, or /mcp inside a session, shows whether it connected.
Codex
The Codex CLI, IDE extension and desktop app share ~/.codex/config.toml
(or a project's .codex/config.toml).
Over stdio:
codex mcp add northrelay --env NORTHRELAY_API_KEY=nr_live_your_api_key_here \
-- npx -y @northrelay/mcp-serverwhich writes:
[mcp_servers.northrelay]
command = "npx"
args = ["-y", "@northrelay/mcp-server"]
[mcp_servers.northrelay.env]
NORTHRELAY_API_KEY = "nr_live_your_api_key_here"Against a hosted endpoint, name the environment variable that holds the bearer — the file itself never contains it:
[mcp_servers.northrelay]
url = "https://mcp.example.com/mcp"
bearer_token_env_var = "NORTHRELAY_MCP_AUTH_TOKEN"Claude Desktop
Add the following to your Claude Desktop configuration file:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
Linux: ~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"northrelay": {
"command": "npx",
"args": ["@northrelay/mcp-server"],
"env": {
"NORTHRELAY_API_KEY": "nr_live_your_api_key_here"
}
}
}
}Restart Claude Desktop after saving the configuration.
claude.ai and Claude Desktop custom connectors. The Add custom connector dialog reaches a remote MCP server and authenticates by OAuth — there is no field for a bearer token. A hosted instance running in connector mode is what it needs:
- Settings → Connectors → Add custom connector, with the endpoint URL
(
https://mcp.example.net/mcp), no client id and no secret. - A NorthRelay sign-in page opens. It names the client that is asking and the address the token will be sent to, and starts from read-only permissions; tick exactly what the connector should be able to do.
- Approving issues an API key named
oauth-<client>-…. It appears under Settings → API Keys, expires after 30 days (the connector renews it itself), and revoking it there ends the connector's access.
A hosted instance that serves only a shared token cannot be added as a connector; use Claude Code or Codex for that one.
Cursor
In Cursor settings, add an MCP server with:
- Name:
northrelay - Command:
npx @northrelay/mcp-server - Environment:
NORTHRELAY_API_KEY=nr_live_your_api_key_here
Windsurf
Add to your Windsurf MCP configuration:
{
"mcpServers": {
"northrelay": {
"command": "npx",
"args": ["@northrelay/mcp-server"],
"env": {
"NORTHRELAY_API_KEY": "nr_live_your_api_key_here"
}
}
}
}Hosted mode
By default the server speaks stdio and each client spawns its own copy. Pass
--http to serve Streamable HTTP instead, so a team, a CI job, or an agent
running elsewhere can share one instance without holding the API key:
NORTHRELAY_API_KEY=nr_live_xxx \
NORTHRELAY_MCP_AUTH_TOKEN=$(openssl rand -hex 32) \
npx @northrelay/mcp-server --http --port 3010- The MCP endpoint is
/mcp;GET /healthzis an unauthenticated probe. - A bearer token is required. The endpoint can send real mail, rewrite
templates and clear suppressions, so the server refuses to start without
NORTHRELAY_MCP_AUTH_TOKENand answers401to a wrong one. Every caller presentsAuthorization: Bearer <token>. - It binds
127.0.0.1by default. Put it behind a reverse proxy that terminates TLS rather than exposing the port directly. - A session idle past
NORTHRELAY_MCP_SESSION_TTL_MSis closed, so a client that crashes without ending its session is not held forever.
To check an endpoint from a client machine:
curl -s https://mcp.example.com/healthz # {"status":"ok","sessions":N}
curl -s -o /dev/null -w '%{http_code}\n' \
-X POST https://mcp.example.com/mcp # 401: the bearer is being checkedConnector mode
Set NORTHRELAY_MCP_PUBLIC_URL to the origin the instance is reached at,
and a caller may present its own NorthRelay API key as the bearer
instead of the shared token:
NORTHRELAY_MCP_PUBLIC_URL=https://mcp.example.net \
npx @northrelay/mcp-server --http --port 3010Each session then acts as the key it presented — scoped and revocable in
that account's key list — and the process needs no NORTHRELAY_API_KEY of
its own unless it also serves a shared token. The server publishes
/.well-known/oauth-protected-resource naming the NorthRelay platform as
its authorization server, which is how the claude.ai and Claude Desktop
connectors find their way to the sign-in page. A key is checked with the
platform once, when a session opens; a revoked key is refused at the next
session.
| Variable | Description |
|---|---|
NORTHRELAY_MCP_PUBLIC_URL | This endpoint's public origin, https:// (or http://localhost for a local instance), no path |
The platform side needs nothing configured: every NorthRelay account can
approve connectors, and OAUTH_CONNECTORS_DISABLED=true on the platform
switches the authorization server off.
Tool Groups
The MCP server (1.7.0) exposes 149 tools in 21 groups.
| Group | Tools | Description |
|---|---|---|
| emails | send_email, get_email_events | Send email and read its delivery events |
| templates (deprecated) | list_templates, get_template, create_template, update_template, delete_template, render_template, get_template_blocks, add_template_block, update_template_block, delete_template_block, reorder_template_blocks, list_template_versions, restore_template_version, test_send_template, import_templates, export_templates | Legacy template API, sunset 30 April 2027: use the designs tools |
| designs | list_email_template_catalog, inspect_email_template, adopt_email_template, sync_email_template_source, list_email_brands, create_email_brand, update_email_brand, bind_email_brand, get_email_brand_usage, delete_email_brand, set_default_email_brand, list_email_assets, upload_email_asset, delete_email_asset, upload_email_brand_logo (deprecated), get_email_capabilities, list_email_designs, get_email_design, create_email_design, update_email_design, set_email_design_lifecycle, delete_email_design, preview_email_design, publish_email_design, rollback_email_design, send_email_design, get_email_design_releases, get_email_design_deliveries, get_email_design_usage, export_email_design_manifest, apply_email_design_manifest | Templates and brands (NorthRelay Studio); dashboard templates use application key account |
| campaigns | list_campaigns, get_campaign, create_campaign, update_campaign, submit_campaign, send_campaign, delete_campaign | Bulk campaigns |
| brand_theme | list_brand_themes, get_brand_theme, create_brand_theme, update_brand_theme, delete_brand_theme (deprecated), set_brand_tracking_domain, verify_brand_tracking_domain, remove_brand_tracking_domain | Tracking domains (Studio brand API); the *_brand_theme tools are deprecated in favour of the email brand tools |
| contacts | list_contacts, get_contact, create_contact, update_contact, delete_contact, bulk_create_contacts, bulk_delete_contacts, add_contact_tags, remove_contact_tag, list_contact_lists, get_contact_list, create_contact_list, update_contact_list, delete_contact_list, add_list_members, remove_list_members, list_list_members | Contacts, lists and list members (by contact id or email) |
| subscriptions | subscribe_contact, unsubscribe_contact, get_subscription_status, update_subscription_preferences, resend_subscription_confirmation | Opt-in with consent and double opt-in, opt-out by scope, status and preferences |
| forms | list_forms, get_form, create_form, update_form, set_form_fields, archive_form | Hosted signup forms |
| domains | check_deliverability, check_blocklist, check_dns | Deliverability, blocklist and DNS checks |
| webhooks | list_webhooks, get_webhook, create_webhook, update_webhook, delete_webhook, rotate_webhook_secret, test_webhook, get_webhook_health | Event notification endpoints |
| api_keys | list_api_keys, create_api_key, revoke_api_key | API key management |
| credentials | list_credentials, create_credential, update_credential, rotate_credential, revoke_credential | Application credentials |
| events | list_events, get_event, count_events | Email event log |
| analytics | query_analytics, get_engagement_heatmap, get_geographic_data, get_provider_stats, export_analytics | Engagement and delivery analytics |
| metrics | get_metrics, get_metrics_summary | Delivery metrics |
| suppressions | list_suppressions, add_suppression, remove_suppression, bulk_add_suppressions | Account-level suppressions |
| suppression_groups | list_suppression_groups, get_suppression_group, create_suppression_group, update_suppression_group, delete_suppression_group, list_suppression_group_members, add_suppression_group_member, remove_suppression_group_member, bulk_update_suppression_group_members | Topics and their opt-outs (a member is an address that opted out) |
| subusers | list_subusers, get_subuser, create_subuser, update_subuser, delete_subuser, suspend_subuser, reactivate_subuser, get_subuser_usage | Subuser accounts |
| identity | list_identities, get_identity, create_identity, verify_identity, get_profile | Sender identities and profile |
| ip_pools | list_ip_pools, get_ip_pool, create_ip_pool, update_ip_pool, list_pool_ips | IP pools |
| ips | list_ips, get_ip, get_ip_warmup_status, update_ip_warmup | IPs and warmup |
The contact, subscription, topic and form tools return structured content and carry MCP annotations (read-only, destructive, idempotent). Their errors keep the API code, status and fix_action.
Usage Examples
Once configured, interact with NorthRelay through natural language in your AI client.
Sending Emails
"Send a welcome email to jane@example.com with the subject 'Welcome to our platform' and an HTML body with a greeting."
The AI agent will call send_email with the appropriate parameters.
Managing Templates
"Create an account template called 'Password Reset' with the subject 'Reset your password' and a reset link variable, preview it with sample data, and publish it."
"List my templates and tell me which campaigns use 'Monthly newsletter'."
"Preview the welcome template with the 'Acme dark' brand."
"Retire the template called 'Old Newsletter' and point to 'Newsletter 2026' as its replacement."
Checking Delivery Status
"What's the delivery status of the last 10 emails I sent?"
"Show me the delivery analytics for March 2026."
Deliverability Checks
"Check the DNS records and blocklist status for mail.mycompany.com."
The agent calls check_dns and check_blocklist.
Campaign Management
"Create a campaign called 'Spring Sale' using my 'promotion' template, targeting my 'active-customers' contact list, scheduled for March 25th at 10am UTC."
"Show me the details of my 'Spring Sale' campaign." (
get_campaign)
Mailing Lists and Subscriptions
"Subscribe jane@example.com to my 'Monthly newsletter' list with double opt-in, and record that she ticked 'Send me the monthly newsletter'."
The agent calls subscribe_contact with doubleOptIn and the consent text. Jane stays pending until she clicks the confirmation link.
"Is bob@example.com still subscribed? Which topics has he opted out of?"
The agent calls get_subscription_status, which returns his lists, topics, consent evidence and any suppression.
"Unsubscribe bob@example.com from product updates only."
The agent calls unsubscribe_contact with scope: "topic". Someone who unsubscribed is never re-added unless you explicitly confirm fresh consent (resubscribe).
"Add these five addresses to the 'Beta testers' list, creating contacts that don't exist yet."
The agent calls add_list_members with emails and createMissing; unsubscribed or suppressed addresses come back in blocked.
"Who on the 'Beta testers' list would not receive a campaign, and why?"
The agent calls list_list_members with filter: "suppressed"; each member carries its suppressionStatuses.
See the Subscriptions API for the underlying endpoints. These tools need an API key with contacts:read and contacts:write.
Troubleshooting
Server Not Starting
Verify your API key is set:
echo $NORTHRELAY_API_KEYTest the server manually:
NORTHRELAY_API_KEY=nr_live_xxx npx @northrelay/mcp-serverTools Not Appearing in AI Client
- Restart your AI client after updating the configuration
- Verify the configuration file path is correct for your OS
- Check that
npxis available in your system PATH
Authentication Errors
Ensure your API key:
- Starts with
nr_live_(production) ornr_test_(testing) - Has not been revoked
- Has the necessary permissions for the operations you are performing — a
key missing a scope answers
403 SCOPE_INSUFFICIENTnaming the scope
Hosted Endpoint Connects But Shows No Tools
-
The client presented the wrong bearer: the server answers
401, which most clients report as a failed connection. Check the token the client was given againstNORTHRELAY_MCP_AUTH_TOKENon the host. -
The client was pointed at the host root instead of
/mcp. -
Adding it as a claude.ai custom connector without
NORTHRELAY_MCP_PUBLIC_URLset on the instance: that dialog cannot send a bearer token, so the instance has to run in connector mode. -
The connector reports "Couldn't register with … sign-in service", or connects but never offers to sign in: the instance's
/.well-known/oauth-protected-resourceis not reachable through the reverse proxy, or names a platform origin the client cannot reach.curl -s https://mcp.example.net/.well-known/oauth-protected-resourceshould answer withauthorization_servers. A proxy that forwards only/mcpand/healthzhas to forward that path too — the instance answers both the bare path and the/mcp-suffixed form, so with Caddy:handle /.well-known/oauth-protected-resource* { reverse_proxy 127.0.0.1:3010 }
Related
- TypeScript SDK for programmatic Node.js integration
- Python SDK for programmatic Python integration
- API Reference for raw HTTP API details