mcp-facebook-ads
# Custom Facebook Ads MCP Server
Eigen MCP server (Python) voor Meta/Facebook Business Manager, met:
- data inladen via Marketing API (insights/reporting),
- campagnes opbouwen (campaigns, ad sets, ads, creatives),
- optimalisatie en budgetadvies op basis van best practices.
## Architectuur en endpoints
- **MCP-transport**: stdio. Cursor (of een andere MCP-client) start het proces; er is **geen** apart HTTP-endpoint voor de MCP zelf.
- **Meta API**: alle calls gaan via de Graph API / Marketing API zoals de SDK (`facebook-business`) die gebruikt. Basis-URL:
`https://graph.facebook.com/{META_API_VERSION}/...`
De standaard API-versie in deze codebase is **`v25.0`** (zet `META_API_VERSION` in `.env` gelijk aan wat je app ondersteunt).
## 1) Vereisten
- Python 3.11+
- [`uv`](https://docs.astral.sh/uv/)
- Een Meta Developer-app met **Marketing API** en een geldig **access token** met rechten op het gewenste ad account
**Benodigde permissies (scopes) op het token:**
- `ads_management`
- `ads_read`
- `business_management`
- `pages_read_engagement`
Zonder deze scopes kunnen tools deels of volledig falen.
## 2) Meta Developer-app aanmaken
1. Ga naar [Meta for Developers](https://developers.facebook.com/).
2. Maak een **app** (kies het type dat past bij Marketing API / zakelijke ads).
3. Voeg het product **Marketing API** toe en voltooi de stappen die Meta vraagt.
4. Noteer onder **App settings → Basic**:
- **App ID** → `META_APP_ID`
- **App Secret** → `META_APP_SECRET`
## 3) Ad account en token (aanbevolen: System User)
**Aanbevolen** voor een stabiele, team- of server-achtige setup: een **System user** in [Meta Business Settings](https://business.facebook.com/settings) (Gebruikers → System users), met toegewezen assets (het juiste **ad account** en indien nodig **Pages**). Genereer daar een token met de scopes hierboven.
- **Ad account-ID** in `.env` als `act_<numeriek_id>`. De server voegt `act_` automatisch toe als je alleen het nummer invult.
**Alternatieven (minder ideaal voor productie):**
- Korte tokens uit de Graph API Explorer (vooral geschikt om even te testen).
- OAuth-flow (zie hieronder) om user tokens of long-lived tokens te verkrijgen.
## 4) OAuth- en token-endpoints (Meta)
Vervang `{VERSION}` door dezelfde waarde als `META_API_VERSION` (bijv. `v25.0`). Vervang placeholders door je echte app- en redirect-gegevens.
| Stap | URL |
|------|-----|
| Gebruiker inloggen en scopes goedkeuren | `https://www.facebook.com/{VERSION}/dialog/oauth?client_id={APP_ID}&redirect_uri={REDIRECT_URI}&scope=ads_management,ads_read,business_management,pages_read_engagement&response_type=code` |
| **Authorization code** omzetten naar **short-lived** access token | `GET https://graph.facebook.com/{VERSION}/oauth/access_token?client_id={APP_ID}&client_secret={APP_SECRET}&redirect_uri={REDIRECT_URI}&code={CODE}` |
| **Short-lived** token omzetten naar **long-lived** token (ca. 60 dagen) | `GET https://graph.facebook.com/{VERSION}/oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&fb_exchange_token={SHORT_LIVED_TOKEN}` |
**Redirect URI**: in de Meta-app (bijv. onder Facebook Login / OAuth-instellingen) moet de **OAuth redirect URI** exact overeenkomen met `{REDIRECT_URI}` in bovenstaande requests.
## 5) Installatie en `.env`
```bash
git clone <jouw-repo-url> mcp-facebook-ads
cd mcp-facebook-ads
uv sync
cp .env.example .env # of maak .env handmatig
```
Vul `.env` (zie `.env.example` voor veldnamen):
```env
META_APP_ID=...
META_APP_SECRET=...
META_ACCESS_TOKEN=...
META_AD_ACCOUNT_ID=act_...
META_API_VERSION=v25.0
```
De server laadt `.env` via `python-dotenv` vanaf de **working directory** van het proces. Zorg dat de MCP in Cursor `cwd` op de projectroot heeft staan als je geen inline `env` in de MCP-config gebruikt.
**Commit `.env` nooit**; die staat in `.gitignore`.
## 6) Cursor-configuratie
Je kunt de MCP op twee manieren configureren:
1. **Project** (aanbevolen voor dit repo): `.cursor/mcp.json` in de projectroot (staat mogelijk niet in git; maak hem lokaal aan).
2. **Globaal**: Cursor Settings → MCP, afhankelijk van je Cursor-versie.
Voorbeeld `.cursor/mcp.json` (geldige JSON — geen `//`-comments). Vervang het pad door je eigen projectmap; secrets horen in `.env` in die map, niet in git.
**Optie A** — `cwd` op de projectroot (aanbevolen; `.env` wordt dan automatisch geladen):
```json
{
"mcpServers": {
"facebook-ads": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_facebook_ads.server"],
"cwd": "/ABSOLUUT/PAD/NAAR/mcp-facebook-ads"
}
}
}
```
**Optie B** — zonder `cwd`, met expliciet pad in `uv run`:
```json
{
"mcpServers": {
"facebook-ads": {
"command": "uv",
"args": [
"run",
"--directory",
"/ABSOLUUT/PAD/NAAR/mcp-facebook-ads",
"python",
"-m",
"mcp_facebook_ads.server"
]
}
}
}
```
Secrets kun je óf in `.env` in die `cwd` laten staan, óf (minder ideaal) als `env`-object in `mcp.json` zetten — let op dat je die file dan niet deelt.
## 7) Delen met collega’s
**Wel delen:** repository, deze README, `App ID`, bekende ad account-identifiers waar ze toch al toegang toe moeten hebben, en het Cursor-commando + `cwd`.
**Niet delen** (alleen via een secrets manager of 1:1 veilig kanaal): `META_APP_SECRET`, `META_ACCESS_TOKEN`. Liever per persoon of per omgeving een eigen System user-token met minimale benodigde rechten dan één gedeelde token zonder rotatie.
## 8) Sneltest tegen de Graph API
Controleer of je token werkt (zelfde `{VERSION}` als in `.env`):
```http
GET https://graph.facebook.com/{VERSION}/me?access_token={META_ACCESS_TOKEN}
```
Als dit faalt, los eerst app- en tokenconfiguratie op voordat je de MCP gebruikt.
## 9) Starten als MCP-server (handmatig)
```bash
uv run python -m mcp_facebook_ads.server
```
Draait op stdio; normaal start Cursor dit proces zelf.
## 10) Beschikbare toolgroepen
### Account Management
- `get_ad_accounts`
- `get_account_info`
- `get_account_pages`
### Campaigns
- `get_campaigns`
- `get_campaign_details`
- `create_campaign`
- `update_campaign`
- `delete_campaign`
### Ad Sets
- `get_adsets`
- `get_adset_details`
- `create_adset`
- `update_adset`
- `delete_adset`
### Ads
- `get_ads`
- `get_ad_details`
- `create_ad`
- `update_ad`
- `delete_ad`
### Creatives & Media
- `get_ad_creatives`
- `upload_ad_image`
- `create_ad_creative`
- `create_asset_feed_ad_creative`
- `update_ad_creative`
- `get_ad_previews`
### Insights & Reporting
- `get_insights`
- `get_account_insights`
- `compare_performance`
### Audience & Targeting
- `search_interests`
- `search_behaviors`
- `search_demographics`
- `search_geo_locations`
- `estimate_audience_size`
### Best Practices & Optimization
- `get_campaign_templates`
- `create_campaign_from_template`
- `analyze_campaign`
- `suggest_budget_allocation`
## 11) Best-practice defaults
- Nieuwe entities worden standaard met status `PAUSED` aangemaakt.
- Templates zijn beschikbaar voor `awareness`, `traffic`, `leads`, `sales`.
- Adviezen sturen op CTR/CPC, en op funnel-gescheiden structuur.
## 12) Veiligheid en governance
- Gebruik bij voorkeur een **system user**-token, niet je persoonlijke user-token.
- Geef productie-activatie (`ACTIVE`) pas na review.
- Log API-fouten en `fbtrace_id`-waarden voor snelle debugging bij Meta.
## 13) Testen
```bash
uv run pytest
```
TDQS
Scored across 39 tools
Most tools target clearly distinct resources and actions (campaign vs adset vs ad vs creative). Some overlap remains between get_insights, get_account_insights, and compare_performance, and between get_ad_accounts and get_account_info, but descriptions help differentiate.
The set is predominantly snake_case with a verb_noun pattern. Minor deviations exist, such as get_account_info versus get_campaign_details/get_adset_details/get_ad_details, and multiple similar prefixes for creative tools.
39 tools is heavy for this domain and exceeds the 25+ threshold for 'too many'. Several tools could be consolidated, including the multiple creative-creation tools, insight variants, and separate targeting search tools.
Core CRUD/lifecycle coverage exists for campaigns, adsets, ads, and creatives, plus targeting searches, uploads, previews, and insights. Minor gaps include no delete_ad_creative and no clear adset/ad-level insights tool.