> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Operate Jelliu from an AI assistant

In this recipe you connect an AI assistant to your Jelliu workspace through the hosted MCP server, verify the connection from the command line, and use it to inspect agents, build a campaign and read results. You also set it up so the assistant can only do what you intend.

The [MCP server](/mcp) reference lists endpoints, OAuth metadata and error codes. This page is the hands-on walkthrough.

## What you will build

* An assistant that can answer questions like "which campaign converted best this week?" from live workspace data.
* A write-enabled setup that can create agents, load contacts and send messages, with confirmation before anything that costs money.
* A strictly read-only setup for reporting, enforced by the server rather than by the prompt.

## How it works

```mermaid
flowchart LR
    A[AI client<br />Claude Code, Cursor, Claude.ai] -- JSON-RPC over HTTPS<br />Bearer jl_... or OAuth token --> B[mcp.jelliu.co]
    B -- validate key, check plan and suspension --> B
    B -- lists only tools the key's scope allows --> A
    B -- each tool call becomes a REST request --> C[api.jelliu.co /api]
    C -- scopes, roles, validation, rate limits, audit --> D[(Your workspace)]
    B -- one entry per tool call --> E[Agent action ledger]
```

Every tool is a thin wrapper over a REST endpoint. When the assistant calls a tool, the MCP server calls the API **with the same credential**, so the call passes the same scope checks, role gates, validation, rate limits and audit logging as a request you make yourself. The assistant can never do more than the key or token it holds.

## Prerequisites

* A Jelliu workspace on an active plan. The MCP server is included in every current plan.
* For API-key clients: the workspace **owner**, to create a key in **Settings → API Keys**.
* One of: Claude Code, Cursor, Claude.ai (or another client that supports remote MCP servers over Streamable HTTP).
* `curl`, Node.js 18+ or Python 3.9+ with `requests` for the verification step.

## Choose the credential and scope

| You want the assistant to                                                                                                   | Use                                                                                                   | Connect to                          |
| --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------- |
| Answer questions and build reports, and never change anything                                                               | A `read` key, or any key on the read-only surface                                                     | `https://mcp.jelliu.co/p/read-only` |
| Create agents, add contacts, place calls, send WhatsApp and email                                                           | A `write` key                                                                                         | `https://mcp.jelliu.co`             |
| Also create, update, activate, pause and delete campaigns, delete agents, read billing usage, plan limits and the audit log | A `full` key                                                                                          | `https://mcp.jelliu.co`             |
| Connect from Claude.ai without handling a key                                                                               | OAuth: the approver's role decides (`full` for owners and admins, `read` + `write` for everyone else) | `https://mcp.jelliu.co`             |

How scope shapes what the assistant sees:

| Scope   | Read tools | Write and operational tools | Admin-only tools (campaign changes, agent deletion, billing usage and limits, audit) |
| ------- | ---------- | --------------------------- | ------------------------------------------------------------------------------------ |
| `read`  | Listed     | Not listed                  | Not listed                                                                           |
| `write` | Listed     | Listed                      | Not listed                                                                           |
| `full`  | Listed     | Listed                      | Listed                                                                               |

Admin-only tools, such as `jelliu_campaigns_create`, `jelliu_agents_delete`, `jelliu_billing_usage` and `jelliu_audit_list`, are listed only to a `full` key, so a `read` or `write` key never offers the assistant a tool that could only fail with `403`. If the assistant is supposed to manage campaigns, give it a `full` key.

### The read-only surface

`https://mcp.jelliu.co/p/read-only` serves only the tools that do not change anything, whatever the scope of the key you send. Use it for reporting assistants: even if someone later reuses a `write` key there, write tools are not listed at all.

## Build it

#### Create a key

In the dashboard, open **Settings → API Keys** and create a key:

* **Name:** something that identifies the assistant and person, for example `Claude Code - Ana`.
* **Scope:** per the table above. Start with `read` and widen it only when you need to.
* **Expiry:** optional. A key without one never expires.

Copy the key when it is shown. It is displayed only once. Export it in the shell you will use:

```bash
export JELLIU_API_KEY="jl_..."
```

See [API keys](/platform/api-keys) for scope details and rotation.

#### Check the server is reachable

The health check needs no credentials:

```bash
curl -sS https://mcp.jelliu.co/health
```

Expected response fields:

| Field       | Value                                                                                                                                                |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`    | `ok`                                                                                                                                                 |
| `server`    | `jelliu`                                                                                                                                             |
| `version`   | Server version, for example `1.0.0`                                                                                                                  |
| `transport` | `streamable-http`                                                                                                                                    |
| `auth`      | `api-key + oauth2`                                                                                                                                   |
| `toolCount` | Size of the full tool catalog. It grows as tools are added.                                                                                          |
| `profiles`  | Narrowed surfaces, each with `id`, `toolCount` and `rationale`. `read-only` is the one intended for you; the others are used by Jelliu's own agents. |

#### Verify your key lists tools

Before touching a client, confirm the key works and see exactly which tools it unlocks. The server is stateless, so a single `tools/list` request is enough. Send both `application/json` and `text/event-stream` in `Accept`, as MCP clients do.

**`cURL`**

```bash title="cURL"
curl -sS -X POST "https://mcp.jelliu.co" \
  -H "Authorization: Bearer $JELLIU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

**`Node.js`**

```javascript title="Node.js"
const res = await fetch('https://mcp.jelliu.co', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JELLIU_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json, text/event-stream',
  },
  body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }),
});

const body = await res.json();
if (!res.ok || body.error) {
  console.error(res.status, body.error);
  process.exit(1);
}

const tools = body.result.tools;
const writes = tools.filter((t) => !t.annotations?.readOnlyHint);
console.log(`${tools.length} tools, ${writes.length} can change data`);
for (const t of tools) {
  const flag = t.annotations?.destructiveHint ? ' [destructive]' : '';
  console.log(`- ${t.name}${flag}`);
}
```

**`Python`**

```python title="Python"
import os
import sys

import requests

res = requests.post(
    "https://mcp.jelliu.co",
    headers={
        "Authorization": f"Bearer {os.environ['JELLIU_API_KEY']}",
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream",
    },
    json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
    timeout=30,
)
body = res.json()
if res.status_code != 200 or "error" in body:
    print(res.status_code, body.get("error"))
    sys.exit(1)

tools = body["result"]["tools"]
writes = [t for t in tools if not t.get("annotations", {}).get("readOnlyHint")]
print(f"{len(tools)} tools, {len(writes)} can change data")
for t in tools:
    flag = " [destructive]" if t.get("annotations", {}).get("destructiveHint") else ""
    print(f"- {t['name']}{flag}")
```

Expected output with a `read` key (abridged; counts depend on the catalog and on which apps are connected):

```text
<total> tools, 0 can change data
- jelliu_agents_list
- jelliu_agents_get
- jelliu_campaigns_list
- jelliu_calls_list
- jelliu_conversations_list
- jelliu_analytics_campaign
- jelliu_voices_list
...
```

With a `write` or `full` key you also see tools such as `jelliu_agents_create`, `jelliu_campaign_contacts_bulk_add`, `jelliu_whatsapp_send`, `jelliu_email_send` and `jelliu_calls_initiate [destructive]`. Only a `full` key also sees the admin-only tools, such as `jelliu_campaigns_create`.

Tools for connected apps (for example accounting integrations) only appear when that app is connected to the workspace. If you expect one and it is missing, check the integration first.

#### Connect your client

#### Claude Code

Add the server for your user, reading the key from your environment:

```bash
claude mcp add --transport http jelliu https://mcp.jelliu.co \
  --header "Authorization: Bearer $JELLIU_API_KEY"
```

To share the setup with your team without committing a key, add a project `.mcp.json` that expands the variable at runtime:

**`.mcp.json`**

```json title=".mcp.json"
{
  "mcpServers": {
    "jelliu": {
      "type": "http",
      "url": "https://mcp.jelliu.co",
      "headers": {
        "Authorization": "Bearer ${JELLIU_API_KEY}"
      }
    }
  }
}
```

For a reporting-only setup, replace the URL with `https://mcp.jelliu.co/p/read-only`. Run `/mcp` inside Claude Code to confirm the server is connected.

#### Cursor

Add the server to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project):

**`mcp.json`**

```json title="mcp.json"
{
  "mcpServers": {
    "jelliu": {
      "url": "https://mcp.jelliu.co",
      "headers": {
        "Authorization": "Bearer jl_your_api_key"
      }
    }
  }
}
```

Keep project-level files that contain a key out of version control.

#### Claude.ai and other connector-based clients

Add a custom connector with the URL `https://mcp.jelliu.co`. No key is needed: the client discovers Jelliu's OAuth server, you sign in to Jelliu and approve the connection on a consent screen, and the client receives its own credentials.

* The access token lasts **1 hour** and is refreshed automatically; refresh tokens last **90 days** and each can be used once. Replaying an already-used refresh token revokes the whole chain of tokens.
* The token never has more access than you: an owner or admin approval yields `full`, anyone else `read` + `write`.
* If you leave the workspace, the connection stops refreshing.

#### Ask for something read-only first

Start with a question that only needs read tools, so you can see the assistant pick tools without side effects:

> List my agents and, for each one, how many calls it handled in the last 7 days and the most common outcome.

A good response names the agents, then summarizes calls. Behind the scenes the assistant calls `jelliu_agents_list`, then `jelliu_calls_list` filtered by `agentId`.

#### Do real work, with confirmation

With a `full` key, you can run a complete workflow from one conversation:

> Create a sales agent called "Renovaciones" in es-CO with this prompt: "Llamas a clientes cuya póliza vence este mes para ofrecer la renovación". Then create a draft voice campaign for it in America/Bogota, weekdays 9 to 18, and add these three contacts: +573001112233 Ana, +573004445566 Luis, +573007778899 Marta. Do not activate it. Show me the campaign before anything else.

Then, after reviewing:

> Activate the campaign you just created.

The assistant uses `jelliu_voices_list` (or picks a default voice), `jelliu_agents_create`, `jelliu_campaigns_create`, `jelliu_campaign_contacts_bulk_add` and, only on the second message, `jelliu_campaigns_activate`. Activation starts real calls; see [Outbound voice campaign](/recipes/outbound-voice-campaign) for what has to be in place first.

## Example prompts

| Goal                | Prompt                                                                                                                             | Minimum scope    |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| Campaign health     | "For each active campaign, show pending vs. called contacts and pause any with more than 30% failed calls. Ask me before pausing." | `full` (pausing) |
| Call review         | "Summarize the last 20 completed calls of campaign X: outcomes, objections and anything that needs a human."                       | `read`           |
| Conversation triage | "Which WhatsApp conversations from today are waiting for a human? Give me the contact and the last message."                       | `read`           |
| Contact loading     | "Add the contacts in this table to campaign X. Skip rows without a valid E.164 phone."                                             | `write`          |
| One-off follow-up   | "Send a WhatsApp to contact Y saying we received their documents."                                                                 | `write`          |
| Plan check          | "How many minutes have we used this month, and what are our plan limits?"                                                          | `full`           |
| Security review     | "List the last 50 audit entries for resource type api\_key."                                                                       | `full`           |

Give the assistant IDs when you have them ("campaign 0f3c8b52-..."). List tools return up to 20,000 characters per result; very long lists are truncated, so ask for filtered or paginated results rather than "everything".

## Keep it safe

### What the server enforces

* **Scope decides visibility.** Tools a key cannot use are not listed, and admin-only operations are refused by the API even if called.
* **Never available over MCP:** creating or revoking API keys, billing purchases (checkout, minute packs, phone numbers) and contact data erasure. These stay in the dashboard.
* **Compliance still applies.** Sending messages and placing calls through the assistant goes through the same opt-out, do-not-contact and daily cap checks as the REST API.
* **Destructive tools are marked.** Deletes and `jelliu_calls_initiate` carry `destructiveHint: true`, which tells clients that honor annotations to ask before running them. The server's own instructions also tell the model to confirm with you before calling them.
* **Data from your systems is fenced.** Tool results are passed to the model wrapped as external data, so text written by third parties (a CRM note, an email subject) is not treated as instructions.
* **Suspended workspaces are cut off** immediately, even if a session is open.

### What you should configure

* **Keep approval prompts on** in your client for any tool that is not read-only. Do not enable "always allow" for `jelliu_calls_initiate`, `jelliu_whatsapp_send`, `jelliu_email_send` or any `*_delete` tool.
* **Use the read-only surface** (`/p/read-only`) for dashboards, BI and exploratory assistants.
* **One key per person and client.** Revoking one never disconnects the others, and activity stays attributable.
* **Do not paste keys into prompts or chat history.** Put them in the client configuration or an environment variable.
* **Review what the assistant did.** Every tool call writes one entry to the agent action ledger, with redacted parameters, outcome (`success`, `error`, `denied`) and duration.

### Review the assistant's activity

With a `full` key, read the ledger and filter by tool, outcome or time range:

**`cURL`**

```bash title="cURL"
curl -sS "https://api.jelliu.co/api/agent-actions?from=2026-09-14T00:00:00Z&limit=50" \
  -H "Authorization: Bearer $JELLIU_API_KEY"
```

**`Node.js`**

```javascript title="Node.js"
const url = new URL('https://api.jelliu.co/api/agent-actions');
url.searchParams.set('from', '2026-09-14T00:00:00Z');
url.searchParams.set('limit', '50');

const res = await fetch(url, {
  headers: { Authorization: `Bearer ${process.env.JELLIU_API_KEY}` },
});
const { data } = await res.json();
for (const a of data.filter((row) => row.tool_source === 'mcp_gateway')) {
  console.log(a.occurred_at, a.tool_name, a.action_kind, a.outcome, `${a.duration_ms}ms`);
}
```

**`Python`**

```python title="Python"
import os

import requests

res = requests.get(
    "https://api.jelliu.co/api/agent-actions",
    params={"from": "2026-09-14T00:00:00Z", "limit": 50},
    headers={"Authorization": f"Bearer {os.environ['JELLIU_API_KEY']}"},
    timeout=30,
)
res.raise_for_status()
for a in res.json()["data"]:
    if a["tool_source"] == "mcp_gateway":
        print(a["occurred_at"], a["tool_name"], a["action_kind"], a["outcome"], f"{a['duration_ms']}ms")
```

Expected output:

```text
2026-09-14T15:02:11.482Z jelliu_agents_list read success 184ms
2026-09-14T15:02:13.020Z jelliu_campaigns_create write error 97ms
2026-09-14T15:03:40.771Z jelliu_campaign_contacts_bulk_add write success 412ms
```

Query parameters: `agentId`, `conversationId`, `callId`, `toolName`, `outcome` (`success`, `error`, `denied`, `timeout`, `indeterminate`), `actionKind` (`read`, `write`, `unknown`), `from` and `to` (ISO 8601), `limit` (1 to 200, default 50) and `offset`. To stream these entries into your SIEM instead, subscribe a webhook to `agent.action_recorded`; see [Webhooks](/webhooks).

## Troubleshooting

#### HTTP 401, JSON-RPC code -32001

The key is missing, malformed, revoked or expired. The header must be exactly `Authorization: Bearer jl_` followed by 64 lowercase hex characters, with no quotes or trailing spaces. In `.mcp.json`, check that the environment variable is actually set in the shell that launched the client. A revoked key can take about 10 seconds to be refused everywhere, and a newly created key works immediately.

#### HTTP 403, JSON-RPC code -32003

The workspace has no active subscription (`Este espacio de trabajo no tiene una suscripción activa, así que el gateway MCP no está disponible. Actívala en Ajustes → Plan.`), or its plan does not include the MCP server. Choose a plan in **Settings → Plan**; the client reconnects without changes.

#### HTTP 403 or 503, JSON-RPC code -32002

`403` means the workspace is suspended; contact support. `503` with `Retry-After: 5` is a temporary problem on Jelliu's side; the client can retry after a few seconds.

#### HTTP 404, JSON-RPC code -32004

The URL names a surface that does not exist, for example a typo in `/p/read-only`. Fix the path or use `https://mcp.jelliu.co`.

#### HTTP 405 on GET

The server only accepts JSON-RPC over `POST`. `GET` and `DELETE` on the MCP endpoint return `405`. Use `GET /health` for a liveness check.

#### The assistant says a tool failed with FORBIDDEN

Tool errors come back to the model as text in the form `Jelliu API error [CODE] (HTTP status): message`. `FORBIDDEN` with `This operation requires an API key with the 'full' scope` means the operation is admin-only; `API key lacks the 'write' scope` means the key is `read`-only. Create a key with the right scope and update the client.

#### A tool I expect is not listed

Three things narrow the list: the key's scope (write tools are hidden for `read` keys, and admin-only tools such as campaign changes, billing usage and the audit log for every key below `full`), the surface (`/p/read-only` hides every write tool) and connected apps (integration tools only appear when that app is connected). Re-run the `tools/list` check above with the same key and URL as the client.

#### RATE\_LIMIT\_EXCEEDED

The MCP endpoint accepts 120 requests per minute per client IP, and each tool call is also an API request counted against your workspace's [rate limits](/rate-limits). Mutations such as creating agents and campaigns share a budget of 10 per minute, and contact imports 5 per minute. Ask the assistant to batch, for example one bulk contact import instead of one call per contact.

#### Creating an agent is slow, or a call right after fails with AGENT\_PROVISIONING

Agent creation provisions the voice agent and can take tens of seconds. If the client gives up before it finishes, the agent may still be created: ask the assistant to check with `jelliu_agents_list` before retrying, so it does not create a duplicate. Using a brand-new agent for a call before provisioning finishes returns `AGENT_PROVISIONING` (`Agent is still being configured — try again in a few seconds`); wait a few seconds and retry.

## Limits

| Limit                        | Value                                                                            |
| ---------------------------- | -------------------------------------------------------------------------------- |
| Requests to the MCP endpoint | 120 per minute per client IP                                                     |
| Each tool call               | Counts against the workspace [rate limits](/rate-limits) of the underlying route |
| Tool result size             | 20,000 characters per result; longer results are truncated                       |
| Plan                         | MCP access is part of every current plan                                         |
| Scope                        | Decided by the key or OAuth token; see [API keys](/platform/api-keys)            |

## Related

#### [MCP server reference](/mcp)

Endpoint, OAuth metadata and error codes.

#### [API keys](/platform/api-keys)

Scopes, rotation and auditing.

#### [Outbound voice campaign](/recipes/outbound-voice-campaign)

What has to be in place before activating a campaign.

#### [Webhooks](/webhooks)

Stream agent actions and audit events to your systems.