# Compeller MCP endpoint (`/api/mcp`)

The Compeller MCP endpoint implements the [Model Context Protocol](https://modelcontextprotocol.io) as a thin JSON-RPC 2.0 wrapper over the existing v1 REST API. It is intended for agent integrators (Claude Desktop, Cursor, custom MCP clients, DigiRAMP) that speak MCP natively rather than raw HTTP.

- **Transport:** Streamable HTTP (single JSON-RPC message per HTTP POST).
- **URL:** `POST https://compeller.ai/api/mcp`
- **Protocol version:** `2024-11-05`
- **Server name / version:** `compeller-mcp` / see `initialize` result.
- **Tool contract:** The tool list below is the public integration contract. Use `tools/list` for the runtime-advertised set on the deployed server.
- **Directory listings:** [Official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=ai.compeller%2Fcompel) · [Smithery](https://smithery.ai/servers/info-nijd/compeller)

[![smithery badge](https://smithery.ai/badge/info-nijd/compeller)](https://smithery.ai/servers/info-nijd/compeller)

## Authentication

Anonymous (discovery) methods: `initialize`, `tools/list`, `ping`, `notifications/initialized`, plus the anonymous tools `get_capabilities`, `get_pricing`, `list_styles`.

Every other tool requires a Compeller API token passed on the HTTP request itself, **not inside the JSON-RPC body**. Either header works:

```
Authorization: Bearer <api-token>
X-API-Token: <api-token>
```

Tokens are issued per Compeller `User` (same tokens used by `/api/v1/*`). Agents can obtain one in either of two ways:

1. Ask the user to log in, open **Account → API Access**, reveal the token, and paste it into the agent's secret store.
2. Use the existing login endpoint and send `access_token` as the bearer token. No Cookie header is needed or expected:

```bash
curl -s -X POST https://compeller.ai/api/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"artist@example.com","password":"..."}'
```

Normal users receive `username` and `access_token`. `roles` appears only for accounts with roles beyond baseline `ROLE_COMPELLER`; `refresh_token` and `expires_in` appear only when non-empty.

3. Or exchange credentials through the v1 auth helper, which returns the persistent API token:

```bash
curl -s -X POST https://compeller.ai/api/v1/auth/token \
  -H 'Content-Type: application/json' \
  -d '{"email":"artist@example.com","password":"..."}'
```

A missing or invalid token surfaces as a *tool error* (`isError: true`) with message `"API token required."` / `"Invalid API token."`, not as a JSON-RPC error, so MCP clients can prompt the user for credentials.

## JSON-RPC methods

| Method | Purpose | HTTP result |
|---|---|---|
| `initialize` | Capability handshake. Returns `protocolVersion`, `serverInfo`, `capabilities`. | 200 JSON-RPC result |
| `notifications/initialized` | Client acknowledgement. No response body. | 204 |
| `tools/list` | List every tool with schema + description. | 200 JSON-RPC result |
| `tools/call` | Invoke a tool. `params = {name, arguments}`. | 200 JSON-RPC result (tool errors come back as `{isError: true, content: [...]}`) |
| `ping` | No-op keepalive. | 200 JSON-RPC `result: {}` |

Unknown methods return JSON-RPC error `-32601 Method not found`. Unknown tool names return `-32602 Unknown tool`. A malformed JSON body returns `-32700 Parse error`. A missing / wrong `jsonrpc` or missing `method` returns `-32600 Invalid Request`.

## Tools

All tools return a single `content` entry of `type: text` whose `text` field is JSON-formatted structured output. On failure, the same response shape is returned with `isError: true` and a human-readable error message in `content[0].text` — never as a JSON-RPC `error`.

### Discovery (no auth)

| Tool | Inputs | Returns |
|---|---|---|
| `get_capabilities` | — | `productName`, `version`, `capabilities[]` |
| `get_pricing` | — | `plans[]` with `id`, `name`, `monthlyUsd`, `features[]` |
| `list_styles` | — | `styles[]` with `id`, `name`, `description` |

### Media and music (auth required unless noted)

| Tool | Required | Optional | Returns |
|---|---|---|---|
| `search_music` | `query` | `limit` | Public music search results suitable for `create_compel_from_music`. No auth required. |
| `upload_media` | — | `name`, `mime_type`, `type` | Upload instructions pointing at `POST /api/v1/media` |
| `search_media` | — | `type` (`audio`/`image`/`video`/`text`), `limit` (≤100, default 20), `offset` | `media[]`, `paging` |

### Compels (auth required)

| Tool | Required | Optional | Returns |
|---|---|---|---|
| `create_compel_from_music` | `track_id` | `title`, `style`, `target_platform`, `aspect_ratio`, `artist_context`, `auto_start` | `compel_id`, `status`, `next_action` |
| `create_compel` | `title`, `primary_media_id` | `style`, `target_platform`, `aspect_ratio`, `artist_context` | `compel_id`, `status: QUEUED` |
| `get_compel` | `compel_id` | — | `compel_id`, `title`, `status`, `progress_percent`, `stage`, `rendering_id`, `created_at` |
| `start_render` | `compel_id` | — | Starts final rendering when the compel is ready; returns status and next action. |
| `list_compels` | — | `limit` (≤100), `offset` | `compels[]`, `paging` |
| `search_compels` | `query` | `limit` | `compels[]`, `count` |

### Renderings (auth required)

| Tool | Required | Returns |
|---|---|---|
| `list_renderings` | `compel_id` | `compel_id`, `renderings[]` with `rendering_id`, `status`, `download_url` |
| `get_rendering` | `rendering_id` | `rendering_id`, `compel_id`, `status`, `download_url` |

`download_url` points at `GET /api/v1/renderings/{id}/download` (supports HTTP Range).
Completed compel/rendering responses also include a `react` handoff with the free REACT download (`https://compeller.ai/download/desktop`) and learn-more URL (`https://compeller.ai/react`) so agents can tell users how to experience the compel as a live performance system.

### Webhooks (auth required — COM-281)

Backed by the COM-291 webhook CRUD. Agents that integrate with Compeller can self-register for signed push notifications of compel terminal events instead of polling `get_compel`.

| Tool | Required | Optional | Returns |
|---|---|---|---|
| `register_webhook` | `url` (HTTPS, ≤2048 chars) | `events[]` — defaults to `["*"]`; known values: `*`, `compel.completed`, `compel.failed` | `webhook_id`, `url`, `events`, **`secret`** (returned exactly once), `active`, `created_at` |
| `list_webhooks` | — | — | `webhooks[]` — `webhook_id`, `url`, `events`, `active`, `created_at`, `updated_at`. Secrets are **never** returned by this tool. |
| `update_webhook` | `webhook_id` | `url`, `events[]`, `active` — at least one | `webhook_id`, `url`, `events`, `active`, `created_at`, `updated_at`. Secrets are **never** returned; use `rotate_webhook_secret` for that. |
| `delete_webhook` | `webhook_id` | — | `webhook_id`, `deleted: true` |
| `test_webhook_delivery` | `webhook_id` | — | `webhook_id`, `event_id`, `event_type: "webhook.test"`, `delivered`, `response_status?`, `response_body_preview?`, `latency_ms`, `error?`. Synchronous — the tool waits for the integrator's endpoint to respond (max 5s). Secrets are **never** returned. |
| `rotate_webhook_secret` | `webhook_id` | — | `webhook_id`, `url`, `events`, `active`, **`secret`** (new — returned exactly once), `created_at`, `updated_at`. Old secret is invalidated immediately. |

Unknown event names collapse silently to the wildcard `*`; this mirrors `POST /api/v1/webhooks` so an agent never creates a no-op subscription.

`register_webhook` rejects destinations that point at internal infrastructure with a tool error: loopback, RFC1918 private ranges, link-local (including cloud metadata IPs like `169.254.169.254`), IPv6 ULA, CGNAT, multicast, the unspecified address, and hostnames ending in `.local` / `.internal` / `.localhost`. The same check re-runs at delivery time against resolved DNS, so a hostname that rebinds to a blocked IP after registration is silently skipped (logged, not retried).

`test_webhook_delivery` sends a synthetic `webhook.test` event with an HMAC-SHA256 signature and waits synchronously for the endpoint's response. It ignores the endpoint's subscribed `events` (always delivered) and applies the same URL safety check as real deliveries. A non-2xx response is surfaced as `delivered: false` but the MCP call itself still returns successfully — the result is the payload.

`update_webhook` accepts any of `url`, `events`, `active` (at least one). URL validation mirrors `register_webhook`. Secrets are never returned by this tool.

`rotate_webhook_secret` mints a fresh 64-char hex signing secret, returns it exactly once, and invalidates the previous secret immediately. Store the new secret on receipt before the next real delivery.

Every delivery is signed exactly like the REST path — see [`openapi.yaml`](openapi.yaml)'s `Webhooks` section for the full envelope and header contract.

## Example session

```bash
# 1. Handshake
curl -s https://compeller.ai/api/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}'

# 2. List tools
curl -s https://compeller.ai/api/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# 3. Register a webhook (auth required)
curl -s https://compeller.ai/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <api-token>' \
  -d '{
        "jsonrpc":"2.0",
        "id":3,
        "method":"tools/call",
        "params":{
          "name":"register_webhook",
          "arguments":{
            "url":"https://hooks.my-agent.io/compeller",
            "events":["compel.completed","compel.failed"]
          }
        }
      }'
```

The response to step 3 is a JSON-RPC `result` containing `content[0].text` — itself a JSON document with `webhook_id`, `secret`, etc. Store `secret` immediately; the server will not return it again.

## Error codes

| Code | Meaning | Cause |
|---|---|---|
| `-32700` | Parse error | Body is not valid JSON |
| `-32600` | Invalid Request | Missing/wrong `jsonrpc`, missing `method`, empty body |
| `-32601` | Method not found | Unknown JSON-RPC method |
| `-32602` | Invalid params | Unknown tool, missing tool `name`, bad `params` shape |
| `-32603` | Internal error | Unhandled exception (logged server-side) |

Tool-level failures (validation, auth, not-found) are returned inside a successful JSON-RPC response as `{result: {isError: true, content: [{type: "text", text: "..."}]}}`. This is by MCP convention — it lets the LLM see and surface the failure verbatim.

Agent audio decision tree: if the user provides MP3/WAV/FLAC, use `upload_media` then `create_compel`; if the user provides only a song/artist string, use `search_music` then `create_compel_from_music`; do not synthesize a tone unless explicitly asked for generated test audio.
