Cluvia

Cluvia API — MCP server

Drive your Cluvia companies from Claude Code, Cursor, VS Code or any other Model Context Protocol (MCP) client: list your companies, give agents tasks, read what they produced, decide approvals, schedule recurring work and grow the team.

MCP server URLhttps://mcp.usecluvia.com/mcp
AuthenticationAuthorization: Bearer cluvia_pat_…
TransportStreamable HTTP, JSON responses
Documentationhttps://www.usecluvia.com/developers

1. Overview #

Cluvia runs AI companies: each company has a team of AI agents (a CEO, marketers, writers, website builders and more) that do real work — research, writing, websites — on Cluvia's servers. The Cluvia MCP server is the programmatic door to the same account you use in the app. Anything that speaks MCP can connect to it with a personal access token and act on your behalf.

With it you can:

The server exposes 15 tools. It does not expose prompts or resources. A typical session: list_companies for a companyKey, get_company for the team, assign_task to give an agent work, then wait_for_run for the result. Runs start on Cluvia's servers within seconds when there is room, use your plan's credits, and appear on the app's task board when they finish.

The server also sends these instructions to your client when it connects, so your AI assistant knows how to start:

Cluvia runs AI companies whose agents do real work. Start with list_companies for a companyKey and get_company for the team, then assign_task to give an agent work and wait_for_run for the result. Runs start on Cluvia's servers within seconds when there is room. hire_agent is queued and applies within about a minute (by the Cluvia app when it has that company open, or by Cluvia when nobody does); set_policy applies at once.

2. Quick start #

Step 1: create an access token

  1. Sign in to Cluvia at www.usecluvia.com.
  2. Open Connections in the sidebar.
  3. In the panel Connect Claude Code, Cursor or Claude Desktop, give the token a name (for example “My laptop”).
  4. Tick the Permissions it needs. The default, Read and Run tasks, is enough for this quick start. Add Approve and perform to decide approvals and Administer to hire agents or set policies (see permissions).
  5. Leave Company on All companies, or pick one company to limit the token to it.
  6. Click Create token. The token (cluvia_pat_ followed by 32 letters and digits) is shown once, with ready-made setup snippets. Copy it now.
A token acts as you. Treat it like a password: keep it out of source control and shared chats. If it leaks, revoke it on the Connections page.

Step 2: add the server to Claude Code

claude mcp add --transport http cluvia https://mcp.usecluvia.com/mcp --header "Authorization: Bearer cluvia_pat_YOUR_TOKEN"

Replace cluvia_pat_YOUR_TOKEN with your token. Check the connection with claude mcp list, or with /mcp inside a Claude Code session. Other clients are covered in Connecting your client.

Step 3: make a first call

Ask your assistant in plain words. It picks the tools.

You sayWhat happens
“List my Cluvia companies.”The assistant calls list_companies and shows each company's name, industry, team size and open tasks, with its companyKey.
“Who is on the team at Sunrise Bakery?”get_company: every agent's id, name, role, department and status, plus open tasks, recent outputs and pending approvals.
“Ask the Content Writer at Sunrise Bakery to draft a launch email for the spring menu, and show me the result.”assign_task queues a run and returns a runId; wait_for_run waits (up to 50 seconds by default, longer if asked) and returns the finished text. The task also appears on the app's task board.
“Is anything waiting for my approval?”list_approvals lists pending actions, such as publishing a site. With the Approve and perform permission, the assistant can approve or deny them when you tell it to.

Prefer raw HTTP? The same first call with curl:

curl -sS https://mcp.usecluvia.com/mcp \
  -H "Authorization: Bearer $CLUVIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_companies","arguments":{}}}'

3. Endpoint and transport #

Endpoint

POST https://mcp.usecluvia.com/mcp

The path is exactly /mcp. The earlier address, https://companyos-ai-proxy.adamidrissi177.workers.dev/mcp, is the same server and keeps working, so existing client configurations do not need to change. New setups should use https://mcp.usecluvia.com/mcp.

The host mcp.usecluvia.com serves only the MCP endpoint. Opening https://mcp.usecluvia.com/ (or any other path) in a browser redirects to this page; paths under /.well-known/ and requests other than GET or HEAD to any path but /mcp answer 404.

Transport

  • Streamable HTTP, request–response only. Send one JSON-RPC 2.0 message per POST. Every answer is a single JSON object with Content-Type: application/json and Cache-Control: no-store. The server never opens a Server-Sent Events stream, so the Accept header does not change the answer.
  • No sessions. The server issues no Mcp-Session-Id; every request stands alone and must carry the Authorization header.
  • No batches. A JSON array is refused with HTTP 400.
  • Request body limit: 64 KB. Larger bodies get HTTP 413.
  • GET, DELETE, OPTIONS and every method other than POST get HTTP 405 with Allow: POST. There is no stream to open with GET and no session to end with DELETE.
  • Notifications (messages without an id, such as notifications/initialized or notifications/cancelled) are accepted with HTTP 202 and no body. Responses a client sends (messages with result or error and no method) also get 202. The server never sends requests or notifications to the client.
  • Headers MCP-Protocol-Version, Mcp-Method and Mcp-Name are informational. A request is never refused because of them; the protocol version is read from the message body (below).
  • Browsers: a request carrying an Origin header from another website is refused with HTTP 403 (a guard against DNS rebinding). The endpoint sends no CORS headers. Call it from an MCP client, a script or a server, not from a web page.

Protocol versions

Supported MCP protocol versions, newest first:

VersionHow the server answers
2026-07-28Current shape: every successful result also carries "resultType": "complete" and _meta["io.modelcontextprotocol/serverInfo"]. An unknown method gets HTTP 404.
2025-11-25Earlier shape: the client opens with initialize; results are plain JSON-RPC results. An unknown method gets HTTP 200 with a JSON-RPC error.
2025-06-18
2025-03-26

How the version is chosen:

  • Only params._meta["io.modelcontextprotocol/protocolVersion"] in the request body decides. If it is missing, the request is served in the earlier shape and is never refused for that.
  • If it is 2026-07-28, the request is served in the current shape. Any other supported version gets the earlier shape.
  • If it is present but not a supported version, the server answers HTTP 400 with JSON-RPC error -32022 "Unsupported protocol version" and data: { "supported": [...], "requested": ... }.
  • initialize answers with the protocolVersion the client asked for when it is 2025-11-25, 2025-06-18 or 2025-03-26, and with 2025-11-25 otherwise.

Methods

MethodResult
initialize{ protocolVersion, capabilities: { tools: { listChanged: false } }, serverInfo, instructions }
ping{}
server/discover{ supportedVersions, capabilities: { tools: {} }, instructions }
tools/list{ tools } — only the tools your token's permissions allow. The list does not change during a connection (listChanged: false) and is not paginated.
tools/callRuns one tool. See Tool results.
anything elseJSON-RPC error -32601 "Method not found: <method>".

serverInfo is { "name": "cluvia", "title": "Cluvia", "version": "1.0.0", "websiteUrl": "https://www.usecluvia.com/developers" }. websiteUrl is left out for 2025-06-18 and 2025-03-26, whose schemas do not define it. Authentication is checked before the body is read, so even initialize needs a valid token.

Example: initialize

Request:

POST /mcp HTTP/1.1
Host: mcp.usecluvia.com
Authorization: Bearer cluvia_pat_YOUR_TOKEN
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": { "name": "example-client", "version": "1.0.0" }
  }
}

Response (HTTP 200):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": false } },
    "serverInfo": { "name": "cluvia", "title": "Cluvia", "version": "1.0.0" },
    "instructions": "Cluvia runs AI companies whose agents do real work. Start with list_companies for a companyKey and get_company for the team, then assign_task to give an agent work and wait_for_run for the result. ..."
  }
}

(A client asking for 2025-11-25 also gets "websiteUrl": "https://www.usecluvia.com/developers" in serverInfo.) Then send the notifications/initialized notification (answered with 202) and call tools/list.

Example: a 2026-07-28 request

Clients on 2026-07-28 name the version in _meta on each request:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": { "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28" } }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "complete",
    "tools": [
      {
        "name": "list_companies",
        "title": "List companies",
        "description": "List the companies in this Cluvia account: name, industry, team size and open tasks. Each has a companyKey the other tools take.",
        "inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
        "annotations": { "readOnlyHint": true, "openWorldHint": false }
      }
    ],
    "_meta": {
      "io.modelcontextprotocol/serverInfo": { "name": "cluvia", "title": "Cluvia", "version": "1.0.0", "websiteUrl": "https://www.usecluvia.com/developers" }
    }
  }
}

(The real answer lists every tool your token may call; one is shown.)

Tool results

A successful tools/call returns the data twice: as pretty-printed JSON text in content[0].text, and as an object in structuredContent.

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{ "type": "text", "text": "{\n  \"companies\": [ ... ]\n}" }],
    "structuredContent": { "companies": [ ... ] },
    "isError": false
  }
}

A tool that cannot do what was asked returns a normal result with isError: true and a readable sentence, and no structuredContent. The HTTP status is 200. Your assistant can read the sentence and correct itself.

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{ "type": "text", "text": "No company has the companyKey \"company-x:2026-01-01T00:00:00.000Z\" — call list_companies for the keys." }],
    "isError": true
  }
}

Every tool's input schema sets additionalProperties: false. Read-only tools carry the annotations { "readOnlyHint": true, "openWorldHint": false }; the others list their own annotations below.

4. Authentication #

Every request needs a personal access token in the Authorization header:

Authorization: Bearer cluvia_pat_YOUR_TOKEN

The word Bearer followed by one space is required, with that capitalisation.

Personal access tokens

  • Create: in the app, Connections → Connect Claude Code, Cursor or Claude Desktop → Create token (steps in Quick start). Token management needs a signed-in app session: a token cannot create, list or revoke tokens.
  • Format: cluvia_pat_ followed by 32 base62 characters (0-9, A-Z, a-z), for example cluvia_pat_Ab12Cd34Ef56Gh78Ij90Kl12Mn34Op56 (not a real token).
  • Shown once: Cluvia stores only a SHA-256 hash of the token, so it cannot show it again. The token list shows its name, the first characters (cluvia_pat_XXXX…), its permissions and when it was last used.
  • Limit: up to 10 live tokens per account.
  • Expiry: tokens do not expire. Revoke a token you no longer use.
  • Revoke: click the trash icon next to the token on the Connections page. It stops working on the next request.
  • No plan requirement: every plan, Free included, can create tokens and use the MCP server. What a call may spend is governed by your plan (see Plans and credits).

Permissions

Each token carries one or more permissions. They are independent: Administer does not include Read. The app ticks Read and Run tasks for a new token; change the ticks before you create it. tools/list shows only the tools a token may call.

In the appNameToolsNote
Readreadlist_companies, get_company, list_outputs, get_output, run_status, wait_for_run, list_approvals, list_schedulesReads everything in the account the token can reach.
Run taskstasksassign_task, create_schedule, delete_scheduleSpends your credits. There is no separate spending cap on a token.
Approve and performapproveapprove, denyApproving carries the action out: publish a site, send an email.
Administeradminhire_agent, set_policyChanges the team and the agents' standing policies.

Calling a tool without its permission returns an isError result and does nothing (it does not count toward the rate limit):

This access token doesn't have the "admin" permission this needs. Create a token that has it on the Connections page in Cluvia.

Tokens created before permissions existed act as the whole account and appear in the app as Full access (legacy). Replace them with a token that has only the permissions you need.

Company-limited tokens

  • A token limited to one company reaches only that company. list_companies returns just that one.
  • Any other company answers exactly like a company that does not exist: No company has the companyKey "…" — call list_companies for the keys.
  • Runs, approvals and schedules are judged by the company they belong to: another company's run answers No run "…" in this account., its approval Approval not found., its schedule Schedule not found.
  • Cluvia's site-management routes, which the app uses, refuse a one-company token any site that is not bound to its company (the app warns: “One-company tokens can't publish or manage sites.”). Over MCP, such a token with Approve and perform can still approve a publish_site approval of its own company.
  • Deleting the company revokes every token limited to it. Account-wide tokens are not affected.
  • Deleting your account deletes all of its tokens.

What a refused request looks like

Authentication failures are answered at the HTTP level, before the message is read, with a JSON-RPC error body (code -32600, no id). A 401 also carries WWW-Authenticate: Bearer realm="cluvia".

HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer realm="cluvia"

{"jsonrpc":"2.0","error":{"code":-32600,"message":"This access token is not valid — it may have been revoked. Create a new one on the Connections page in Cluvia."}}
HTTPMessageWhat to do
401Connect with a Cluvia access token: send Authorization: Bearer cluvia_pat_… (create one on the Connections page in Cluvia).Add the header, spelled exactly Authorization: Bearer <token>.
401This access token is not valid — it may have been revoked. Create a new one on the Connections page in Cluvia.The token is mistyped or revoked. Create a new one.
401This access token's permissions could not be read, so it was refused. Revoke it and create a new one on the Connections page in Cluvia.Revoke it and create a new one.
401The account this access token belongs to can no longer use Cluvia.The account was deleted or suspended. Contact support if this is unexpected.
401Guest sessions cannot use Cluvia tools — create an account or sign in.Only for an app guest session used as a bearer. Create an account and a token.
503Could not check your access token right now — try again in a moment.Retry after a short wait.

There is no OAuth: the server publishes no OAuth or protected-resource metadata, and a token is the only way for an outside tool to sign in. (The app's own sign-in session also works as a bearer, with every permission, but outside tools should use a token.)

5. Connecting your client #

Any MCP client that supports the Streamable HTTP transport and lets you set a request header can connect. Each client writes its configuration a little differently; the snippets below follow each client's own documentation. In every snippet, replace the token placeholder with yours, or keep it in an environment variable as shown.

Claude Code

claude mcp add --transport http cluvia https://mcp.usecluvia.com/mcp --header "Authorization: Bearer cluvia_pat_YOUR_TOKEN"
  • Add --scope user to use the server in every project, or --scope project to share it through the project's .mcp.json. The default scope is local.
  • Check it with claude mcp list, claude mcp get cluvia, or /mcp in a session.
  • In a shared .mcp.json, keep the token out of the file with an environment variable:
{
  "mcpServers": {
    "cluvia": {
      "type": "http",
      "url": "https://mcp.usecluvia.com/mcp",
      "headers": { "Authorization": "Bearer ${CLUVIA_TOKEN}" }
    }
  }
}

Reference: Claude Code MCP documentation.

Cursor

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

{
  "mcpServers": {
    "cluvia": {
      "url": "https://mcp.usecluvia.com/mcp",
      "headers": { "Authorization": "Bearer ${env:CLUVIA_TOKEN}" }
    }
  }
}

The snippet the Cluvia app shows after you create a token also includes "type": "http"; Cursor's documentation shows remote servers without it, and the form above follows Cursor's documentation. Reference: Cursor MCP documentation.

VS Code

VS Code's own format uses a top-level servers key (not mcpServers) and requires type. Put this in .vscode/mcp.json in a workspace, or in your user configuration (command MCP: Open User Configuration). The inputs entry makes VS Code prompt for the token instead of keeping it in the file. The tools are then available in VS Code's Chat:

{
  "inputs": [
    { "type": "promptString", "id": "cluvia-token", "description": "Cluvia access token", "password": true }
  ],
  "servers": {
    "cluvia": {
      "type": "http",
      "url": "https://mcp.usecluvia.com/mcp",
      "headers": { "Authorization": "Bearer ${input:cluvia-token}" }
    }
  }
}

References: Add and manage MCP servers in VS Code, MCP configuration reference.

Windsurf

Windsurf's MCP documentation now lives on docs.devin.ai, where the product is called Devin Desktop. Remote servers are written with serverUrl. The file is ~/.config/devin/mcp_config.json (%APPDATA%\devin\mcp_config.json on Windows); if your version reads a different file, open it from the editor's MCP settings.

{
  "mcpServers": {
    "cluvia": {
      "serverUrl": "https://mcp.usecluvia.com/mcp",
      "headers": { "Authorization": "Bearer ${env:CLUVIA_TOKEN}" }
    }
  }
}

Claude Desktop and Claude.ai

Claude Desktop and Claude.ai add remote servers as custom connectors (Customize → Connectors → Add custom connector on Free, Pro and Max, or Organization settings → Connectors for Team and Enterprise owners).

  • If your organization has custom request headers for connectors (Anthropic offers this to a limited set of organizations): choose authentication No sign-in, then under Request headers add authorization with the full value Bearer cluvia_pat_YOUR_TOKEN.
  • Otherwise, custom connectors sign in with OAuth or not at all. Cluvia does not offer OAuth yet, so a custom connector cannot connect to Cluvia today. Use Claude Code, or the local bridge below.

Local bridge for Claude Desktop. Claude Desktop's claude_desktop_config.json (macOS ~/Library/Application Support/Claude/, Windows %APPDATA%\Claude\) runs local servers. The community tool mcp-remote (not made by Cluvia or Anthropic; needs Node.js) bridges a local entry to a remote server:

{
  "mcpServers": {
    "cluvia": {
      "command": "npx",
      "args": ["mcp-remote", "https://mcp.usecluvia.com/mcp", "--header", "Authorization:${AUTH_HEADER}", "--transport", "http-only"],
      "env": { "AUTH_HEADER": "Bearer cluvia_pat_YOUR_TOKEN" }
    }
  }
}

The header value goes in env because some clients (Claude Desktop on Windows among them) do not pass spaces inside args correctly. --transport http-only matches Cluvia, which has no SSE stream. References: Claude custom connectors, MCP: connect to remote servers.

Any other MCP client

Use these settings:

TransportStreamable HTTP (sometimes labelled “HTTP” or “streamable-http”). Not SSE.
URLhttps://mcp.usecluvia.com/mcp
HeaderAuthorization: Bearer cluvia_pat_YOUR_TOKEN
Auth typeStatic header / bearer token. Not OAuth.

Many clients read the common mcpServers JSON form:

{
  "mcpServers": {
    "cluvia": {
      "type": "http",
      "url": "https://mcp.usecluvia.com/mcp",
      "headers": { "Authorization": "Bearer cluvia_pat_YOUR_TOKEN" }
    }
  }
}

A client that only speaks the older SSE transport, or only stdio, needs a bridge such as mcp-remote (above).

6. Concepts #

Companies and companyKey

Your account holds one or more companies. Every tool that works on a company takes a companyKey, a string built from the company's id and creation time, for example company-7f3a9e2b:2026-09-01T09:30:00.000Z. Get it from list_companies and pass it back unchanged; treat it as opaque. Companies are read from your cloud workspace, the same data the app shows.

Agents

Each company has a team of agents, each with an id, name, role, department and status (working while one of its tasks runs, online, or idle). Tools that take an agent accept:

  1. the agent's exact id; otherwise
  2. a role or name, matched case-insensitively and exactly ("content writer" matches “Content Writer”). If several agents match, active agents are preferred, and exactly one must remain. Otherwise the tool answers Several agents match "…": … Pass an agent id.

Inactive or retired agents can still be matched, but a run given to one fails when it starts (error code agent_missing). Use ids from get_company to be exact.

Tasks and runs

assign_task creates a run: one piece of work by one agent. Runs started over MCP have origin: "mcp" and taskId: null; the app adds the task to its board when the run finishes. A run starts within seconds when there is room. When your plan's tasks at once are in use, or Cluvia is busy, it waits its turn. A run still running 15 minutes after it starts is stopped and fails with error code stale.

StatusMeaningFinished?
queuedWaiting to be picked up.No
runningExecuting. run.progress says what it is doing. In its first seconds startedAt can still be null.No
waitingPaused on an approval for a connected-tool call. It continues by itself after you decide. run.waitingFor names the approval.No
doneFinished; the result is in run.content (or run.siteFiles for a website).Yes
failedFailed; see run.error and run.errorCode.Yes
cancelledCancelled (for example from the app).Yes

Run modes. assign_task picks the mode from the title and brief, checking in this order:

  • experiment — the title says “A/B test”, “AB test” or “split test”; or it says experiment or variant together with run, start, launch or test, and a page word (page, site, website, headline, hero, button, CTA, pricing, sign-up) appears in the title or brief. The run proposes an A/B test of one page of the company's live site.
  • website — the title names a site (website, web app, webpage, landing page, home page, marketing site, micro site, “the site”), or pairs a build verb (build, create, make, design, develop, ship, launch, prototype, code, implement, redesign, rebuild) with an app, tool, tracker, calculator, to-do, game, dashboard, prototype or MVP. The run builds a website.
  • deliverable — everything else: a written result such as research, a plan, an email or a post.

Error codes on a failed run (run.errorCode): cut_off, narration_only, website_cut_off, cost_cap, task_error, nothing_to_review, proposal_live, key, company_missing, draft, provider_down, stale, workspace_unavailable, agent_missing, credits_exhausted. run.error always carries a readable explanation. Common ones when a run starts:

errorCodeMessageWhat to do
draftStart work first — this company is still in planning.Open the company in the app and start work, then assign the task again.
company_missingThis company is no longer in your cloud workspace.Call list_companies for current keys.
agent_missingThe agent for this run is no longer on the team. (or: has been retired / deactivated)Pick an active agent from get_company.
staleThe run took longer than 15 minutes and was stopped.Try again, perhaps with a narrower task.
credits_exhaustedYou are out of credits, so this run was not started — …Add credits, upgrade, or connect your own OpenRouter key.

Outputs

An output is something an agent produced: a deliverable (text) or a website. Outputs live in the company and are listed by get_company (latest 10) and list_outputs. A deliverable that used web research ends with a ranked source list:

…the deliverable text…

Sources:
1. Example source title — https://example.com/article
2. Another source — https://example.org/page

For a website, the API returns its file names, never the raw HTML.

Approvals

Actions that reach outside Cluvia wait for your decision unless you have told the agent otherwise. A pending approval lapses after 7 days. Kinds:

KindApproving it…
publish_sitePublishes the website a run built.
send_emailSends an email reply (for example to a customer who wrote in through a published site).
start_experimentStarts an A/B test of a page.
finish_experimentEnds a test: choice: "a" keeps the current page, "b" puts version B live.
connected_toolRuns one call to a connected tool exactly as shown (needs its payloadHash); a paused task then continues.

When a website run finishes, the agent's publish_site policy decides: deny publishes nothing, allow publishes without asking, and ask (the default) creates a publish_site approval.

Policies

A policy is an agent's standing rule for one kind of action, set with set_policy. It applies at once. A kind with no policy set is treated as ask, which for web and images means allowed.

KindValuesEffect
publish_site, send_email, http, domainask, allow, denyask waits for your approval; allow acts without asking; deny never acts.
web, imagesallow, denySwitches web research or image generation on or off for the agent. ask is accepted but means the same as allow.

Schedules

A schedule gives an agent the same task each time a cron expression comes round in a time zone. Each slot queues a run with origin: "schedule" and spends credits. A slot is skipped (with the reason recorded) when the plan has no autonomous schedules, the daily run limit or the plan's scheduled-runs-per-day limit is reached, credits run out, the company's spend limit is reached, or the company was switched off or deleted. Schedules need a paid plan. An account can keep up to 100 schedules.

Cron syntax: exactly five fields — minute (0–59), hour (0–23), day of month (1–31), month (1–12), day of week (0–7, where 0 and 7 are Sunday). Supported: *, lists (1,15), ranges (1-5) and steps (*/15, 0-30/10, 5/10). Not supported: names (MON, JAN), macros (@daily) or seconds. When both day fields are restricted, a day matches if either matches. Examples: 0 9 * * 1 (Mondays at 09:00), 30 8 * * 1-5 (weekdays at 08:30), 0 */6 * * * (every six hours).

Team changes and hiring

The team lives in the company document that the app edits, so hire_agent does not change it directly. It queues a hire, which is applied within about a minute: by the Cluvia app if it has the company open, or by Cluvia's servers when nobody does. The answer's note says which applies. Checks that can be made when queuing (role in the library, role limit, team size, company not in planning) are made then, so most refusals come back at once. If the token is revoked or loses Administer before the hire applies, the hire is withdrawn. Up to 100 changes can wait per account.

Plans, credits and limits

Calls that start work run through the same checks as the app: your plan's daily run limit, how many tasks may run at once, and your credits. The defaults per plan:

PlanPriceRuns a dayTasks at onceScheduled runs a daySchedulesCreditsOwn OpenRouter keyCompanies
Free$02010No1,000 to start (once)No1
Starter$24/month50220Yes6,000 a monthYes3
Pro$59/month150575Yes15,000 a monthYes10
Agency$179/month30010200Yes45,000 a monthYes30
  • These are the standard plan limits. Your current limits are always shown in the app under Settings → Plan.
  • Credits: 1 credit = $0.001 of the model cost of a run on Cluvia's key, metered per run and rounded up. While a run is in flight, the credit check sets aside an estimate (500 credits for a written task, 1,250 for a website build); the charge is the metered cost. Paid plans can top up: 200 credits per $1, 220 from $25, 240 from $50, from $1 to $500.
  • Your own OpenRouter key (paid plans): the part of a run billed to your key is never metered as credits. The daily run limit and tasks-at-once limit still apply.
  • Days for the daily run limit are UTC days.
  • The company daily spend limit and the autopilot rules apply to automatic starts only. A task you assign over MCP is not automatic, so they do not apply to it; scheduled runs are subject to the spend limit.
  • Over MCP, a plan refusal comes back as an isError result carrying the message (see Plan and credit refusals).

Not available over MCP yet

Cancelling a run, editing a schedule (delete and re-create it instead), and reading an agent's current policies are done in the app. Tokens, billing, settings and your own OpenRouter key are managed only in the signed-in app.

Ids and timestamps

Ids the server creates have the form <prefix>_<32 lowercase hex>: runs run_…, schedules sch_…, approvals apr_…, queued changes cmd_…. Company, agent, task and output ids come from the app; treat every id as an opaque string. Timestamps are ISO 8601 strings in UTC, or null. All ids in the examples on this page are made up.

7. Tools reference #

Each tool lists the permission it needs, its parameters, what it returns (the structuredContent object; content[0].text carries the same JSON as text), its errors (returned as isError results with the exact sentence shown) and its side effects. Every tool can also return the shared errors in Tool errors, such as the rate limit. Example ids are made up.

ToolPermissionWhat it does
list_companiesreadYour companies, each with its companyKey.
get_companyreadTeam, open tasks, recent outputs, pending approvals.
list_outputsreadA company's outputs, newest first.
get_outputreadOne output in full.
assign_tasktasksGive an agent a task (starts a run).
run_statusreadA run's status, log and result.
wait_for_runreadWait for a run to finish.
list_approvalsreadActions waiting for your decision.
approveapproveApprove and carry out an action.
denyapproveDeny an action.
list_schedulesreadA company's recurring runs.
create_scheduletasksSchedule a recurring run.
delete_scheduletasksDelete a schedule.
hire_agentadminQueue a hire from the role library.
set_policyadminSet an agent's standing policy.

list_companies read-only #

Permission: read · Title: List companies

Lists the companies in this Cluvia account: name, industry, team size and open tasks. Each has a companyKey the other tools take. A company-limited token sees only its company.

Parameters

None. Send "arguments": {}.

Returns

FieldTypeDescription
companies[].companyKeystringThe key other tools take.
companies[].namestringCompany name.
companies[].industrystring | nullIndustry.
companies[].agentsnumberNumber of agents, inactive ones included.
companies[].tasksnumberTasks not yet complete.

Errors

  • Could not reach your cloud workspace — run it again in a moment. / Could not load your company from the cloud — run it again in a moment. — retry shortly.

Side effects

None.

Example

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_companies","arguments":{}}}
{
  "companies": [
    {
      "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
      "name": "Sunrise Bakery",
      "industry": "Food and drink",
      "agents": 5,
      "tasks": 3
    }
  ]
}

get_company read-only #

Permission: read · Title: Get a company

A company's team (each agent's id, name, role, department and status), its open tasks, its latest outputs with a short excerpt, and how many approvals are waiting for a decision.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company, as a companyKey from list_companies. Up to 200 characters.

Returns

FieldTypeDescription
companyKey, namestringThe company.
industry, missionstring | nullAs set in the app.
agents[]array{ id, name, role, department (or null), status, active }. status is working while one of the agent's tasks is running, online, or idle. active is false for a deactivated agent.
openTasks[]arrayUp to 50: { id, title, status (or null), assigneeId, assignee (role, or null), brief (or null) }.
recentOutputs[]arrayUp to 10, newest first: { id, title, agentId, agent (role, or null), taskId (or null), generatedAt (or null), excerpt, website }. excerpt is the text with whitespace collapsed, at most 300 characters plus “…”. website is true for a website output.
pendingApprovalsnumberApprovals waiting for a decision.

Errors

  • companyKey is required.
  • No company has the companyKey "…" — call list_companies for the keys.
  • The workspace errors listed under list_companies.

Side effects

Pending approvals of this company older than 7 days are marked expired before they are counted.

Example

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_company","arguments":{"companyKey":"company-7f3a9e2b:2026-09-01T09:30:00.000Z"}}}
{
  "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
  "name": "Sunrise Bakery",
  "industry": "Food and drink",
  "mission": "Fresh bread for the neighbourhood, ordered online.",
  "agents": [
    { "id": "agent-1a2b3c", "name": "Maya", "role": "CEO", "department": "Executive", "status": "idle", "active": true },
    { "id": "agent-4d5e6f", "name": "Theo", "role": "Content Writer", "department": "Marketing", "status": "working", "active": true }
  ],
  "openTasks": [
    { "id": "task-77ab", "title": "Write the spring menu page copy", "status": "running", "assigneeId": "agent-4d5e6f", "assignee": "Content Writer", "brief": null }
  ],
  "recentOutputs": [
    {
      "id": "output-91c2",
      "title": "Competitor price check",
      "agentId": "agent-1a2b3c",
      "agent": "CEO",
      "taskId": "task-5c3d",
      "generatedAt": "2026-10-06T14:02:11.000Z",
      "excerpt": "Three bakeries within two miles sell sourdough between £3.20 and £4.50…",
      "website": false
    }
  ],
  "pendingApprovals": 1
}

list_outputs read-only #

Permission: read · Title: List outputs

A company's outputs (the deliverables and websites its agents produced), newest first, each with a short excerpt.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.
limitintegerNoHow many to return: 1 to 50. Default 20.

Returns

FieldTypeDescription
outputs[]arrayOutput summaries, as in get_company's recentOutputs.
totalnumberHow many outputs the company has in all.

Errors

  • companyKey is required.
  • limit must be a whole number from 1 to 50.
  • No company has the companyKey "…" — call list_companies for the keys.

Side effects

None.

Example

{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_outputs","arguments":{"companyKey":"company-7f3a9e2b:2026-09-01T09:30:00.000Z","limit":2}}}
{
  "outputs": [
    {
      "id": "output-a410",
      "title": "Spring menu landing page",
      "agentId": "agent-8e9f0a",
      "agent": "Website Builder",
      "taskId": "task-61fe",
      "generatedAt": "2026-10-07T08:15:40.000Z",
      "excerpt": "Spring menu landing page with opening hours and an order form.",
      "website": true
    },
    {
      "id": "output-91c2",
      "title": "Competitor price check",
      "agentId": "agent-1a2b3c",
      "agent": "CEO",
      "taskId": "task-5c3d",
      "generatedAt": "2026-10-06T14:02:11.000Z",
      "excerpt": "Three bakeries within two miles sell sourdough between £3.20 and £4.50…",
      "website": false
    }
  ],
  "total": 14
}

get_output read-only #

Permission: read · Title: Get an output

One output in full: its text, and for a website, its file names.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.
outputIdstringYesAn output id from list_outputs or get_company. Up to 200 characters.

Returns

FieldTypeDescription
id, title, agentIdstringThe output.
agent, taskId, generatedAtstring | nullThe agent's role, the task, when it was produced.
contentstringThe full text, with its Sources list if any (for a website, the text saved with it); "" when there is none.
websiteobjectWebsite outputs only: { entry (or null), files: [file names] }.

Errors

  • companyKey and outputId are required.
  • No output "…" in this company — call list_outputs for the ids.
  • No company has the companyKey "…" — call list_companies for the keys.

Side effects

None.

Example

{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_output","arguments":{"companyKey":"company-7f3a9e2b:2026-09-01T09:30:00.000Z","outputId":"output-a410"}}}
{
  "id": "output-a410",
  "title": "Spring menu landing page",
  "agentId": "agent-8e9f0a",
  "agent": "Website Builder",
  "taskId": "task-61fe",
  "generatedAt": "2026-10-07T08:15:40.000Z",
  "content": "Spring menu landing page with opening hours and an order form.",
  "website": { "entry": "index.html", "files": ["index.html", "menu.html", "styles.css"] }
}

assign_task #

Permission: tasks · Title: Assign a task · Annotations: readOnlyHint false, destructiveHint false, idempotentHint false, openWorldHint true

Gives an agent a task. It starts on Cluvia's servers within seconds when there is room, with the company's context, and appears on the app's task board when it finishes. A title asking for a website builds one (see run modes). Returns a runId for wait_for_run and run_status.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.
agentstringYesThe agent: its id, or its role or name when only one agent has it (see Agents). Up to 120 characters.
titlestringYesThe task, e.g. “Draft a launch email for the bakery offer”. Up to 500 characters; a longer title is refused.
briefstringNoDirection: audience, constraints, what good looks like. Up to 2,000 characters; longer text is cut to 2,000.

Returns

FieldTypeDescription
runIdstringThe new run (run_…).
statusstringNormally queued.
modestringdeliverable, website or experiment.
agentobject{ id, name, role } of the agent that got the task.
notestring“The run starts within seconds when there is room. Call wait_for_run with this runId for the result.”

Errors

  • companyKey, agent and title are required (title up to 500 characters).
  • brief must be a string.
  • No company has the companyKey "…" — call list_companies for the keys.
  • Several agents match "…": … Pass an agent id. / No agent matches "…". The team: …. (without Read, the team is not listed)
  • You've reached today's limit of N agent runs — more tomorrow.
  • Your plan runs a limited number of tasks at once — wait for one to finish or upgrade.
  • You are out of credits — add credits, upgrade, or connect your own OpenRouter key to keep working. (on Free: You are out of free credits — upgrade to keep working.)
  • This email has already had its free trial credits. Pick a plan to keep going. / Free trial credits need a permanent email address. …

Side effects

  • Creates a queued run every time it is called; it is not idempotent. Retrying after a timeout can create a second run, so check with get_company or run_status first.
  • Spends credits when the run executes (not when it is queued), and counts toward the daily run limit and the tasks-at-once limit.
  • A new account's first start receives Free's one-time 1,000 trial credits.
  • A company still in planning is not refused here; its run fails when it starts, with error code draft.
  • A website run that finishes may create a publish_site approval, depending on the agent's policy.

Example

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "assign_task",
    "arguments": {
      "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
      "agent": "Content Writer",
      "title": "Draft a launch email for the spring menu",
      "brief": "Warm, local tone. Under 150 words. Mention the Saturday tasting."
    }
  }
}
{
  "runId": "run_3f9c2a7b1e4d4c0a9b8e7f6a5d4c3b2a",
  "status": "queued",
  "mode": "deliverable",
  "agent": { "id": "agent-4d5e6f", "name": "Theo", "role": "Content Writer" },
  "note": "The run starts within seconds when there is room. Call wait_for_run with this runId for the result."
}

run_status read-only #

Permission: read · Title: Run status

A run's status, what it is doing while it runs, its latest log lines, and its result once done: the text of a deliverable, or a website's file names. Returns at once; use wait_for_run to wait.

Parameters

NameTypeRequiredDescription
runIdstringYesA runId from assign_task. Up to 100 characters.

Returns

{ run, events }. The run object:

FieldTypeDescription
id, companyKey, agentId, agentRole, titlestringWhat the run is.
taskId, briefstring | nulltaskId is null for runs started over MCP.
modestringdeliverable, website, experiment (runs started in the app can have other modes).
statusstringqueued, running, waiting, done, failed or cancelled (see statuses).
originstringWhat queued it: app, schedule, mcp or site.
scheduleId, scheduledForstring | nullSet when a schedule queued it.
createdAt, updatedAt, startedAt, finishedAtstring | nullISO timestamps. startedAt is null until the run actually starts (it can be null while status is running).
contentstring | nullA deliverable's text, with its Sources list if any.
siteFilesstring[]Website runs only: the file names built. The HTML itself is not returned.
error, errorCodestring | nullWhy a failed run failed (see error codes).
costUsdnumber | nullWhat the run cost in US dollars.
modelstring | nullThe model used.
approvalIdstring | nullThe approval this run produced, such as a publish_site.
progressobject | nullOnly while running: { chars, outputTokens?, currentFile?, filesDone?, phase, note? }, where phase is queued, thinking, writing, searching, reading, drawing or finishing.
waitingForobject | nullOnly while waiting: { approvalId, summary, since }.
segmentsnumberHow many times the run continued after an approval.
stopstring | nullOn a failed paused run: unknown (an approved action may already have happened; running again could repeat it) or lapsed (no decision came; nothing was sent).
actedboolean | nullWhether an approved action happened in this run; null when not known.
startIdstring | nullThe app's start id; null for MCP runs.

events is the last 20 log lines, oldest first: [{ at, kind, text }]. Kinds include queued, deferred, requeued, started, model, tool, sources, image, warning, approval, published, denied, email, paused, resumed, finished, failed and cancelled; more may be added, so handle unknown kinds. Each text is at most 500 characters, and a run's log keeps at most 200 lines.

Errors

  • runId is required.
  • No run "…" in this account. (also for a run in a company the token cannot reach)

Side effects

None.

Example

{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"run_status","arguments":{"runId":"run_3f9c2a7b1e4d4c0a9b8e7f6a5d4c3b2a"}}}
{
  "run": {
    "id": "run_3f9c2a7b1e4d4c0a9b8e7f6a5d4c3b2a",
    "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
    "taskId": null,
    "agentId": "agent-4d5e6f",
    "agentRole": "Content Writer",
    "title": "Draft a launch email for the spring menu",
    "brief": "Warm, local tone. Under 150 words. Mention the Saturday tasting.",
    "mode": "deliverable",
    "status": "running",
    "scheduleId": null,
    "scheduledFor": null,
    "origin": "mcp",
    "startedAt": "2026-10-07T10:01:03.000Z",
    "finishedAt": null,
    "createdAt": "2026-10-07T10:00:12.000Z",
    "updatedAt": "2026-10-07T10:01:20.000Z",
    "content": null,
    "error": null,
    "costUsd": null,
    "model": null,
    "approvalId": null,
    "progress": { "chars": 412, "phase": "writing" },
    "waitingFor": null,
    "segments": 0,
    "stop": null,
    "acted": null,
    "errorCode": null,
    "startId": null
  },
  "events": [
    { "at": "2026-10-07T10:00:12.000Z", "kind": "queued", "text": "Queued from an outside tool (MCP)." },
    { "at": "2026-10-07T10:01:03.000Z", "kind": "started", "text": "Started (deliverable)." }
  ]
}

wait_for_run read-only #

Permission: read · Title: Wait for a run

Waits for a run to finish, up to timeoutSec seconds, then returns what run_status returns. finished is false when it was still going. The server checks the run once a second and answers as soon as it is done, failed or cancelled — or waiting, since a run paused on your approval will not move until you decide.

Parameters

NameTypeRequiredDescription
runIdstringYesA runId from assign_task.
timeoutSecintegerNoHow long to wait, in seconds: 1 to 120. Default 50.

The default of 50 seconds stays under the 60-second request timeout many MCP clients use. If your client allows longer requests, pass up to 120; otherwise call again while finished is false.

Returns

FieldTypeDescription
finishedbooleanTrue when the run is done, failed or cancelled. False when the wait timed out or the run is waiting on an approval.
waitedSecnumberHow long the server waited.
run, eventsobject, arrayAs in run_status.

Errors

  • runId is required.
  • timeoutSec must be a whole number from 1 to 120.
  • No run "…" in this account.

Side effects

None. One wait counts as one call toward the rate limit, however long it waits.

Example

{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"wait_for_run","arguments":{"runId":"run_3f9c2a7b1e4d4c0a9b8e7f6a5d4c3b2a","timeoutSec":50}}}
{
  "finished": true,
  "waitedSec": 21,
  "run": {
    "id": "run_3f9c2a7b1e4d4c0a9b8e7f6a5d4c3b2a",
    "status": "done",
    "mode": "deliverable",
    "origin": "mcp",
    "finishedAt": "2026-10-07T10:01:44.000Z",
    "content": "Subject: Spring has landed at Sunrise Bakery\n\nHi neighbour,\n\nOur spring menu is here…",
    "error": null,
    "errorCode": null,
    "costUsd": 0.0042,
    "...": "the other run fields, as in run_status"
  },
  "events": [
    { "at": "2026-10-07T10:00:12.000Z", "kind": "queued", "text": "Queued from an outside tool (MCP)." },
    { "at": "2026-10-07T10:01:03.000Z", "kind": "started", "text": "Started (deliverable)." },
    { "at": "2026-10-07T10:01:44.000Z", "kind": "finished", "text": "Finished." }
  ]
}

(The run object is shortened here; the real answer carries every field listed under run_status.)

list_approvals read-only #

Permission: read · Title: List approvals

The actions agents want to take that are waiting for your decision, newest first. All pending kinds are returned — publish_site, send_email, start_experiment, finish_experiment and connected_tool — even though the tool's short description mentions only publishing a website.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.

Returns

{ approvals: [ … ], now }, at most 100 approvals. Each approval:

FieldTypeDescription
idstringThe approval (apr_…).
companyKey, runId, agentId, agentRolestring | nullWhere it came from.
kindstringSee Approvals.
summarystringWhat the agent wants to do, in words.
payloadobjectThe details. publish_site: { runId, projectSlug, company }. send_email: { runId, siteId, messageId, to, subject, body }. connected_tool: { connectionId, connectionName, toolName, args, schemaDigest, connectionVersion, agentId, companyKey, runId, reason, payloadHash, continues? }.
statusstringpending here.
createdAt, expiresAtstringexpiresAt is 7 days after createdAt.
decidedAt, decidedNote, decidedBy, resultnull while pendingFilled in once decided. result can carry the error of a failed earlier approval attempt.
updatedAtstringLast change.

Errors

  • companyKey is required.
  • No company has the companyKey "…" — call list_companies for the keys. (company-limited token, other company). With an account-wide token, a key that matches no company returns an empty list.

Side effects

Pending approvals older than 7 days are marked expired first.

Example

{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"list_approvals","arguments":{"companyKey":"company-7f3a9e2b:2026-09-01T09:30:00.000Z"}}}
{
  "approvals": [
    {
      "id": "apr_8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f",
      "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
      "runId": "run_0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e",
      "agentId": "agent-8e9f0a",
      "agentRole": "Website Builder",
      "kind": "publish_site",
      "summary": "Publish the Sunrise Bakery site",
      "payload": { "runId": "run_0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e", "projectSlug": "sunrise-bakery", "company": "Sunrise Bakery" },
      "status": "pending",
      "createdAt": "2026-10-07T08:16:02.000Z",
      "expiresAt": "2026-10-14T08:16:02.000Z",
      "decidedAt": null,
      "decidedNote": null,
      "decidedBy": null,
      "result": null,
      "updatedAt": "2026-10-07T08:16:02.000Z"
    }
  ],
  "now": "2026-10-07T10:05:00.000Z"
}

approve #

Permission: approve · Title: Approve · Annotations: readOnlyHint false, destructiveHint false, idempotentHint false, openWorldHint true

Approves a pending approval and carries out its action: publishing the site, sending the email, starting or finishing an A/B test, or running a connected-tool call once. If the action fails, the approval stays pending with the error.

Parameters

NameTypeRequiredDescription
approvalIdstringYesAn approval id from list_approvals. Up to 100 characters.
notestringNoA note recorded with the decision. Up to 1,000 characters; longer text is cut.
choice"a" | "b"For finish_experimentThe version to keep: "a" keeps the current page, "b" puts version B live.
payloadHashstringFor connected_toolThe approval's payload.payloadHash from list_approvals (64 lowercase hex characters), so only the exact call you saw runs.

Returns

{ ok: true, approval }, where approval has the fields shown in list_approvals with status: "approved", decidedBy naming the token, and result:

Kindresult
publish_site{ url, siteId } — the live address.
send_email{ sent, reason? }. If email is not set up, the approval stays approved with sent: false and the reason.
start_experiment, finish_experiment{ experimentId, status, minPerArm?, endsBy?, winner?, page?, url? }
connected_tool{ state: "succeeded" | "failed" | "unknown", text?, isError?, error? }. A task paused on it then continues.

Errors

  • approvalId is required, and note must be a string.
  • Approval not found.
  • This approval expired. / This approval was already approved. / This approval was already denied.
  • choice must be "a" or "b". / Choose the version to keep: approve with {"choice": "a"} or {"choice": "b"}.
  • payloadHash must be the 64-character hash shown with the approval. / Approving a connected tool call needs the payloadHash listed with it, so exactly the call you saw is the one that runs. / This action is not the one you were shown. Reload approvals and look at it again.
  • Publishing: The run behind this approval no longer exists., The run behind this approval has no finished site to publish., This site belongs to another account., Another of your companies already has a live site at this address — rename this company (or that site) and publish again., The run's output isn't a website that can be published., Publishing the site failed — please try again.
  • Experiments: The test behind this approval no longer exists., This test is no longer waiting to start…

Side effects

  • Carries out a real action outside Cluvia: a site goes live, an email is sent, a test starts or ends, a connected tool is called.
  • If the action fails, the approval returns to pending with result: { error }, so you can try again; it is withdrawn instead when the failure is final, and left as it is when the call may already have been sent.
  • Approving a start_experiment uses the experiment's stored minimum sample size; it cannot be changed over MCP.

Example

{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"approve","arguments":{"approvalId":"apr_8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f","note":"Looks good."}}}
{
  "ok": true,
  "approval": {
    "id": "apr_8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f",
    "kind": "publish_site",
    "status": "approved",
    "decidedAt": "2026-10-07T10:06:30.000Z",
    "decidedNote": "Looks good.",
    "decidedBy": "token:pat_2d4f6a8c0e1b3d5f7a9c1e3b5d7f9a1c",
    "result": { "url": "https://(the site's live address)/", "siteId": "(site id)" },
    "...": "the other approval fields, as in list_approvals"
  }
}

deny #

Permission: approve · Title: Deny · Annotations: readOnlyHint false, destructiveHint false, idempotentHint false, openWorldHint false

Denies a pending approval: its action never happens.

Parameters

NameTypeRequiredDescription
approvalIdstringYesAn approval id from list_approvals.
notestringNoWhy, recorded with the decision. Up to 1,000 characters; longer text is cut.

Returns

{ ok: true, approval } with status: "denied".

Errors

  • approvalId is required, and note must be a string.
  • Approval not found.
  • This approval expired. / This approval was already approved. / This approval was already denied.

Side effects

  • The run's log records Denied: <your note, or the approval's summary>.
  • For an experiment approval, the experiment is denied.
  • A task paused on a connected_tool approval continues without that call.

Example

{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"deny","arguments":{"approvalId":"apr_8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f","note":"Wait for the new photos."}}}
{
  "ok": true,
  "approval": {
    "id": "apr_8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f",
    "kind": "publish_site",
    "status": "denied",
    "decidedNote": "Wait for the new photos.",
    "...": "the other approval fields, as in list_approvals"
  }
}

list_schedules read-only #

Permission: read · Title: List schedules

A company's recurring runs: agent, task, cron expression, time zone, and when each runs next. Ordered by creation.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.

Returns

{ schedules: [ … ] }. Each schedule:

FieldTypeDescription
idstringThe schedule (sch_…).
companyKey, agentId, agentRole, titlestringWho does what.
briefstring | nullDirection for each run.
cron, timezonestringWhen it runs.
nextRunAt, lastRunAtstring | nullNext and last slot. nextRunAt is null when disabled.
enabledbooleanWhether slots fire.
createdAt, updatedAtstringTimestamps.

Errors

  • companyKey is required.
  • No company has the companyKey "…" — call list_companies for the keys. (company-limited token, other company). With an account-wide token, a key that matches no company returns an empty list.

Side effects

None.

Example

{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"list_schedules","arguments":{"companyKey":"company-7f3a9e2b:2026-09-01T09:30:00.000Z"}}}
{
  "schedules": [
    {
      "id": "sch_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
      "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
      "agentId": "agent-4d5e6f",
      "agentRole": "Content Writer",
      "title": "Write this week's newsletter",
      "brief": "Lead with the weekend specials.",
      "cron": "0 9 * * 1",
      "timezone": "Europe/London",
      "nextRunAt": "2026-10-12T08:00:00.000Z",
      "lastRunAt": "2026-10-05T08:00:00.000Z",
      "enabled": true,
      "createdAt": "2026-09-20T17:44:10.000Z",
      "updatedAt": "2026-10-05T08:00:02.000Z"
    }
  ]
}

create_schedule #

Permission: tasks · Title: Create a schedule · Annotations: readOnlyHint false, destructiveHint false, idempotentHint false, openWorldHint false

Schedules a recurring run: the agent gets this task each time the cron expression comes round in the time zone. Needs a paid plan.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.
agentstringYesThe agent: id, or a role or name only one agent has.
titlestringYesThe task each run does. Up to 500 characters; longer is refused.
briefstringNoDirection for each run. Up to 2,000 characters; longer text is cut.
cronstringYesA 5-field cron expression (minute hour day-of-month month day-of-week), e.g. "0 9 * * 1" for Mondays at 09:00. Up to 100 characters. See cron syntax.
timezonestringNoAn IANA time zone, e.g. "Europe/Paris". Default "UTC". Up to 64 characters.
enabledbooleanNoWhether slots fire. Default true.

Returns

{ schedule }, with the fields shown in list_schedules. nextRunAt is the next slot after now (null if disabled).

Errors

  • companyKey, agent, title and cron are required. (companyKey or agent missing)
  • companyKey, agentId, title and cron are required. (title or cron missing or too long — the message says agentId even over MCP)
  • brief, agentRole and timezone must be strings. / enabled must be true or false.
  • Cron: A schedule needs five fields: minute hour day-of-month month day-of-week., Invalid <field> "<part>"., The <field> "<part>" is outside <min>-<max>., The <field> step in "<part>" must be at least 1., That schedule never comes round — check the day and month fields.
  • Unknown time zone "…".
  • An account can keep at most 100 schedules.
  • Autonomous schedules need a paid plan — upgrade to put work on a schedule. (Free)
  • Company and agent errors, as in assign_task.

Side effects

  • Saves the schedule at once. Every slot that fires queues a run (origin: "schedule") that spends credits and counts toward the daily limits, including the plan's scheduled-runs-per-day limit.
  • Not idempotent: calling twice creates two schedules.

Example

{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": {
    "name": "create_schedule",
    "arguments": {
      "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
      "agent": "agent-4d5e6f",
      "title": "Write this week's newsletter",
      "brief": "Lead with the weekend specials.",
      "cron": "0 9 * * 1",
      "timezone": "Europe/London"
    }
  }
}
{
  "schedule": {
    "id": "sch_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
    "agentId": "agent-4d5e6f",
    "agentRole": "Content Writer",
    "title": "Write this week's newsletter",
    "brief": "Lead with the weekend specials.",
    "cron": "0 9 * * 1",
    "timezone": "Europe/London",
    "nextRunAt": "2026-10-12T08:00:00.000Z",
    "lastRunAt": null,
    "enabled": true,
    "createdAt": "2026-10-07T10:10:00.000Z",
    "updatedAt": "2026-10-07T10:10:00.000Z"
  }
}

delete_schedule #

Permission: tasks · Title: Delete a schedule · Annotations: destructiveHint true, idempotentHint true, openWorldHint false

Deletes a schedule. Runs it already queued are not affected.

Parameters

NameTypeRequiredDescription
scheduleIdstringYesA schedule id from list_schedules. Up to 100 characters.

Returns

{ ok: true }

Errors

  • scheduleId is required.
  • Schedule not found.

Side effects

The schedule is removed permanently; no further slots fire.

Example

{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"delete_schedule","arguments":{"scheduleId":"sch_0a1b2c3d4e5f60718293a4b5c6d7e8f9"}}}
{ "ok": true }

hire_agent #

Permission: admin · Title: Hire an agent · Annotations: readOnlyHint false, destructiveHint false, idempotentHint false, openWorldHint false

Asks Cluvia to add an agent to the team. The team lives in the company, so the hire is queued and applies within about a minute: the Cluvia app applies it when it has the company open, and Cluvia applies it itself when nobody does (the answer's note says which). Only roles from the role library are hired, and a team past 8 active agents needs a justification.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.
rolestringYesA role from the role library (below), e.g. "Growth Marketer". Case-insensitive. Up to 120 characters; longer is refused.
namestringNoA name; the app picks one otherwise. Up to 80 characters (longer is cut).
departmentstringNoA department; the app infers it from the role otherwise. Up to 80 characters (cut).
instructionsstringNoStanding instructions for the agent. Up to 2,000 characters (cut).
specializationstringNoA focus, shown as “Role · Specialization”, e.g. "Website". Up to 60 characters (cut).
justificationstringNoWhy this role, and why now. Required once the team would pass 8 active agents. Up to 300 characters (cut).

Role library and how many of each role a company can have:

DepartmentRoles (maximum per company)
ExecutiveStrategy Lead (1)
OperationsOps Manager (2)
ProductProduct Manager (3), Product Builder (2)
EngineeringEngineering Lead (1), Solutions Architect (2), Automation Developer (4), Website Builder (2)
ResearchResearch Analyst (4), Data Analyst (3)
MarketingContent Lead (1), Marketing Manager (2), Content Writer (4), Editor (2), Copywriter (3), Growth Marketer (3), SEO Specialist (3), Social Media Manager (2), Local SEO Specialist (2)
SalesSales Lead (1), Account Manager (4)
SupportCustomer Success Manager (2), Review/Reputation Manager (1)

CEO is also a library role, at most one per company; the “not in the role library” message leaves it out. A few common aliases also resolve, such as “writer” (Content Writer), “designer” (Website Builder) and “market researcher” (Research Analyst). Older roles that are no longer hired (for example “Software Engineer”) are refused with the library role to hire instead.

Returns

FieldTypeDescription
commandIdstringThe queued change (cmd_…).
statusstringpending.
notestringapplies within about a minute, or applies when the Cluvia app next has this company open.

Errors

  • companyKey and role are required (role up to 120 characters).
  • name, department, instructions, specialization and justification must be strings.
  • "…" is not in the role library. Hire one of: ….
  • <Role> is a legacy role: existing teams keep it, but new hires come from the role library. Hire <a Replacement> instead.
  • This company is still being planned: its team comes from the plan. Hire once work has started in the Cluvia app.
  • Role limit reached: this company can only have <n> <Role>.
  • A <Role> hire is already waiting for the Cluvia app (cmd_…); it applies when the app next syncs.
  • This would make N active agents. A team above 8 needs a justification: why this role, and why now. (hires already waiting count toward N)
  • 100 changes are already waiting for the Cluvia app — open it so it can apply them first.
  • No company has the companyKey "…" — call list_companies for the keys.

Side effects

  • Queues a change; the team changes when it is applied (usually within about a minute). The app checks the role and team size again when it applies the hire.
  • If the token is revoked or loses the admin permission first, the hire is withdrawn and never applied.
  • Not idempotent, but a second hire of the same role while one is waiting is refused.

Example

{
  "jsonrpc": "2.0",
  "id": 14,
  "method": "tools/call",
  "params": {
    "name": "hire_agent",
    "arguments": {
      "companyKey": "company-7f3a9e2b:2026-09-01T09:30:00.000Z",
      "role": "Growth Marketer",
      "specialization": "Local ads",
      "instructions": "Focus on customers within three miles of the shop."
    }
  }
}
{
  "commandId": "cmd_5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
  "status": "pending",
  "note": "applies within about a minute"
}

set_policy #

Permission: admin · Title: Set an agent's policy · Annotations: readOnlyHint false, destructiveHint false, idempotentHint true, openWorldHint false

Sets an agent's standing policy for a kind of action. publish_site, send_email, http and domain: ask (wait for approval), allow or deny. web and images: allow or deny. It applies at once.

Parameters

NameTypeRequiredDescription
companyKeystringYesThe company.
agentstringYesThe agent: id, or a role or name only one agent has.
kindstring (enum)YesOne of publish_site, send_email, http, domain, web, images.
policystring (enum)YesOne of ask, allow, deny. For web and images, ask means the same as allow; only deny switches the capability off.

Returns

{ status: "applied", agentId, agentRole, kind, policy }

Errors

  • companyKey, agent, kind and policy are required.
  • kind must be one of: publish_site, send_email, http, domain, web, images.
  • policy must be one of: ask, allow, deny.
  • An account can keep at most 2000 agent policies.
  • Company and agent errors, as in assign_task.

Side effects

Writes the policy immediately; nothing is queued. Setting the same value again changes nothing (idempotent). allow on publish_site or send_email lets the agent act outside Cluvia without asking you.

Example

{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"set_policy","arguments":{"companyKey":"company-7f3a9e2b:2026-09-01T09:30:00.000Z","agent":"Website Builder","kind":"publish_site","policy":"ask"}}}
{
  "status": "applied",
  "agentId": "agent-8e9f0a",
  "agentRole": "Website Builder",
  "kind": "publish_site",
  "policy": "ask"
}

8. Errors #

There are three layers. Check them in this order: the HTTP status, then a JSON-RPC error, then result.isError.

HTTP and JSON-RPC errors

These come back as a JSON-RPC error object: {"jsonrpc":"2.0","id":…,"error":{"code":…,"message":"…"}} (no id when the request could not be read that far).

HTTPCodeMessageWhat to do
202—(no body)Normal answer to a notification or a client response.
400-32700Parse error: send one JSON-RPC message as JSON.Send valid JSON.
400-32600Batches are not supported: send one JSON-RPC message per request.Send messages one per request.
400-32600Invalid request: expected a JSON-RPC 2.0 message. / Invalid request: method is required. / Invalid request: id must be a string or a number.Fix the envelope: "jsonrpc": "2.0", a method, and a string or number id.
400-32602params must be an object.Send params as an object.
400-32022Unsupported protocol versionUse a version from error.data.supported.
401-32600See Authentication.Fix or replace the token.
403-32600Requests from this origin are not allowed.Do not call from a web page on another site; call from a client or server without an Origin header.
404 or 200-32601Method not found: <method>Use a supported method. (404 for 2026-07-28 requests, 200 otherwise.)
405-32600This MCP endpoint takes POST requests only.Use POST.
413-32600Request body is too large.Keep the body under 64 KB.
200-32602Unknown tool: <name> / tools/call needs the tool name. / arguments must be an object.Call tools/list for the names; send arguments as an object.
500-32603Something went wrong on the server — please try again.Retry; contact support if it persists.
501 / 503-32603 / -32600A setup or availability message.Retry later; contact support if it persists.

Tool errors (isError)

When a tool refuses or fails, the answer is HTTP 200 with a normal result: isError: true and the reason in content[0].text. The tool's own errors are listed in its section. Errors any tool can return:

MessageWhat to do
This access token doesn't have the "<permission>" permission this needs. Create a token that has it on the Connections page in Cluvia.Create a token with that permission.
Rate limit reached — try again in a minute.Wait until the next minute (see Rate limits).
The rate limiter is unavailable — try again shortly.Retry shortly.
No company has the companyKey "…" — call list_companies for the keys.Use a key from list_companies. A company-limited token gets this for every other company.
Could not reach your cloud workspace — run it again in a moment.Retry shortly.
Something went wrong on the server — please try again.Retry; contact support if it persists.

Plan and credit refusals

Tools that start work (assign_task, create_schedule) are refused with an isError result when a plan limit applies. The server never answers HTTP 429 or 402 on /mcp; only the message comes through.

MessageCauseWhat to do
You've reached today's limit of N agent runs — more tomorrow.Daily run limit (UTC day).Wait until 00:00 UTC, or upgrade.
Your plan runs a limited number of tasks at once — wait for one to finish or upgrade.Tasks-at-once limit.Wait for a run to finish (wait_for_run), or upgrade.
You are out of credits — add credits, upgrade, or connect your own OpenRouter key to keep working.No credits left (paid plan).Top up, upgrade, or add your own OpenRouter key in the app.
You are out of free credits — upgrade to keep working.No credits left (Free).Pick a paid plan.
This email has already had its free trial credits. Pick a plan to keep going.Trial already used by this person.Pick a paid plan.
Free trial credits need a permanent email address. …Disposable email address.Pick a plan, or sign up with your usual email.
Autonomous schedules need a paid plan — upgrade to put work on a schedule.create_schedule on Free.Upgrade.

9. Rate limits and usage #

LimitValue
Tool calls20 tools/call requests per minute per account, across all of the account's tokens, counted in fixed one-minute windows (UTC). initialize, tools/list, ping and server/discover are not counted. A call refused for a missing permission is not counted. This allowance is separate from the app's own.
Runs per dayYour plan's daily run limit (Free 20, Starter 50, Pro 150, Agency 300), per UTC day, shared with runs started in the app.
Tasks at onceYour plan's limit (Free 1, Starter 2, Pro 5, Agency 10). A run waiting on an approval does not hold a slot.
Scheduled runs per dayFree 0, Starter 20, Pro 75, Agency 200.
CreditsSpent per run, metered at 1 credit = $0.001 of model cost (not for the part billed to your own OpenRouter key).
Schedules100 per account.
Access tokens10 live tokens per account.
Queued team changes100 waiting per account.
Agent policies2,000 per account.
Request body64 KB.
wait_for_runUp to 120 seconds per call (default 50).
Run lengthA run is stopped after 15 minutes.
ApprovalsLapse after 7 days.

Being rate limited is an isError result (“Rate limit reached — try again in a minute.”), never HTTP 429. To stay well inside the limit, prefer one wait_for_run with a long timeout over many run_status calls.

10. Security and data #

  • A token acts as you. Anything it is permitted to do happens as if you did it in the app, and spends your credits. Give each tool its own token with only the permissions it needs, and limit it to one company where you can.
  • Your account only. A token reaches only the account that created it, and a company-limited token only that company. Data from other accounts is never visible.
  • Outside actions wait for approval by default. Publishing a site, sending email and similar actions create approvals unless you have set the agent's policy to allow. Only a token with Approve and perform can approve, and approving carries the action out. A connected_tool approval runs only the exact call whose payloadHash you pass.
  • Stored safely. Cluvia keeps only a SHA-256 hash of each token. The token is shown once, at creation.
  • Revoke at any time on the Connections page; the token stops working on the next request. Queued hires from a revoked token are withdrawn. Deleting a company revokes the tokens limited to it; deleting your account deletes all of your tokens.
  • Account checks. On each request the server confirms the token's account can still use Cluvia. That check is cached for up to 10 minutes.
  • What a token cannot do: manage tokens, settings, billing, plan checkout or your own OpenRouter key. Those need a signed-in app session.
  • Transport. Use HTTPS. Requests from web pages on other sites are refused. Keep tokens in environment variables or your client's secret storage, not in files you commit.
  • Data handling is described in the Privacy policy and the Subprocessors list. Use of the API is covered by the Terms.
  • Report a security problem to usecluvia@gmail.com. Please include what you found and how to reproduce it, and do not access other people's data.

11. Changelog #

Changes to the MCP server and to this page, newest first.

2026-10-08
Runs start within seconds when there is room (run queue), instead of at the next minute. startedAt can be null for a run's first seconds in running. New event kinds: deferred and requeued.
2026-10-07
Docs published; MCP server available at https://mcp.usecluvia.com/mcp. The earlier workers.dev address keeps working. Server version 1.0.0: 15 tools; protocol versions 2026-07-28, 2025-11-25, 2025-06-18 and 2025-03-26.

12. Support #

Questions, bugs or feature requests: usecluvia@gmail.com. Include the tool name, the time of the call (UTC) and the error text, and never send your token.

Back to Cluvia