Meta Ads MCP
# Meta Ads MCP
Meta Ads MCP is a minimal, read-only Python MCP server that pulls Meta
(Facebook) Ads campaign, ad-set, and ad performance so a human can spot
problems and adjust them in Ads Manager earlier. It never changes budgets,
statuses, or creatives.
## Tools
| Tool | Purpose |
| --- | --- |
| `list_ad_accounts` | List the Meta ad accounts available to the configured access token. |
| `get_campaigns` | Get campaigns for an explicit or configured default ad account. |
| `get_adsets` | Get ad sets beneath an account or campaign parent. |
| `get_ads` | Get ads beneath an account, campaign, or ad-set parent. |
| `get_insights` | Get raw Meta performance metrics for an object and reporting period. |
| `performance_summary` | Rank campaigns by spend and flag performance symptoms for review. |
The flags from `performance_summary` are **HEURISTIC** signals, not diagnoses.
Conversions and ROAS are platform-reported (Meta attribution), **not incremental**;
a holdout or lift test is required to claim incrementality.
## Prerequisites
- Python 3.10 or newer
## Meta credentials
1. Create a Meta app at [Meta for Developers](https://developers.facebook.com/).
2. Add the Marketing API to the app.
3. Generate a long-lived User access token with the `ads_read` permission and
access to the required ad account.
4. Find the ad account ID in Ads Manager. It must use the `act_<digits>` form,
such as `act_123456789`.
Meta's screens and access-token options can change. Follow the official
[Marketing API Get Started documentation](https://developers.facebook.com/docs/marketing-apis/)
for the current screens. Treat the access token as a secret.
## Quick setup
One command does the whole setup:
```console
python -m meta_ads_mcp setup
```
For the full walkthrough - creating the Meta app, redirect URI rules, giving
teammates access, and a troubleshooting table - see
[docs/SETUP.md](docs/SETUP.md).
It asks for the app id and app secret (the secret is never echoed), writes
`.env` with owner-only permissions, runs the browser login, lists the ad
accounts the token can read so you pick one from a numbered menu, and registers
the server in `claude_desktop_config.json` (backing up any existing file).
Restart Claude Desktop afterwards.
If `META_ACCESS_TOKEN` is already set in `.env`, setup offers to keep it and
skips the login entirely. Useful options:
| Option | Purpose |
| --- | --- |
| `--client-config PATH` | Patch a different MCP client config. |
| `--command-path PATH` | Command the client should launch. |
| `--env-file PATH` | Write to a different dotenv file. |
The manual route below is still supported if you would rather set each piece
yourself.
## Environment setup
Copy the template and fill in your credentials:
```powershell
Copy-Item .env.example .env
```
There are two ways to supply the token. Pick one.
### Route A - paste a token
```dotenv
META_ACCESS_TOKEN=your-long-lived-user-access-token
META_AD_ACCOUNT_ID=act_123456789
META_GRAPH_API_VERSION=v23.0
```
A **system user** token from Business Manager (Business Settings > Users >
System Users) never expires and needs no login flow. A manually generated user
token expires in about 60 days.
### Route B - browser login
Put the app credentials in `.env` and leave `META_ACCESS_TOKEN` blank:
```dotenv
META_APP_ID=your-app-id
META_APP_SECRET=your-app-secret
META_AD_ACCOUNT_ID=act_123456789
```
Then authorize in a browser:
```console
python -m meta_ads_mcp login
```
The command opens the Facebook Login dialog, receives the redirect on
`http://localhost:8721/callback`, exchanges the code for a long-lived token,
and caches it at `~/.local/share/meta-ads-mcp/token.json` (`%LOCALAPPDATA%`
equivalent via `XDG_DATA_HOME`) with owner-only permissions. The server picks
that cached token up automatically, so no token ever needs to sit in a config
file.
`http://localhost:8721/callback` must be listed under **Facebook Login >
Settings > Valid OAuth Redirect URIs** on the app. Change the port or scheme
with `META_OAUTH_REDIRECT_URI` if needed.
If Meta rejects a localhost redirect URI, use the paste flow instead - it uses
Meta's own desktop redirect page and needs no registered URI:
```console
python -m meta_ads_mcp login --paste
```
Log in, then copy the full URL from the browser address bar and paste it back
at the prompt.
Related commands:
| Command | Purpose |
| --- | --- |
| `python -m meta_ads_mcp status` | Show the cached token's expiry. |
| `python -m meta_ads_mcp logout` | Delete the cached token. |
Meta issues no refresh tokens, so a long-lived user token lasts about 60 days;
rerun `login` when `status` shows it near expiry. Only a system user token
(route A) avoids re-authorizing entirely.
`META_ACCESS_TOKEN` always wins over the cached token when both are present.
`META_GRAPH_API_VERSION` is optional and defaults to `v23.0`. The `.env` file
is git-ignored; never commit it.
## Install
Create a virtual environment and install the package with its development
dependencies:
```powershell
python -m venv .venv
.venv\Scripts\python.exe -m pip install -e ".[dev]"
```
If you already use `uv`, the install step can instead be:
```powershell
uv pip install -e ".[dev]"
```
`uv` is optional and is not required.
## Run
From an activated virtual environment, start the stdio server with:
```console
python -m meta_ads_mcp
```
The installed console entry point is also `meta-ads-mcp`. A stdio MCP server
waits on standard input, so it is normally launched by an MCP client rather
than run interactively.
## MCP client configuration
For Claude Desktop, add a server entry under `mcpServers` in
`claude_desktop_config.json`. Replace the example path and credentials:
```json
{
"mcpServers": {
"meta-ads": {
"command": "C:\\Users\\your-name\\path\\to\\meta-ads-mcp\\.venv\\Scripts\\python.exe",
"args": ["-m", "meta_ads_mcp"],
"env": {
"META_ACCESS_TOKEN": "your-long-lived-user-access-token",
"META_AD_ACCOUNT_ID": "act_123456789"
}
}
}
}
```
For Claude Code, put the equivalent configuration in the project-level
`.mcp.json`; it mirrors the Desktop configuration:
```json
{
"mcpServers": {
"meta-ads": {
"command": "C:\\Users\\your-name\\path\\to\\meta-ads-mcp\\.venv\\Scripts\\python.exe",
"args": ["-m", "meta_ads_mcp"],
"env": {
"META_ACCESS_TOKEN": "your-long-lived-user-access-token",
"META_AD_ACCOUNT_ID": "act_123456789"
}
}
}
}
```
The token and account ID can live in the client's `env` block instead of
`.env`. Keep client configuration containing a real token private.
If you used browser login (route B), omit `META_ACCESS_TOKEN` from the `env`
block entirely - the server reads the cached token from
`~/.local/share/meta-ads-mcp/token.json`. Leaving a placeholder string there
would override the cached token and every call would fail with an auth error.
## Graph API version
The server pins Graph API `v23.0` by default. If Meta deprecates that version,
set `META_GRAPH_API_VERSION` to a current supported version.
## Smoke test
After filling `.env`, run the manual real-network check:
```console
python scripts/smoke_test.py
```
It lists up to five accessible ad accounts and up to five campaigns from the
configured default account.
## Security
Never commit `.env` or paste the access token into logs, issues, or chat. The
server and all six tools are read-only.
The app secret is used only for the server-side code exchange and is never put
in a URL sent to the browser. The cached token file is written with `0600`
permissions inside a `0700` directory, and the login flow validates the OAuth
`state` value with a constant-time comparison before exchanging any code.
## Development
Run the test suite with:
```console
pytest
```
TDQS
Scored across 6 tools
Each tool targets a distinct resource (ad accounts, campaigns, ads, ad sets, insights, performance summary) with clear boundaries and explicit 'do not use' guidance, eliminating ambiguity.
Most tools follow a 'get_<resource>' pattern (get_campaigns, get_ads, get_insights, get_adsets), with one using 'list_' (list_ad_accounts) and another using a noun phrase (performance_summary), which is a minor inconsistency.
The 6 tools are well-scoped for a read-only Meta Ads analytics server, covering account discovery, hierarchical retrieval, insights, and a summary function without being excessive.
The surface is read-only and covers listing and insights, but lacks any create, update, or delete capabilities, and misses tools for single-object retrieval or ad account details beyond listing.