MCP server
Pluto Ads runs a Model Context Protocol server, so an agent in Claude, Cursor or your own code can do what a person does in the app: draft launches, add media and ads, comment, review, publish once someone approves the spend, and run the workspace (members, API keys, agents, webhooks, the Meta connection). Every tool calls the same commands as the app and the public API, with the same permissions, idempotency and audit log. Agents explains the same from the app's side: connecting, what people see, and disconnecting.
API messages use typographic apostrophes; the examples on this page show them as '.
Connect in two minutes
The server URL is:
https://api.plutoads.ai/api/v1/mcpThe API page of the app shows it under Connect an MCP client, with the command or config for Claude, ChatGPT, Cursor, Claude Code, VS Code and Codex.
Sign in from your client. Add the server URL in your client. The client opens a browser, you sign in to Pluto Ads, choose the workspace and what the agent may do, and approve. No key to copy:
claude mcp add --transport http plutoads https://api.plutoads.ai/api/v1/mcp
# then /mcp in Claude Code, pick plutoads and Authenticate
codex mcp add plutoads --url https://api.plutoads.ai/api/v1/mcp
# Codex opens the sign-in itself; codex mcp login plutoads starts it again- Claude (web and desktop): Customize, Connectors, Add custom connector, paste the URL.
- ChatGPT: turn on Developer mode in Settings, Security and login, then create an app under Plugins with the URL and OAuth.
- Cursor:
{ "mcpServers": { "plutoads": { "url": "https://api.plutoads.ai/api/v1/mcp" } } }in~/.cursor/mcp.json. - VS Code:
{ "servers": { "plutoads": { "type": "http", "url": "https://api.plutoads.ai/api/v1/mcp" } } }through MCP: Open User Configuration.
How it works, and what the agent can do, is under Sign in from Claude or Cursor.
Or use an API key, for scripts and clients that can send a header:
- An admin opens the API page of the app and selects Create key, with the access the agent needs. Copy it; you see it once.
- Add the server to your client with the key as a Bearer token, as below.
The server URL is also the resource in the server's metadata:
curl -s https://api.plutoads.ai/api/v1/.well-known/oauth-protected-resource | jq -r .resource
# https://api.plutoads.ai/api/v1/mcpClaude Code. Run in your terminal:
claude mcp add --transport http plutoads https://api.plutoads.ai/api/v1/mcp \
--header "Authorization: Bearer $PLUTO_API_KEY"
claude mcp get plutoads
# Status: ConnectedClaude Desktop. Claude Desktop's own connectors sign in with OAuth (see above) and can't send an API
key, so a key goes through mcp-remote, which needs Node.js. Open Settings, Developer, Edit Config, add
this, then restart Claude:
{
"mcpServers": {
"plutoads": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.plutoads.ai/api/v1/mcp", "--header", "Authorization:${PLUTO_AUTH_HEADER}"],
"env": { "PLUTO_AUTH_HEADER": "Bearer sk_live_..." }
}
}
}Keep Authorization:${PLUTO_AUTH_HEADER} without spaces: some clients split arguments on spaces.
Cursor. Add to ~/.cursor/mcp.json (or .cursor/mcp.json in a project). This is Cursor's
Streamable HTTP format:
{
"mcpServers": {
"plutoads": {
"url": "https://api.plutoads.ai/api/v1/mcp",
"headers": { "Authorization": "Bearer sk_live_..." }
}
}
}Any other client. Point a Streamable HTTP client at the URL and send the header on every request. With the official TypeScript SDK:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(new URL("https://api.plutoads.ai/api/v1/mcp"), {
requestInit: { headers: { Authorization: `Bearer ${process.env.PLUTO_API_KEY}` } },
});
const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(transport);
const { tools } = await client.listTools();Then ask the agent to call get_context. It answers with the credential, workspace and permissions:
{"actor":{"id":"0199f0a2-6c1e-7b3d-9a41-5e2f8c7d1a90","kind":"api_key","name":"Reporting agent","permissions":["read","review","write","publish"],"userId":null},"workspace":{"id":"0199f0a1-3b7c-7e52-8d14-2a6f9b0c4e17","name":"North Studio","version":1}}(workspace trimmed.)
Authentication
The endpoint is an OAuth 2.1 protected resource. Every request needs Authorization: Bearer with one of:
- An access token from signing in (authorization code with PKCE): an agent a person connected from Claude, Cursor or another MCP client. See the next section.
- An access token from registering with auth.md: an agent that signed itself up with a person's email, which the person then confirmed. See Register with auth.md.
- A Pluto API key (
sk_live_...orpk_live_...). A publishable key can only call read tools. - An access token for an agent client registered on the API page under Agent credentials, which exchanges its
client ID and secret for a short-lived token (client credentials, see
Authentication). Pass
resource=<the MCP URL>so the token's audience is this server.
A request without a credential gets 401 and a pointer to the metadata (RFC 9728):
curl -s -i https://api.plutoads.ai/api/v1/mcp -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | grep -i 'www-authenticate\|jsonrpc'
# www-authenticate: Bearer resource_metadata="https://api.plutoads.ai/api/v1/.well-known/oauth-protected-resource"
# {"error":{"code":-31001,"message":"Authorization needed."},"jsonrpc":"2.0"}
curl -s https://api.plutoads.ai/api/v1/.well-known/oauth-protected-resource | jq -c '{resource,bearer_methods_supported,resource_documentation}'
# {"resource":"https://api.plutoads.ai/api/v1/mcp","bearer_methods_supported":["header"],"resource_documentation":"https://docs.plutoads.ai/mcp"}authorization_servers in the same document names the authorization server that issues tokens. Clients
read its own metadata (/.well-known/oauth-authorization-server on that server) for the authorization,
token and registration endpoints, so nothing about it needs to be configured by hand. The protected
resource metadata is also served at the origin root, where RFC 9728 clients look when there is no
WWW-Authenticate hint: https://api.plutoads.ai/.well-known/oauth-protected-resource and
https://api.plutoads.ai/.well-known/oauth-protected-resource/api/v1/mcp.
Sign in from Claude or Cursor
The flow is the MCP authorization spec. People sign in with their Pluto Ads account on our own pages:
- The client gets the
401and reads the metadata above, then the authorization server's metadata (PKCES256, Client ID Metadata Documents and Dynamic Client Registration are supported). - It identifies itself with a Client ID Metadata Document (an HTTPS URL as
client_id) or registers with Dynamic Client Registration, then opens the authorization endpoint with a PKCE challenge andresource=<the MCP URL>. - The browser opens Pluto Ads on Connect an agent. The person signs in if needed, then picks the workspace and the access: Read only, Read and write, or Read, write and publish; people who administer the workspace also see Workspace admin, and organization owners and admins Organization admin (see Who can do what). Continue records the connection.
- A consent screen shows which app is asking and where it returns to. Allow sends the code to the
client, which exchanges it (with the PKCE verifier and
resource) for a short-lived access token, plus a rotating refresh token when it asked foroffline_access.
The token's audience is the MCP URL, and it is bound to that one connection and client: a token issued
to another client can't use it. The agent then acts in that workspace as "Claude via Anna" (the
client's name via the person's), with the chosen access, never more than the person's current role.
Activity, comments and the audit log name it that way, and get_context returns it with the person's
userId.
Everyone sees the agents they connected on the API page under Connected agents (admins see all of the
workspace's), with access, when each connected and when it was last used. Disconnect stops the agent on
its next request with a 401, so the client asks to sign in again. Removing or suspending the person
does the same. A connection the client never finishes within an hour can't be used.
Register with auth.md
Agents that can't open a sign-in flow in a client, such as a coding agent in a terminal or a script, can register themselves with auth.md, an open protocol for agent registration. The guide an agent follows is published at the origin root:
curl -s https://api.plutoads.ai/auth.md | head -n 5An agent can also find it from the 401 alone: the protected resource metadata names the authorization
server, and that server's metadata (/.well-known/oauth-authorization-server) carries an agent_auth
block with the guide (skill) and the registration endpoints.
- The agent registers with the email of the person it works for (
POST <identity_endpoint>with{"type": "service_auth", "login_hint": "<email>"}) and gets back a claim token and a verification link. - It shows the person the link. The link opens Pluto Ads on Register an agent: the person signs in as that email, picks the workspace and the access (the same levels as when signing in), and selects Continue. The page shows a short code.
- The person reads the code back to the agent, which completes the claim (
POST <claim_endpoint>/completewith the claim token and the code) and receives an identity assertion. - The agent exchanges the assertion at the token endpoint (
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer, withresource=<the MCP URL>) for a short-lived access token, and calls the MCP server or the public API with it.
The agent then acts in that workspace as "Registered agent via Anna", with the chosen access and never
more than the person's current role, exactly like an agent connected by signing in: activity and the
audit log name it that way, it's listed under Connected agents as registered with auth.md, and
Disconnect (or revoke_agent_connection) stops it on its next request with a 401. Removing or
suspending the person does the same. After a disconnect, the agent has to register again.
Only registrations a person has claimed get access. A registration without a person (anonymous) can do
nothing until someone claims it this way, and a registration can be claimed once, into one workspace.
The link only works for the person whose email the agent registered with. The code works once and
expires within minutes; if it expires, the agent starts a new claim attempt and shows a new link.
Identity assertions from agent providers (ID-JAG) aren't accepted.
Requests that carry an Origin header must come from the app's origin; anything else gets 403
(Requests from this origin aren't allowed.). Native clients send no Origin.
Who can do what
Every credential acts for a person and can only ever do what that person can do right now: the same scope and permissions as in the app, never more. The rules are the same as for the public API:
-
Whose person. An agent connected by signing in or registered with auth.md acts for the person who connected it; an
mcpagent client for the admin who registered it; an API key orm2magent client for its owner. -
Checked on every call. The tools a credential may use are its access, narrowed by its person's current role and workspace access. Demoting the person narrows the agent on its next call; suspending or removing them stops it. Every tool says the permission it needs (
_meta["ai.plutoads/permission"]). -
Access levels.
Level Scopes Adds Read only readReading launches, ads, media and results Read and write read,review,writeDrafts, edits, media, comments and reviews Read, write and publish + publishPublishing and live budgets, which spends money Workspace admin + adminThis workspace's members and guests, keys, agent clients, webhooks, the Meta connection and settings Organization admin + organizationThe organization's people, workspaces, plan, usage limits, extra storage and billing details Workspace admins give up to Workspace admin; only organization owners and admins give Organization admin. A Workspace admin credential never reaches the organization, even when its person administers it.
-
Who connects agents. Organization owners and admins, workspace admins and members connect agents for themselves, up to their own role; members only while Members can connect agents is on in the organization's General settings (turning it off stops members' agents on their next call). Guests can't connect agents. Only admins create API keys and agent clients.
-
Person-only. No tool grants or removes ownership, deletes the organization or deletes an account, and no credential removes the person it acts for. Paying happens in a browser: billing tools return a link for a person to open. Approving the in-app Agent's proposals is the person's own step in the app.
-
Seeing and ending access. Organization admins list every credential in the organization with
list_organization_credentials, revoke any withrevoke_organization_credentialand take over a leaving person's API keys withtransfer_credentials. People see and disconnect their own agents in their account settings. Every tool call a credential makes withadminororganizationaccess is in the audit log.
Protocol versions
The server speaks both MCP eras at the same URL, decided per request. There are no sessions: no
Mcp-Session-Id, no server-to-client stream, and GET or DELETE on the endpoint answers 405.
| Era | Versions | How a request looks |
|---|---|---|
| Stateless | 2026-07-28 | Every request carries _meta["io.modelcontextprotocol/protocolVersion"] and _meta["io.modelcontextprotocol/clientCapabilities"], plus the headers MCP-Protocol-Version, Mcp-Method and, for tools/call, Mcp-Name. Headers must match the body. Discovery is server/discover. |
| Handshake | 2025-11-25, 2025-06-18, 2025-03-26 | initialize negotiates the version (we answer with yours when we support it, else 2025-11-25). Later requests send MCP-Protocol-Version; without it we assume 2025-03-26. |
A stateless discovery request:
curl -s https://api.plutoads.ai/api/v1/mcp -H "authorization: Bearer $PLUTO_API_KEY" \
-H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2026-07-28' -H 'mcp-method: server/discover' \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' \
| jq -c '.result | {supportedVersions,capabilities,resultType}'
# {"supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26"],"capabilities":{"tools":{"listChanged":false}},"resultType":"complete"}A handshake request:
curl -s https://api.plutoads.ai/api/v1/mcp -H "authorization: Bearer $PLUTO_API_KEY" \
-H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
| jq -c '.result | {protocolVersion,serverInfo}'
# {"protocolVersion":"2025-06-18","serverInfo":{"name":"plutoads","title":"Pluto Ads","version":"0.1.0","websiteUrl":"https://plutoads.ai"}}A stateless tools/call without Mcp-Name is refused before anything runs:
{"error":{"code":-32020,"message":"Header mismatch: Mcp-Name header is required"},"id":9,"jsonrpc":"2.0"}The body is one JSON-RPC message; batches aren't supported. Notifications get 202. The server offers
tools only: no resources, prompts or sampling. tools/list is the same for every caller (cache it; the
stateless result says ttlMs: 300000), and permissions are checked per call.
How the tools behave
- Start with
get_context. It returns the credential, its workspace and its permissions. Refer to everything by ID; names aren't unique. - Writes are commands. Every write tool takes an optional
idempotencyKey. Reuse it when you retry and the stored result comes back instead of a second change. Without one, the key is derived from the credential, the JSON-RPC request ID, the tool and its arguments, so a transport-level retry of the same request is still safe. - Batch tools (
create_launches,create_ads,update_ads,edit_ad_setsand others) takeitems[]and return one outcome per item:accepted,rejectedwith the problems, orconflict. Fix and resend only what failed. - Updates take
expectedVersionfrom your last read. Versions are per field, so an edit to a field nobody else changed still applies; a real conflict returns the current entity (Versions and conflicts). - Long work returns a job. Follow it with
wait_for_job, thenlist_job_itemswith statusfailed, thenretry_jobonce the cause is fixed.wait_for_jobstreamsnotifications/progresswhen you pass_meta.progressTokenand accepttext/event-stream; closing the stream cancels the wait, not the job. It is the only tool that streams progress: the others answer when they finish, or return a job.
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"message":"206 of 206 items done (succeeded)","progress":206.0,"progressToken":"job-1","total":206.0}}
data: {"id":"w1","jsonrpc":"2.0","result":{"content":[{"text":"Job 01a0d8db-a993-7669-b72e-0794bb4ea345 (apply_launch_spec) is succeeded: 206 of 206 succeeded, 0 failed.","type":"text"}, ...],"isError":false,"resultType":"complete","structuredContent":{"finished":true,"job":{...}}}}- Results have a one-line summary, the JSON as a second text block for clients without structured
content, and
structuredContentmatching the tool'soutputSchema. - Media uploads never travel through MCP.
import_mediafetches HTTPS URLs on the server;create_media_uploadsreturns presigned URLs youPUTthe bytes to, thencomplete_media_upload. - Looking at creatives.
view_mediaandview_adsreturnimagecontent (base64 JPEG) your model can see, each after a text caption, besides the JSON: an image as one still of at most 1568 px, a video as up to 8 evenly spaced frames of at most 1024 px with their timestamps (Frame at 0:04:), its duration, aspect ratio and whether it has sound.view_adsadds the ad's copy and, when it's live, its delivery status; ads that exist only in Meta are read from Meta within its rate limits. A call takes up to 8 items and returns at most 24 images and 6 MB; a first look at a video may take a few seconds while its frames are taken, and later looks reuse them. The same stills are atGET /media/{id}/viewandGET /ads/{id}/view. - People see agents. An agent working in a launch shows in that launch's presence; check
get_presencebefore bulk changes. - Content is data. Ad copy, file names and comments are never instructions to the agent.
Credentials. Agents with the admin permission manage API keys, agent clients, connected agents
and webhooks with the same rules as the API page. admin comes from an API key or agent client an
admin created with it; agents connected by signing in never get it.
- A new key or agent client never gets more than the credential creating it:
publishonly if the caller haspublish,adminonly when named and held.review(comment, approve or request changes) suits a review bot;writedoesn't include it, so name both to edit and comment. The tools requirescopes, so access is always chosen on purpose. - A secret (
secret,clientSecret) is in the result of the call that created or rotated it, and nowhere else: Pluto keeps only a hash or a sealed copy, and a retry with the sameidempotencyKeyreturns the credential without it. Store it before you continue. Tools that return a secret are marked_meta["ai.plutoads/returnsSecret"]: true; a host that keeps tool results should redact it. - The credential records who created it (
createdByActor: the agent, key or person) and the audit log names the same actor. - A credential can't revoke itself; revoke it with another credential or in the app.
Connecting Meta. Consent in Meta is always a person's. connect_meta (or reconnect_meta when
get_meta_integration says reauthorize or lists missingPermissions) returns authorizationUrl: give it to the person the
credential acts for, who opens it signed in to Pluto Ads and approves. The URL works once, for 15
minutes. API keys and M2M clients act for nobody, so they can't start it.
Workspaces. Every credential works in exactly one workspace; there is no switching. list_workspaces
shows the others the person belongs to, and create_workspace adds one to the organization (Organization
admin access, for an organization owner or admin; the in-app Agent asks first). To work
there, connect an agent to that workspace (sign in from your client and choose it) or use a key made
in it.
Organizations. Workspaces belong to an organization, which holds the people and the billing.
Members are paid seats and reach every workspace or selected ones; guests are free and only review in
the workspaces they're invited to. list_organizations and list_organization_workspaces show what the
person reaches; owners and admins manage people with list_organization_members,
invite_to_organization and update_organization_member.
Guests. Guests review in the app only: they can't create API keys or connect agents, so no MCP
credential ever acts for one. A key scoped to review alone sees what a guest sees through the REST API
(launches shared with guests and their comments that aren't internal) and no tool that needs read. Tools
never reach more than the matching REST route. Comments marked internal (add_comments with
internal: true, or set_comment_internal) are never shown to guests; sharing a launch with guests is
update_launches with sharedWithGuests. New launches start unshared unless an admin turns on
shareNewLaunchesWithGuests with update_workspace_settings.
Publishing. publish_launch, publish_revisions, set_delivery_status, set_budget, set_bid,
set_schedule, duplicate_at_meta and delete_at_meta spend money. They need the publish permission, and publish_launch needs an approval that echoes
get_publish_preview's launchVersion, adAccountId and budgetSummary unchanged. An agent must show
those to a person and get a yes before it calls the tool; the server refuses any approval that no longer
matches the launch. Nothing is live until the publish job reads the objects back from Meta. Creatives in
Ungrouped and ad sets without creatives are left out of a publish (excluded in the preview); tell the
person, and move creatives with move_ads if they should go live too.
Any Meta object. The live tools take a Pluto ID or the Meta ID of any object the sync lists
(list_meta_campaigns, list_meta_ad_sets, list_meta_ads), so an agent can pause, rebudget, rebid,
reschedule, copy or delete campaigns, ad sets and ads made in Meta Ads Manager without importing them first.
Campaigns are campaignId (a Meta ID) or launchId (the campaign a launch created). An object that belongs
to a launch changes through the launch. set_bid follows Meta's rules per bid strategy: bidAmount for a
bid cap or cost per result goal, roasGoal for a ROAS goal. duplicate_at_meta copies paused by default;
follow its job with wait_for_job to get each copy's Meta ID. Each of these returns a job; the change is read
back into the synced lists as soon as Meta applies it.
Comments on ads. The comments people leave on the posts your Meta ads run as (Facebook Page posts and
Instagram media) have their own tools, named *_social_comment* because list_comments and
add_comments are the team's conversation on an ad in Pluto. list_social_comments pages through them,
newest first, with replies, the ad each is on and what's pending. Replying, hiding, unhiding and deleting
act publicly as the brand's Page or Instagram account, so they need publish, like putting ads live; the
in-app Agent asks the person to approve them first. delete_social_comment can't be undone in Meta and
refuses to run without confirm: true: confirm with the person, or hide the comment instead. Each of these
returns the queued action; Meta usually confirms within seconds. Follow it with get_social_comment until
pending is empty, or failed says why. When access.missingPermissions in the list isn't empty, the Meta
connection lacks a permission comments need: reconnect_meta returns the Facebook Login URL for a person to
grant it.
Leads. The leads people submit on the connected Pages' lead forms arrive through Meta's leadgen
webhook within seconds, and a scheduled read backfills any a webhook missed, so each lead arrives once.
list_lead_forms and list_leads need read and never return answers (fields names the questions a lead
answered). get_lead returns the answers, which are personal data: it needs write, and each read is
recorded in the audit log. Use answers for what the person asked, and don't repeat them where they aren't
needed. Each new lead is the lead.received event, which the Loops "New lead" trigger and webhooks follow.
Notifying people. notify_members posts a notification to the Inbox of people in this workspace and,
with channels: ["email"], emails them. It reaches only people in the workspace: to: "me" (the person you
act for, the default), to: "workspace" (every owner, admin and member, never guests) or
userIds from list_members. Someone outside the workspace is refused and nothing is sent. A guest can be
named only about a launch shared with guests: pass its launchId (or an adId in it); otherwise the call is
refused and nobody is notified. Give a
message (up to 2,000 characters) and/or a subject (up to 200, the Inbox title and email subject). It
needs write. Each credential may send 30 notifications an hour and a workspace 200 (then rate_limited
with retryAfter); each person gets at most 20 of these emails a day and after that only the Inbox
notification (email: "limited" in the result). The Loops Notify people step sends through this tool.
Events. Status changes of campaigns, ad sets and ads in Meta (also for ones made in Ads Manager), new
comments on ads' posts, uploaded media, metric thresholds and budgets crossed, and new leads are events of the
workspace. The Loops event triggers start on them, and a webhook can subscribe to them: list_webhook_events
lists meta.campaign.status_changed, meta.ad_set.status_changed, meta.ad.status_changed,
social.comment.created, media.uploaded, metric.threshold_crossed, budget.spent and lead.received
with the rest.
Loops. A loop is an automation drawn in the loop builder, saved as a LoopDefinition
({ version: 1, name, trigger, nodes, edges }; create_loop and update_loop describe its shape and list
the blocks). update_loop replaces the whole definition, so read it with get_loop, change it and send it
all back with expectedVersion; a save since then is a conflict carrying the current loop. A malformed
definition is refused with every problem and its path (definition/nodes/2/block); unfinished drafts save,
and issues lists what's left; validate_loop checks a definition without saving it. Numbers and money
in a step's config are decimal strings. Every save is a revision naming who made it and how (via: app,
agent_chat, mcp or api); get_loop with revisions: true returns them. A loop the in-app Agent
builds keeps its chat as the loop's history; an MCP client can attach a chat its person started with
sourceChatId.
A new loop is a draft. set_loop_enabled turns it on (active: its trigger starts runs) or off (paused).
Turning it on checks that it's complete and, for a schedule, that its time zone is known; every problem comes
back by step (steps/<id>). Try a loop first with test_run_loop: it reads your data, checks conditions and
runs AI steps, then plans every action, notification and wait without doing any. run_loop runs it now,
whatever its trigger; a live run while another is going is a conflict. Both return the run queued; follow
it with get_loop_run, which has every step per item (planned in a test run, with what it would call),
stepStates, the items with their outcome and the approvals. list_loop_runs is the history. Turning a
loop off stops new runs; cancel_loop_run stops one already going. undo_loop_changes sets back what a
step changed in Meta (its changes in get_loop_run), as you, through the same per-change guards; undoing twice
changes nothing more.
Runs act with the loop owner's permissions, capped by the person who last changed its steps (runsAs on the
loop), as "Pluto Agent via" and the owner's name, and only on objects Pluto manages. A loop's steps are the
presets create_loop lists; catalogue tools aren't steps of their own. The presets and the Agent step call the
tools meant for automations (reading performance, profit, comments and leads; budgets, bids, delivery, copies,
comment moderation, labels, notifications and Slack), never admin, lifecycle, credential, webhook, billing,
member, organization, integration-connect, chat, job or plumbing tools. Budgets, bids, delivery, deletes in Meta
and public comment replies and moderation wait for a person to approve in the app (the run is
awaiting_approval), unless the step's ask lets them through and a person with publish allowed that
version of the loop to act, in the app. An Agent step that asks sends all its changes in a run as one change
set to the Inbox. An agent can't approve or allow: tell the person the run is waiting. AI steps count toward
AI usage like the in-app Agent, on every plan including the trial, for the person the loop acts for. While the
organization is read-only, no loop runs: scheduled and triggered runs are skipped (one a day), a run in progress
stops before its next step as cancelled, both with the payment_required problem as error, and run_loop and
test_run_loop answer payment_required; set_loop_enabled (off) and archive_loop still work. Missed runs
aren't made up once the organization pays.
Every AI step (Agent, Classify, Write reply, Summarize) needs instructions: what to do, in plain words. A
loop saved with an AI step without them is a draft validate_loop lists as unfinished, and set_loop_enabled
refuses it. rules, the older name, is accepted and saved as instructions.
What an Agent step may change is stored tool by tool: tools (catalogue tools) and actions (budgets,
status, bids). It always reads. As a shorthand, create_loop and update_loop accept groups, the switches
people see in the app, and save them as the tools they allow: spend (budgets, pausing and resuming, bids and
schedules), comments (reply, hide, unhide, delete), ads (update_ads), targeting, duplicate and delete
(in Meta) and notify (Notify people, Post to Slack, review comments). An unknown group is refused. A tool
added to a group later is never given to a saved loop by itself: someone turns it on.
The Agent step manages budgets like a media buyer, within guardrails in its config (accounts, actions,
goal with goalValue, maxChange, dailyCap, minSpend, ask); create_loop describes them. A loop's
limits live in its steps: maxChange is how far one object's budget or bid may move in a run, and dailyCap
is what the ad account's active daily budgets may add up to. Action steps carry their own amounts, floors and
ceilings. Every automated change, from a loop, a change set, an agent or an API key, also passes the
per-change guards: a budget moves at most +100% or -90% in one change, never to 0, and only in a known
currency. Turning something back on counts as raising spend, and each change is checked again right before
it runs.
Change sets
A change set is a plan of changes to live budgets, delivery and bids that a person approves in Pluto Ads.
When a request changes more than one live object, propose them together with propose_changes instead of
calling set_budget, set_delivery_status or set_bid one by one: the person reviews one plan with the
totals per ad account, edits it, sends it back or approves it. Pluto builds every change from your call and
the synced object (the value now and after, money as a decimal string with its currency) and leaves out
anything over the per-change guards, saying why in leftOut. Give each change a reason and write the
summary for the person: what changes and why, with the numbers.
get_change_set shows where it stands: pending, revising, approving, then applied,
partially_applied (with each change's result: applied, conflict when the value moved since you
planned, blocked by a limit, failed in Meta), rejected (with the person's reason), expired (24 hours
without a decision) or superseded. When the person sends it back (revising, with their feedback in
revising.feedback), make the next version with propose_changes, changeSetId and the complete new list
of changes. edit_change_set removes changes or sets a new value without AI, revise_change_set sends a
plan back with feedback (a loop's plan is revised by Pluto's AI; with retryConflicts, only the changes that
conflicted, planned again from the current values), and undo_change puts an applied change back. An applied
change's result.readback says whether Meta shows it (live) or something else (differs, with the value). Approving and rejecting are the person's, in the app: tell them the plan is waiting.
Errors and limits
A domain problem is a tool result with isError: true, so the model can read it and act:
{"content":[{"text":"You can view this workspace but not change it. Ask an admin to give this credential the access it needs; it never does more than its person can.","type":"text"},{"text":"{\"code\":\"forbidden\",\"message\":\"You can view this workspace but not change it.\"}","type":"text"}],"isError":true}Billing refusals come back as tool errors with the same fields as the API: payment_required (the
organization is read-only), plan_limit (the plan doesn't include it: more workspaces, workspace limits, usage
by workspace or connected agent, or more media storage, with upgradeTo, contact_sales with the sales booking in salesUrl, or keep_plan) or, for
Agent and Loops work past a usage limit, spend_limit (reason: trial_cap, included_used, spend_limit,
workspace_limit, member_limit or complimentary_used), each with the page an owner or admin can open
(API errors). Billing tools keep working either way, so an agent can always read
billing and usage and hand over the link.
A read-only organization allows the same through MCP as in the app and the API: every read tool (exports
included, such as export_workspace); pausing with set_delivery_status and lowering with set_budget;
set_loop_enabled to turn a loop off, archive_loop and delete_loop (a draft that never ran); undo_change and
undo_loop_changes when the undo pauses or lowers a budget; cancel_publish, cancel_job and cancel_loop_run;
the revoke_* tools (invitations included) and delete_webhook; disconnect_meta, disconnect_slack and
disconnect_pluto_profit; archive_workspace, delete_workspace,
remove_organization_member and edit_members when every item removes someone; and the billing tools. Everything else,
manual refreshes such as sync_leads and sync_social_comments and the upload targets
(create_comment_attachment_upload, create_workspace_logo_upload) included, answers payment_required. Background
syncs from Meta continue.
Validation problems list every field. Transport and credential problems are JSON-RPC errors:
| HTTP | Code | Meaning |
|---|---|---|
| 401 | -31001 | No credential, or it is invalid, expired or revoked. WWW-Authenticate names the metadata. |
| 403 | -31003 | The credential can't use this workspace, or the Origin isn't allowed. |
| 429 | -31029 | Over the credential's rate limit; Retry-After and data.retryAfter give the seconds. |
| 503 | -31503 | Authentication is unavailable; retry shortly. |
| 400 | -32020 | A stateless request whose headers don't match its body. |
| 400 | -32022 | Unsupported protocol version; data.supported lists ours. |
| 400 | -32600, -32602, -32700 | Invalid request, invalid params, unparsable JSON. In the handshake era, invalid params come back with 200. |
| 200 | -32603 | Our fault. Retry with the same idempotencyKey. |
The rate limits are the API's, MCP and REST combined: 1,000 requests per 10 seconds per credential, and 3,000 for all API keys and agent clients of a workspace together (Rate limits).
HTTP/1.1 429 Too Many Requests
retry-after: 6
{"error":{"code":-31029,"data":{"retryAfter":6},"message":"Too many requests. Try again in 6 seconds."},"jsonrpc":"2.0"}Finding the right tool
The server offers every Pluto Ads action as a tool, more than 200 in all. That's more than a model should read on every turn, so let your client discover tools instead of loading every definition up front:
- Claude (API and apps): turn on tool search for this server. With the MCP connector, set
default_config: { "defer_loading": true }on themcp_toolset, and keep a few tools you use constantly loaded throughconfigs(for exampleget_context,list_launches,get_launchandget_insights). Claude then searches names, descriptions and argument names and loads only what it needs (tool search). - Cursor and Claude Code: both load only tool names up front and fetch full definitions when needed; no setup is needed.
- Your own agent: cache
tools/list(the order is stable, so a cached prompt prefix keeps working) and search it yourself. Every tool carries what a search needs:- Names are distinct and say the verb and the object (
list_launches,set_budget,reply_to_social_comment). Related tools share their object word, so a search for one finds its neighbours. - Descriptions start with the verb and the object ("Changes a live budget...", "Lists the lead forms...").
FINANCIAL:andDELIVERY:mark tools that affect live spend or delivery. _meta["ai.plutoads/area"]groups tools by area:launches,publishing,targeting,integrations(Meta sync),analytics,profit,media,collaboration,social,leads,loops,change_sets,slack,jobs,workspace,settings,organizations,credentials,webhooks,billingand more.- Annotations follow MCP's
ToolAnnotations:readOnlyHintfor reads;destructiveHintfor tools that remove, overwrite or spend;idempotentHintfor tools that set a value, so repeating them changes nothing more;openWorldHintfor tools that reach Meta or other outside services. A client can auto-approve read-only tools and confirm the rest. - Examples: tools whose arguments are easy to get wrong (live budgets, bids, schedules and targeting,
ad updates, loop definitions, change sets, notifications, tag filters, evidence corrections and labels,
precision estimates) carry examples in their
inputSchema's standard JSON Schemaexampleskeyword. Pass them to your model: with Claude, as the tool'sinput_examples.
- Names are distinct and say the verb and the object (
Creative Memory
Use get_creative_context to resolve original media (kind: media), a current Pluto draft (kind: ad),
or a recorded Meta ad (kind: meta_ad). It returns exact copy, available media, source version and explicit
completeness without fetching or analyzing anything. Published Meta context never substitutes edited draft copy.
A Meta ad named with or without adAccountId is the same subject with the same fingerprint, so relationships
and experiments recorded either way are found either way.
Use compare_variants with 2-8 distinct subjects in that same format to assemble recorded copy differences
and deduplicated media evidence for comparison or a next-creative brief. Each media item has at most 64
observations, with an explicit truncation flag. Missing evidence stays unknown; shared media does not establish
family lineage and differences do not identify what caused performance. The read starts no inference.
Its lineage lists only relationship claims already recorded for these exact revisions (relations and
families, each with its recorded status and basis, and variantIndex pointing into variants); claims
about other revisions of the same ads or media are not carried over.
Use get_creative_performance for original provider-reported ad/day facts over 1-100 Meta ads and at most
31 inclusive account-local days. Pass adIds, since, until and an explicit attribution window.
The response retains currency, timezone, denominators, fetch time and composition coverage. An
observed_stable composition assumes continuity between matching snapshots; mixed and unknown days
cannot be assigned to a creative. Outcomes are never allocated to each asset, missing values stay unknown,
and provider attribution is not causal evidence. The read starts no fetching, analysis or model call.
Use search_creative_memory and get_creative_memory to reuse published creative evidence before requesting
another analysis. Results include original-media timestamps and examined coverage. Missing evidence is unknown,
not proof that an event is absent; search does not silently run paid analysis or promise exhaustive counts.
tags (a list of tag IDs) narrows the search to media used by an ad with any of those tags, exactly as
list_media does with the same argument; coverage then counts that media only.
Use search_creative_ads to find ads (current drafts and ads observed at Meta) by their copy and the recorded
evidence of their media, or by similarity to an indexed example file. It takes the same body as
POST /creative-memory/ad-search and returns each ad's current copy and media, matching copy fields or media
moments, and coverage of the ads in scope, including those still being prepared. Quoted phrases match exact
wording only; similarity does not establish lineage or performance. filters.tags keeps ads whose Pluto ad has any of
those tags (ads observed at Meta through the Pluto ad that published them; others have none), and coverage then
counts the tagged ads only.
evaluate_creative_condition checks a media item with structured speech/text, interpreted-feature, time and
boolean conditions. It returns true, false or unknown, citations, coverage and source/evaluation versions.
Unknown stays unknown through not. A literal false describes the recorded text, not physical absence.
The endpoint accepts at most 64 nodes, 6 levels and 500 characters per phrase, and starts no inference.
get_creative_memory_usage reports organization-pooled deep-analysis and basic-indexing allowances. Use
quote_creative_memory with kind (index or deep) and items containing media IDs and optional paired
startMs/endMs video sections. A quote starts no inference. Present its included units and any
onDemand.maxUsageUsd ceiling before accepting it with request_creative_memory and the exact quoteId.
The in-app Agent asks for approval; MCP callers are responsible for authorization of the quoted effect.
The server rechecks freshness and reserves all new work atomically. Reusing results and retrying the same
command do not consume allowance twice. A stale quote needs a fresh preview.
Follow list_creative_memory_requests and the returned durable job IDs. Cancel unfinished processing through
cancel_creative_memory_request; it works in a read-only organization too. Successful published stages remain,
and unused reservations are released once bounded running calls finish. Reads need read; quote, accept and
cancel need write. These tools are discovered on demand rather than added to the Agent's permanent core set.
For one explicit question that published evidence cannot answer, quote_creative_precision estimates a
targeted inspection of an image or an original video interval of at most 90 seconds (visual, native_av or
word_timing); present it and accept it with request_creative_precision. get_creative_precision withholds a
result whose source, evidence or corrections changed. Timing conditions in evaluate_creative_condition never
start a precision request.
get_creative_correction_context returns the exact snapshot for correct_creative_evidence and
record_creative_learning_label. A correction is an attributed assertion (origin: authorized_correction),
immediately searchable by wording; observations that disagree with it are marked revalidation_required and
prove nothing. Labels are separate judgments and edit no evidence.
record_creative_lineage records a variant or family claim against exact revisions (the fingerprint from
get_creative_context) with documented provenance. Similarity can only suggest a candidate; confirming needs
a creator declaration or a recorded workflow. Amend with supersedesId and a reason; earlier records stay
readable through get_creative_lineage.
register_creative_experiment records an immutable design: hypothesis, changed and held-constant components,
allocation, 2-8 groups of exact revisions, primary metric, account-local window and stopping rule. Nothing is
launched, allocated or spent, and a declared randomized allocation is not proof that it ran. Only a design
registered before its window, with no prior results declared or assessed, is preregistered; amendments after
the window starts or after any assessment never become preregistered. observe_creative_experiment snapshots
the stored ad/day facts of each group's delivery ads over completed days; every outcome is descriptive_only
and is never allocated to assets.
Tool catalogue
Every tool the server offers. Permission is what the credential needs; effect comes from the tool's
annotations (read, create: repeating with a new key adds again, update: repeating changes nothing
more, destructive: removes, overwrites or spends). "Affects live spend" marks the tools the server
labels financial. The full input and output schemas are in tools/list.
Start here
| Tool | Permission | Effect | What it does |
|---|---|---|---|
get_context | read | read | Returns the calling credential (person, agent client or API key), its workspace and the permissions it holds. |
get_presence | read | read | Lists the people and agents currently in a launch (or another room: workspace, media, ad:<id>), with what each is focused on. |
Launches, ad sets and ads
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_launches | read | read | Lists launches, most recently changed first, with ad and ad set counts. |
search_ads | read | read | Finds ads in every unarchived launch by label, review status, assignee or name (q matches literally). |
get_launch | read | read | Returns the whole launch document: settings, ad account and campaign, ad sets (targeting, overrides) and ads (media, overrides and effective values, review status, assignee, labels) with IDs and versions. |
get_launch_review | read | read | Returns the launch's readiness checklist: every problem that blocks publishing (missing Page, copy, media still processing, invalid combinations) per launch, ad set and ad. Each item's fix is a pluto: link to where it is fixed in the Pluto Ads app (see Links into the app). |
get_launch_stats | read | read | Returns workspace-wide counts: open and published launches, ads in review and ads with changes requested, plus the review work assigned to the person this credential acts for. |
create_launches | write | create | Creates draft launches. |
update_launches | write | update | Changes launch name, ad account, campaign or settings. |
duplicate_launch | write | create | Creates a new draft launch copying the launch's settings, ad sets and ads (nothing is published). |
archive_launch | write | destructive | Archives a launch (or restores it with archived: false), or with delete: true deletes a draft that never created anything at a provider. |
edit_ad_sets | write | destructive | Creates, updates (name, targeting, overrides), reorders or deletes ad sets. |
duplicate_ad_set | write | create | Copies an ad set with its ads, in its launch or into another launch (launchId). |
create_ads | write | create | Adds ads to a launch, one per media item, in bulk (up to 500 per call). |
update_ads | review | update | Changes ads one by one: copy and other content (edit, in effective values), overrides, creative, ad set, name, review status, assignee and labels. Changing only the review status needs review; anything else needs write. |
move_ads | write | update | Moves ads into an ad set (or ungroups them with adSetId: null), optionally before a given ad and into another launch (launchId). |
duplicate_ads | write | create | Copies ads right after their originals, back at the default review status. |
delete_ads | write | destructive | Deletes ads from their launch. |
list_archived | read | read | Lists a launch's archived ad sets and a page of its archived ads (deleted in Meta, or deleted with comments or activity). |
restore_archived | write | create | Puts archived ads or ad sets back on the board as drafts. |
Launch specs
| Tool | Permission | Effect | What it does |
|---|---|---|---|
validate_launch_spec | write | read | Validates a declarative launch as a dry run (the launch with its ad sets and ads as one document, keyed by your externalKeys): returns every error at once, the plan (what would be created or updated) and what would still block publishing. |
apply_launch_spec | write | update | Applies a declarative launch: creates what's new and updates what changed (matched by externalKey), as one command. |
Bulk edits
| Tool | Permission | Effect | What it does |
|---|---|---|---|
preview_bulk_edit | write | read | Previews a rule-based edit of many ads or ad sets: a selector plus operations. |
apply_bulk_edit | write | destructive | Applies a previewed bulk edit to exactly the previewed items (same operations). |
get_changeset | read | read | Returns a multi-item change: who made it, the selector, and before/after per field and item. |
revert_changeset | write | destructive | Restores the recorded values of items unchanged since the changeset; the rest come back as conflicts. |
Publishing and live changes
| Tool | Permission | Effect | What it does |
|---|---|---|---|
get_publish_preview | read | read | Returns what publishing a launch would do and what still blocks it (checked against synced Meta data), plus the exact values an approval must echo: launchVersion, adAccountId and budgetSummary (currency, per-ad-set budgets, totals as decimal strings). Only ad sets with creatives and the ads in them are published; excluded counts the creatives in Ungrouped and lists ad sets without creatives. Each checklist item's path names the value to fix and its fix links to where it is fixed. |
publish_launch | publish | destructive | Publishes a launch to Meta, creating campaigns, ad sets and ads that can spend the approved budgets. Affects live spend. |
cancel_publish | publish | destructive | Stops a publish that hasn't finished (e.g. a scheduled one). |
get_publication | read | read | Returns a launch's published objects with their Meta IDs, configured and effective status, review feedback and, for failures, the field to fix. |
get_revisions | read | read | Lists edits to published objects waiting beside the live version: per object and field the live and pending value, whether Meta can change it, whether it sends ads back to review and whether it restarts learning, with the impact and a version per object and change. |
get_ad_meta_preview | read | read | Meta's own preview of an ad in one placement (Facebook or Instagram feed, story or reels, or Instagram Explore): a www.facebook.com preview link with its size, of the live ad (source: ad) or of its current values (source: draft). not_previewable with missing when Meta can't draw it yet. |
publish_revisions | publish | destructive | Applies pending changes to live objects in Meta, all of them or only the changes you pick, one coalesced call per object (budget changes over Meta's hourly limit wait). Affects live spend. |
discard_revisions | write | destructive | Reverts pending changes to published objects: the whole launch, or only the objects or fields in changes. |
set_delivery_status | publish | destructive | Pauses or activates a campaign (launchId, or any synced campaign's Meta ID as campaignId), ad set or ad (Pluto ID or Meta ID, including objects made in Meta Ads Manager) in Meta, then reads it back. Affects live spend. |
set_budget | publish | destructive | Changes the budget of a live ad set (Pluto ID or Meta ID) or campaign (launchId, or a synced campaign's Meta ID as campaignId) as a decimal string in the ad account's currency (e.g. "49.99"), keeping its period. Affects live spend. |
set_bid | publish | destructive | Changes a live ad set's bid cap or cost per result goal (bidAmount) or ROAS goal (roasGoal), following Meta's rules for its bid strategy; bidStrategy switches the strategy for ad sets with their own budget. Affects live spend. |
set_schedule | publish | destructive | Moves a live ad set's start or end, or sets its ad scheduling (day parts on the hour; lifetime budgets only), then reads it back. Affects live spend. |
duplicate_at_meta | publish | destructive | Copies a live ad, ad set or campaign in Meta, paused unless statusOption says otherwise, with deepCopy and renameOptions; the copies appear in the synced lists as soon as they exist. An active copy spends. |
delete_at_meta | publish | destructive | Deletes a live ad (adId), or a live ad set with its ads (adSetId), in Meta (Pluto ID or Meta ID) and reads it back as deleted; a launch's object is then removed from the launch, any other leaves the synced lists. Irreversible; affects live spend. |
set_targeting | publish | destructive | Changes a live ad set's targeting in Meta (locations, excluded locations, age, gender, placements, custom and excluded audiences), by adSetId or metaAdSetId. Also detailed targeting (interests, behaviours and demographics from search_targeting, as narrowing groups). Detailed exclusions can only be removed. Enforces Meta's special ad category rules: lookalikes, behaviours and demographics are refused in housing, employment and financial ads. Reaching the EU needs beneficiary and payer unless the ad set or its ad account has them; otherwise it's refused with a fix link. Affects live delivery. |
search_targeting | read | read | Searches Meta's detailed targeting options for an ad account: interests, behaviours and demographics, with Meta's ID, type, taxonomy path, description and size estimate. With a special ad category, only what that category may use. |
list_audiences | read | read | An ad account's custom and lookalike audiences, read from Meta: size estimate (null when unknown), status, and whether restricted categories may use them. |
Jobs
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_jobs | read | read | Lists running jobs and those that finished in the last 15 minutes, with progress, recent throughput and whether each can be cancelled or retried. Filter by status, startedBy: me or kind. |
get_job | read | read | Returns a job's status and counts (total, succeeded, failed). |
wait_for_job | read | read | Follows a job until it finishes or timeoutSeconds pass (default 60, max 300), streaming notifications/progress (items done of total) when the request carries a progress token. |
list_job_items | read | read | Pages through a job's per-item outcomes (key, status, attempts, provider ID, output, error). |
retry_job | write | destructive | Queues a finished job's failed items again with fresh attempts and re-opens the job; follow it with wait_for_job. |
cancel_job | write | destructive | Stops a job's queued items. |
Media
| Tool | Permission | Effect | What it does |
|---|---|---|---|
evaluate_creative_condition | read | read | Check recorded evidence with three-valued conditions and citations. |
get_creative_context | read | read | Resolve creative composition and available source media. |
compare_variants | read | read | Compare recorded creative copy and deduplicated evidence. |
get_creative_performance | read | read | Read ad/day facts, denominators and observed composition coverage. |
get_creative_memory_usage | read | read | Read pooled deep-analysis and basic-indexing allowance, reservations and trial/month window. |
search_creative_memory | read | read | Search published evidence and matching original-media moments with explicit coverage. |
search_creative_ads | read | read | Find ads by copy, name and media evidence, or by an indexed example, with explicit coverage. |
get_creative_memory | read | read | Read published interpretation, cited evidence, coverage and processing requests for media. |
quote_creative_memory | write | create | Quote selected media or sections without inference or allowance reservation. |
request_creative_memory | write | destructive | Accept the exact approved quote, reserve the whole request and start processing. |
list_creative_memory_requests | read | read | List creative processing requests and durable job IDs. |
cancel_creative_memory_request | write | update | Stop unfinished processing and release unused reservations; published work remains. |
create_creative_library_import | write | destructive | Start one basic-index import of all media at a pinned cutoff, or of 1-1,000 selected media; each item reserves the included allowance. |
get_creative_library_import | read | read | Read an import's scope, cutoff, checkpoint and progress joined with each accepted request's real status. |
list_creative_library_imports | read | read | List library imports, newest first. |
list_creative_library_import_items | read | read | List each source revision and attempt an import considered, with its request and status. |
pause_creative_library_import | write | update | Stop new submissions at the checkpoint; accepted requests continue. Works while read-only. |
resume_creative_library_import | write | destructive | Continue a paused import from its checkpoint, reserving allowance for new items. |
cancel_creative_library_import | write | update | Stop new submissions for good; accepted requests keep running and are reported as they are. Works while read-only. |
retry_creative_library_import_item | write | destructive | Queue a new attempt for a failed or cancelled item with a fresh reservation; the earlier attempt stays. |
list_creative_monitors | read | read | List coverage runs of loops' creative monitor steps. |
get_creative_monitor | read | read | Read a coverage run: membership cutoff, completion, freshness, true/false/unknown counts and offered or limited matches. |
list_creative_monitor_items | read | read | List the subjects a coverage run examined, with truth, selection and the loop item's outcome. |
quote_creative_precision | write | create | Quote one explicit question about an indexed image or an original video interval of at most 90 seconds, without starting it. |
request_creative_precision | write | destructive | Accept the exact approved precision estimate and reserve it before any work. |
get_creative_precision | read | read | Read a precision result with raw and aligned timing kept separate; a stale result is withheld. |
cancel_creative_precision | write | update | Stop an unfinished precision request and release its reservation. Works while read-only. |
get_creative_correction_context | read | read | Read correctable evidence, its provenance and the exact snapshot a correction or label must send. |
correct_creative_evidence | write | update | Replace or retract one evidence entry as an attributed correction; no model runs. |
release_creative_corrections | write | destructive | Remove all active corrections for a source and restore its recorded evidence; history stays. |
get_creative_correction | read | read | Read one immutable correction or release. |
list_creative_corrections | read | read | List corrections and releases, newest first. |
record_creative_learning_label | write | create | Record a content-accuracy, query-relevance or user-utility annotation; it edits no evidence. |
get_creative_learning_label | read | read | Read one learning label. |
list_creative_learning_labels | read | read | List learning labels, newest first. |
register_creative_experiment | write | create | Register an immutable experiment design pinned to exact creative revisions; nothing is launched or spent. |
amend_creative_experiment | write | create | Append a new design version for the reviewed version; earlier versions keep their qualification. |
set_creative_experiment_status | write | update | Record that an experiment is running, completed or cancelled; controls no delivery. |
observe_creative_experiment | write | create | Snapshot stored ad/day facts for the registered delivery ads; descriptive only. |
get_creative_experiment | read | read | Read an experiment with a current or earlier design version. |
list_creative_experiments | read | read | List registered experiments, newest first. |
list_creative_experiment_outcomes | read | read | List an experiment's immutable outcome snapshots. |
record_creative_lineage | write | create | Record a variant or family claim with exact revisions and provenance; similarity never confirms. |
list_creative_lineage | read | read | List current relationship claims, optionally for one exact revision. |
get_creative_lineage | read | read | Read one relationship claim, current or superseded. |
list_media | read | read | Pages through the media library (archived media excluded unless asked for; deleted media never), newest first by default, with a short-lived thumbnail URL and stable links per item. |
get_media | read | read | Returns media by ID (archived included, since ads may still use it) with short-lived read URLs (urls: original, download, preview, expiresAt) and stable links. |
import_media | write | create | Imports images and videos the server downloads from HTTPS URLs (no private or local addresses; size and type limits apply). |
create_media_uploads | write | create | Starts direct uploads for files you hold: returns, per file, presigned URLs to PUT the bytes to (single or multipart parts), then call complete_media_upload. |
complete_media_upload | write | update | Finishes an upload after the bytes are PUT (multipart: every part's ETag) and queues processing. |
update_media | write | update | Renames media or moves it to a folder (folderId: null is the library root). |
move_media | write | update | Moves many media to a folder (or the root) in one command with a changeset. |
archive_media | write | destructive | Hides media from the library in one command with a changeset. |
unarchive_media | write | update | Brings archived media back into the library in one command with a changeset: each item returns to its folder, and the launches that use it are unchanged. |
delete_media | write | destructive | Deletes media for good: its files are removed and it stops counting toward the organization's media storage. |
list_media_folders | read | read | Lists media folders by name with how many media each holds. |
edit_media_folders | write | destructive | Creates, renames or deletes media folders. |
view_media | read | read | Shows you media library images and videos as images you can see: one still per image, and for a video up to 8 evenly spaced frames with their timestamps, plus its duration, aspect ratio and whether it has sound. 1 to 8 media per call. |
view_ads | read | read | Shows you ads: a launch's ads (drafts and published) with their creative and effective copy, and ads that exist only in Meta (by Meta ad ID) with their creative read from Meta; delivery status when live. 1 to 8 ads per call. |
Comments, activity and inbox
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_comments | read | read | Lists comment threads on an ad, in order, each with replies, reactions and attachments. |
add_comments | review | create | Posts comments or replies on ads. Members can start an internal thread (internal: true) that guests never see. |
create_comment_attachment_upload | review | read | Returns a presigned URL to PUT a file to (with the exact headers to send) for attaching to a comment that isn't in the media library. |
update_comments | review | destructive | Changes comments, per comment: edit (your own; body, expectedVersion), delete (yours, or any as admin), restore (undo a delete within an hour), resolve / reopen a thread, react / unreact with an emoji, follow / unfollow a thread. |
set_comment_internal | write | update | Marks a comment thread internal (the workspace's guests never see it, its replies or its reactions) or visible to guests again. |
get_ad_activity | read | read | Returns an ad's history, oldest first: every change with who made it (person, agent or API key) and the comment threads. |
follow_ad | read | update | Subscribes you to an ad's comments and changes (Subscribe in the app), or with subscribed: false unsubscribes, and returns who is subscribed. Needs a person. |
follow_thread | read | update | Subscribes you to a comment thread (Subscribe to thread in the app), or with subscribed: false unsubscribes. Needs a person. |
get_inbox | read | read | Lists the acting person's notifications (mentions, replies, status and assignment changes), newest first, with the unread count. |
update_inbox | read | update | Marks notifications read or unread, archives or unarchives them, or snoozes them until a time (until: null unsnoozes). |
notify_members | write | create | Posts a notification to the Inbox of people in this workspace and/or emails them (to: me, workspace or people with userIds; channels: inbox, email). Only people in the workspace; rate limited. |
Performance
| Tool | Permission | Effect | What it does |
|---|---|---|---|
get_insights | read | read | Returns Meta ad performance (spend, impressions, clicks, CTR, CPC, CPM, purchases, revenue, ROAS, results and cost per result, leads, outbound clicks, video plays at 25/50/75/95 %, reach and frequency, ...) for a period, optionally filtered and broken down, with an optional comparison period. |
get_launch_results | read | read | Returns how one launch's published ads performed (same metrics as get_insights, broken down by ad set unless breakdown says otherwise). |
refresh_insights | write | update | Imports Meta insights for every synced ad account now instead of at the next hourly run, or returns the import already queued or running. |
Profit (Pluto Profit)
Revenue ROAS counts sales; these tools say whether the store, its products and its ads make money after
product costs, fees, shipping and the ad spend itself, so an agent can optimize for profit. They read the
Pluto Profit store a workspace admin connected. Connecting is a person's approval in Pluto Profit:
connect_pluto_profit returns the link for them to open. Connecting with the store's API key is done by a
person in the app; no tool takes a key.
| Tool | Permission | Effect | What it does |
|---|---|---|---|
get_pluto_profit_integration | read | read | Returns whether a Pluto Profit store is connected (connected, reauthorize or disconnected), how (oauth or apiKey), which store, who connected it and when, and how current the store's data is. |
get_profit_summary | read | read | Returns the store's profit for a period: revenue, costs, gross, contribution and net profit, blended ROAS, POAS, MER, break-even ROAS, spend and platform-reported revenue per ad channel, cost coverage and freshness. A value Pluto Profit can't compute is null, never 0. |
get_product_profitability | read | read | Returns product contribution after allocated ad spend: revenue, COGS, gross margin, allocated fees, shipping and ad spend, and contribution per product, worst margin first by default. |
get_ad_profitability | read | read | Estimates whether each Meta ad, ad set or campaign makes money, from its spend and purchase value against the store's break-even ROAS, worst first. Always labelled an estimate. |
connect_pluto_profit | admin | create | Starts connecting a Pluto Profit store and returns authorizationUrl for a person to open: they sign in to Pluto Profit, pick the store and approve read access. Needs a credential that acts for a person. |
reconnect_pluto_profit | admin | create | Starts the approval again for a connection that needs it (reauthorize), to pick another store, or to move from an API key; returns authorizationUrl like connect_pluto_profit. |
disconnect_pluto_profit | admin | destructive | Disconnects the Pluto Profit store: deletes the stored approval or API key and cached figures. |
Slack
Post what an agent found or did to the Slack channels a workspace admin connected. Connecting is a person's
consent in Slack: connect_slack returns the link for them to open. Messages go only to connected channels,
at most once per idempotency key; broadcast mentions (<!channel>, <!here>) are sent as plain text.
| Tool | Permission | Effect | What it does |
|---|---|---|---|
get_slack_integration | read | read | Returns whether a Slack workspace is connected (connected, reauthorize or disconnected), which one, and the connected channels with their status. |
connect_slack | admin | create | Starts connecting Slack and returns authorizationUrl for a person to open and approve in Slack (the agent can't consent for them). Needs a credential that acts for a person. |
disconnect_slack | admin | destructive | Disconnects Slack: deletes the stored access, so nothing can post until an admin connects again. |
list_available_slack_channels | admin | read | Pages through the Slack channels Pluto Ads can post in (public ones, and private ones it was invited to), marking the connected ones. |
set_slack_channels | admin | update | Replaces the connected channels (up to 50) after Slack confirms each new one exists and isn't archived. |
post_to_slack | write | create | Posts a message (optional title, text in Slack formatting) to a connected channel and returns its Slack timestamp. A retry whose first attempt may have been posted is refused instead of posting twice. |
Loops
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_loops | read | read | Lists loops, most recently changed first: drafts and loops that are on or off by default, or one status (draft, active, paused, archived); mine narrows to the acting person's. Each has enabled, its trigger and its settings (triggerConfig), nextRunAt, lastRun (with finishedAt), its owner and updatedAt. |
get_loop | read | read | Returns a loop with its whole definition, version, what's still to finish, the latest change, whether it's on and whether it may act without asking; revisions: true adds every saved version. |
create_loop | write | create | Saves a new loop draft from a LoopDefinition; malformed definitions are refused with every problem. |
update_loop | write | update | Saves a new version: the definition replaces the saved one, checked against expectedVersion. |
validate_loop | read | read | Checks a LoopDefinition without saving it: errors (malformed, with paths), issues (unfinished) and ready. Use it before create_loop or update_loop. |
archive_loop | write | destructive | Archives a loop: it stops running, leaves the list and can't change; its chat, revisions and runs stay. |
unarchive_loop | write | update | Brings an archived loop back: Off when it was on once, else a draft. |
delete_loop | write | destructive | Deletes a draft that was never turned on and never ran, with its revisions; a loop with a history is refused (archive it instead). Its chat stays with its person. |
set_loop_enabled | write | update | Turns a loop on or off. Turning it on checks that it's complete (every problem by step) and that a schedule's time zone is known. |
run_loop | write | create | Starts a live run now, whatever the trigger, and returns it queued. Changes that need publish wait for a person's approval in the app unless the loop may act. |
test_run_loop | write | create | Starts a test run: reads, checks conditions and runs AI steps, and plans every action, notification and wait without doing any. |
list_loop_runs | read | read | Lists a loop's runs, newest first (mode: live or test), with status, what started them and counts by outcome. |
get_loop_run | read | read | Returns one run with every step per item, stepStates, the items with their outcome and the approvals with their exact effect. |
cancel_loop_run | write | update | Stops a run that's still going: nothing more starts, what ran stays done and its pending approvals are withdrawn. |
undo_loop_changes | write | destructive | Sets each budget or delivery status a step changed back to what it replaced (or one change, index), as you, through the per-change guards. Needs publish for money and delivery. |
Change sets
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_change_sets | read | read | Plans of live changes, newest first (status: open, decided or statuses; loopId, runId, chatId, mine). |
get_change_set | read | read | Returns one plan with its summary, every change (before and after with currency, reason, exact call, result), totals and versions; version shows an older one. |
propose_changes | write | create | Proposes budget, delivery and bid changes as one plan for the acting person to approve in the app; with changeSetId, the next version of a plan you proposed. |
revise_change_set | write | update | Sends a plan back with feedback; the agent that proposed it makes a new version. The person it's for, or an organization owner or admin. |
edit_change_set | write | update | A new version without AI: remove changes by key, set new values, checked against the limits. |
undo_change | publish | destructive | Puts one applied change back to its value before, through the per-change guards. |
Meta connection and import
| Tool | Permission | Effect | What it does |
|---|---|---|---|
get_meta_integration | read | read | Returns the Meta connection status (connected, reauthorize or disconnected) with the reason to reconnect (expired or permission, and the missing permission), the last sync, the ad accounts with currency, timezone and the Pages each may publish from, and the available Pages. |
connect_meta | admin | create | Starts connecting Meta and returns authorizationUrl, a Facebook Login for a person to open and approve (the agent can't consent in Meta). Needs a credential that acts for a person. |
reconnect_meta | admin | create | Starts Facebook Login again for a connection in reauthorize (expired, or a permission withdrawn), or one whose missingPermissions isn't empty, and returns the URL for a person to open. |
sync_meta | write | update | Queues a sync of the Meta connection's ad accounts (currency, timezone), Pages, campaigns, ad sets and ads, or returns the sync already queued or running. |
list_meta_campaigns | read | read | Pages through one ad account's synced Meta campaigns (for launching into an existing campaign, and for loop conditions), by ID: objective, status, budgets, when it was created (ageDays), special ad categories and the delivery issues Meta reports. |
list_meta_ad_sets | read | read | Pages through the synced Meta ad sets (read-only structure the Meta sync keeps current), by Meta ID: of one ad account or campaign, or matching search (part of the name, or the exact ID). Each carries its learning phase (learningStatus), delivery issues, placements, age and special ad categories. |
list_meta_ads | read | read | Pages through the synced Meta ads (read-only structure), by Meta ID: of an ad account, campaign or ad set, or matching search. Each has its creative type, delivery issues and age, and its ad set's learning phase and placements. |
get_meta_delivery | read | read | Returns, for synced campaigns, ad sets and ads by ID (mixed, up to 500), whether each is in Meta's learning phase and the delivery issues Meta reports; IDs the sync doesn't have come back in unknownIds. |
preview_meta_import | read | read | Shows what importing picked campaigns or ad sets would create, without importing: the same preview as Import from Meta in the app. |
import_from_meta | write | create | Imports picked campaigns or ad sets of one ad account into Pluto: each becomes a launch linked to its live Meta objects (read back from Meta), so its status, budgets and pending changes can then be managed like a launch Pluto published. |
get_import_report | read | read | Returns the report of an import_from_meta job, live while it runs: per pick its status and, once imported, the launch, every ad set and ad that came along with what stays managed in Meta (managedInMeta: field, metaField, reason), what didn't come along and why (notImported), and how many files were copied or already in the library. |
set_meta_account_pages | admin | update | Sets which Facebook Pages an ad account may publish from (replaces the list). |
set_spending_limit | admin | destructive | Sets, changes or removes (amount: null) an ad account's spending limit in Meta: when the account has spent this amount, Meta stops every ad in it. A decimal string in the ad account currency; the change is read back from Meta. The in-app Agent always asks first. Affects live spend. |
disconnect_meta | admin | destructive | Disconnects Meta: revokes Pluto Ads' access in Meta when possible, deletes the stored token and cancels open syncs. |
Comments on ads (Facebook and Instagram)
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_social_comments | read | read | Pages through the comments on the posts your Meta ads run as, newest first, with replies, the ad, who in Pluto replied or moderated and pending actions; filter by platform, hidden, unanswered, adId or search. access names missing Meta permissions. |
get_social_comment | read | read | Returns one comment with its replies, the ad and actions waiting for Meta. |
reply_to_social_comment | publish | create | Replies publicly as the Page or Instagram account, in the comment's thread (up to 2,200 characters). |
hide_social_comment | publish | update | Hides a comment from everyone except its author and your Page's admins. Reversible. |
unhide_social_comment | publish | update | Shows a hidden comment again. |
delete_social_comment | publish | destructive | Deletes a comment and its replies in Meta. Irreversible; needs confirm: true. |
sync_social_comments | write | update | Reads the comments of every ad's post from Meta now, or returns the read already queued. |
Leads
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_lead_forms | read | read | Lists the lead forms of the connected Facebook Pages: questions (never answers), Meta's lead count, the leads Pluto holds and how far they've been read. |
list_leads | read | read | Pages through leads newest first, without answers; filter by formId, pageId, adId or since. access names missing Meta permissions. |
get_lead | write | read | Returns one lead with its answers (personal data) and disclaimer responses. Each read is audited. |
sync_leads | write | update | Reads every Page's lead forms and new leads from Meta now, or returns the read already queued. |
Workspace and settings
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_members | read | read | Lists workspace members (name, email, role, status, assigned ads) in join order. |
invite_members | admin | create | Invites people by email; each invitation is emailed to them and expires after 7 days. |
list_invitations | read | read | Lists invitations, newest first: pending ones by default, or all with their status (pending, accepted, revoked, expired). |
revoke_invitation | admin | destructive | Cancels a pending invitation so its link stops working. |
edit_members | admin | destructive | Changes members: role sets owner, admin or member (owners for anything involving the owner role); suspend blocks their access (sessions and agents acting for them) while keeping their work; reactivate restores it; remove takes them out of the workspace (their work stays). |
rename_workspace | admin | update | Renames the workspace (1-80 characters). |
update_workspace_settings | admin | update | Changes workspace settings: shareNewLaunchesWithGuests: true makes every new launch start shared with the workspace's guests, whoever creates it. Read the current value in get_context. |
create_workspace_logo_upload | admin | read | Returns a presigned URL to PUT a new logo to (PNG, JPEG or WebP, at most 2 MB) with the exact headers to send. |
set_workspace_logo | admin | update | Makes an uploaded image (the objectKey from create_workspace_logo_upload, after the PUT) the workspace logo, or removes the logo with objectKey: null. |
get_workflow_settings | read | read | Returns the workspace's review statuses (with the default and usage counts), labels (tags), copy templates and country groups, with their IDs and versions. |
edit_statuses | write | destructive | Creates, updates, archives, unarchives, reorders, deletes or sets the default review status. |
edit_labels | write | destructive | Creates, updates, archives, unarchives or deletes labels (tags). |
edit_copy_templates | write | destructive | Creates, updates or deletes reusable ad copy templates (primary text, headline, description, call to action). |
edit_country_groups | write | destructive | Creates, updates or deletes named groups of countries (ISO 3166-1 alpha-2 codes) used for targeting. |
get_meta_defaults | read | read | Returns the settings every new launch starts with: the workspace defaults (Page, Instagram account, copy, call to action, pixel and conversion event, attribution, placements, creative enhancements, URL/UTM, naming, delivery) and each ad account's overrides with its effective settings. |
set_meta_defaults | write | update | Changes launch defaults for the workspace, or overrides them for one ad account (adAccountId). |
get_dashboard | read | read | Returns the workspace's analytics dashboard layout (sections and metrics) and its version. |
set_dashboard | write | update | Replaces the whole analytics dashboard layout with sections (as returned by get_dashboard; sections and cards you leave out are removed), or restores the default with reset: true. One call is one save, for everyone in the workspace. Give expectedVersion: if someone saved since, nothing changes and the conflict returns the current layout. |
get_preferences | read | read | Returns the acting person's saved view preferences in this workspace (Board or Table, grouping, ordering, visible properties, notification settings, Getting started). |
set_preference | read | update | Saves one of the acting person's view preferences by key: creativeView, boardDisplay, tableDisplay or notifications (an object of settings), gettingStarted.skipped (a list of meta, invite, launch, loop, agent), gettingStarted.hidden (true or false) or agentApprovals (risky, always or auto; only the person signed in to Pluto chooses auto). |
list_models | read | read | Lists the models the in-app Agent and Loops' AI steps run on (today Claude Opus 5.5), with the default and the workspace's model settings. |
update_model_settings | admin | update | Sets the workspace's default model and the models members may use. Owners and admins; give expectedVersion. |
get_agent_approval_settings | read | read | Whether members may let the in-app Agent act without asking, the budget guardrail, and whether the caller may choose Run everything now. |
set_agent_approval_settings | admin | update | Sets whether members may choose Run everything and the budget guardrail (a percent, and an optional amount per change in one currency). Owners and admins; give expectedVersion. |
list_agent_chats | read | read | Lists the person's chats with the in-app Agent, most recently updated first, with their status and whether an answer is unread. Needs a person. |
get_agent_chat | read | read | Returns one of the person's Agent chats with its newest messages (100 by default; before pages back) and their approvals. Needs a person. |
rename_agent_chat | read | update | Renames one of the person's Agent chats, or shares a loop's chat with the people who can see the loop. Needs a person. |
delete_agent_chat | read | destructive | Deletes one of the person's Agent chats with its messages; a running answer stops first. Needs a person. |
stop_agent_chat | read | update | Stops the answer the Agent is writing in one of the person's chats; what already ran stays done. Needs a person. |
mark_agent_chat_read | read | update | Marks the latest answer in one of the person's Agent chats as read. Needs a person. |
get_agent_guidance | read | read | Returns the person's own guidance for the in-app Agent in this workspace. Needs a person. |
set_agent_guidance | write | update | Replaces the person's guidance (up to 4,000 characters; empty clears it). It applies to chats they start from then on, never to loops, and never grants permissions. Needs a person. |
list_agent_skills | read | read | Lists the person's skills by name and description, without their prompts. Needs a person. |
get_agent_skill | read | read | Returns one of the person's skills with its prompt, by skillId or name. The in-app Agent loads skills this way. Needs a person. |
create_agent_skill | write | create | Creates a skill: a name run as /name, a description the Agent picks it by, and the prompt (body). Up to 50 per person. Needs a person. |
update_agent_skill | write | update | Changes a skill's name, description or prompt; give expectedVersion. Needs a person. |
delete_agent_skill | write | destructive | Deletes one of the person's skills. Needs a person. |
list_webhook_events | read | read | Lists the events a webhook can subscribe to (plus * for everything), e.g. launch.published, ad.review_changed. |
Workspaces
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_workspaces | read | read | Lists the workspaces the person behind the credential belongs to, with their role in each; current marks the one the credential acts in. Keys and M2M clients see only their own. |
create_workspace | organization | create | Adds a new, empty workspace to the organization (Add workspace in the app). Needs Organization admin access and a person who is an organization owner or admin; the in-app Agent asks first. The credential stays in its current workspace. |
Organizations
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_organizations | read | read | Lists the organizations the person behind the credential belongs to, with their role and how many workspaces they can open in each. Keys and M2M clients see only their own. |
get_organization | read | read | Returns an organization: name, slug, audience, your role, paid members and free guests, and its workspace count. |
update_organization | organization | update | Renames the organization, changes its slug, sets who it's for (audience: brand, multi_brand or agency), or whether members can connect agents (membersCanConnectAgents). Organization owners and admins. |
get_ai_improvement | read | read | Whether the organization shares usage data to improve Pluto's AI ("Help improve Pluto's AI"): while on, the text of its Agent and loop AI runs is kept with their traces, redacted; once it's turned off, kept text is deleted within 30 days. Never used to train models. Off by default. Members, not guests. |
set_ai_improvement | organization | update | Turns "Help improve Pluto's AI" on or off. Organization owners and admins; works while the organization is read-only. The in-app Agent can't call it. |
list_organization_workspaces | read | read | Lists the workspaces you can open in an organization, recently opened first, and whether you may add one (with the price one more adds). |
list_archived_workspaces | organization | read | Lists the organization's archived workspaces and the ones scheduled for deletion, with the date each is deleted for good. These are the ones restore_workspace brings back. |
get_workspace_status | read | read | Returns whether a workspace is active, archived (read-only) or scheduled for deletion, who did it and when, and the date it's deleted for good. |
archive_workspace | organization | destructive | Archives a workspace: read-only, off the workspace switcher and the plan's workspace count, all data and connections kept. Its loops are paused and queued work is cancelled; ads live in Meta keep running. Not the organization's last active workspace. |
delete_workspace | organization | destructive | Schedules a workspace for deletion (confirmName: its exact name): nobody can open it, and it's deleted for good after 30 days unless restored. Its API keys, agents, webhooks, guests, invitations and Meta, Slack and Pluto Profit connections end at once; nothing changes in Meta, so live ads keep running. |
restore_workspace | organization | update | Brings an archived or scheduled workspace back to where it was; coming back active counts toward the plan's workspace limit. Loops stay paused; credentials, guests and connections aren't restored. |
export_workspace | organization | read | Returns the download URLs of a workspace's export: launches, ad sets and ads as CSV, and media originals as a zip. Download them with the same credential; the files don't pass through MCP. Active, archived and scheduled workspaces. |
list_organization_members | organization | read | Lists members (paid seats) and guests (free) with their role, workspace access and workspaces, plus pending invitations. |
update_organization_member | organization | update | Changes a person's organization role, workspace access, workspaces or status. Making a guest a member adds a paid seat. |
remove_organization_member | organization | destructive | Removes a person from the organization and every workspace in it; their work stays. Their API keys, agent clients and connected agents are revoked; their loops are paused and, with their Agent chats, move to transferTo (an owner or admin) to review. |
get_member_offboarding | organization | read | Returns what suspending or removing someone touches: the loops they own, how many Agent chats they have, and the credentials that stop working. |
invite_to_organization | organization | create | Invites someone by email as a member (every workspace or selected ones), an admin, or a free guest who reviews in the selected workspaces. |
revoke_organization_invitation | organization | destructive | Cancels a pending organization invitation in every workspace it grants. |
resend_organization_invitation | organization | update | Sends a pending or expired invitation's email again; it expires 7 days from now. |
list_organization_credentials | organization | read | Lists every API key, agent client and connected agent in the organization: who it acts for, the workspace, scopes, last use (time and address), created and expiry. Never returns a secret. |
revoke_organization_credential | organization | destructive | Revokes any credential in the organization; its next request is refused. Not the credential you're calling with. |
transfer_credentials | organization | update | Takes over a leaving person's API keys: each is re-issued to the person you act for with a new secret (in this result only) and the old key stops at once. Agent clients and connected agents aren't re-issued (skipped) and stop when the person leaves. |
Credentials and webhooks
| Tool | Permission | Effect | What it does |
|---|---|---|---|
list_api_keys | admin | read | Lists active API keys (name, kind, prefix, scopes, who created them, last use). Never returns a secret. |
create_api_key | admin | create | Creates an API key with the scopes you choose, never beyond your own, and an optional expiresAt. The key is in secret in this result only. |
rotate_api_key | admin | destructive | Replaces an API key with a new one (same name, scopes and expiry) and revokes the old one in the same step. The new key is in secret in this result only. |
revoke_api_key | admin | destructive | Revokes an API key; its next request is refused. Not the key you're calling with. |
list_agent_clients | admin | read | Lists registered agent clients (client credentials) with their kind, scopes and creator. Never returns a secret. |
create_agent_client | admin | create | Registers an agent client (m2m, or mcp acting for your person) with scopes never beyond your own. The secret is in clientSecret in this result only. |
revoke_agent_client | admin | destructive | Disconnects an agent client; its tokens are refused from the next request. Not the client you're calling as. |
list_agent_connections | read | read | Lists agents people connected by signing in from their MCP client or claimed after they registered with auth.md; admins see everyone's, others their own. |
revoke_agent_connection | write | destructive | Disconnects a connected agent, so it has to sign in or register again. Not the connection you're calling through. |
list_webhooks | admin | read | Lists webhooks with their URL, events, version and latest delivery outcome. Never returns a secret. |
create_webhook | admin | create | Creates a webhook for chosen events at a public HTTPS URL. The signing secret is in secret in this result only. |
update_webhook | admin | update | Changes a webhook's URL and/or events (expectedVersion from list_webhooks); the secret stays. |
rotate_webhook_secret | admin | destructive | Replaces the signing secret at once; the new one is in secret in this result only. |
test_webhook | admin | create | Sends one signed webhook.test event to that endpoint and returns the delivery and its job. |
delete_webhook | admin | destructive | Deletes a webhook; deliveries stop at once. |
list_webhook_deliveries | admin | read | Pages through a webhook's recent deliveries: status, attempts, the endpoint's HTTP status and the last error. |
list_audit_log | admin | read | Lists the workspace's audit log, newest first: every change people, the in-app Agent, loops, API keys and MCP clients made, with the person it was for (onBehalfOf), where it came from (origin: the Agent chat or loop run) and who approved it. Same filters as GET /audit-log. |
Billing
Billing belongs to the organization that holds the workspace: one subscription and one invoice for all its
workspaces. The plans are Pro (1 workspace included and up to 5), Business (10 workspaces included and more without
a cap, priced in steps for workspaces 11-25, 26-100 and 101 and up, each lower per workspace; with workspace limits, usage by
workspace and connected agent, and CSV export) and Enterprise (through sales: volume pricing and custom terms on a
1 to 3 year agreement, invoiced with net 30 terms); every new organization starts with a 14-day free trial:
one workspace, a set amount of AI usage for the whole trial, 5 deep analyses, and usage by member and source. The Agent and Loops work on every plan, including the trial. Paid members include AI
usage, pooled across the organization; guests are free.
Usage beyond what's included, and extra media storage, only run when an owner or admin turns them on, each up to
a monthly limit it never goes past (each has a default an owner or admin can change); owners and admins
can also set a monthly limit per member, and on Business per workspace.
Agents you connect here (your own Claude, Cursor or scripts) aren't billed as usage: only the rate limits apply.
Billing tools need organization access (an organization owner or admin, and a credential with Organization
admin access), except get_usage, export_usage and
get_workspace_usage, which return the acting person's own usage to members and everything to owners and admins.
export_usage, get_workspace_usage (the cost of one workspace) and get_workspace_costs (the cost of each
workspace, for rebilling clients) are Business; without it they answer plan_limit (usage_breakdown,
upgradeTo: business).
Payment always happens in a browser: the tools return a Stripe link for a person to open, and the subscription
changes once Stripe confirms. See Billing.
| Tool | Permission | Effect | What it does |
|---|---|---|---|
get_billing | organization | read | Returns the organization's plan and subscription: status, what the plan includes, members and guests, workspaces used and allowed with the next workspace's price and, on Business, its price steps (workspaces.tiers), Pro and Business with this organization's prices (estimates, with workspaceTiers on Business) and the one that fits the team (recommended), volumePricing (Business from 100 workspaces: an optional "Talk to us about volume pricing" at salesUrl), the Enterprise agreement (enterprise: its term and end date, contracted against used members, workspaces and included AI, and notices from 80%; null without one), a scheduled plan change, the next invoice, trial or read-only state, media storage (used per workspace, extra storage and its limit), recent invoices with their lines in a fixed order (plan, members, workspaces by tier, AI usage, deep_analysis, storage), tax, reverse charge, collection, dueDate and poNumber, and a payment waiting for authentication (with the link a person opens to confirm it). The fields match GET /organizations/{id}/billing. |
start_checkout | organization | create | Returns a link to subscribe to a plan (plan: pro or business; cadence: monthly or yearly, 20% off) for the organization's members. Pro is per member with 1 workspace included and up to 5, each extra one a monthly add-on; Business is a base price with 5 members and 10 workspaces, then per extra member and per extra workspace, priced in steps for workspaces 11-25, 26-100 and 101 and up, each lower per workspace. get_billing returns this organization's prices (plans[].estimates). A person opens it and pays; nothing is charged by the tool. Enterprise is through sales. |
change_plan | organization | update | Changes a subscribed organization's plan (plan, optional cadence): to Business at once, prorated; to Pro at the end of the period, when the organization has up to 5 workspaces (more answers conflict, reason: too_many_workspaces). Workspace limits stop applying on Pro and are kept. Choosing the current plan cancels a scheduled change. Enterprise plans change through sales. Returns the same overview as get_billing. |
get_billing_details | organization | read | Returns what invoices show about the organization: business name (the legal or billing name, never a person's), billing email, billing address, tax IDs (type, value, country and verification status), tax exemption, taxStatus (ready, address_needed: a US address needs its state and ZIP code, or not_collecting), poNumber (Enterprise) and paymentTerms (card or net_30). |
update_billing_details | organization | update | Changes the business name, billing email, billing address (the next invoice uses them, and tax follows the address) or, on Enterprise, the PO number printed on every invoice (poNumber, 1 to 40 characters, null removes it), and adds (addTaxId: type, value) or removes (removeTaxId) a tax ID such as an EU VAT number. Returns the billing details. |
open_billing_portal | organization | create | Returns a link to the billing portal, where a person updates the payment method (flow: "payment_method"), cancels (flow: "cancel"), resumes a scheduled cancellation, downloads invoices or changes details. On Enterprise there's no cancelling: the agreement runs until its end date, and flow: "cancel" answers conflict (reason: agreement_term, runsUntil). |
sync_billing | organization | update | Reads the subscription from Stripe now instead of waiting for Stripe's notification, and returns the same overview as get_billing. |
start_trial | organization | update | Starts the organization's free trial when it has no plan and never had a trial or a subscription, and returns the same overview as get_billing. Otherwise refused: choose a plan with start_checkout. |
get_usage | read | read | Returns AI usage for the current or previous billing period (period): the included pool and what's used, why the Agent stopped if it did, by source and by day, and with by a breakdown by member, source, loop or model on every plan; on Business also by workspace or connected agent (by: agent: each MCP client, agent client or API key, whose activity is counted in requests and never charged), with the workspaceId and agentId filters. On request it adds the records (search and filters). Members get their own usage. |
export_usage | read | read | Returns the billing period's usage records as CSV (the same scope and filters as get_usage): time, workspace, member, source, provider, model, tokens and charge. Business (the trial has no CSV); members get their own. |
get_workspace_costs | organization | read | Returns what each workspace cost in a billing period (period: current, previous or an id from cycles; or from and to) or on one invoice (invoice: an invoice's id from get_billing, its per-client statement), for rebilling clients: its share of the workspace add-on (fee: 0.00 for the workspaces the plan includes, prorated when added or removed mid-period; on Business each extra workspace carries the average of the price steps), its AI usage (aiIncluded, paid by the plan; aiOnDemand, billed) and total, archived and deleted workspaces that cost something included, plus a Shared row for the base price, members, extra media storage and adjustments. The rows add up to the period's charges before tax (invoiced), or with invoice to that invoice's subtotal before tax, to the cent. format: "csv" also returns them as CSV. Business. |
get_usage_limits | organization | read | Returns the organization's limit on usage beyond what's included (on or off, amount), every member limit and every workspace limit, each with what's used this period and its version, and workspaceLimits: whether workspace limits apply on this plan (Business). |
set_extra_storage | organization | update | Turns extra media storage on or off (enabled) and sets its monthly limit (limit as a decimal string, or null for the default), with expectedVersion. Only with a subscription. Returns the storage overview. |
set_usage_limit | organization | update | Sets or removes a monthly limit: scope organization (usage beyond what's included; enabled turns it on or off), workspace (setting one is Business; removing one works on every plan) or member (targetId is the workspace or person), limit as a decimal string or null (for organization, the default; there's always a limit), and expectedVersion. |
contact_sales | write | create | Sends an Enterprise enquiry to the sales team for the organization (name, work email, company, optionally members, workspaces and a message) and returns its id. Any member. |
get_workspace_usage | read | read | Returns the current workspace's AI usage this billing period, its monthly limit if the organization set one, its stored media and usage per person. Owners and admins see everyone's usage in the workspace; members see their own. Business. |
To list the tools yourself:
curl -s https://api.plutoads.ai/api/v1/mcp -H "authorization: Bearer $PLUTO_API_KEY" \
-H 'content-type: application/json' -H 'mcp-protocol-version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq -r '.result.tools[].name'Not in MCP
Everything a person does in the app has a tool, except what only a person in a browser can do:
- Consenting to an agent connection. Connecting Claude or Cursor by signing in, or claiming an agent that registered with auth.md, is the person's consent; an agent can't give it for itself. It can list and disconnect connections.
- Approving in Meta.
connect_metastarts Facebook Login, but the person approves it. Over REST,POST /integrations/meta/connectstarts the same login for a credential and returns the same URL. - Approving in Pluto Profit.
connect_pluto_profitandreconnect_pluto_profitstart the approval, but the person signs in to Pluto Profit, picks the store and allows read access. Over REST,POST /integrations/pluto-profit/authorizereturns the same URL. Connecting with an API key instead takes the store's key, which an agent never handles, because it would end up in the agent's transcript and logs: a workspace admin pastes it in Integrations, or sends it toPOST /integrations/pluto-profit/verifyandPOST /integrations/pluto-profit/connectfrom their own script. - Approving in Slack.
connect_slackstarts the install, but the person allows Pluto Ads in Slack. - Paying.
start_checkoutandopen_billing_portalreturn a Stripe link; a person enters the card and confirms the purchase or change there. A payment the bank wants authenticated (3D Secure) is confirmed by a person on the invoice link inget_billing(paymentActionRequired). - Switching a session's workspace. Credentials don't switch; see Workspaces above.
- Person-only actions: ownership, deleting or restoring the organization, and your own account. These
stay with the person, signed in to the app, and no API key, agent, MCP tool or REST credential can do
them: granting or removing ownership (
POST /organizations/{id}/members/{userId}/ownership), deleting and restoring an organization (POST /organizations/{id}/delete,/restore), and everything under/account(exporting your data, deleting your account). They end or hand over everything an agent could act with, so a leaked or misused credential must never reach them. None removes the person it acts for either: a person leaves a workspace or the organization themselves. Archiving, deleting and restoring a workspace, suspending and removing people are tools, with theorganizationscope, and the in-app Agent asks the person before each. - Your account's list of connected agents. A person sees and disconnects their own agents across
workspaces in their account settings. Agents use
list_agent_connectionsandrevoke_agent_connectionin their workspace, and organization adminslist_organization_credentials. - Chatting in a loop's builder.
POST /loops/{id}/chatstarts the in-app Agent chat a person uses in the loop builder; an MCP client is the agent itself.create_loopcan attach a chat withsourceChatId. - Approving what the in-app Agent proposed. Publishing, billing, people and other admin changes the
Agent proposes in a chat wait for the person to approve or decline them in the app, so an agent never
approves its own proposals.
get_agent_chatshows what's waiting. Asking the in-app Agent itself (starting a chat, sending a message, retrying an answer) isn't a tool either: an MCP client is already an agent and calls the tools directly. The chat tools act for a person, so API keys and M2M clients can't use them. - Approving or rejecting a change set. A plan of live changes is approved or rejected by the person
it's for (or an organization owner or admin), signed in to the app, so an agent never approves what an
agent proposed.
get_change_setshows what's waiting. - Approving a loop's actions, and letting a loop act without asking. Both are a person's decision about
money and public actions, made signed in to the app.
get_loop_runshows what's waiting. - Signing in and out, offline sync and live updates. These carry the app itself; agents read and write through the tools, which run the same commands.
Links into the app
Checklist items (get_launch_review, get_publish_preview) carry a fix: a pluto: link to the exact place the
item is fixed in the Pluto Ads app. The in-app Agent shows these links as links. Other clients can show the item's
label and message and leave the link out.
| Link | Opens |
|---|---|
pluto:launch/<launchId>, pluto:ad-set/<launchId>/<adSetId>, pluto:ad/<launchId>/<adId> | The launch, ad set or ad. |
The same with #<path> | The field to fix, by the API path the item names (#adAccountId, #settings.page, #targeting.budget). |
pluto:launch/<launchId>#add-creatives, #ungrouped | Adding creatives to the launch, or its Ungrouped creatives. |
pluto:page/<page> | One of the app's pages: integrations, billing, members, tags, statuses, media, loops, inbox, developers, ai. |