adlibrary.com Logoadlibrary.com
Share
Platforms & Tools,  Advertising Strategy

Meta ads MCP for agencies: multi-account architecture

How agencies route MCP prompts to the right client account, isolate writes, and maintain a clean audit trail across 30 accounts.

Comparison matrix showing agency AI ad builder criteria including multi-brand workspace, client permissions, voice lock and cost per seat

Meta ads MCP for agencies isn't the same problem as Meta ads MCP for a solo buyer. Single-account demos look clean. The real question is whether multi-account meta ads automation for agencies can route a prompt to the right client account, isolate writes so client A's campaigns never touch client B's ad sets, and produce an audit log your client can read after the fact. This post covers the architecture — OAuth scoping, routing manifests, write-logs, naming enforcement, and the failure modes that look like success until they don't.

If you're evaluating the full agency tooling landscape first, the meta ads software for agencies guide covers nine options including MCP-compatible platforms. And if cost is part of the decision, the meta ads automation software pricing guide gives a current market view.

TL;DR: Agency MCP architecture rests on three pillars — per-client OAuth scopes (one Business Manager system user per account), a routing manifest that maps client names to account IDs, and a structured write-log per client that records every agent action. Get those three right and you can scale to 30 accounts without cross-contamination or audit failure. Miss any one of them and the first bad prompt is a fireable event.

The 30-account problem MCP doesn't solve out of the box

The reason meta ads MCP for agencies isn't just "MCP × N" is that the protocol has no native concept of multi-tenancy. Meta's MCP server authenticates against a single access token per session. In a solo setup that's fine. In an agency setup with 20 clients, you need a layer above the protocol that decides which token to pass, which account to target, and which log to write to — before the first tool call fires.

The three problems that compound at scale:

Context-switching cost. Senior buyers currently open Business Manager, filter to a client account, do their work, close, and repeat. Agency claude code setups don't eliminate that cognitive load — they just move it to prompt-writing. If your prompts don't encode the client account explicitly, Claude will guess, and a wrong guess against 30 live accounts is not a recoverable mistake.

Naming drift. Every agency has a naming convention. Campaigns named [ClientX]_TOFU_Cold_240501_v2 don't happen by accident — they're enforced, or they aren't. Without campaign structure discipline at the MCP layer, an AI that can create campaigns at speed will produce naming entropy faster than any human team.

Audit isolation. When a client asks "what changed in my account on Thursday," you need a per-client log, not a shared log you have to grep through. The agentic AI layer must produce client-isolated audit records from day one — retrofitting them later is painful.

None of these are MCP flaws. They're integration architecture decisions your agency has to make. The rest of this post walks each one. For context on what agencies are solving beyond MCP specifically, see meta ads tools for marketing agencies and facebook ads campaign manager alternatives — both cover the tooling landscape this architecture sits inside.

Step 0: find the angle on adlibrary before any prompt fires

Before you configure a routing manifest or write a single MCP prompt, your senior buyers need to have done angle work per client. This is the step MCP cannot do for you.

What does angle work look like in practice? Pull the competitor ad landscape for each client vertical on adlibrary's unified ad search. Use saved ad collections to build a per-client reference set of in-market hooks — the specific opening frames that are working in cold traffic right now. Pin the 3-5 angles your strategist would actually test. Then, and only then, write the MCP prompt that creates the campaign.

Why does this order matter? Because MCP can automate the creation of campaigns at scale — but if the brief going in is weak, the campaigns coming out are weak, just faster. The media buyer workflow that wins on MCP isn't the one with the most automation. It's the one whose senior buyers spent the saved time on better angles.

This is also why the adlibrary API is useful as a pre-flight step even inside automated pipelines: an agent can pull current in-market data before writing the campaign brief, which means the creative strategy is grounded in live signal rather than last quarter's gut feel. See how AI agents use ad data for the pattern.

Per-client OAuth: one system user per account

The single biggest architectural mistake agencies make when first setting up meta ads MCP is using their personal admin token as the MCP credential. That token has access to every account in your Business Manager. One bad prompt — wrong account ID in the manifest, a hallucinated account name, a copy-paste from the wrong client's file — and you're creating campaigns in the wrong client's account with your personal credentials attached.

The correct model: one Meta Business Manager system user per client account, scoped only to that account.

How to set it up:

  1. In Meta Business Manager, create a system user named after the client: client-x-mcp-agent.
  2. Assign that system user to only client X's ad account with ADVERTISER access (not admin).
  3. Generate a long-lived token for that system user with scopes: ads_management, ads_read, business_management.
  4. Store the token in your secrets manager (not in the routing manifest file itself — the manifest references the secret key, not the value).

This gives you the isolation guarantee you need. When the routing manifest selects client X, it loads client X's system user token. That token physically cannot touch client Y's account. Cross-account leaks become impossible by construction rather than by discipline.

For read-only junior buyer access, generate a separate system user token with ads_read only. Escalation to write access happens per-client, per-task — never as a blanket permission upgrade.

See the meta-ads-mcp-setup-guide for the full token provisioning walkthrough. For agencies that want MCP running continuously across accounts, the meta-ads-mcp-247-agent post covers the always-on deployment model where session management and token refresh run without manual intervention, and Anthropic's MCP documentation for how credential injection works in Claude Code.

The routing manifest: how Claude picks the right account

The routing manifest is a versioned YAML file your agent loads at session start. It maps client names to account IDs, secret references, and permission tiers. Claude reads the manifest, looks up the client name from the prompt, and uses the corresponding credentials and account ID for every downstream tool call.

yaml
# agency-mcp-routing.yaml
# v2.1 — last updated 2026-05-05
# Source of truth for client → account routing
# DO NOT store tokens here; reference secret keys only

clients:
  acme-corp:
    account_id: "act_1234567890"
    secret_key: "ACME_MCP_TOKEN"
    scope_tier: "advertiser"          # advertiser | read_only
    budget_cap_daily_usd: 5000
    naming_prefix: "ACME"
    slack_webhook_key: "ACME_SLACK_WEBHOOK"
    enabled: true

  globex-retail:
    account_id: "act_9876543210"
    secret_key: "GLOBEX_MCP_TOKEN"
    scope_tier: "advertiser"
    budget_cap_daily_usd: 12000
    naming_prefix: "GLBX"
    slack_webhook_key: "GLOBEX_SLACK_WEBHOOK"
    enabled: true

  initech-b2b:
    account_id: "act_1122334455"
    secret_key: "INITECH_MCP_TOKEN"
    scope_tier: "read_only"           # junior buyer access only
    budget_cap_daily_usd: 0           # writes blocked at scope level
    naming_prefix: "INTCH"
    slack_webhook_key: "INITECH_SLACK_WEBHOOK"
    enabled: true

A few things this manifest enforces by design:

  • Budget caps are specified per client. Before any spend-modifying tool call, the agent checks that the proposed change stays within the daily cap. This is a safety rail against the "apply this budget to all ad sets" prompt that looked harmless and wasn't.
  • enabled: false lets you pause a client's MCP access instantly without deleting config — useful during account reviews or onboarding freezes.
  • Naming prefixes feed directly into the validator (covered next section).

The routing logic in your agent prompt should be explicit: "Before any tool call, confirm which client this prompt is for. Look up their account_id and scope_tier in the routing manifest. If no client is specified, stop and ask."

That last clause matters. Never let the agent default to "most recent" or "most active" client. Require an explicit client selection on every session. See also the meta-ads-mcp-adlibrary-workflows post for prompt templates that enforce this. Agency mcp setup at scale also benefits from a curated meta-ads-mcp-prompts-library — pre-validated prompts reduce the surface area where hallucinated account selection can occur.

Write-logs: the audit line every agent action must produce

Every write action the meta ads MCP for agencies stack takes — create campaign, update budget, pause ad set, duplicate creative — must produce a structured log line written to a per-client log file before the action is considered complete. This is non-negotiable for agencies. Without it, you cannot answer "what did the agent do on Thursday" without replaying chat history.

The log line format:

json
{
  "ts": "2026-05-05T09:14:32.411Z",
  "session_id": "sess_a3f9b2c1",
  "client": "acme-corp",
  "account_id": "act_1234567890",
  "tool": "ads_create_campaign",
  "args": {
    "name": "ACME_TOFU_Cold_260505_v1",
    "objective": "OUTCOME_LEADS",
    "status": "PAUSED",
    "daily_budget": 150
  },
  "result": {
    "id": "23860123456789012",
    "status": "PAUSED"
  },
  "buyer": "claude-code",
  "human_approved": true,
  "approval_method": "cli_confirm"
}

Key fields:

  • session_id ties every action in a session together so you can reconstruct the full session in order.
  • human_approved + approval_method records whether a human confirmed before execution. For write operations, this should always be true in production agency setups.
  • buyer records whether the action was Claude-initiated or human-typed — critical for audit disambiguation.

Log files live at ./logs/<client-slug>/YYYY-MM-DD.jsonl (one file per day per client, newline-delimited JSON). Your ops tooling should ingest these into your reporting stack. A Slack webhook notification per write action (using the slack_webhook_key from the manifest) gives real-time visibility without waiting for log review.

The Pipeboard meta-ads-mcp server provides hooks for custom logging middleware — check their docs for the middleware injection pattern if you're building on top of their implementation.

Naming convention enforcement at the MCP layer

For any meta ads MCP for agencies deployment, naming convention enforcement is where campaign structure discipline either holds or breaks down. A human team can enforce conventions through review. An AI that creates 20 campaigns in 10 minutes will create 20 incorrectly named campaigns if the validator isn't inline.

The pattern: a pre-launch Python validator that runs before any ads_create_campaign or ads_create_ad_set tool call. The agent calls the validator as a tool, and the validator either returns {"valid": true} or blocks the launch with a reason string.

python
import re

AGENCY_PATTERN = re.compile(
    r'^(?P<prefix>[A-Z]{3,5})_'
    r'(?P<funnel>TOFU|MOFU|BOFU|RETGT)_'
    r'(?P<audience>[A-Za-z0-9]+)_'
    r'(?P<date>\d{6})_'
    r'(?P<version>v\d+)$'
)

def validate_name(name: str, expected_prefix: str) -> dict:
    match = AGENCY_PATTERN.match(name)
    if not match:
        return {"valid": False, "reason": f"Name '{name}' does not match pattern PREFIX_FUNNEL_AUDIENCE_YYMMDD_vN"}
    if match.group('prefix') != expected_prefix:
        return {"valid": False, "reason": f"Prefix '{match.group('prefix')}' does not match client prefix '{expected_prefix}'"}
    return {"valid": True}

This 15-line validator blocks launches with wrong prefix (cross-account contamination signal), wrong funnel label, or missing date/version. The expected_prefix comes from the routing manifest, so the client binding is enforced structurally, not by memory.

For agencies managing Advantage+ campaigns alongside manual campaigns, add an ASC funnel label to the regex and enforce it — Advantage+ campaigns have a distinct learning phase profile and need to be identifiable in reports without manual filtering. The learning phase calculator can help estimate when each campaign exits learning based on budget and conversion volume.

Cost attribution: which client paid for which Claude turn

In any meta ads MCP for agencies setup running across multiple clients, MCP API usage costs money. At scale — 10 buyers, 30 clients, 40 prompts a day — the Anthropic bill is meaningful. Without attribution, it disappears into overhead. With attribution, it's a per-client line item that belongs in your agency's cost model.

The simple version: log the session_id, client, and prompt character count (as a proxy for token usage) in the same write-log described above. At month end, aggregate by client. The correlation between character count and token count is tight enough for billing purposes.

The rigorous version: use Anthropic's usage API to pull token counts per session ID, then join on the session-to-client mapping from your logs. This requires that every Claude Code session starts with a unique session ID that gets written to your manifest state file before any tool calls fire.

For agencies using Pipeboard's MCP server, note that the tool call overhead adds tokens on top of your prompt — factor in roughly 300-400 tokens per tool invocation when estimating per-client cost.

Two tools worth running per client before each campaign cycle: the CPA calculator to sanity-check the budget math the agent is about to execute, and the LTV calculator to ensure ROAS targets are grounded in actual customer value rather than platform-reported ROAS. The agent can get these inputs from the routing manifest or from a per-client brief file.

Failure modes that look like success

This is the section most agency MCP posts skip. It's the most important one.

Hallucinated account selection. Claude picks an account from the routing manifest based on a fuzzy match of the client name in your prompt. If you write "run this for Acme" and you have both "Acme Corp" and "Acme Retail" in the manifest, the agent will pick one. It may not pick the right one. Mitigation: require the exact manifest key in every prompt (client: acme-corp), not a natural-language name.

Scope drift over time. System users accumulate permissions. Someone added admin access to debug something six months ago and never revoked it. Now the MCP agent has more scope than the routing manifest says it does. Audit system user permissions quarterly. Automate it — the ads_get_ad_accounts tool call can surface permissions for each system user, log the output, and alert on changes.

Cross-account creative contamination. Saved audiences, custom conversions, and pixel events are account-scoped, but creative assets in the Media Library can be shared across accounts within a Business Manager. An agent instructed to "reuse the top-performing creative from last month" may pull a creative from a different client's account if the instruction isn't account-scoped. Always specify account_id explicitly in creative-fetch tool calls.

The "looks published" bug. Campaign created, status shows ACTIVE, MCP reports success. The campaign is actually in review and will be rejected in 4 hours. Your write-log shows a successful creation. The audit is clean. The campaign never ran. Mitigation: add a post-create verification step that polls status for 60 seconds after any campaign creation and logs the final status, not the creation status.

These failure modes appear in the meta-ads-mcp-debugging guide. Worth reading alongside this architecture post before you run the first production prompt against a client account.

For a broader comparison of what MCP replaces versus what it doesn't touch in the agency stack, see meta-ads-mcp-vs-ads-manager. And if you're evaluating whether to build your own server rather than use an existing one, build-your-own-adlibrary-mcp-server covers the tradeoffs.

The deeper architecture for multi-account meta ads automation that spans competitor research to campaign creation adds another layer on top of the routing manifest — but the foundation described here has to be solid first.

Frequently asked questions

Can one Claude Code instance manage multiple client accounts simultaneously?

Not safely without the routing architecture described above. Claude Code runs one session at a time. Within that session, the routing manifest ensures each prompt resolves to a single client's credentials before any tool call fires. Running concurrent sessions for different clients is possible but requires process isolation — separate MCP server instances, each initialized with a single client's token, not a shared instance with context switching between clients.

What happens if the routing manifest has the wrong account ID?

The MCP server will make API calls against the wrong account ID using whatever token is loaded for that client. If the token doesn't have access to the wrong account, the call fails with a permission error — which is recoverable. If the token does have access (e.g., because you used a broad admin token instead of per-client system users), the call succeeds against the wrong account. This is why per-client OAuth scoping is the first architectural decision, not an optional enhancement.

How do junior buyers use the agency MCP setup without write access?

Set the scope_tier to read_only in the routing manifest for any client the junior buyer is working on. The system user token for that client should have ads_read only — no ads_management. The routing manifest's scope tier is a label; the actual enforcement is the OAuth scope on the token. Both layers must agree. Junior buyers can run reporting, creative analysis, and competitor research prompts freely. Write access requires escalation to a system user token with ads_management, provisioned per-client per-task.

Is meta ads MCP for agencies compliant with Meta's terms of service?

Meta's system user documentation explicitly covers agency access patterns. A system user acting on behalf of an ad account under a Business Manager you administer is a supported pattern. The key requirements: the agency must have Business Manager admin access to the client's account (not just ad account access), and the system user must be created within that Business Manager. Automated tool calls via the Marketing API are explicitly permitted within rate limits. Review Meta's current API rate limits before running high-volume operations.

How do you handle the learning phase across multiple client accounts with different budgets?

Each client's campaigns have independent learning phase dynamics based on their individual budget, conversion volume, and campaign structure. The routing manifest's budget_cap_daily_usd field gives the agent a per-client budget ceiling, but learning phase optimization requires per-client data. Use the learning phase calculator per client before any budget restructuring prompt — especially before consolidating ad sets, which resets learning and can spike CPAs during the reset window. The ad data for AI agents use case covers how to feed per-client performance data into the agent context before budget decisions.

Bottom line

The agency that wins with meta ads MCP isn't the one with the most automation — it's the one that built the containment layer first. Per-client OAuth scopes, a versioned routing manifest, and structured per-client write-logs aren't overhead. They're what separates a 30-account agency deployment from a 30-account liability. Get those three right, then automate aggressively.

Originally inspired by mcp.facebook.com. Independently researched and rewritten.

Related Articles