MCP reference
What Tempio exposes
Tempio is one workspace over several ad platforms. Every read is normalised to the same shape whichever platform it came from, and every write is a change request first: a draft that is checked against the workspace's guardrails when it is written and again at the gate, applied only when a person or a rule the workspace wrote clears it.
Tempio's programmatic surface is a Model Context Protocol server. There is no separate REST API; the MCP server and the in-app agent call the same org-scoped tool layer, so a capability behaves identically whichever client calls it.
- Endpoint:
https://app.tempio.ai/api/mcp(streamable HTTP; no SSE transport) - Credentials: a bearer key minted under Settings → MCP, or an OAuth token from a client that signs in instead of sending a header. Either way the workspace is taken from the verified credential, never from tool arguments.
- Rate limits: 120 requests per minute per IP before authentication, 60 per minute per credential after.
Two tools carry the whole surface. tempio_help describes it and tempio_manage runs it, dispatching on {entity, action} onto the same capabilities the explicit surface exposes one tool each.
Which of the two a client sees is the credential's surface, and every credential carries one. compact is the default: a key already in use registers tempio_manage and tempio_help in place of the individual tools, and reaches every capability through them. A key minted as explicit registers the individual tools, plus tempio_help beside them. The capabilities, the scopes and the answers are the same on both.
The first call: tempio_help
Ask before you guess. The catalogue is generated at request time from the same tables the tools run on — the route table, the tool registry, the change operations and their payload schemas, the warning registry, and each live connection's own capabilities — so it cannot drift from the surface, and it is filtered to the scopes your credential holds, which means discovery, description and execution agree.
tempio_help({
path?: string, // "manage", "manage campaign", "manage campaign create", "drafts", "money"
q?: string, // keyword search across names, descriptions and payload field names
detail?: "summary" | "full", // "full" adds parameters and never removes them
})| Parameter | What it does |
|---|---|
path | Walks the catalogue from the root down to one entity, then to one verb. Called with no arguments it returns the root: what Tempio is, the entity model, the verbs, the draft lifecycle, the rules about money and ratios, warnings and links, and two worked calls. |
q | Searches names, descriptions and payload field names. This is how you find a capability nobody told you the path of. |
detail | "full" adds each call's parameters to whatever path or q selected. It only ever adds. |
Before telling someone a capability is unavailable, search here:
tempio_help({ q: "<what they asked for>" })The catalogue is generated from the same tables the tools run on, so if it is not here, it is not there either — and if it is here, the path says how to call it.
The entity model
The model is workspace → connection → ad account → campaign → ad group → ad, and it is the same on every platform. Creatives, keywords, media assets, negative lists, asset groups and lead forms hang off the level that holds them. Where a platform's own levels differ (an ad set, an asset group, a product group), Tempio maps them onto these, so one loop over campaigns, ad groups and ads works everywhere. Every entity carries the id Tempio gave it; pass that id back, never the platform's. Beside that tree, a brand_kit is the workspace's own brand as data; getting it returns the colour roles with their contrast, the type, the voice, a compliance-line template with its permit slots, the forbidden words and the labelled products. Scraping one reads a public website and answers a proposed kit — name, colour roles, type — that nothing saves until it is passed to update. A preset is a kind of still the workspace can make; listing them returns the copy fields each template renders and the ceiling on each. A creative_asset is a still Tempio made — a scene with no text in it and the copy drawn over it, one composite per aspect ratio; listing them returns each with its copy fields and a URL per ratio, and it reaches an ad only by being registered as a media asset for one account, uploaded by an upload_media draft and named in a create_ad draft's creative.media. A creative_batch is one generation request: getting it returns what was asked for, each generation's status and why one failed, and the stills that have landed so far.
What you can do: tempio_manage
tempio_manage({
entity: ManageEntity,
action: ManageAction,
params?: Record<string, unknown>,
})The outer schema is deliberately loose on params, because MCP publishes one JSON Schema per tool and a discriminated union of every entity and action would put every capability's own schema into that one envelope. The real contract is the inner parse: {entity, action} selects a capability, and that capability's own schema is what params is validated against. A wrong field answers invalid_payload: <path>: <message> — the field's own message and its path, not a generic refusal. Runtime dispatch selects the typed schema; it never replaces it.
Scopes are enforced on the call, not by hiding it. A route whose scope your credential does not hold answers forbidden: <entity>.<action> needs the '<scope>' scope; this credential holds <held>, so an agent is told what to ask for instead of concluding the capability does not exist.
| Entity | Action | Scope | Resolves to | Status |
|---|---|---|---|---|
connection | list | read | list_connections | Available |
account | list | read | list_ad_accounts | Available |
update | draft | draft_changes (update_account_defaults · account) | Available | |
campaign | list | read | list_campaigns | Available |
get | read | get_campaign | Available | |
create | draft | draft_changes (create_campaign · campaign) | Available | |
update | draft | draft_changes (update_budget, update_status, update_name, update_bid, update_schedule, update_targeting · campaign) | Available | |
remove | draft | draft_changes (remove_entity · campaign) | Available | |
ad_group | list | read | list_structure | Available |
create | draft | draft_changes (create_ad_group · ad_group) | Available | |
update | draft | draft_changes (update_budget, update_status, update_name, update_bid, update_schedule, update_targeting · ad_group) | Available | |
remove | draft | draft_changes (remove_entity · ad_group) | Available | |
ad | list | read | list_structure | Available |
create | draft | draft_changes (create_ad · ad) | Available | |
update | draft | draft_changes (update_status, update_name, update_creative · ad) | Available | |
remove | draft | draft_changes (remove_entity · ad) | Available | |
creative | list | read | list_structure | Available |
create | draft | draft_changes (create_creative · creative) | Available | |
update | draft | draft_changes (update_creative · creative) | Available | |
keyword | list | read | list_structure | Available |
create | draft | draft_changes (create_keyword · keyword) | Available | |
update | draft | draft_changes (update_keyword · keyword) | Available | |
remove | draft | draft_changes (remove_entity · keyword) | Available | |
negative_list | create | draft | draft_changes (create_negative_list · negative_list) | Available |
update | draft | draft_changes (update_negative_list · negative_list) | Available | |
asset_group | create | draft | draft_changes (create_asset_group · asset_group) | Available |
update | draft | draft_changes (update_asset_group · asset_group) | Available | |
lead_form | create | draft | draft_changes (create_lead_form · lead_form) | Available |
media | list | read | list_media_assets | Available |
create | draft | draft_changes (upload_media · media) | Available | |
draft | list | read | list_drafts | Available |
get | read | list_drafts | Available | |
create | draft | draft_changes | Available | |
update | draft | update_draft | Available | |
approve | approve | approve_drafts | Available | |
reject | approve | reject_drafts | Available | |
revert | draft | revert_draft | Available | |
plan | list | read | list_media_plans | Available |
get | read | get_media_plan | Available | |
create | draft | create_media_plan | Available | |
update | draft | update_media_plan | Available | |
materialize | draft | materialize_plan | Available | |
plan_item | create | draft | upsert_plan_item | Available |
update | draft | upsert_plan_item | Available | |
remove | draft | delete_plan_item | Available | |
metrics | list | read | get_metrics | Available |
campaign_metrics | list | read | get_campaign_metrics | Available |
brief | get | read | get_account_brief | Available |
research | search | read | research_keywords | Available |
geo | search | read | geo_search | Available |
search_term | list | read | get_search_terms | Available |
placement | list | read | get_placements | Available |
change_event | list | read | get_change_events | Available |
platform_request | get | read | platform_api_request | Available |
mutate | draft | platform_api_mutate (platform_api_request · account) | Available | |
library_ad | get | read | get_library_ad | Available |
search | read | search_library | Available | |
library_advertiser | list | read | list_library_advertisers | Available |
get | read | get_library_advertiser | Available | |
create | draft | add_library_advertiser | Available | |
search | read | list_library_advertisers | Available | |
library_watch | list | read | list_library_watches | Available |
create | draft | set_library_watch | Available | |
update | draft | set_library_watch | Available | |
remove | draft | set_library_watch | Available | |
swipe | list | read | list_swipe | Available |
create | draft | update_swipe | Available | |
update | draft | update_swipe | Available | |
remove | draft | update_swipe | Available | |
swipe_collection | list | read | list_swipe | Available |
create | draft | update_swipe | Available | |
update | draft | update_swipe | Available | |
remove | draft | update_swipe | Refused — A swipe collection is not removed on its own: it exists while ads are saved into it. Remove the ads with update_swipe and `remove: true`, one adId at a time. | |
playbook | list | read | list_playbooks | Available |
get | read | get_playbook | Available | |
guardrail | list | read | list_guardrails | Available |
get | read | list_guardrails | Available | |
approval_chain | list | read | list_approval_chains | Available |
get | read | list_approval_chains | Available | |
connection_permission | list | read | list_connection_permissions | Available |
allocation | propose | draft | propose_reallocation | Available |
timeline | get | read | get_timeline | Available |
pacing | get | read | get_pacing | Available |
phase | list | read | list_phases | Available |
create | draft | upsert_phase | Available | |
update | draft | upsert_phase | Available | |
remove | draft | delete_phase | Available | |
envelope | list | read | list_envelopes | Available |
goal | create | draft | upsert_goal | Available |
update | draft | upsert_goal | Available | |
remove | draft | delete_goal | Available | |
brand_kit | get | read | get_brand_kit | Available |
update | draft | update_brand_kit | Available | |
scrape | read | scrape_brand_kit | Available | |
preset | list | read | list_creative_presets | Available |
creative_asset | list | read | list_creative_assets | Available |
create | draft | generate_creatives | Available | |
resize | draft | resize_creative | Available | |
register | draft | register_creative_media | Available | |
creative_batch | get | read | get_creative_batch | Available |
lane_colour | update | draft | planner_colour_set | Available |
lane_order | update | draft | planner_order_set | Available |
plan_dependency | create | draft | plan_dependency_add | Available |
remove | draft | plan_dependency_remove | Available | |
key_date | list | read | planner_key_dates_list | Available |
create | draft | planner_key_date_upsert | Available | |
update | draft | planner_key_date_upsert | Available | |
remove | draft | planner_key_date_remove | Available |
108 routes over 46 entities, 107 of them available today. A route whose scope the credential does not hold is refused by name rather than hidden.
The platform_request entity is the escape hatch: a call straight at a platform's own API, through that platform's client, for the endpoint Tempio has not normalised yet. It is off until an owner turns it on, and a mutation through it is still a change request. The escape hatch covers what it refuses and what Tempio does not verify.
A connection whose coverage is connect_only lists its ad accounts and takes raw requests, and Tempio models nothing else on it: no campaigns, no daily numbers, and an empty operations list, so no modelled draft reaches it. Read it with platform_request.get, which executes the read on the platform's own API and returns the platform's own answer in the platform's units. Change it with platform_request.mutate and a sentence saying what the request changes and why; the change waits at the gate for a person, because no approval rule clears a raw request. Both are off until a workspace owner turns raw requests on in Settings.
entity: "draft", action: "create" is the n-ary form: params is a list of operations with dependsOn indices, and it is how a campaign, its ad group and its ads arrive as one reviewable batch. The single-entity write forms are sugar over it — campaign.create is a one-entry batch through the same call — so there is one write path and not two.
A row that reads Refused is live and reachable and answers a refusal by design, because the verb has nothing to do at that entity. The cell carries the sentence it refuses by, and that sentence names what to call instead.
The ad library
Five entities read and write the ad library — a corpus of other people's ads captured from the public transparency archives, shared by every workspace rather than scoped to yours.
library_adsearches the corpus and reads one ad. Results are deliberately not filtered to your workspace, because the corpus is public data about third parties; what is yours alone issaved,savedNoteandwatchedon each row.library_advertiserlists and reads the registry, adds an advertiser by website, name or a pasted archive URL, and answers the similar-advertisers read throughsearchwithsimilarTo.library_watchis competitor tracking: the watches this workspace holds, its unread alerts, and one call that watches, unwatches and changes the alert and digest settings, because all three are one row.swipeandswipe_collectionare the swipe file.listreturns the collections beside the items, so the file renders in one call.
Three of those carry the draft scope and create no change request. Drafting is for a write to an ad entity in an account you connected; adding an advertiser to a shared corpus, watching one, or saving somebody else's public ad to a board changes nothing in anyone's ad account and has nothing to apply. draft is the scope because it is what a tool may put in front of a person, not a promise that it drafts — the same reason create_media_plan carries it.
Read the warnings on a library answer before reading an absence. A run that stopped at its ceiling makes an advertiser's ads a sample; an archive that covers only some regions makes an absence here not an absence everywhere; a spend range is always labelled an estimate with its basis named, and is null rather than zero where no platform published one; and the hook, offer, angle and audience are Tempio's reading of the ad's own copy, never something the advertiser said. Coverage is the rule all of that follows from.
The envelope
Every result is { data, report?, warnings }. warnings is always present and empty when nothing is wrong. Each warning carries a class (stale_sync, connection_expired, permission_missing, write_gate_closed, budget_guardrail, currency, other), a severity (info, degraded, blocking) and a sentence saying what to do. State a degraded warning before the number it qualifies; never infer a value a warning says is unavailable; a blocking warning means say what is blocked and stop. Every record a result names carries a url a person can open in Tempio.
Every call returns { data, report?, warnings }. The envelope is assembled in one place for every capability, so no tool can forget it and its shape cannot vary by tool. report states once, authoritatively, what a scope covers: its currency (null when the scope spans currencies), the timeZone the dates are in, the dateRange, the entity level, and whether the scope is the returned_rows or all_rows.
warnings is a list, always present, [] when there is nothing wrong. Each entry carries a code (snake_case, an open set), a class, a severity, a message written as what to do about it, and a subject with that record's deep-link when it is about one record.
| Class | What it says |
|---|---|
stale_sync | The numbers are older than they look, or incomplete. |
connection_expired | A grant needs re-authorising, and until it does this platform's rows stop moving. |
permission_missing | The credential cannot do what was asked, and what it would need. |
write_gate_closed | A draft cannot be written here, said before one is attempted. |
budget_guardrail | The amount is close to, or over, a cap — said on the read, not only at the refusal. |
currency | The money is not what it appears to be. |
other | The degrade target for a code no registry row covers, and the home for a registered code that is about none of the other six. |
Severity belongs to the code, not the class: info, degraded, blocking, least to most severe.
The class is the stable thing to branch on; the code set is open and adapters add to it. A code with no registry row degrades to class other at severity info and keeps its own message rather than failing the call. A warning never changes a value: an FX warning does not round a number and a staleness warning does not hide one.
A link on every record
Every record a result names carries an absolute url into the app, because an agent hands the string to a person and a relative path is not something a person can open.
| Kind | Path |
|---|---|
draft | /drafts/{id} |
batch | /drafts?batch={correlationId} |
campaign | /campaigns/{id} |
ad_group | /campaigns/{campaignId}/ad-groups/{id} |
ad | /campaigns/{campaignId}/ad-groups/{adGroupId}?ad={id} |
plan | /plans/{id} |
media | /media?asset={id} |
connection | /connections?connection={id} |
account | /connections?account={id} |
library_advertiser | /library/advertisers/{id} |
library_ad | /library/ads/{id} |
swipe | /library/swipe?item={id} |
playbook | /docs/playbooks/{slug} |
A link is built from a row a query already returned under the workspace, never from an id in the arguments, so an id from another workspace never produces a URL because it never produces a row. The URL is not a grant either: opening it needs a session, and the page checks membership regardless.
Drafts and the gate
A draft moves pending_approval → applying → applied, partial or failed, or is rejected. It is applied when a person approves it from the review queue or when an approval rule the workspace wrote clears it, and the row records which (approverKind). Approving is a separate call from drafting, and a credential needs the approve scope for it. While a draft is applying, its outcome is in flight or unknown: do not retry, do not draft it again; read it back.
Applying a cleared change to a platform is in private beta.
Money and the numbers
Money is an integer in minor units beside its currency, and the server stamps the currency from the account, so never convert or round. Ratios (CPC, CPM, CTR, ROAS) are computed at read time and are null when the denominator is zero, never 0. A blended figure across currencies is a labelled estimate, not a total. A ranking marked raw is an order, not a ranking. A date range is in the workspace's reporting time zone, stated in the report.
Playbooks and prompts
Beyond the tools, this server serves resources and prompts. A resource is a document a client can attach to a conversation: the playbook shelf at tempio://playbooks, one playbook at tempio://playbooks/{platform}/{topic}, this catalogue at tempio://help, and an account brief at tempio://workspace/accounts/{adAccountId}/brief. A playbook is what Tempio keeps about a platform, or about one part of Tempio itself; every one carries the date a person last confirmed it, and one that has gone more than ninety days without a review says so in its warnings rather than leaving a reader to notice. A prompt is a task handed over with the workspace context and the relevant playbook already composed into it: plan a flight, review an account, explain a set of warnings, write the brief a reviewer reads at the gate. Both are filtered to this credential's scopes exactly as the tools are, and a workspace resource reaches only the accounts this credential's workspace owns.
Two calls
Two calls that cover most sessions. First, read the account:
tempio_manage({ entity: "brief", action: "get", params: { adAccountId } })returns what is running, what it spent and what changed, with its warnings (omit adAccountId for every account the credential can see). Second, draft a campaign with an ad group and two ads as one reviewable batch:
tempio_manage({ entity: "draft", action: "create", params: { connectionId, operations: [ create_campaign, create_ad_group (dependsOn 0), create_ad (dependsOn 1), create_ad (dependsOn 1) ] } })and hand the person the url on the batch. Nothing reaches a platform until it is cleared.
Credentials
Two kinds reach the same surface, and past the door nothing knows which one arrived.
- A bearer key. Minted under Settings → MCP, prefixed
tmpio_, shown once, hashed at rest, revocable, and bound to one workspace. The creator's membership is re-read on every call, so a key dies with the membership. This is the right credential for a server-to-server agent and forclaude mcp add. - An OAuth token. For a client that signs in through OAuth rather than sending a bearer header — the clients a connector directory lists cannot send a header of their own. The workspace is chosen once on the consent screen and the token is bound to it for life, exactly as a key is. OAuth for MCP clients covers the flow end to end. Tempio names a particular directory's connector as supported only once a handshake against a real workspace has been recorded; until then what is true is the sentence above — a client that signs in through OAuth reaches this surface under the same scopes a key does.
A credential also carries a surface. The choice is made at the mint, under Settings → MCP: compact registers tempio_manage and tempio_help, and explicit registers each capability as a tool of its own alongside tempio_help. Both reach the same capabilities under the same scopes; the choice is how much of your agent's context the tool list costs.
Scopes
Every credential carries one or more scopes.
| Scope | What it allows |
|---|---|
read | Connections, ad accounts, campaigns, structure, media assets, metrics, drafts, media plans. |
draft | Create change requests (drafts), edit and undo them, and edit media plans. Nothing reaches a live ad account. |
approve | Approve or reject pending drafts. Opt-in when the credential is minted. The in-app agent never holds this scope; a draft is also applied when a rule the workspace wrote approves it, and the result says which. |
The capabilities
| Scope | Capability | Purpose |
|---|---|---|
read | geo_search | Resolve a place name to the Google location ids a campaign can target. |
get_account_brief | What Tempio has learned about each connected ad account from its last 90 days: coverage, totals, a 28-day period against the one before it, the objective mix, spend concentration, the biggest campaigns, ROAS and CPA rankings, spenders with no conversions, and weekday rhythm. | |
get_brand_kit | Read the workspace's brand kit: its name, the logo, the six colour ROLES (primary, secondary, accent, surface, onSurface, onAccent) each with its hex, the intent a template reads it for, and the two contrast ratios a composite draws text in (onSurface over surface, onAccent over accent), the typography with the descriptor a model acts on, the voice lines to write copy in, the compliance-line template with its {permit}/{permits} slots, the forbidden words, and the labelled products. | |
get_campaign | Read ONE campaign by its Tempio id: status, objective, budget, flight window, delivery status and the platform's own external id, plus `url`, an absolute link a person can open in Tempio. | |
get_campaign_metrics | Per-campaign performance for a date range as { rows, ranking }, highest spend first. | |
get_change_events | Google's own change history: who changed what, when, from where, and exactly which fields moved. | |
get_creative_batch | Read one creative batch: its status (queued, running, complete, partial, failed), how many generations are in each state, what was asked for, each generation with its model and — when it failed — why, and every asset that has landed so far with its copy fields and one composite URL per aspect ratio. | |
get_library_ad | One ad from the library, in full: every creative in order, the landing page it points at and how many of the advertiser's other ads point at the same one, the derived hook, offer, angle and audience (Tempio's reading of the ad's own copy, never something the advertiser said), every region the platform published reach or spend for, the longevity score with the formula version it was computed under, and this workspace's own swipe entries for it — collection, note and all. | |
get_library_advertiser | One advertiser: its identity on each public archive with that source's last run, its status and whether the source is enabled at all; ad counts by status and format and how many ads appeared in the last thirty days, all computed at read time; the landing-page rollup with the query strings intact, because the UTM parameters ARE the competitor's campaign structure; and the advertisers described the same way. | |
get_media_plan | Get one media plan with its items in board order. | |
get_metrics | Blended cross-channel KPIs plus a per-channel breakdown for a date range. | |
get_pacing | How one campaign or one planned flight is tracking against its plan, day by day. | |
get_placements | Where a Google display, video or Performance Max campaign's money actually went: the sites, apps and YouTube channels the ads ran on. | |
get_playbook | Read one playbook. | |
get_search_terms | What people actually typed before a Google search ad showed, with the keyword that matched them and how. | |
get_timeline | The workspace's operating calendar: every live campaign and every planned flight that touches the window, grouped by ad account. | |
list_ad_accounts | List canonical ad accounts. | |
list_approval_chains | List the approval chains this workspace has written, in the order they are considered: a chain says how many signatures a change needs and whose, and the first enabled chain that matches a draft is snapshotted onto it when the draft is created. | |
list_campaigns | List ALL campaigns (including ones with no recent delivery) with canonical ids, status, objective and daily budget. | |
list_connection_permissions | Who reaches each connection in this workspace, and how far. | |
list_connections | List the ad platform connections in this workspace with their status, last sync time, last sync attempt and what each platform can actually do. | |
list_creative_assets | The creative gallery: this workspace's generated stills, newest first, each with its copy fields, one composite URL per aspect ratio, its warnings and a deepLink to the batch it came from. | |
list_creative_presets | List the creative presets this workspace can generate with: the seven system presets (product_shot, graphic_ad, marketplace_listing, prize_hero, spec_sheet, urgency_story, early_bird) and any this workspace cloned, a workspace row shadowing the system row of the same key. | |
list_drafts | List change-request drafts and their statuses, newest first, or one batch in the order it applies when you name its correlationId. | |
list_envelopes | The budget envelopes a person has set on these campaigns or plan items. | |
list_guardrails | The caps a budget change on each ad account is held to: the daily cap, the first-budget cap and the movement ceiling, with where each one came from and the workspace override behind it when there is one. | |
list_library_advertisers | The advertiser registry: who the library holds ads for, alphabetically, with each one's ad count computed at read time, the domain it was resolved from, its industry, category and tags, and whether THIS workspace watches it. | |
list_library_watches | List the advertisers this workspace tracks, with their alert and digest settings, plus the unread activity alerts they have produced. | |
list_media_assets | List the workspace's media library — the images and videos Tempio holds and has handed to the platforms — newest first. | |
list_media_plans | List media plans newest-first with their item count and total budget. | |
list_phases | Every phase on one campaign or plan item, earliest first. | |
list_playbooks | List the playbooks Tempio keeps: researched per-platform operating notes, plus the house's own notes under the `tempio` platform. | |
list_structure | List the ad groups, ads, creatives or keywords under a campaign or ad group, newest first. | |
list_swipe | This workspace's swipe file: every collection it has, and the saved ads themselves, newest first. | |
planner_key_dates_list | The key dates in a window: the marketable periods of the year for a region, and the dates this workspace has added of its own, earliest first. | |
platform_api_request | Read straight from a connected platform's own API on Meta, Google, TikTok, Reddit, X and Microsoft Advertising. | |
research_keywords | Google keyword, topic and interest research, for resolving what to target BEFORE drafting it. | |
scrape_brand_kit | Read a public website and PROPOSE a brand kit from it — nothing is saved. | |
search_library | Search the ad library — other people's live ads, captured from the public transparency archives of Meta, Google, LinkedIn and TikTok. | |
draft | add_library_advertiser | Add an advertiser to the shared library by website, by name, or by pasting a Meta, Google, LinkedIn or TikTok ad-library URL — at least one is required, and a pasted URL is the manual override where a domain resolves to nothing. |
create_media_plan | Create a media plan (free to edit — plans are working documents; only materializing creates gated campaign drafts). | |
delete_goal | Remove one goal by id. | |
delete_phase | Remove one phase by id. | |
delete_plan_item | Remove an item from a plan. | |
draft_changes | Draft one or more changes as ONE reviewable batch. | |
generate_creatives | Queue a batch of ad stills from a preset over the workspace's brand kit. | |
materialize_plan | Turn every plan item into a create-campaign DRAFT at the approval gate — nothing goes live and nothing spends until each draft is approved at the gate — by a person, or by a rule the workspace wrote (a rule never approves a create, so these wait for a person). | |
plan_dependency_add | Link two planner lanes: `successor` starts after `predecessor` ends. | |
plan_dependency_remove | Remove the link between two planner lanes. | |
planner_colour_set | Give one or many planner lanes a colour, or clear it. | |
planner_key_date_remove | Delete one of this workspace's own key dates by its `id`, as planner_key_dates_list returns it in `custom`. | |
planner_key_date_upsert | Add a key date to this workspace's planner, or edit one: pass `id` to edit in place, omit it to add. | |
planner_order_set | Place one planner lane directly before or after another lane in the same account group. | |
platform_api_mutate | Draft a raw write to a connected platform's own API on Meta, Google, TikTok, Reddit, X and Microsoft Advertising. | |
propose_reallocation | Run the budget allocator over this workspace: every active campaign with an enabled envelope, scaled by the multiplier of the phase it is in today, funded in phase order under the workspace's daily cap and each account's own, clamped to the guardrail's maximum daily movement. | |
register_creative_media | Use one composite of a creative asset in an ad on one ad account. | |
resize_creative | Render one more aspect ratio of an existing creative asset from the scene it already has — no model call and no generation spent, so it is free and takes seconds. | |
revert_draft | Undo a change that already landed by drafting its inverse at the same gate — never an automatic rollback. | |
set_library_watch | Watch an advertiser, stop watching it, or change its alert and digest settings — one call for all three, because it is one row. | |
update_brand_kit | Create or change the workspace's brand kit — owner or admin only; anyone else is refused with `forbidden`. | |
update_draft | Edit the payload of a draft still waiting at the gate, or one that failed, instead of rejecting it and drafting again. | |
update_media_plan | Rename a plan, rewrite its brief, move its window, or archive it. | |
update_swipe | Save an ad to this workspace's swipe file, write or change its note, move it to another collection, or remove it. | |
upsert_goal | Put a goal on one campaign or plan item, or edit one in place with `id`. | |
upsert_phase | Add or edit a phase on one campaign or plan item: pass `id` to edit in place, omit it to add. | |
upsert_plan_item | Add or update a planned campaign: platform, concept name, WHOLE-FLIGHT budget in minor units of the plan's currency, flight dates inside the plan window. | |
approve | approve_drafts | APPROVE a batch, or a set of loose drafts, as one act. |
reject_drafts | Reject a batch, or a set of loose drafts, so none of them can ever be applied. |
68 capabilities. On the explicit surface each is a tool of its own; on the compact surface each is a row of the matrix above, and both reach it under the scope named here.
get_account_brief follows the money rules per account: one brief is one account in one currency, so its money is never comparable with another brief's without converting, and a brief older than seven days is flagged stale — say when it was computed before quoting it.
Drafting a batch
draft_changes is the one write capability, reached as entity: "draft", action: "create". It takes a connection and a list of operations and writes them as a single reviewable batch of change requests. A campaign, the ad group inside it and the ad inside that are three entries in one call; dependsOn is the index of the earlier entry that creates this one's parent, so a child never carries an id that does not exist yet. A batch carries at most 50 entries, and a longer one is refused rather than truncated.
{
"connectionId": "8f2a1c34-6d0b-4f19-9c73-2b5e7a1d0c48",
"operations": [
{
"operation": "create_campaign",
"targetLevel": "campaign",
"payload": {
"adAccountId": "1d9c4e57-3a20-4b6f-8e11-77c0d4a9b312",
"name": "AU spring intake — traffic",
"objective": "OUTCOME_TRAFFIC",
"budget": { "mode": "daily", "amountMinor": 25000 },
"specialAdCategories": []
}
},
{
"operation": "create_ad_group",
"targetLevel": "ad_group",
"dependsOn": 0,
"payload": {
"name": "AU 25-54 broad",
"optimizationGoal": "LINK_CLICKS",
"billingEvent": "IMPRESSIONS",
"bidStrategy": "lowest_cost",
"targeting": {
"geo": { "include": [{ "type": "country", "code": "AU" }] },
"age": { "min": 25, "max": 54 }
}
}
},
{
"operation": "create_ad",
"targetLevel": "ad",
"dependsOn": 1,
"payload": {
"name": "Spring intake — single image",
"adType": "SINGLE_IMAGE",
"identity": { "pageId": "102938475610293" },
"creative": {
"primaryTexts": ["Every ad account, one room."],
"headlines": ["Spring intake is open"],
"finalUrls": ["https://example.com/spring"],
"media": [
{
"mediaAssetId": "b41f8e02-95c7-4a3d-8f60-1e2d5c9a7b04",
"role": "image",
"format": "square"
}
]
}
}
}
]
}Money in that call is minor units: 25000 is 250.00 in the ad account's own currency. Which currency that is, is not yours to state — the server stamps it onto every payload from the target account and overwrites whatever arrives in a currency field. Every create lands paused on the platform, so approving this batch builds the structure without spending; going live is a second update_status change through the same gate.
The result is the batch, its deep-link, and who cleared it:
{
"correlationId": "3c7d5a90-1b48-4e62-a0f5-9d6c2e18b473",
"reviewUrl": "https://app.tempio.ai/drafts?batch=3c7d5a90-1b48-4e62-a0f5-9d6c2e18b473",
"approvedBy": "pending",
"drafts": [
{
"id": "5a0e7c21-98d4-4b3f-8607-c1f2a9b40d56",
"operation": "create_campaign",
"targetLevel": "campaign",
"targetEntityId": null,
"status": "pending_approval",
"correlationId": "3c7d5a90-1b48-4e62-a0f5-9d6c2e18b473",
"batchSeq": 0,
"approverKind": null,
"validation": null,
"warnings": []
}
]
}reviewUrl is the gate for the whole group and never one row of it: give it to the person you drafted for. It is absolute, so it opens from wherever your agent is running. approvedBy is "pending" when the batch is waiting for a person and "policy" when a rule the workspace wrote cleared every row of it on the way in — those are the two authorities, and the field says which one this batch got. batchSeq is the row's place in the run, counting from zero, and approverKind fills in with human, mcp_key or policy once something has cleared the row.
A draft that needs a change does not have to be rejected and drafted again: update_draft replaces a waiting or failed row's payload whole, which re-validates it, re-stamps the currency, re-runs the guardrails, and puts a failed row back to waiting, which is how a stopped batch is resumed. revert_draft undoes a change that already landed by drafting its exact inverse at the same gate, so an undo is a change request like any other rather than a rollback behind your back.
approve_drafts and reject_drafts each take either a correlationId — the whole batch — or a draftIds list of loose rows, and move them whole or not at all. Approving claims the rows and the apply runs on its own moments later, so read the outcome back with list_drafts; approving a correlation again continues a stopped batch from the rows that already landed, rather than starting it over.
Reading structure
list_structure answers what sits under a campaign: ad_group, ad, creative or keyword, newest first, with a parentId to scope it to one campaign or one ad group. Each row carries its Tempio id — the id a draft points at — the platform's own externalId, name, status, money in minor units of the row's own currency where the level holds any, and writable.operations: the operations of that level intersected with what the connected platform can express. An operation missing from that list cannot be drafted at all, and an empty list means the connection has no adapter registered. writable is a capability answer and not a permission: every change it allows is still a change request at the gate.
list_media_assets lists the images and videos the workspace holds, each with the ad account it belongs to, its kind, its fileName, its dimensions, and the platform side of the record — platformExternalId (a Meta image hash, a TikTok video id, a Google asset resource name) and platformStatus. Reference an asset from a creative by its platformExternalId, the id the platform holds those bytes under, rather than uploading the same file twice. The media library covers how a file gets there.
list_connections carries the same kind of answer one level up. Its capabilities object holds operations (the change operations the platform can express), budgetLevels (where money attaches — Google budgets are campaign-level, so a Google ad group has no budget to change), objectives (the accepted values, or the string "unrestricted"), deleteSemantics, and supportsValidate (whether a draft can be dry-run against the platform before it is applied). deleteSemantics: "remove" — Google and TikTok — means a removal is irreversible: the entity is gone, not archived, and nothing here brings it back. "archive_only" is Meta, whose archive is update_status with status archived, and that is undoable.
Renamed tools
The write surface was five narrow tools over four operations; it is draft_changes over every operation it drafts (the raw platform request is its own entity). The old names are deleted rather than aliased, because no external consumer held them.
create_campaign_draftisdraft_changeswith onecreate_campaignentry.update_campaign_budget_draftisdraft_changeswith oneupdate_budgetentry, at whichever level holds the budget.update_campaign_status_draftisdraft_changeswith oneupdate_statusentry.approve_draftisapprove_drafts, which takes acorrelationIdor a list of ids.reject_draftisreject_drafts, on the same selection.
Client setup
Claude Code
claude mcp add --transport http tempio https://app.tempio.ai/api/mcp --header "Authorization: Bearer YOUR_KEY"Cursor (mcp.json)
{
"mcpServers": {
"tempio": {
"url": "https://app.tempio.ai/api/mcp",
"headers": { "Authorization": "Bearer YOUR_KEY" }
}
}
}Any MCP client that can send an Authorization header over streamable HTTP works the same way. A client that cannot send a header of its own signs in through OAuth instead: OAuth for MCP clients.
The draft model
Every write from every client is a change request. A draft records who asked for it (a person, the in-app agent, or an MCP credential), what it changes, the state the entity was in before it, and the guardrail checks it passed. Applying it is a separate act under the approve scope: the guardrails run again, the change goes through the platform adapter, Tempio verifies it landed, and the audit log records it. The authority that cleared it is on the row either way — you, a credential you minted, or a rule your workspace wrote.
Security
Where your platform credentials live, how a workspace is isolated, what a key can reach, what is on the record — and what is not true yet.
Resources and prompts
Beyond the tools, the Tempio MCP server serves documents your client can attach to a conversation and tasks it can hand to a model. What each one is, how to read it, and how the age of a document reaches you.