# MCP server

> Connect Claude Code, Cursor, Codex, or any MCP client to Adeli with your existing API key.

Canonical: <https://www.tryadeli.com/docs/mcp>

Adeli runs a [Model Context Protocol](https://modelcontextprotocol.io) server,
so an AI agent can use the API as typed tools instead of hand-written HTTP
requests. The tools are the REST API: each one calls the same endpoint with the
same validation, profile scoping, errors, and side effects. Nothing is
available over MCP that is not available over REST.

## Endpoint and authentication

```http
POST https://app.tryadeli.com/mcp
Authorization: Bearer rk_live_...
```

- **Transport** — Streamable HTTP. The server is stateless and answers each
  request with JSON; there is no session to keep and no event stream to hold
  open.
- **Key** — the same `rk_live_` API key you use for REST, created under
  [Settings → API keys](https://app.tryadeli.com/settings/api-keys). See [Authentication](https://www.tryadeli.com/docs/authentication).
- **Scope** — the key belongs to your organization and reaches every profile in
  it. Every tool acts on one profile, and nothing defaults: pass `profileId`, or
  an `accountId`, which names its own profile. `list_profiles` and
  `create_profile` are the exceptions, acting on the whole organization.

A missing or revoked key fails the connection with `401 unauthorized` and a
`WWW-Authenticate: Bearer` header, before any tool runs.

> **Warning: The key lives in your client's config**
>
> MCP clients store the header in a local config file. Read the key from an
> environment variable where your client supports it, give each machine or
> agent its own key, and never commit the file. If one leaks, delete that key
> under Settings → API keys — revocation is immediate.

## Connect your client

**Claude Code**

```bash
claude mcp add --transport http adeli https://app.tryadeli.com/mcp \
  --header "Authorization: Bearer $ADELI_API_KEY"
```

**Cursor**

```json
// .cursor/mcp.json
{
  "mcpServers": {
    "adeli": {
      "url": "https://app.tryadeli.com/mcp",
      "headers": { "Authorization": "Bearer ${env:ADELI_API_KEY}" }
    }
  }
}
```

**VS Code**

```json
// .vscode/mcp.json
{
  "inputs": [
    { "type": "promptString", "id": "adeli-key", "description": "Adeli API key", "password": true }
  ],
  "servers": {
    "adeli": {
      "type": "http",
      "url": "https://app.tryadeli.com/mcp",
      "headers": { "Authorization": "Bearer ${input:adeli-key}" }
    }
  }
}
```

**Codex**

```toml
# ~/.codex/config.toml
[mcp_servers.adeli]
url = "https://app.tryadeli.com/mcp"
bearer_token_env_var = "ADELI_API_KEY"
```

**stdio clients**

```json
// Claude Desktop's claude_desktop_config.json, or any client that only runs local servers
{
  "mcpServers": {
    "adeli": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://app.tryadeli.com/mcp", "--header", "Authorization:${ADELI_AUTH}"],
      "env": { "ADELI_AUTH": "Bearer rk_live_..." }
    }
  }
}
```

Claude Code expands `$ADELI_API_KEY` when you run the command and stores the
result in its config. Clients that can only launch local processes reach the
server through [`mcp-remote`](https://www.npmjs.com/package/mcp-remote), which
bridges stdio to HTTP.

## Check the connection

List the tools with a single request. You should get back the tools described
in [MCP tools](https://www.tryadeli.com/docs/mcp/tools).

**cURL**

```bash
curl https://app.tryadeli.com/mcp \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

To call tools interactively, run `npx @modelcontextprotocol/inspector`, choose
**Streamable HTTP**, and add the `Authorization` header.

## How tools behave

- **Results are REST bodies.** A tool returns exactly the JSON the matching
  endpoint returns, as `structuredContent` and as text.
- **Partial is not an error.** Reads that span providers can succeed for some
  accounts and fail for others. That result carries `status: "partial"` and an
  `errors` list, as described in [Core concepts](https://www.tryadeli.com/docs/concepts), and is not
  marked as a tool error.
- **Errors keep the envelope.** Any `4xx` or `5xx` becomes a tool error whose
  content is the usual `{ "error": { "code", "message", "details" } }` body.
  See [Errors](https://www.tryadeli.com/docs/errors) for every code.
- **Tools say what they change.** Read-only tools are annotated
  `readOnlyHint`. `disconnect_account` and `ads_set_status` are annotated
  `destructiveHint`, so clients that ask before acting will ask.
- **Large reads are truncated in text only.** Past about 100 KB, the text copy
  is cut short and `structuredContent` still holds the whole body. Filter by
  `platform` or `accountId` to keep responses small.

## What MCP does not do

- **No OAuth sign-in.** The server takes an API key, so clients that can only
  connect through an OAuth flow, such as claude.ai custom connectors, cannot
  add it directly yet. Use `mcp-remote` from a desktop client instead.
- **No multipart uploads.** `create_post` takes the JSON body of
  `POST /api/v1/posts`. For TikTok and YouTube video, pass a public HTTPS
  `video.url` rather than base64. Send large files through REST.
- **No key management, and no profile deletion.** Create keys in the dashboard.
  Agents can list and create profiles, but deleting a profile is not a tool.
- **WhatsApp sends templates only.** `send_message` sends one of the business's
  approved WhatsApp templates; free-form WhatsApp replies stay in the dashboard
  inbox. Connecting a WhatsApp number is done on the dashboard's Accounts page.

## For agents

If you are an AI agent reading this page, the essentials are:

1. Call `list_profiles` first, and pick the profile the user means; use
   `create_profile` for a new customer. Nothing defaults to a profile.
2. Call `list_accounts` with that `profileId`. Every account-level tool takes an
   `accountId` from it, which names its profile, so `profileId` can then be left
   out. Every ads tool takes an `adAccountId` from `ads_list_accounts`.
3. Ask the user before calling `create_post`, `send_message`,
   `disconnect_account`, `ads_create_campaign`, `ads_create_account`,
   `ads_update_account`, or `ads_delete_campaign`. These act on a real customer's real accounts, and an ad
   account, once created, cannot be deleted.
4. **Never** call `ads_set_status` with `ACTIVE` unless the user has confirmed
   the budget and dates in this conversation. It starts spending money.
5. Publishing to TikTok and YouTube, and Facebook Reels and videos, is
   asynchronous: poll the matching status tool with the returned `publishId`.
   Ask the user for a YouTube video's visibility and whether it is made for
   kids; never choose either for them.
6. For anything not covered here, read the `adeli://docs/llms.txt` resource.
   It is this entire documentation site as plain text.
