Announcing CIMD support for MCP Client registration
Learn more

Meta Ads MCP vs Meta Ads API for AI Agents

Shri Mithran
Director of Marketing

TL;DR

  • Meta's hosted Ads MCP server, in open beta since April 29, 2026, documents 91 tools in seven categories. Lead retrieval, Conversions API event sending, automated ad rules, ad duplication, async insights jobs, and Ads Webhooks are absent; the Marketing API covers them all.
  • Meta's MCP docs specify OAuth or a bearer user access token. The API also documents system user tokens that do not expire.
  • Meta issues no OAuth refresh token. Long-lived user tokens last about 60 days; recovery after expiry or revocation is a re-consent flow.
  • Neither path stores, rotates, or revokes per-user tokens for a multi-tenant agent.
  • Scalekit's Meta Ads connector (148 tools) and Virtual MCP servers handle that layer for direct tool calls and MCP alike.

The decision your Meta Ads agent is facing

Your agent needs to work inside your customers' Meta ad accounts. It pulls spend and CTR, pauses the ad set that is burning budget, and pushes a refreshed customer list into a custom audience. Meta now ships a hosted Model Context Protocol (MCP) server for ads next to the Marketing API. Both reach the same ad accounts. They differ on tool coverage, token model, and who carries the guardrails when an agent can spend money. For a multi-tenant agent, one of those differences decides the architecture. Here is the decision framework.

What Meta Ads MCP and the Meta Ads API actually are

Both sit on the same ad account graph. What differs is the shape of the surface and who maintains it.

Meta Ads MCP server

The ads MCP server is a Meta-hosted remote MCP server, part of Meta's ads AI connectors alongside the Ads CLI. Meta announced it in open beta on April 29, 2026. A July 16, 2026 update added two things agent builders care about: you can connect your own AI application through your own Meta developer app, and admins with full control of a business portfolio can set ads MCP server rules that limit what agents may do, from budget changes to catalog edits.

Write tools create campaigns, ad sets, and ads paused. Spending starts only through a separate ads_activate_entity call. Meta's official reference is the Ads MCP Server section of the Ads AI Connectors documentation on Meta for Developers.

Meta Ads API (the Marketing API)

What most teams call the Meta Ads API is the Marketing API, built on the Graph API and currently at v25.0. It covers campaigns, ad sets, ads, creatives, synchronous and asynchronous Insights, custom audiences, catalogs, lead ads, the Conversions API, the Ad Rules Engine, and Ads Webhooks.

Every call carries an access token: a user access token from Facebook Login, or a system user token for server-to-server work. New apps start on the Limited access tier. Full access runs through App Review and requires at least 500 Marketing API calls in the last 15 days with an error rate under 15%. The official reference is the Marketing API section of Meta's Ads and Commerce documentation.

Comparing them where it matters for agents

Four dimensions decide the build: capability coverage, auth model, operational surface area, and fit per use case.

What your agent can actually do

Meta's MCP inventory is broad on structure, reporting, and catalogs. The gaps sit where revenue agents work hardest.

Capability
Meta Ads MCP
Meta Ads API
Create and edit campaigns, ad sets, ads
Yes, paused
Yes
Start spend
ads_activate_entity
status on create or update
Performance reporting
ads_get_ad_entities
Insights API
Async insights jobs
No
Report runs
Creative creation
Single-image link only
Image, video, carousel, dynamic
Duplicate ads, ad sets, campaigns
No
Yes
Custom audiences, hashed uploads
Yes
Yes
Catalogs, feeds, product sets
Yes
Yes
Lead form retrieval
No
Yes
Conversions API events
Reads dataset stats only
Send and read
Automated ad rules
No
Yes
Real-time change events
No
Ads Webhooks
A/B tests and lift studies
Yes
Yes
Ad Library search
ads_library_search
Separate Ad Library API

Where the MCP ceiling sits

An agent that syncs lead form submissions into a CRM cannot run on MCP; there is no lead retrieval tool. Neither can one that forwards server-side purchase events, since the signals tools read dataset stats and Event Match Quality but do not send events. Large historical pulls, such as reach breakdowns over long date ranges, need the async report-run workflow. Reacting to a disapproval or creative fatigue as it happens needs Ads Webhooks.

None of these are edge cases. They are the core of lead-gen, e-commerce, and agency automation.

What MCP does better

MCP ships guardrails you would otherwise build: paused-by-default writes, an explicit activation step, and portfolio-level rules owned by the customer's admin. It also ships packaged analysis the raw API leaves to you, including ads_get_opportunity_score, ads_insights_anomaly_signal, ads_insights_industry_benchmark, and ads_get_errors for delivery-blocking issues.

ads_get_field_context returns field types and enum values before the model builds a query, which cuts malformed calls. Meta maintains every schema, so you write no tool definitions.

The auth path each one puts you on

The token models differ more than the capability table suggests.

MCP auth: Facebook Login or a bearer user token

Meta documents two methods. With OAuth, the MCP client redirects the user to the Facebook Login for Business dialog, where they sign in with a Facebook account or a Meta Managed Account and approve permissions. For programmatic setups, you pass a user access token in the Authorization: Bearer header. That token needs ads_mcp_management, ads_read, ads_management, catalog_management, business_management, pages_show_list, and instagram_basic.

Individual advertisers can connect supported AI clients without owning a Meta app. For your own agent product, the redirect URL lives in your developer app's Facebook Login for Business settings. MCP does not remove your Meta app from the picture; it moves it.

API auth: user tokens, long-lived tokens, and system users

The Marketing API uses the same Facebook Login Authorization Code flow. Short-lived user tokens expire in one to two hours. Your server exchanges them through the fb_exchange_token grant for a long-lived token, which Meta says generally lasts about 60 days. An expired token cannot be exchanged; the user signs in again. Password changes and permission revocation invalidate tokens early.

System user tokens do not expire and suit server-to-server work with no user present. If your app manages other people's ad accounts, Meta requires advanced access to ads_read or ads_management through App Review.

The headless and multi-tenant reality

Headless MCP works with a bearer user token, but it is still a user token with a roughly 60-day life and no refresh token. System users avoid expiry, yet when client ad accounts are shared into your Business Manager, one non-expiring credential becomes the blast radius for every client.

Both paths require per-user credential isolation in a multi-tenant B2B agent. MCP's OAuth flow gives you a token per user. Direct API calls give you a credential per user. In neither case does the path itself solve storage, rotation, or revocation; those are infrastructure problems regardless of which path you choose.

Recommended reading: How to Handle Token Refresh for AI Agents

What you own in production

Hosting is the smallest line on the operational bill.

On the MCP path

Meta runs the server, maintains tool schemas, and enforces paused-by-default writes and portfolio rules. You still own token custody per user, expiry and revocation detection, the re-consent flow, and tenant isolation inside your own system.

You also absorb schema drift. Hosted tools change on Meta's schedule, and the server is still in open beta. An agent prompt tuned against one tool shape can break without a deploy on your side.

On the API path

You own everything the MCP path leaves to you, plus request construction, pagination cursors, async report polling, and handling Meta's numeric error codes and subcodes. You pin a Graph API version and migrate on Meta's deprecation schedule. You read throttle state from the X-Ad-Account-Usage, X-Business-Use-Case, and X-FB-Ads-Insights-Throttle headers.

You also build the guardrail MCP gives you for free: nothing stops a create call from setting status to ACTIVE.

Rate limits that shape agent design

Marketing API calls are scored per ad account: a read costs 1 point and a write costs 3. On the Limited tier the ceiling is 60 points, with a 300-second block when you hit it; Full access raises it to 9,000 with a 60-second block. Mutations cap at 100 requests per second per app and ad account. Each ad set's budget can change only 4 times per hour, and account spend caps 10 times per day.

A budget-pacing agent that nudges spend every ten minutes hits the ad set wall in its first hour. Meta's MCP docs publish no separate quota table, so design for the same account-level limits.

When to use MCP, when to use the API

Both lists are specific to Meta Ads workloads, not generic MCP advice.

Use Meta Ads MCP when

  • You are building an interactive assistant where a media buyer asks about spend, CTR, and delivery errors, then approves each activation in the chat.
  • You want Meta's paused-by-default writes and the customer's own ads MCP server rules as the spend guardrail, instead of building one.
  • The job is catalog diagnostics, signal quality checks, or Ad Library research, where Meta's packaged tools replace several stitched endpoints.
  • You are prototyping a Meta Ads agent before committing to App Review and your own tool schemas.

Use the Meta Ads API when

  • The agent runs headless: nightly reporting, scheduled budget pacing, or any run with no user present.
  • The job involves lead form sync into a CRM, Conversions API event forwarding, or offline conversion uploads.
  • The agent reacts to Ads Webhooks, such as disapprovals or creative fatigue.
  • You need async insights over long date ranges, bulk duplication, or automated ad rules.
  • You run a deterministic pipeline pinned to a Graph API version.

The credential problem that exists on both paths

Picture 40 media buyers across 12 client businesses. That is 40 Meta user tokens, each with a roughly 60-day life, each revocable by its owner at any time, each invalidated by a password change. There is no refresh token to retry with. When one dies, the only recovery is getting that user back through Facebook Login, and your agent has to notice before the Monday report ships blank.

The token type is identical across paths. So is the management burden. Understanding secure token management for AI agents at scale is essential before committing to either path in production.

Where Scalekit fits

Scalekit's Meta Ads connector runs the Facebook Login flow through your Meta app, stores each user's credential in an AES-256 encrypted token vault, and keeps it out of the agent runtime and the model's context. Each connected account carries a status, so the agent checks for ACTIVE and sends a re-authorization link instead of failing mid-run.

One precision point: Scalekit's catalog lists one Meta Ads connector, built on the Marketing API. It does not proxy Meta's hosted MCP server. You reach its 148 tools as direct tool calls or as an MCP endpoint through a Scalekit Virtual MCP server, so the MCP vs API decision doesn't change your auth infrastructure.

Recommended reading: Token Vault: Why It's Critical for AI Agent Workflows

Using Scalekit to build a Meta Ads agent

The build below is a read-only weekly performance agent. It runs first as direct tool calls in Python with the Claude SDK, then as a Virtual MCP server consumed by a Mastra agent in TypeScript.

The connector and its 148 tools

The connection name is metaads, and auth is OAuth 2.0. The tools cover the MCP gaps named above.

Job
Example tools
Reporting
metaads_account_insights_get, metaads_account_insights_create_report_run, metaads_report_run_get_insights
Structure
metaads_campaign_create, metaads_adset_update, metaads_ad_copy
Creative
metaads_adcreative_create, metaads_adimage_create, metaads_advideo_create
Audiences and signals
metaads_customaudience_add_users, metaads_adspixel_send_events
Leads and automation
metaads_leadgen_form_list_leads, metaads_adrule_create
Catalog and commerce
metaads_productitem_batch_upsert, metaads_order_list

Full schemas live in the Meta Ads connector docs.

Set up the connection once

Connection setup happens once per environment, not once per user.

  1. Create a Business-type app on Meta for Developers and add the Marketing API and Facebook Login products.
  2. In the Scalekit dashboard, open AgentKit > Connections > Create Connection and pick Meta Ads. Scalekit's shared credentials work for testing.
  3. For production, choose Use your own credentials, copy the redirect URI into Facebook Login > Settings > Valid OAuth Redirect URIs, and paste your App ID and App secret into the connection.
  4. Confirm each authorizing user holds an admin, advertiser, or analyst role on the ad account.

The connection_name in your code must match the connection name in the dashboard exactly. This is the single most common integration error.

pip install scalekit-sdk-python anthropic python-dotenv
# .env SCALEKIT_ENVIRONMENT_URL=<your-environment-url> SCALEKIT_CLIENT_ID=<your-client-id> SCALEKIT_CLIENT_SECRET=<your-client-secret> ANTHROPIC_API_KEY=<your-anthropic-api-key>

Authorize each user

Each user signs in to Meta once. The agent confirms the connected account is ACTIVE before every run; in production, send the link through your UI or email instead of input().

# weekly_report_agent.py import json import os import anthropic from dotenv import load_dotenv from google.protobuf.json_format import MessageToDict from scalekit import ScalekitClient from scalekit.v1.tools.tools_pb2 import ScopedToolFilter load_dotenv() scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) actions = scalekit_client.actions claude = anthropic.Anthropic() # reads ANTHROPIC_API_KEY CONNECTION_NAME = "metaads" # must match AgentKit > Connections exactly IDENTIFIER = "user_123" # your app's stable ID for this user account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=IDENTIFIER ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=IDENTIFIER ) print("Authorize Meta Ads:", link.link) input("Press Enter after authorizing...") account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=IDENTIFIER ) if account.connected_account.status != "ACTIVE": raise RuntimeError( f"Meta Ads is {account.connected_account.status}, not ACTIVE. Re-authorize and retry." )

Retrieve the tools this user is authorized to call

The agent does not load the connector catalog. list_scoped_tools returns the tools the current user's connected account is authorized to call, filtered here to six read-only reporting tools. Handing the model all 148 is an accuracy problem and a cost problem: at Scalekit's rough figure of 200 tokens per tool, that is nearly 30,000 tokens before the agent does any work. The fix is not better prompting. It is surface reduction.

READ_ONLY_TOOLS = [ "metaads_adaccount_get", "metaads_campaign_list", "metaads_account_insights_get", "metaads_account_insights_create_report_run", "metaads_report_run_get_status", "metaads_report_run_get_insights", ] scoped_response, _ = scalekit_client.tools.list_scoped_tools( IDENTIFIER, filter=ScopedToolFilter( connection_names=[CONNECTION_NAME], tool_names=READ_ONLY_TOOLS, ), page_size=50, ) llm_tools = [] for scoped_tool in scoped_response.tools: definition = MessageToDict(scoped_tool.tool).get("definition", {}) if definition.get("name") not in READ_ONLY_TOOLS: continue # second guard: no write tool ever reaches the model llm_tools.append( { "name": definition["name"], "description": definition.get("description", ""), "input_schema": definition.get("input_schema", {"type": "object"}), } )

Run the agent loop with the Claude SDK

execute_tool runs each call under the resolved user identity. Scalekit attaches the user's Meta token server-side, so the loop never touches a credential. Tool errors, such as a Meta throttling response, go back to the model as is_error results instead of crashing the run.

messages = [ { "role": "user", "content": ( "For ad account 123456789012345, compare the last 7 days of spend, " "CTR, and CPC by campaign. Flag any active campaign with CTR under 0.5%." ), } ] while True: response = claude.messages.create( model="claude-sonnet-5", max_tokens=2048, tools=llm_tools, messages=messages, ) messages.append({"role": "assistant", "content": response.content}) if response.stop_reason != "tool_use": print("".join(block.text for block in response.content if block.type == "text")) break tool_results = [] for block in response.content: if block.type != "tool_use": continue try: result = actions.execute_tool( tool_name=block.name, tool_input=block.input, identifier=IDENTIFIER, connection_name=CONNECTION_NAME, ) content, is_error = json.dumps(result.data, default=str), False except Exception as exc: # return Meta or Scalekit errors to the model content, is_error = f"Tool call failed: {exc}", True tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": content, "is_error": is_error, } ) messages.append({"role": "user", "content": tool_results})

Serve the same tools over a Virtual MCP server

A Virtual MCP server is defined once per agent role and serves every user. This role combines read-only Meta Ads reporting with Gmail send, so the digest agent reaches two connectors through one endpoint. Save config_id and mcp_server_url; every session reuses them.

# vmcp_setup.py: run once per agent role, not once per user import os from dotenv import load_dotenv from scalekit import ScalekitClient from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping load_dotenv() actions = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ).actions vmcp = actions.mcp.create_config( name="meta-ads-weekly-digest", description="Read-only Meta Ads reporting plus Gmail send", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="metaads", tools=[ "metaads_campaign_list", "metaads_account_insights_get", "metaads_account_insights_create_report_run", "metaads_report_run_get_status", "metaads_report_run_get_insights", ], ), McpConfigConnectionToolMapping( connection_name="gmail", tools=["gmail_send_message"], # send only: no inbox read, no delete ), ], ) print("config_id:", vmcp.config.id) print("mcp_server_url:", vmcp.config.mcp_server_url)

Mint a session token before each run

Before each run, the backend confirms every connection is active for this user, then mints a short-lived session token. The default lifetime is about an hour; there is no refresh endpoint, so you call create_session_token again when you need a new one.

# mint_session.py: run before every agent session, per user import os from datetime import timedelta from dotenv import load_dotenv from scalekit import ScalekitClient load_dotenv() actions = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ).actions identifier = "user_123" config = actions.mcp.list_configs(filter_name="meta-ads-weekly-digest").configs[0] auth_state = actions.mcp.list_mcp_connected_accounts( config_id=config.id, identifier=identifier, include_auth_link=True, ) inactive = [ a for a in auth_state.connected_accounts if a.connected_account_status != "ACTIVE" ] for a in inactive: print(f"{a.connection_name} needs authorization: {a.authentication_link}") if inactive: raise SystemExit("Authorize the connections above, then run again.") session = actions.mcp.create_session_token( mcp_config_id=config.id, identifier=identifier, expiry=timedelta(minutes=30), ) # Hand these to the agent process for this user only print(f"SCALEKIT_MCP_SERVER_URL={config.mcp_server_url}") print(f"SCALEKIT_MCP_SESSION_TOKEN={session.token}")

Connect a Mastra agent in TypeScript

The Node SDK does not mint session tokens yet, so the Python backend above mints them and hands the URL and token to the Mastra app. Mastra discovers tools and schemas from the server and prefixes each with the server key, so metaads_account_insights_get reaches the model as scalekit_metaads_account_insights_get. Never share a session token across users; any request carrying it runs as that user. Set OPENAI_API_KEY alongside the two Scalekit values.

npm install @mastra/core@1 @mastra/mcp@2 @ai-sdk/openai@4 dotenv
// agent.mts (run with: npx tsx agent.mts) import { Agent } from '@mastra/core/agent'; import { MCPClient } from '@mastra/mcp'; import { openai } from '@ai-sdk/openai'; import 'dotenv/config'; // Minted by your backend for the current user; never shared between users const mcpServerUrl = process.env.SCALEKIT_MCP_SERVER_URL; const mcpToken = process.env.SCALEKIT_MCP_SESSION_TOKEN; if (!mcpServerUrl || !mcpToken) { throw new Error('Set SCALEKIT_MCP_SERVER_URL and SCALEKIT_MCP_SESSION_TOKEN from the mint step'); } const mcp = new MCPClient({ servers: { scalekit: { url: new URL(mcpServerUrl), requestInit: { headers: { Authorization: `Bearer ${mcpToken}` }, }, }, }, }); try { const tools = await mcp.listTools(); const agent = new Agent({ id: 'meta-ads-weekly-digest', name: 'Meta Ads weekly digest', instructions: 'You report on Meta Ads performance and never change campaigns. ' + 'Summarize spend, CTR, and CPC by campaign, then email the digest.', model: openai('gpt-4o'), tools, }); const result = await agent.generate( "Build last week's digest for ad account 123456789012345 and email it to marketing-ops@example.com." ); console.log(result.text); } finally { await mcp.disconnect(); }

Full walkthroughs: Set up and connect a Virtual MCP server, Mastra example, and Anthropic example.

Why the Scalekit way pays off for Meta Ads agents

Two capabilities matter most once the agent spends real money across real tenants.

Agent auth logs for every downstream tool call

Scalekit logs every tool call with full attribution: who authorized it, which agent ran it, and the response. Logs export to your SIEM, with failures separated by source, and each execute_tool response carries an execution_id you can store next to your own run records. Meta's activity log tells you which Meta identity changed a budget. It cannot tell you which of your agents, or which run, issued that change. When a customer's security team asks why an ad set paused at 3am, the answer has to span both layers.

Recommended reading: Audit Trails for Agent Auth in B2B SaaS

Virtual MCP for multi-tool and multi-tenant agents

Meta's ads MCP server rules govern what any agent may do on a customer's portfolio, set by that customer's admin. A Scalekit Virtual MCP server governs what one agent role in your product may see, across Meta Ads and every other connector it needs. The two stack.

One server definition serves all users. Each run gets a session token scoped to one user, and the agent sees only the tools you allowed. The endpoint is static; the identity is per user. There is no MCP server to deploy, host, or maintain.

When a Meta Ads tool you need is missing

If your agent needs a Marketing API edge the prebuilt tools do not cover, custom tools proxy the call through the same connected account. Auth, token custody, and logging stay unchanged, so the edge you add inherits the same per-user attribution as the 148 prebuilt tools. This is the same pattern used for tool calling auth patterns across any connector.

Which one to build against

If your agent is an interactive assistant for media buyers who are present to sign in, who ask about spend, delivery errors, and catalog health, and who approve each activation, Meta's Ads MCP server is the fast path. Its paused-by-default writes are a feature there.

If your agent runs headless, syncs leads, forwards conversions, reacts to webhooks, or pulls large async reports across many client accounts, build against the Marketing API. Most production Meta Ads agents need both modes. Either way, every user brings a token with a 60-day life and no refresh token. That credential problem is identical on both paths, and it is what needs production-grade infrastructure.

For teams moving from single-tenant prototypes to multi-tenant production, the auth architecture shift is significant. How tool calling auth changes when you move from single-tenant to multi-tenant covers that transition in detail.

Talk to us about your Meta Ads agent

Building a Meta Ads agent that runs across many tenants? Talk to the Scalekit team for immediate help with connection setup, tool scoping, and Virtual MCP design. Start with the Meta Ads connector docs, browse the Scalekit connector catalog and all AgentKit connectors, or review Scalekit pricing.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.