> ## Documentation Index
> Fetch the complete documentation index at: https://plainrouter.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Plainrouter Meta Ads MCP tools: inputs and limits

> Reference for Plainrouter MCP tools, workspace grants, conditional Launcher availability, and four synthetic test-mode tools.

Plainrouter MCP server version `0.3.2` exposes ten tools with the default configuration. Four additional legacy `launcher.*` tools are available only when Launcher is explicitly enabled. Use MCP discovery to check the tools available on your connection. Account-scoped tools use the advertising account selected during workspace-token issuance.

Start with the [MCP introduction](/docs/mcp/overview) for supported tasks and access boundaries. For client configuration and your first account read, follow [Setup](/docs/mcp/setup).

The server version is separate from the MCP protocol revision. For custom clients, see the [stateless discovery and request-header requirements](/docs/mcp/setup#which-mcp-protocol-should-my-client-use). MCP App support is advertised under `capabilities.extensions["io.modelcontextprotocol/ui"]` in the [server card](https://plainrouter.com/.well-known/mcp/server-card.json).

| Tool                         | Type                            | Purpose                                                                                                       |
| ---------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `get_account_state`          | Read-only, idempotent           | Confirm authorization, account, workspace, destination, and available capabilities.                           |
| `get_signal_health`          | Read-only, idempotent           | Diagnose event flow, delivery, match quality, and reconciliation. Not evidence for spend-affecting proposals. |
| `get_performance`            | Read-only, idempotent           | Read the admissible 1–90 day stored gateway-versus-Meta comparison for proposal evidence.                     |
| `verify_signal_ingestion`    | Idempotent verification write   | Write one identity-free verification event and confirm ledger receipt.                                        |
| `propose-actions`            | Idempotent write to Plainrouter | Submit an evidence-backed recommendation to policy and approval.                                              |
| `get-creative-library`       | Read-only, open-world           | Read Meta images and videos with associated ads and last-30-day history.                                      |
| `upload-asset`               | Idempotent proposal write       | Stage a JPEG or PNG and create a governed upload proposal.                                                    |
| `duplicate-ad-with-creative` | Idempotent proposal write       | Propose a permanently paused copy of a source Meta ad with a selected asset.                                  |
| `revoke_grant`               | Idempotent authorization write  | Revoke an authorized grant in the caller's workspace.                                                         |
| `show_spend_cap_approval`    | Read-only MCP App preview       | Render a static approval-card mockup with disabled controls and no account read or write.                     |

**Conditional legacy tools**

| Tool                     | Type                              | Purpose                                                                       |
| ------------------------ | --------------------------------- | ----------------------------------------------------------------------------- |
| `launcher.draft_batch`   | Non-destructive Plainrouter write | Create a Launcher draft batch from already-synced Drive assets.               |
| `launcher.batch_status`  | Read-only, idempotent             | Read the bounded status of one Launcher batch.                                |
| `launcher.preview_batch` | Non-destructive Plainrouter write | Evaluate the authoritative preflight gate without provider work.              |
| `launcher.execute_batch` | Closed execution entry            | Returns a bounded refusal during Launcher Phase B-1 and performs no mutation. |

<Note>
  The four `launcher.*` entries above describe the conditional legacy contract. Launcher is disabled by default, so those tools are absent from discovery and the file-import route returns `404`.
</Note>

<Warning>
  Proposal-producing tools do not write to Meta during the MCP request. In **Suggest only**, approval ends without execution. In an executable mode, supported creative work can run only after the policy and approval pipeline authorizes it.
</Warning>

## Test mode and production boundaries

Use `https://plainrouter.com/mcp/sandbox` to prove an integration without an
account or credential. It exposes four tools:

| Tool                     | Purpose                                                                    |
| ------------------------ | -------------------------------------------------------------------------- |
| `get_account_state`      | Return an invented account, workspace, connection, and Signal destination. |
| `get_signal_health`      | Return synthetic event flow, delivery, and match-quality diagnostics.      |
| `get_performance`        | Return synthetic reconciliation evidence for a requested 1–90 day window.  |
| `validate_sandbox_event` | Validate an identity-free synthetic conversion event.                      |

Every result is marked with `"sandbox": true`. The endpoint reads no tenant
data, writes nothing, and contacts no advertising provider. It does not expose
production proposal, creative, approval, Launcher, or spend-affecting tools.

The production endpoint is `https://plainrouter.com/mcp`. Its handshake, tool
and resource catalogs, and static `ui://` MCP App shells are publicly
discoverable. Every tool call and account-data read requires an account-bound
workspace execution token.

## Busy-workspace errors

A concurrent operation can return `workspace_lock_timeout` through MCP. Affected JSON HTTP routes return HTTP `503` with `Retry-After: 1`; the browser shows **The workspace is busy. Retry the operation shortly.** Wait before retrying and retain the original idempotency key for the same operation.

## Shared authorization and scope

Signals read tools require an active principal with `ad-account.read`. `verify_signal_ingestion` requires `signals.verify`. `propose-actions` requires `actions.propose`. Creative-library reads require `creative.read`. Creative proposal tools require both `creative.write` and `actions.propose`. Every Launcher tool requires the `ad-account.read` baseline.

Clients authenticate with an owner-issued [workspace execution token](/docs/mcp/workspace-tokens). A **Read** token derives the read scopes. **Write** and **Admin** tokens also derive the proposal scopes. OAuth management credentials cannot authenticate to the MCP server.

`launcher.batch_status` accepts a Read, Write, or Admin token. `launcher.draft_batch`, `launcher.preview_batch`, `launcher.execute_batch`, and the file-import adapter require the Write or Admin tier. Drafting also requires `creative.write` and `actions.propose`; preview and the closed execution entry require `actions.propose`.

Every production call also checks an active, unexpired grant for the same workspace and the requested operation. Revoked grants cannot authorize a call. The `revoke_grant` tool applies its separate revocation-authority check described below.

Scoped responses identify:

```json theme={null}
{
  "workspace_id": 42,
  "account_id": 91,
  "destination_id": "01J...",
  "dataset_id": "1234567890"
}
```

`destination_id` and `dataset_id` are `null` until the authorized account has a matching destination. The caller cannot provide a different account ID.

## `get_account_state`

Call this first in every account-specific workflow. It returns:

* Agent ID, name, and granted permissions.
* Shared scope.
* Advertising account ID, platform-native ID, name, platform, currency, timezone, and status.
* Workspace ID and name.
* Platform connection status and expiry.
* Signal ID, status, collection domain, and connected destination.
* Available capability flags.
* Response generation time.

Current capability flags are intentionally explicit:

```json theme={null}
{
  "signal_health": true,
  "verified_performance": true,
  "marketing_api_reads": false,
  "writes": false
}
```

`verified_performance` becomes true only when the matching destination is active. It describes stored reconciliation availability, not live target performance. The `marketing_api_reads` and `writes` flags describe this Signals-oriented account-state call; use the separately permissioned creative tools for live creative-library reads and governed creative proposals.

## `get_signal_health`

Use this diagnostic for collection, delivery, and stored account health. Both production and sandbox accept the same optional reporting window:

| Field  | Type    | Required | Rules                               |
| ------ | ------- | -------- | ----------------------------------- |
| `days` | Integer | No       | `7`, `30`, or `90`. Default is `7`. |

```json theme={null}
{
  "days": 30
}
```

The result includes the shared scope, status, label, summary, Signal and destination state, and:

* `activity.completed_window`: arrivals for the retained completed-day reporting window.
* `activity.today_so_far`: the separate incomplete local-day arrival count.
* `activity.receiving` and `activity.last_event_at`: collection activity and freshness.
* `delivery`: non-test delivery counts and acceptance rate for the last 24 hours, independently of the requested arrival window.
* `match_quality`: the latest stored Meta score, measurement time, and history when available.
* `performance`: stored reconciliation for the requested window.
* `diagnostics`, `data_sources`, and `generated_at`: explanations, provenance, and response freshness.

Arrival windows use completed days in the account timezone and are clipped to retention. Do not assume the fixed 24-hour delivery diagnostics cover that same period. Arrival totals remain complete when a strict-EU component is withheld: `partial` marks that component, not a reduced total. Missing platform history is not zero activity. See [Signals measurements and troubleshooting](/docs/signals/health-and-performance).

This call reads stored measurements. It does not trigger a live Meta Marketing API read.

<Warning>
  Treat this tool as diagnostic. Plainrouter rejects it as evidence for any proposal that can affect spend or delivery, including a mixed batch containing one such action. Use `get_performance` for stored reconciliation-derived performance evidence.
</Warning>

## `get_performance`

Use this tool for a stored reconciliation window.

Plainrouter excludes modeled and portfolio-only events from this proposal-evidence path. Only eligible observed reconciliation data contributes to its performance fields.

### Input

| Field  | Type    | Required | Rules                       |
| ------ | ------- | -------- | --------------------------- |
| `days` | Integer | No       | From 1 to 90. Default is 7. |

```json theme={null}
{
  "days": 30
}
```

### Output

The response includes:

* Shared scope.
* Requested period with `days`, `since`, and `until`.
* Measurability state.
* Gateway-verified accepted count.
* Meta-reported count.
* Observed gap.
* Alignment percentage.
* Number of alert days.
* Latest report date.
* Totals grouped by event.
* Daily report rows.
* `data_source: "stored_reconciliation_reports"`.
* `marketing_api_called: false`.
* Response generation time.

The response also includes stored account-level `platform_claimed_clicks`, `platform_impressions`, `platform_spend`, per-day `platform_daily` read states, and `claimed_clicks_last_read_at` when available. These platform fields are descriptive context, not admissible spend or target-performance evidence for Actions proposals.

### Measurability

```json theme={null}
{
  "status": "measurable",
  "reason": null,
  "report_count": 7,
  "latest_report_at": "2026-08-14T00:00:00+00:00",
  "stale_after_hours": 48,
  "counts_semantics": "observed_reconciliation_counts",
  "quantitative_citations_allowed": true
}
```

Possible non-measurable reasons are `no_history` and `stale_history`. When `quantitative_citations_allowed` is false, an agent must not cite count, gap, alignment, alert-day, daily, or by-event values as proposal evidence.

## `verify_signal_ingestion`

Use this tool to run an identity-free write-path diagnostic against an active Signal. It accepts no input and resolves the Signal from the authorized workspace.

The tool writes one idempotent `signal_verification` event and returns:

```json theme={null}
{
  "event_id": "signal_verification",
  "duplicate": false
}
```

The event is identity-free, modeled, excluded from enforcement, and never dispatched to Meta. It does not count as real arrival evidence or complete onboarding. A repeated call returns the same event with `duplicate: true`. The tool has no spend capability.

The call fails if the connection lacks `signals.verify` or the authorized workspace has no active Signal.

## `revoke_grant`

Revoke a workspace grant using its 26-character ULID in `grant_id`. Obtain the grant ID from the authorized token-management response; it is different from the token ID and plaintext credential.

```json theme={null}
{
  "grant_id": "01K2A000000000000000000001"
}
```

The caller needs an active execution credential in the same workspace and either an active `revoke_grant` permission or authority as the human who delegated the target agent. Admin-tier grants include revocation permission. Knowing a grant ID does not grant access to another workspace.

The tool returns `grant_id`, `workspace_id`, and `revoked_at`. It records the revocation in the audit chain and prevents credentials backed by that grant from authorizing subsequent operations. An authorized repeat returns the existing revocation without creating another one. It does not undo previously executed advertising actions.

Invalid grant-ID input fails validation. Missing or inactive caller authorization, another workspace's grant, or insufficient revocation authority is refused.

## `show_spend_cap_approval`

This read-only, idempotent tool renders the static
`ui://resources/spend-cap-approval-app` MCP App preview. It accepts no input and
returns only a confirmation that the preview card loaded.

The card contains fixed demonstration values and is labeled **Preview — not a
live control**. Its **Approve** and **Reject** buttons are disabled. The tool
does not read an account, submit an approval, call another tool, persist data,
contact Meta, or change spend. Use it only to verify that an MCP host can render
Plainrouter's app presentation.

## `propose-actions`

This tool validates the action shapes, captures fresh evidence provenance, evaluates policy, and creates one batch.

### Top-level input

| Field             | Rules                                                            |
| ----------------- | ---------------------------------------------------------------- |
| `actions`         | 1–25 typed action objects.                                       |
| `rationale`       | Required plain-language batch rationale, up to 4,000 characters. |
| `idempotency_key` | Required stable retry key, up to 64 characters.                  |
| `evidence`        | 1–3 source declarations.                                         |
| `target_source`   | Must be `human_supplied`.                                        |

Each evidence declaration contains:

```json theme={null}
{
  "source_tool": "get_performance",
  "fields_used": [
    "gateway_verified",
    "alignment_percent"
  ]
}
```

Aggregate `source_tool` values are `get_account_state`, `get_signal_health`, and `get_performance`. Creative actions can also use the action-bound sources `get-creative-library` and `staged-asset-manifest`.

Every field path must exist in a fresh server-side capture. Aggregate sources cannot include `action_index`. An action-bound source requires `action_index` and can cite only its matching creative action.

### Action object

Every action has this common shape. The `set_status` example below describes a recommendation only: delivery-status and budget execution are disabled, even after approval. For the executable creative path, use [Create governed Meta creatives](/docs/actions/creative-workflow).

```json theme={null}
{
  "type": "set_status",
  "target_entity": {
    "type": "campaign",
    "id": "120012345",
    "name": "Prospecting"
  },
  "params": {
    "status": "paused"
  },
  "rationale": "Pause the human-selected campaign for manual review."
}
```

Supported target types are `ad_account`, `campaign`, `ad_set`, and `ad`.

### Action parameters

<AccordionGroup>
  <Accordion title="adjust_budget">
    Requires `new_daily_budget_minor`, a positive integer in the account currency's minor unit. For example, `4900` means 49.00 for a two-decimal currency.
  </Accordion>

  <Accordion title="set_status">
    Requires `status` equal to `active` or `paused`.
  </Accordion>

  <Accordion title="duplicate_ad_with_creative">
    Requires `asset_id` and `status: "paused"`. An optional `name_suffix` can identify the duplicate.
  </Accordion>

  <Accordion title="upload_asset">
    Requires `staged_asset_id`, `asset_type` (`image` or `video`), `filename`, and a lowercase 64-character `content_sha256`. Optional fields include detected `mime_type` and positive `size_bytes`.
  </Accordion>
</AccordionGroup>

### Evidence restrictions

Plainrouter rejects evidence paths that claim campaign, ad set, ad, target, CPA, spend, budget, or learning-phase state from aggregate Signals reads.

`get_signal_health` is diagnostic and cannot support a batch containing `adjust_budget`, `set_status`, or `duplicate_ad_with_creative`. This rule applies to the entire batch: adding an `upload_asset` action does not make a mixed spend-affecting proposal eligible. An upload-only `upload_asset` batch is the current non-spend exception and may retain signal-health context.

Use `get_performance` when a spend-affecting proposal needs stored reconciliation-derived performance evidence.

It also rejects:

* Unknown source tools.
* Field paths absent from the cited response.
* Quantitative performance fields from non-measurable history.
* A target source other than `human_supplied`.
* Proposals from a connection without `actions.propose`.

### Response

The response includes the batch ID, scope, evidence provenance, status, policy decision and reasons, approval requirement, queue URL, proposer, rationale, and normalized action records.

Evidence provenance records the source service, scope level, exact fields, observation time, reporting window, measurability, and the target-selection boundary.

## `get-creative-library`

This tool makes live Meta Marketing API reads for the approved account. It requires `creative.read` and an active Meta connection with `ads_read` or `ads_management`.

### Input

| Field        | Required | Rules                                                                             |
| ------------ | -------- | --------------------------------------------------------------------------------- |
| `asset_type` | No       | `all`, `image`, or `video`. Default is `all`.                                     |
| `limit`      | No       | Integer from 1 to 100 per requested asset type. Default is 25.                    |
| `cursor`     | No       | Opaque Meta cursor from a previous call. Choose `image` or `video` when using it. |

### Output

The response includes:

* Images as `image:<hash>` and videos as `video:<id>`.
* Names, status, dimensions or duration, previews, and creation time when available.
* Ads historically associated with each asset.
* Aggregated last-30-day impressions, clicks, spend, and conversions.
* Separate image and video pagination cursors.
* `performance_window: "last_30d"`.

<Warning>
  The performance block is descriptive account history, not causal lift. When this tool supplies proposal provenance, Plainrouter persists only the selected asset's identity, account ownership, readiness, and status.
</Warning>

## `upload-asset`

This tool validates and privately stages an image, then creates one canonical `upload_asset` proposal. It requires `creative.write` and `actions.propose`.

| Field             | Required        | Rules                                                                                 |
| ----------------- | --------------- | ------------------------------------------------------------------------------------- |
| `asset_type`      | Yes             | Must be `image`.                                                                      |
| `filename`        | Yes             | Up to 100 characters with `.jpg`, `.jpeg`, or `.png` matching the detected MIME type. |
| `content_base64`  | Yes in practice | Raw base64 image bytes without a data URI prefix.                                     |
| `rationale`       | Yes             | 1–1,000 characters.                                                                   |
| `idempotency_key` | Yes             | Stable retry key up to 64 characters.                                                 |

The response wraps the canonical proposal with its ID, scope, evidence provenance, immutable execution-mode snapshot, status, policy result, approval requirement, approval-queue URL, and action record.

Plainrouter freezes the staged bytes and records only the approved manifest fields: staged ID, SHA-256 digest, detected MIME type, byte size, account binding, and frozen/immutable state. Raw bytes do not enter the action or audit record.

Only JPEG and PNG upload execution is supported. Video upload execution remains blocked.

## `duplicate-ad-with-creative`

This tool creates one canonical `duplicate_ad_with_creative` proposal. It requires `creative.write` and `actions.propose`.

| Field             | Required | Rules                                                   |
| ----------------- | -------- | ------------------------------------------------------- |
| `source_ad`       | Yes      | Numeric Meta ID of the human-selected ad to copy.       |
| `asset_id`        | Yes      | `image:<hash>` or `video:<id>` returned by Plainrouter. |
| `rationale`       | Yes      | 1–1,000 characters.                                     |
| `name_suffix`     | No       | Optional copied-ad name suffix up to 100 characters.    |
| `idempotency_key` | Yes      | Stable retry key up to 64 characters.                   |

Plainrouter re-reads the source ad and selected asset under the approved account before creating the proposal. The normalized action always contains `status: "paused"`.

If approved in an executable mode, the worker rechecks policy, creates the copy as `PAUSED`, persists the provider ad ID in a durable receipt, and verifies the exact receipt-addressed object. `IN_PROCESS` can enter scheduled verification for up to 48 hours. The batch is not Landed until every receipt is exactly verified.

## `launcher.draft_batch`

Available only when legacy Launcher is enabled. This tool creates a Launcher draft batch from Drive assets already synced into the token's workspace. It writes only the Plainrouter draft model; it does not write to Meta, evaluate the final execution gate, or activate spend.

It requires an owner-issued [workspace token](/docs/mcp/workspace-tokens) with the **Write** or **Admin** tier.

### Input

| Field              | Required | Rules                                                                                                    |
| ------------------ | -------- | -------------------------------------------------------------------------------------------------------- |
| `source_adset_id`  | Yes      | Numeric Meta ad set ID used as the duplication or destination template.                                  |
| `source_ad_id`     | Yes      | Numeric Meta ad ID used as the creative template.                                                        |
| `mode`             | Yes      | `duplicate_adset`, `duplicate_adset_per_media`, or `add_to_adset`.                                       |
| `folder_to_adset`  | No       | Map each source folder to its own ad set. Defaults to `false`.                                           |
| `grouping_enabled` | No       | Select current placement groups together. Defaults to `true`.                                            |
| `items`            | Yes      | One or more ordered items containing an already-synced `drive_link` and optional copy or link overrides. |

Overrides use contract version `v: 1` and can contain up to five `primary_texts`, `headlines`, and `descriptions`, plus `website_url` and `display_link`.

Workspace and account IDs are never accepted as input. Plainrouter derives them from the execution token. A Drive link that is unknown, stale, failed, removed, or belongs to another workspace is rejected without widening the token scope.

### Output

The response includes the draft batch ID and status, frozen tier and capacity limits, item and media counts, accepted items, bounded rejected-item reason codes, import provenance when present, and an advisory gate result.

Possible rejection codes are:

* `drive_asset_unresolved`
* `overrides_invalid`
* `overrides_limit_exceeded`
* `batch_limit_exceeded`
* `media_limit_exceeded`

The current MCP entry point returns an advisory gate with `not_evaluated`. Treat the result as a draft, not authorization to execute.

## Launcher batch status and preview

These tools are available only when legacy Launcher is enabled. They accept one `batch_id`: the 26-character ULID returned by `launcher.draft_batch` or the file-import adapter. Plainrouter resolves it only inside the workspace and ad account bound to the token. A caller cannot inspect another account by guessing an ID.

### `launcher.batch_status`

Use this read-only tool to inspect a batch without evaluating its gate or contacting a provider. It returns:

* Batch status, mode, and item counts.
* Sub-batch status.
* The latest stored gate result.
* Ordered item status, bounded failure stage and code, and linked Actions proposal IDs.
* Partition status and latest proposal attempts for duplicate-ad-set modes.

The tool requires only a Read-tier token and the shared `ad-account.read` scope.

### `launcher.preview_batch`

Use this non-destructive checkpoint before presenting a batch as ready. Plainrouter re-evaluates the authoritative gate and returns `status: "previewed"` or `status: "gated"`, the resulting batch status, and checks for source relationships, tracking verification, tracked-domain coverage, ledger health, and Actions readiness.

The `previews` array is intentionally empty in this release. No provider preview, Meta write, Actions proposal, or spend activation happens during the call. A successful preview is not execution authorization; any future execution path must evaluate the gate again.

This tool requires a Write or Admin token with `actions.propose`.

### `launcher.execute_batch`

When legacy Launcher is enabled, the execution entry is discoverable but deliberately closed. After validating the token and token-bound batch, every call returns `not_executable_in_phase_b1`.

It does not evaluate the gate, advance the batch, create an Actions proposal or receipt, contact Meta, or mutate provider state.

### Refusals

All three tools can return `invalid_token`, `insufficient_scope`, `invalid_arguments`, `batch_not_found`, or `internal_error`. Preview can also return `batch_not_operable` or `gate_receipt_raced`. Execute returns `not_executable_in_phase_b1` after successful authorization and batch resolution.
