---
name: connect-mcp
order: 0
description: "Connect to Sending's MCP server and get the right permissions. Use this before anything else, and whenever a tool answers forbidden while the connection looks fine."
---

# Connect to Sending over MCP

Endpoint: `https://sending.dev/api/mcp` (Streamable HTTP). The server is remote:
there is no Sending npm package to install, and we publish no SSE endpoint.

## Two credentials, two uses

**API key** (server to server, one fixed workspace):

```http
Authorization: Bearer sk_...
```

You create it in the dashboard under *Settings › API Keys*. The plaintext value is
shown only once, at creation. Scopes are chosen at that moment.

**OAuth 2.1** (on behalf of a user): dynamic client registration at
`https://sending.dev/api/auth/mcp/register` (RFC 7591), then `authorization_code`
with PKCE `S256`. Metadata lives at `/.well-known/oauth-authorization-server` and
`/.well-known/oauth-protected-resource`.

An agent cannot create an account on its own: a human has to open the workspace.

## How the user adds you to their client

If the user is reading you but has not connected you yet, these are the commands to
give them. Full guide: https://sending.dev/docs/mcp/clients

Claude Code:

```bash
claude mcp add --transport http sending https://sending.dev/api/mcp \
  --header "Authorization: Bearer sk_..."
```

Cursor (`.cursor/mcp.json`), Claude Code by hand (`.mcp.json`, which also needs
`"type": "http"`) and generally any client that accepts headers:

```json
{
  "mcpServers": {
    "sending": {
      "url": "https://sending.dev/api/mcp",
      "headers": { "Authorization": "Bearer sk_..." }
    }
  }
}
```

VS Code uses the `servers` key instead of `mcpServers`, and wants `"type": "http"`.

ChatGPT (developer mode) and claude.ai / Claude Desktop (custom connector) take the
URL only: they do not accept headers, so they go through OAuth and the permissions
are picked on the consent screen.

Clients that speak stdio only go through the `mcp-remote` bridge:
`npx -y mcp-remote https://sending.dev/api/mcp --header "Authorization: Bearer sk_..."`.

## When a tool answers `forbidden`

This is the most common case, and reconnecting does not fix it.

MCP clients do not know our scope catalogue, so at `authorize` they almost always
ask for the OIDC scopes alone (`openid`, `profile`, `email`, `offline_access`):
those are in fact the only ones listed in `scopes_supported`.

**The effective permissions are the ones the user picks on the consent screen**,
where the full catalogue is shown with the requested ones pre-selected. They stay
editable afterwards, under *MCP Connections*, without reconnecting the client.

So: when you get `forbidden`, ask the user to open *MCP Connections* and enable the
area you need. Do not retry in a loop, and do not try to request the scope in a new
`authorize` call: nothing would change.

Assignable areas: `automations`, `campaigns`, `commerce`, `contacts`, `domains`,
`email`, `events`, `inbox`, `integrations`, `messages`, `utm`, `webhooks`, in the
`:read`, `:write` or `:send` forms depending on the area, plus `*` for full access.

## Before you start working

Call `get_usage`: it returns the plan, the month's included quota, current
consumption and capacity limits (contacts, domains, inboxes, seats). It saves you
from building a campaign that would be refused at the end of the flow.

Codes: `401` missing or invalid credential, `403` missing scope, `402` plan quota
exceeded.

## Documentation

- MCP overview: https://sending.dev/docs/mcp/overview
- Credentials and scopes: https://sending.dev/auth.md
- REST reference: https://sending.dev/docs/api/rest
