Meta MCP Connector
README.md
# Meta MCP Connector
Plain-language MCP server for **Meta / Facebook Ads** via the Graph API.
Ask about ad accounts, campaigns, ad sets, spend, and performance in natural language — no table names or raw API paths required.
## Features
- Live data from the Facebook Marketing API (Graph API)
- List ad accounts with parent Business Manager
- Campaign / ad set / ad performance (spend, impressions, clicks, purchases)
- Account-level spend dashboard (`account-spend.html`)
- Claude.ai OAuth when deployed to Cloud Run (`MCP_PUBLIC_URL`)
## Project structure
```
meta-mcp-connector/
├── meta_mcp_server.py # MCP server + HTTP routes + tools
├── meta_graph.py # Facebook Graph API client
├── meta_oauth.py # Claude-compatible OAuth provider
├── account-spend.html # Spend-by-account dashboard
├── test-accounts.html # Ad account list test page
├── get-token.html # Helper to obtain FACEBOOK_ACCESS_TOKEN
├── requirements.txt
├── Dockerfile
├── deploy-cloudrun.ps1 # Deploy to Google Cloud Run
├── .env.example
└── .cursor/mcp.json # Local Cursor MCP config (example)
```
## Setup (local)
### 1. Clone and install
```bash
git clone https://github.com/devopsbrandmirchi/meta-mcp-connector.git
cd meta-mcp-connector
python -m venv .venv
# Windows
.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env
```
### 2. Configure `.env`
```env
FACEBOOK_APP_ID=your_app_id
FACEBOOK_APP_SECRET=your_app_secret
FACEBOOK_ACCESS_TOKEN=your_user_token_with_ads_read
FACEBOOK_AD_ACCOUNT_ID=114810198697538
```
**Token requirements**
- Must be a **User Token** (`EAA…`), not an App Token (`app_id|secret`)
- Generated in [Graph API Explorer](https://developers.facebook.com/tools/explorer/) for **your** `FACEBOOK_APP_ID`
- Permissions: `ads_read`, `ads_management`, `business_management`
Use `get-token.html` or run the server and open `http://127.0.0.1:8001/get-token`.
### 3. Start the server
```bash
python meta_mcp_server.py --http
```
| Endpoint | URL |
|----------|-----|
| MCP | `http://127.0.0.1:8001/mcp` |
| Account spend UI | `http://127.0.0.1:8001/account-spend` |
| Account list API | `http://127.0.0.1:8001/api/accounts` |
| Account spend API | `http://127.0.0.1:8001/api/account-spend?date=YYYY-MM-DD` |
### 4. Cursor MCP config
`.cursor/mcp.json`:
```json
{
"mcpServers": {
"meta": {
"url": "http://127.0.0.1:8001/mcp"
}
}
}
```
Reload MCP in Cursor settings after starting the server.
## MCP tools
| Tool | Purpose |
|------|---------|
| `help_meta` | What you can ask |
| `list_accounts` | Ad accounts + parent Business Manager |
| `list_campaigns` | Campaigns in an ad account |
| `list_adsets` | Ad sets in an ad account |
| `list_creatives` | Ads + creative thumbnails / preview URLs |
| `get_integration_status` | Token / app health check |
| `get_ads_summary` | KPI totals for a date range |
| `get_daily_trend` | Day-by-day spend & metrics |
| `get_hourly_performance` | Hourly spend / clicks / purchases |
| `get_performance_breakdown` | By campaign / adset / placement / region / age / gender / … |
| `get_demographics_breakdown` | Age / gender / age×gender |
| `get_conversions_by_region` | Purchases & spend by state/region |
| `get_reach_frequency` | Reach & frequency (delivery pressure) |
| `get_top_ads` | Top ads by spend or other metric |
| `get_campaign_budgets` | Campaign budgets + status |
| `set_object_status` | Pause / activate campaign, ad set, or ad (`ads_management`) |
| `get_adset_targeting` | Who each ad set targets |
| `get_multi_account_spend` | Spend rollup across all accounts |
## Example prompts
- "List my Meta ad accounts"
- "Spend across all accounts yesterday"
- "Meta ads summary from 2026-08-01 to 2026-08-15"
- "Daily spend last 7 days for WOW Ad Account"
- "Hourly performance yesterday"
- "Age and gender breakdown last week"
- "Conversions by state for yesterday"
- "Show creatives with preview URLs"
- "Who does the Prospecting ad set target?"
- "Campaign budgets and status"
- "Pause campaign 120…" / "Activate campaign 120…"
- "Reach and frequency by campaign this month"
- "Top ads by spend yesterday"
- "Break down spend by campaign last week"
## Google Cloud Run
Host the same HTTP MCP endpoint on Cloud Run (same pattern as [vdp-connector](https://github.com/devopsbrandmirchi/vdp-connector)). The Docker image does **not** bake in `.env` or secrets.
### Prerequisites
- [Google Cloud SDK (`gcloud`)](https://cloud.google.com/sdk/docs/install) installed and logged in (`gcloud auth login`)
- A GCP project with **billing enabled**
- Your GCP **Project ID**
- Facebook credentials ready in local `.env` (see `.env.example`)
### Deploy
From this repo folder:
```powershell
# App secret read from .env
.\deploy-cloudrun.ps1 -ProjectId "YOUR_GCP_PROJECT_ID"
# Optional overrides
.\deploy-cloudrun.ps1 -ProjectId "YOUR_GCP_PROJECT_ID" -Region "us-central1" -Service "meta-mcp"
# App secret from a one-line file (gitignored)
.\deploy-cloudrun.ps1 -ProjectId "YOUR_GCP_PROJECT_ID" -SecretFile ".\facebook-app-secret.key"
```
What the script does:
1. Enables Cloud Run, Cloud Build, Secret Manager, Artifact Registry
2. Uploads `FACEBOOK_APP_SECRET` → Secret Manager (`meta-facebook-app-secret`)
3. Uploads `FACEBOOK_ACCESS_TOKEN` → Secret Manager (`meta-facebook-access-token`) if set in `.env`
4. Deploys with `--source .` (Dockerfile), env vars, and secret injection
5. Sets `MCP_PUBLIC_URL` to the service URL (required for Claude OAuth)
6. Prints the connector URL: `https://SERVICE-URL/mcp`
Deploy flags:
- `--set-env-vars MCP_TRANSPORT=http,HOST=0.0.0.0,FACEBOOK_APP_ID=…,FACEBOOK_AD_ACCOUNT_ID=…`
- `--set-secrets FACEBOOK_APP_SECRET=meta-facebook-app-secret:latest,FACEBOOK_ACCESS_TOKEN=meta-facebook-access-token:latest`
- `--allow-unauthenticated` (needed for a working MCP URL on first deploy)
- `--session-affinity`, `--max-instances 1`, `--timeout 300`, `--port 8080`
### Cursor (Cloud Run)
After deploy, put the printed URL in `.cursor/mcp.json`:
```json
"meta": {
"url": "https://YOUR-CLOUD-RUN-URL/mcp"
}
```
You do **not** need a local `python meta_mcp_server.py` process when using Cloud Run.
### Claude custom connector
Claude.ai **requires OAuth** for remote custom connectors. This server embeds a Claude-compatible OAuth provider (Dynamic Client Registration) when `MCP_PUBLIC_URL` is set (the deploy script sets it automatically).
1. Deploy with `.\deploy-cloudrun.ps1 -ProjectId "YOUR_GCP_PROJECT_ID"`
2. Remove any old broken connector
3. **Settings → Connectors → Add custom connector**
4. Paste **exactly**: `https://YOUR-CLOUD-RUN-URL/mcp` (must include `/mcp`)
5. Leave **OAuth Client ID** empty in Advanced settings (DCR is enabled)
6. Click Connect — Claude registers itself and authorizes briefly
After a successful Connect, Claude should **stay connected** when you reopen the app.
OAuth access/refresh tokens are **HMAC-signed and stateless** (no /tmp session store).
They survive Cloud Run deploys and cold starts — this is what stopped the daily
"Your session has expired" loop. Signing uses `MCP_OAUTH_JWT_SECRET` or falls back
to `FACEBOOK_APP_SECRET` (already on Cloud Run via Secret Manager).
You only need to reconnect once after this update is live, or if you remove the connector.
If you see *"Couldn't register with … sign-in service"* (`ofid_…`):
- Confirm the URL ends with `/mcp` (not the bare Cloud Run host)
- Confirm the service is up: open `https://YOUR-CLOUD-RUN-URL/.well-known/oauth-authorization-server` — you should see JSON with `registration_endpoint`
- Confirm `MCP_PUBLIC_URL` matches the service origin (redeploy if you changed the URL)
- Do **not** paste a random OAuth Client ID unless you set up an external IdP
If you see *"This connector has a server configuration issue"*:
- **Redeploy** the latest code (fixes OAuth metadata for Claude: public-client DCR + `/mcp` protected-resource metadata)
- **Remove** the old connector in Claude, then add it again (clears stale OAuth registration)
- Verify health: `https://YOUR-CLOUD-RUN-URL/health` → `oauth_enabled: true`
- Verify protected resource: `https://YOUR-CLOUD-RUN-URL/.well-known/oauth-protected-resource/mcp` → JSON (not 404)
- Verify auth metadata includes `"none"` in `token_endpoint_auth_methods_supported`
### Verify deployment
| Check | URL |
|-------|-----|
| Health | `https://YOUR-SERVICE-URL/health` |
| MCP endpoint | `https://YOUR-SERVICE-URL/mcp` |
| OAuth discovery | `https://YOUR-SERVICE-URL/.well-known/oauth-authorization-server` |
| Protected resource | `https://YOUR-SERVICE-URL/.well-known/oauth-protected-resource/mcp` |
| Account spend UI | `https://YOUR-SERVICE-URL/account-spend` |
### Local vs Cloud
| | Local | Cloud Run |
|---|---|---|
| Start | `python meta_mcp_server.py --http` | `.\deploy-cloudrun.ps1 -ProjectId …` |
| URL | `http://127.0.0.1:8001/mcp` | `https://…run.app/mcp` |
| Secrets | `.env` file | Secret Manager → env injection |
| Host / port | `127.0.0.1:8001` | `0.0.0.0` + `PORT=8080` |
| Claude OAuth | Off (no `MCP_PUBLIC_URL`) | On (`MCP_PUBLIC_URL` = service URL) |
| Tools | Same | Same |
### Security note
`--allow-unauthenticated` makes the Cloud Run URL publicly reachable. OAuth means Claude must complete registration/authorize before tools work; random callers without a token get `401`. Still treat the URL as sensitive and add stronger auth before sharing widely.
### Common pitfalls
1. Claude URL missing `/mcp` → "Couldn't connect to the server"
2. `MCP_PUBLIC_URL` not set → OAuth 404 → "Couldn't register with sign-in service"
3. OAuth metadata missing `/mcp` protected resource or `"none"` auth method → "server configuration issue"
4. Committing `.env` or secret files
5. Forgetting to redeploy after OAuth code changes
6. Deploying without `FACEBOOK_ACCESS_TOKEN` → Graph API returns permission errors
## Push to GitHub
Repo: https://github.com/devopsbrandmirchi/meta-mcp-connector
```powershell
# Sign in to the devopsbrandmirchi GitHub account if needed
git credential-manager github login --force
# Point origin at the org repo (replaces any old personal remote)
git remote remove origin 2>$null
git remote add origin https://github.com/devopsbrandmirchi/meta-mcp-connector.git
git push -u origin main
```
For a **new Cloud Run** service (fresh URL, no old `meta-mcp-connector-git` fallback):
```powershell
.\deploy-cloudrun.ps1 -ProjectId "YOUR_GCP_PROJECT_ID" -Region "us-central1" -Service "meta-mcp"
```
Then in Claude use exactly: `https://YOUR-NEW-SERVICE-URL/mcp`
## Security
- Never commit `.env` — it is gitignored
- `FACEBOOK_ACCESS_TOKEN` is a secret; rotate if exposed
- Cloud Run deploy uses `--allow-unauthenticated` for MCP URL reachability; OAuth protects tool access when `MCP_PUBLIC_URL` is set
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues