> Doc v2.0 · Updated 2026-06-15 · AEvent MCP server "AEvent" · 43-tool consumer surface

# AEvent MCP Connection and Setup Guide

This is the canonical guide for connecting an AI agent (Claude Code, Claude Desktop, Cursor, Cline, Roo, Windsurf, n8n, or your own code) to the AEvent MCP server. The verified MCP endpoint is `https://app-api.aevent.com/mcp` (streamable HTTP).

---

## 1. Overview

### What the AEvent MCP server is

AEvent runs a Model Context Protocol (MCP) server built on the Laravel MCP package (`Laravel\Mcp\Server`). It exposes your AEvent webinar account to any MCP-capable agent as a set of callable tools. Once connected, an agent can list and inspect your campaigns, read merge fields and timelines, schedule webinars, build timeline actions (emails, SMS/MMS, autochat, voicemail, tags, list actions), manage multi-event series, set registration/replay pages, and more.

### What an agent can do with it

The consumer surface is 43 tools. A representative set:

- **Discover:** `list-campaigns`, `list-integrations`, `list-campaign-integrations`, `list-audiences`, `list-multi-events`, `list-videos`, `list-audio`, `list-images`, `list-nifty-images`, `show-campaign`, `show-merge-fields`, `show-transcript`, `view-form`, `billing-details`.
- **Build timeline actions:** `add-email`, `add-text-message`, `add-autochat`, `add-voice-mail`, `add-tag`, `remove-tag`, `add-to-list`, `add-join-message`, `clear-join-messages`.
- **Configure:** `create-campaign`, `add-field`, `add-u-t-m`, `edit-form`, `set-confirmation-page`, `set-expired-page`, `set-replay-page`, `set-replay-sequence`, `switch-video`, `change-setting` (single-op JSON-Patch editor).
- **Schedule and verify:** `schedule-webinar`, `list-upcoming-webinars`, `list-past-webinars`, `list-recurring`, `show-event-info`, `test-integration`, `check-page`, `search-documentation`, `show-lead`.

Full per-tool schemas live in the companion tool catalog (`01-live-tool-catalog.md`). Real example outputs live in `04-worked-examples.md`.

> Note on `change-setting`, `edit-form`, `view-form`: these three are present on the AEvent Support consumer proxy surface (which totals 43 tools). The public `AEventServer` tool registration array (`$tools`) does not include these three as registered MCP `Action` classes (they are backed by REST controllers in the app). A customer agent connecting directly to `/mcp` may therefore see 40 tools rather than the full 43, and may not see `change-setting`/`edit-form`/`view-form` by name. Confirm the live tool list after connecting (Step 3). Likewise, `search-documentation` IS registered on the public server, so it should appear.

### Server identity and versioning

- **Server name:** `AEvent` (Laravel class `App\Mcp\Servers\AEventServer`, `protected string $name = 'AEvent'`).
- **Reported version:** `0.0.1`. This is the framework placeholder, not a meaningful release number. Do not gate behavior on it.
- When you add the connection in your client you can name it anything (`aevent` is recommended in the examples below); the server self-identifies as `AEvent`.
- The endpoint advertises an `mcp-protocol-version` response header and sits behind Caddy plus Cloudflare.

---

## 2. Step 1: Get your token

You authenticate to the MCP endpoint with an HTTP Bearer token. The token is a Laravel Passport personal-access token carrying the ability `mcp:use`.

### Generate your token

Create your token inside the AEvent app:

1. Go to **Settings > Developer**.
2. Click to create a new token, give it a name (for example `mcp`), and check the **`mcp:use`** scope.
3. Click **Create**, then copy the token immediately (you will not be able to read it again).
4. Use that token as `<TOKEN>` in every config below: `Authorization: Bearer <TOKEN>`.

### How the token scopes your account

The token IS the tenant scope. Server middleware is `['auth:api', 'tenant']`: `auth:api` resolves your user from the Bearer token (Passport, guard `api`), then the `tenant` middleware switches the DB to your tenant. There is no per-call tenant parameter when you connect directly; every call automatically operates on the account that owns the token.

---

## 3. Step 2: Connect your agent (client matrix)

All clients connect to the same canonical endpoint with the same auth header. Set it once, then use the per-client shape below.

### Canonical connection (set these three values everywhere)

```
Transport: streamable HTTP (MCP "http" transport)
URL:       https://app-api.aevent.com/mcp
Header:    Authorization: Bearer <TOKEN>
```

Replace `<TOKEN>` with your `mcp:use` token from Step 1. Recommended connection/server name in your client: `aevent`.

### Claude Code (CLI)

```bash
claude mcp add --transport http aevent https://app-api.aevent.com/mcp \
  --header "Authorization: Bearer <TOKEN>"
```

Verify it registered:

```bash
claude mcp list
```

### Claude Desktop

Claude Desktop's most reliable cross-version path for a remote, header-authenticated HTTP MCP server is the `mcp-remote` bridge (run via `npx`). Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`; Windows: `%APPDATA%\Claude\claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "aevent": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://app-api.aevent.com/mcp",
        "--header",
        "Authorization: Bearer <TOKEN>"
      ]
    }
  }
}
```

If your Claude Desktop build supports native remote/custom connectors (Settings > Connectors > Add custom connector), you can instead add the URL `https://app-api.aevent.com/mcp` directly and supply the `Authorization: Bearer <TOKEN>` header there. The `mcp-remote` bridge above is the fallback that works regardless of native-connector availability. Restart Claude Desktop after editing the file.

### Cursor

Edit `~/.cursor/mcp.json` (or the project-local `.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "aevent": {
      "url": "https://app-api.aevent.com/mcp",
      "headers": {
        "Authorization": "Bearer <TOKEN>"
      }
    }
  }
}
```

### Cline / Roo (VS Code) and Windsurf

These use a `mcpServers` map with a remote HTTP entry. In Cline/Roo, open the MCP settings JSON (Cline: "MCP Servers" > "Configure"; the file is `cline_mcp_settings.json`). In Windsurf, edit `~/.codeium/windsurf/mcp_config.json`. Same shape:

```json
{
  "mcpServers": {
    "aevent": {
      "url": "https://app-api.aevent.com/mcp",
      "headers": {
        "Authorization": "Bearer <TOKEN>"
      }
    }
  }
}
```

If a given build only supports stdio servers, use the `mcp-remote` bridge form shown for Claude Desktop (swap `command`/`args` in place of `url`/`headers`).

### n8n (MCP Client node)

Use the **MCP Client** node:

- **Endpoint / Server URL:** `https://app-api.aevent.com/mcp`
- **Transport:** HTTP (streamable)
- **Authentication:** Header Auth credential with header name `Authorization` and value `Bearer <TOKEN>`

The node will list the AEvent tools after it connects; select a tool and map its parameters from prior nodes.

### ChatGPT / custom GPT

Custom GPTs do NOT speak native MCP. They call REST/OpenAPI actions only. You cannot point a custom GPT at the streamable `/mcp` endpoint as an MCP server. Options: (a) use a REST-style direct call (see the curl example below) wrapped as a GPT Action with an OpenAPI schema, or (b) put an MCP-aware proxy in front. Native MCP requires one of the MCP clients above.

### Raw HTTP / curl (direct call, secondary path)

The primary path is the MCP server above. For quick checks or non-MCP runtimes you can speak the streamable-HTTP MCP protocol directly. Initialize, then call a tool:

```bash
# 1) initialize
curl -sS https://app-api.aevent.com/mcp \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18",
                 "capabilities":{},
                 "clientInfo":{"name":"curl","version":"1.0"}}}'

# 2) call a tool (list-campaigns)
curl -sS https://app-api.aevent.com/mcp \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"list-campaigns","arguments":{"search":"Challenge"}}}'
```

A missing or bad token returns HTTP 401 with header `www-authenticate: Bearer realm="mcp", error="invalid_token"` (see Step 3).

### Python (MCP SDK)

```python
import asyncio
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession

URL = "https://app-api.aevent.com/mcp"
HEADERS = {"Authorization": "Bearer <TOKEN>"}

async def main():
    async with streamablehttp_client(URL, headers=HEADERS) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print([t.name for t in tools.tools])
            result = await session.call_tool("list-campaigns", {"search": "Challenge"})
            print(result.content)

asyncio.run(main())
```

### TypeScript (MCP SDK)

```ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://app-api.aevent.com/mcp"),
  { requestInit: { headers: { Authorization: "Bearer <TOKEN>" } } }
);

const client = new Client({ name: "aevent-client", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);

const tools = await client.listTools();
console.log(tools.tools.map((t) => t.name));

const result = await client.callTool({
  name: "list-campaigns",
  arguments: { search: "Challenge" },
});
console.log(result.content);
```

---

## 4. Step 3: Verify the connection (ping)

The cleanest "ping" is `list-campaigns`. It is read-only and returns a `name -> wtl` map, which immediately proves auth, tenant scoping, and tool routing.

Ask your agent: **"Call list-campaigns and show me the result."**

A healthy response is a JSON object of campaign names keyed by their 15-char campaign ID (`wtl`). Real shape (from the demo tenant):

```json
{
  "cAJ3f3AjCeqy01F": "Webinar Scaling Secrets",
  "4WN615HD1rDjDZI": "Multi-Event",
  "5Rb801EvIi0gHDu": "Zoom: AEvent Demo",
  "eNu623BZHtpbYIF": "AStream Example"
}
```

Use the `wtl` from this map for every per-campaign call. Never construct a `wtl` by hand; always resolve it from `list-campaigns`.

### Wrong or missing token signal

If the token is missing, malformed, or expired, the endpoint returns:

```
HTTP/1.1 401 Unauthorized
www-authenticate: Bearer realm="mcp", error="invalid_token"
```

If you see this 401 challenge, your token is the problem. Re-check Step 1 (correct `mcp:use` token, no stray whitespace, `Bearer ` prefix present).

---

## 5. Step 4: First workflow

A safe first workflow that only reads, then makes one scoped write:

1. **Resolve the campaign.** `list-campaigns` (optionally with `search`) to get the `wtl`.
2. **Inspect it.** `show-campaign` with `section: "general"` to read format and settings; `list-campaign-integrations` to get the exact `integrationID`s wired to that campaign; `list-audiences` to get audience IDs (defaults are `3` Registrants, `a1` Attendee, `a2` Non-Attendee).
3. **Check merge fields.** `show-merge-fields` to get valid tokens. AEvent-native tokens use bang-both-sides syntax: `{{!subscriber-firstName!}}`, `{{!subscriber-joinURL!}}`, `{{!subscriber-replayURL!}}`. The two calendar tokens are the exception (no trailing bang): `{{!add_to_google_calendar}}`, `{{!add_to_apple_calendar}}`. Do not use generic `{{first_name}}` / `{{webinar_link}}` style tokens; they are wrong for AEvent-native copy.
4. **Make one action.** For example `add-text-message` with the resolved `wtl`, an `audience` ID, the campaign's Twilio `integrationID`, and a relative `time` string ("15 minutes before"):

```json
{
  "name": "add-text-message",
  "arguments": {
    "wtl": "cAJ3f3AjCeqy01F",
    "message": "{{!subscriber-firstName!}}, we go live in 15 min: {{!subscriber-joinURL!}}",
    "audience": "3",
    "integrationID": "PRuNUNR7aKViPuA",
    "time": "15 minutes before"
  }
}
```

**Discover-before-act is the rule.** Resolve `wtl`, then `integrationID` / `eventID` / `audience`, before any action call. Relative `time` strings format like "1 day 3 hours 30 minutes after" or "1 day before at 3:00pm".

Key concepts an agent should hold:

- **Campaign** (a.k.a. `wtl` / webinarTimeline, a 15-char system ID like `cAJ3f3AjCeqy01F`) is the container. Never construct it; resolve via `list-campaigns`.
- **Webinar** is a scheduled instance of a campaign (`schedule-webinar`, `list-upcoming-webinars`).
- **Timeline** is the action sequence inside a campaign (emails / SMS / autochat / voicemail / tags / webhooks fired at relative times).
- **Integrations** are connected services; resolve per-campaign IDs via `list-campaign-integrations`, never by type alone (a tenant can have several of the same type).
- **Audiences** are segments; **Multi-Events** are additional events (Day 2, encore).
- **Formats:** likeLive / live / hybrid (read off `general.webinarType`: `automatic` = likeLive, `manual` = live, `semi-automatic` = hybrid).

---

## 6. Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| `HTTP 401`, header `www-authenticate: Bearer realm="mcp", error="invalid_token"` | Missing, malformed, or expired Bearer token | Re-issue / re-paste the `mcp:use` token. Ensure the `Bearer ` prefix and no trailing whitespace. |
| Connects but every call returns 401/403, or "account paused" | Token valid but account inactive, or token lacks `mcp:use` ability | The server aborts with 403 "Account is paused or disabled" for inactive accounts. Confirm the account is active and the token carries the `mcp:use` ability (not some other scope). |
| 401 even with a real-looking token, tenant not resolving | Tenant middleware could not switch the DB (token's user has no resolvable tenant) | This is a server-side account-setup issue, not a client config issue. Escalate to AEvent (Alex) with the account email; do not retry blindly. |
| Tool not found (e.g. `change-setting` / `edit-form` / `view-form` missing) | Those three are not registered on the public `AEventServer` tool array; they are proxy-only | Expected on a direct `/mcp` connection. Use the documented REST/form-controller path for form edits, or the Support proxy. Run `tools/list` to see exactly what your connection exposes. |
| Calls 404 or HTML comes back instead of JSON-RPC | Pointed at the wrong host/path (the SPA/sales host) | Use `https://app-api.aevent.com/mcp`. NOT `aevent.com` (WordPress sales site), NOT `app.aevent.com` (the UI SPA). |
| `wtl` rejected / "campaign not found" | Constructed or guessed `wtl`, or used a name instead of the ID | Resolve via `list-campaigns` and pass the exact 15-char key. |
| Action saved but value behaves oddly (`change-setting`) | `change-setting` validates JSON type/structure only, not value enums | Supply correct enum values yourself; garbage enum values persist silently. |

---

## 7. For your agent

### Point your agent at the skill doc

Give your agent the companion tool catalog and worked examples so it knows the exact schemas and real ID shapes:

- Tool catalog (per-tool schemas, required params, gotchas): `01-live-tool-catalog.md`
- Worked examples (real `list-campaigns` / `show-campaign` / `show-merge-fields` outputs): `04-worked-examples.md`
- Claude skills mapping: the AEvent MCP skills doc (`aevent-mcp-claude-skills` v2).

### Recommended MCP server-instructions string

The server's `instructions` field is currently the Laravel placeholder ("Instructions describing how to use the server and its features."). AEvent dev can replace it with the string below so every connecting agent gets correct guidance automatically:

```
You are connected to the AEvent MCP server (server name "AEvent"). It manages
webinar campaigns for the AEvent account that owns your Bearer token. The token
is your tenant scope: all calls operate only on that account, and there is no
tenant parameter.

Core model: a Campaign (a.k.a. wtl / webinarTimeline, a 15-character ID like
cAJ3f3AjCeqy01F) is the container. A Webinar is a scheduled instance of a
campaign. The Timeline is the sequence of actions (emails, SMS/MMS, autochat,
voicemail, tags, list actions, webhooks) inside a campaign, fired at times
relative to the event. Integrations are connected services; Audiences are
segments (defaults: 3 = Registrants, a1 = Attendee, a2 = Non-Attendee);
Multi-Events are additional events (Day 2, encore). Formats are likeLive, live,
and hybrid (read general.webinarType: automatic = likeLive, manual = live,
semi-automatic = hybrid).

Discover before you act. Never construct a wtl; resolve it with list-campaigns.
Before any action call, resolve the integrationID with list-campaign-integrations
and the audience/eventID with list-audiences / list-multi-events. Relative time
strings look like "1 day 3 hours 30 minutes after" or "1 day before at 3:00pm".

Merge tokens use bang-both-sides syntax: {{!subscriber-firstName!}},
{{!subscriber-joinURL!}}, {{!subscriber-replayURL!}}, {{!webinar-esttime!}}, etc.
The two calendar tokens are the exception with no trailing bang:
{{!add_to_google_calendar}} and {{!add_to_apple_calendar}}. Call show-merge-fields
for the authoritative list. Never use generic tokens like {{first_name}}.

Read-only tools (list-*, show-*, view-form, billing-details, test-integration,
check-page, search-documentation) are safe to call freely. Write/mutating tools
(create-campaign, add-*, change-setting, edit-form, schedule-webinar, set-*,
switch-video, clear-join-messages, remove-tag) change live customer data: confirm
the target wtl and parameters before calling, and prefer one scoped change at a
time. change-setting validates JSON type/structure only, not value enums, so
supply correct values.
```
