---
title: Auth & scopes
description: How OAuth works for the MCP server, what each scope grants, what an agent can and cannot change, and how access is revoked.
---

import Callout from '#docs/components/mdx/callout.astro';

The AdCrunch MCP server is OAuth-protected. Your AI client (Claude Desktop, Cursor, ChatGPT, …) goes through a standard OAuth 2.1 PKCE flow with `security.adcrunch.dev` and receives a short-lived bearer token plus a refresh token.

## Scopes

OpenID Connect scopes identify you; AdCrunch **service scopes** gate what the tools can touch. The consent screen lists every scope the client asked for before you approve it.

- `openid`, `profile`, `email` — identifies your account.
- `observe:read` — read your organization's connected ad-account setup and performance (`list_advertisers`, `list_entities`, `get_entity`, `query_insights`, `meta_list_pages`, `meta_list_pixels`).
- `skill:read` — read your organization's ad-ops playbooks (`skill_list`, `skill_get`).
- `skill:write` — create, edit, and delete your playbooks (`skill_create`, `skill_update`, `skill_delete`).
- `brand:read` — read your organization's brand context: identity, voice, guidelines and messaging, plus its files (`brand_list`, `brand_get`, `brand_resolve`, `document_list`, `document_get`).
- `brand:write` — create, edit, and delete brands, attach or detach ad accounts, and manage their files (`brand_create`, `brand_update`, `brand_delete`, `brand_attach_advertiser`, `brand_detach_advertiser`, `document_create_upload`, `document_finalize`, `document_delete`).
- `campaign_plan:read` — read your organization's campaign plans: what it intends to run, on which channels, for how much, over what period ([`campaign_plan_list`](/mcp/tools/campaign-plan-list), [`campaign_plan_get`](/mcp/tools/campaign-plan-get)). A **campaign plan** is AdCrunch's own planning document, not a campaign on Meta or Google.
- `campaign_plan:write` — write and approve those plans and their line items ([`campaign_plan_create`](/mcp/tools/campaign-plan-create), [`campaign_plan_update`](/mcp/tools/campaign-plan-update), [`campaign_plan_delete`](/mcp/tools/campaign-plan-delete), [`campaign_plan_approve`](/mcp/tools/campaign-plan-approve), [`line_item_create`](/mcp/tools/line-item-create), [`line_item_update`](/mcp/tools/line-item-update), [`line_item_delete`](/mcp/tools/line-item-delete), [`line_item_validate`](/mcp/tools/line-item-validate), [`line_item_record_execution`](/mcp/tools/line-item-record-execution)). **Planning is not buying**: approving a plan creates nothing and spends nothing — turning one into live campaigns goes through `mutation:write`.
- `asset:read` — list your organization's source media and where it's registered (`asset_list`, `asset_get`).
- `asset:write` — upload media **and** register it into an ad account's library (`asset_create_upload`, `asset_finalize`, `asset_register`).
- `mutation:write` — change what is running on your connected ad accounts: create campaigns, ad sets, creatives and ads, move budgets, pause, resume and archive ([`meta_create_campaign`](/mcp/tools/meta-create-campaign), [`meta_create_adset`](/mcp/tools/meta-create-adset), [`meta_create_creative`](/mcp/tools/meta-create-creative), [`meta_create_ad`](/mcp/tools/meta-create-ad), [`meta_update_budget`](/mcp/tools/meta-update-budget), [`meta_set_status`](/mcp/tools/meta-set-status), polled with [`get_mutation_status`](/mcp/tools/get-mutation-status)). Meta only — [what each provider supports](/use/connect/providers) says where every other capability stops. This is the only scope that spends money — and nothing is ever _created_ active: everything an agent makes arrives paused.

> **Today, connecting a client grants all of these together**
>
> The consent screen is a single approve-or-decline over the whole set a client
> requested — there is no way to grant `observe:read` while withholding
> `mutation:write`. So authorizing an AI client today gives it the ability to
> create campaigns, change budgets, and pause, resume or archive what is running
> on the ad accounts your organization has connected. We would rather state that
> plainly than imply a choice you don't have. Per-scope consent is planned;
> until it ships, the controls that matter are **which ad accounts you connect**
> and the boundaries below.

`asset:write` covers both uploading and registering, but it is deliberately _not_ `mutation:write`: an Asset landing in a provider's library changes no delivery and spends no money, so granting it does not grant pausing a campaign.

## What an agent can and cannot change

This is the complete list. There is nothing else behind it.

| It can | It cannot |
| --- | --- |
| **Create a campaign, ad set, creative or ad — Meta.** Always paused; never starts spending | Use media it did not upload for you |
| Build a creative from media **you** uploaded — Meta | Change targeting on an ad set that already exists |
| Set a new ad set's countries, age range and gender — Meta | Choose placements, interests, behaviours or audiences |
| Pause, resume or archive a campaign, ad set or ad — Meta | **Hard-delete anything**, on any network |
| Change a daily or lifetime budget on a campaign or ad set — Meta | Change bids or bid strategy |
| Upload creative into an ad account's library — Meta, TikTok | Put anything live on its own |
| Read setup and performance — Meta, TikTok, Google Ads | Touch Google Ads, Snapchat or DV360 in any way |
| Author playbooks and brand context |  |

Three guardrails, all in the shared mutation layer rather than in the individual tools — so no caller can route around them:

- **A create can never start spend.** A campaign is created `PAUSED`, as a typed literal the layer sets itself. There is no `status` input on the create tool and there cannot be one, so nothing an agent makes begins spending until a person or a separate, deliberate call activates it.
- **A budget safety cap.** Any budget above `100,000,000` minor currency units (e.g. €1,000,000) is refused — on a create as well as an update, checked by the same code so the two cannot drift apart.
- **A budget level check.** On an update, a live read decides whether the budget actually lives on the campaign (Advantage campaign budget) or the ad set, and a change aimed at the wrong one is refused by name rather than silently ignored.

> **Archive is close to one-way**
>
> Pausing is a toggle — anything an agent pauses you can un-pause. **Archiving
> is not.** An archived object leaves Ads Manager's default views and cannot be
> returned to delivery. It exists because there is no hard delete: it is how a
> campaign that should never have been created gets retired. Treat it as the
> cleanup for a mistake, not as a stronger pause.

## What the model never sees

Your provider credentials are never exposed to the AI model. The agent holds an AdCrunch bearer token — never a Meta, TikTok or Google one. Provider tokens are resolved inside the worker at the moment of the call, are never tool arguments, and are never written into durable workflow state.

Every tool is scoped to your active organization, and re-checks that the organization owns the ad account before it does anything. "No such account" and "not your account" return the same answer, so a client cannot probe for accounts that aren't yours. [Errors](/mcp/errors) says what that answer looks like, and what else you can meet.

## Every change is on the record

Each write is recorded in a durable ledger before it runs, and updated with its outcome when it finishes. A row holds **who** asked (the user, not just "an agent"), **which** ad account, the **exact** action — level, target and value — the **outcome**, and the timestamps for both. It is visible in the console on the **Activity** page.

The record is written independently of the agent, so an agent cannot act without leaving one, and a failed change is recorded as a failure rather than vanishing.

## Token lifecycle

Access tokens are short-lived; clients refresh them with the refresh token issued at consent.

> **Revoking a client is not yet self-serve**
>
> There is currently no Connected apps screen in the console — revoking an AI
> client's access is not something you can do yourself yet. If you need a client
> revoked now, email us and we will do it. Self-serve revocation is planned
> alongside per-scope consent.
