# MCP server

`@avelto/mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server for Avelto. Add it to Claude Code, Cursor or any MCP client and the agent can do two jobs: **integrate Avelto into the application you are building**, reading the real docs and setting the account up as it goes, and **operate the account** afterwards: send, check delivery, manage domains, webhooks, templates and suppressions. It wraps the [Node SDK](/docs/sdk) and runs over stdio, so there is nothing to host.

**Terminal**

```bash
AVELTO_API_KEY=av_test_... npx -y @avelto/mcp
```

It needs one environment variable, `AVELTO_API_KEY`. `AVELTO_BASE_URL` overrides the API origin and `AVELTO_DOCS_URL` the documentation origin; both are optional.

> **Use a test key while you try it out.** Put an `av_test_...` key in the config to start with. Test sends run the whole pipeline and produce the same events and webhooks, but nothing reaches an inbox. Switch to an `av_live_...` key when you want the agent to deliver.

## Integrating with an agent

The fastest way to add Avelto to a project is to let the agent do it with the server attached. In Claude Code, register the server and run the prompt it provides:

**Terminal**

```bash
claude mcp add avelto -e AVELTO_API_KEY=av_test_... -- npx -y @avelto/mcp
```

**In the Claude Code session**

```text
/mcp__avelto__integrate
```

The prompt is a plan, not a script. The agent calls `check_setup` to learn what your account has, reads the quickstart for your stack with `get_quickstart` and follows its code rather than inventing an integration, writes one sending function and puts the key in the environment, proves it with a sandbox send and `get_email`, and then, if the application needs them, registers a webhook and adds a domain, handing you the DNS records to publish. Other clients expose the same prompt under their own name for MCP prompts, or you can ask in plain words: "integrate Avelto into this app".

`check_setup` is what makes this safe to leave to an agent. It returns the account as an integrator sees it, with nothing secret in it, and a `next_steps` list computed from what is and is not set up:

**check_setup**

```json
{
  "account": { "id": "…", "name": "Acme", "plan": "free", "plan_step": null },
  "key": { "mode": "test", "scopes": ["emails:send", "domains:manage", "webhooks:manage"] },
  "sandbox": {
    "domain": "$sandbox.avelto.dev",
    "recipients": ["you@acme.com", "delivered@$sandbox.avelto.dev", "bounced@$sandbox.avelto.dev", "complained@$sandbox.avelto.dev"],
    "simulator": { "delivered": "delivered@$sandbox.avelto.dev", "bounced": "bounced@$sandbox.avelto.dev", "complained": "complained@$sandbox.avelto.dev" }
  },
  "domains": [],
  "webhooks": [],
  "templates": [],
  "docs_url": "$https://avelto.dev/docs",
  "next_steps": [
    "This is a test key: sends run the full pipeline and produce events, but nothing is delivered. Send from anything@$sandbox.avelto.dev to delivered@$sandbox.avelto.dev with send_email to prove the integration, then get_email to read the events.",
    "No sending domain yet. To send from the application's own addresses, call add_domain with a subdomain such as mail.<their domain>, give the user the dns_records to publish, then poll get_domain_status until it is verified.",
    "No webhook endpoint. If the application needs to react to bounces, complaints or delivery, add a handler that verifies Avelto-Signature (read /docs/webhooks), deploy or tunnel it, then register it with create_webhook and store the secret it returns in the environment.",
    "No templates. Optional: create_template for any email the application sends more than once, then send it with template_slug and variables."
  ]
}
```

## Tools

| Tool | Input | Returns |
| --- | --- | --- |
| `check_setup` | none | Key mode and scopes, the sandbox and its allowed recipients, domains, webhooks, templates, and `next_steps` |
| `get_quickstart` | `framework` | The quickstart for that language or framework, as markdown |
| `read_docs` | `path` | One documentation page as markdown, for example `/docs/webhooks` |
| `send_email` | `from`, `to`, `subject`, `html?`, `text?`, `reply_to?`, `template_slug?`, `template_id?`, `variables?`, `tags?`, `idempotency_key?` | `{ id }` |
| `get_email` | `id` | The email with `status` and `events` |
| `list_emails` | `status?`, `tag?`, `mode?`, `limit?`, `cursor?` | `{ data, next_cursor }` |
| `list_domains` | none | Domains with `status` and `dns_records` |
| `add_domain` | `name` | The domain with the `dns_records` to publish |
| `get_domain_status` | `id` | The domain; verification is re-checked on every call |
| `list_webhooks` | none | Endpoints with their events and `enabled` flag |
| `create_webhook` | `url`, `events?` | The endpoint with its signing `secret`, shown once |
| `list_webhook_deliveries` | `id`, `limit?`, `cursor?` | Recent deliveries with the response status |
| `list_templates` | none | Templates with their variables |
| `get_template` | `id` or slug | One template |
| `create_template` | `name`, `subject`, `html?`, `text?`, `slug?` | The template |
| `update_template` | `id`, `name?`, `subject?`, `html?`, `text?` | The template |
| `list_suppressions` | `limit?`, `cursor?` | Suppressed addresses with `reason` |

The documentation is also offered as MCP resources, `avelto://docs` for the quickstart and `avelto://docs/<page>` for every other page, for clients that attach resources to a conversation rather than call tools.

Every tool returns the API response as JSON, except the docs tools, which return markdown. Errors come back as an error result with `code: message` first, so the agent can read the code and decide what to do:

**Error result**

```text
plan_limit: Monthly limit reached: Free includes 5,000 emails per month and has no overage. Sending resumes on the 1st (UTC), or upgrade to a paid plan to keep sending now.
{
  "limit": 10000,
  "used": 10000,
  "step": "free",
  "resets": "start of next month (UTC)"
}
(HTTP 429)
```

The tool descriptions carry the sending rules, so the agent knows them before it calls anything:

> **Sandbox and test-mode rules.** - With a test key nothing is delivered. The email is accepted and gets synthetic events: `sent` and `delivered`, or `bounced` or `complained` for those simulator addresses. - From the sandbox domain `sandbox.avelto.dev`, the only allowed recipients are your account owner's verified email and the simulator addresses `delivered@sandbox.avelto.dev`, `bounced@sandbox.avelto.dev` and `complained@sandbox.avelto.dev`. Anything else returns `403 sandbox_recipient_not_allowed`. - Any other `from` must be a domain that is added and verified on your account. Call `add_domain`, publish the DNS records, then poll `get_domain_status` until it is `verified`. - Pass `idempotency_key` when retrying a send. A replay returns the original id and does not send twice. - Suppressed recipients return `422 recipient_suppressed`. `429 plan_limit` means the monthly allowance is used up; the agent should stop retrying until it resets or the plan is upgraded.

See [Test mode and sandbox](/docs/test-mode) and [Domains](/docs/domains) for the full rules.

## Claude Code

The command is at the top of this page, under [Integrating with an agent](#integrating-with-an-agent).

## Claude Desktop

Add the server to `claude_desktop_config.json` and restart Claude Desktop.

**claude_desktop_config.json**

```json
{
  "mcpServers": {
    "avelto": {
      "command": "npx",
      "args": ["-y", "@avelto/mcp"],
      "env": { "AVELTO_API_KEY": "av_test_..." }
    }
  }
}
```

## Cursor

Add the server to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for every project.

**.cursor/mcp.json**

```json
{
  "mcpServers": {
    "avelto": {
      "command": "npx",
      "args": ["-y", "@avelto/mcp"],
      "env": { "AVELTO_API_KEY": "av_test_..." }
    }
  }
}
```

## Other clients

Any client that launches stdio servers works with the same command, `npx -y @avelto/mcp`, and the same `AVELTO_API_KEY` environment variable. The manifest at [`/.well-known/mcp.json`](/.well-known/mcp.json) lists the tools, the prompt and the launch command. The package is on npm as [`@avelto/mcp`](https://www.npmjs.com/package/@avelto/mcp).

---

Rendered page: https://avelto.dev/docs/mcp
