meta-mcp
Provides tools for managing Facebook Pages, including listing pages, getting page info, posts, comments, and page insights.
Provides tools for Instagram business accounts, including getting account info, listing media, media insights, user insights, and hashtag search.
Provides read-only access to Meta's Graph API, including Facebook Pages, Instagram, WhatsApp Business, and Ads Marketing APIs.
Provides tools for WhatsApp Business API, including listing business accounts, phone numbers, message templates, and analytics.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@meta-mcp list my ad accounts"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
meta-mcp
Read-only MCP server for Meta (Facebook) Graph API. Plugs into Claude Code (and any other MCP-compatible client) over stdio (local) or Streamable HTTP (remote).
Covers four API surfaces with a single long-lived token:
Marketing API — ad accounts, campaigns, ad sets, ads, creatives, insights
Facebook Pages — pages, posts, comments, page insights
Instagram Graph — business accounts, media, media/account insights, hashtag search
WhatsApp Business — WABAs, phone numbers, message templates, analytics
Plus debug_token (inspect any token) and graph_get (raw GET escape-hatch).
HTTP method is hard-coded to GET everywhere — Claude cannot accidentally create, mutate, or delete anything.
Tools
Specialized (validated with zod):
Surface | Tools |
Common |
|
Pages |
|
Marketing |
|
| |
|
Related MCP server: flin-meta-ads-mcp
1. Get an access token
The token prefix EAA... is the same for every Graph token type — you can't tell which kind you have from the prefix. Run debug_token after first start to identify it. The four kinds:
Kind | When to use | How to obtain |
System User | Recommended for ads / business automation — long-lived, doesn't tie to a human account | Business Manager → Business settings → Users → System users → Generate new token |
Page Access Token | Single page only | Graph Explorer with a user token: |
User OAuth token | Personal access, expires in ~60 days | Standard FB Login OAuth flow |
App access token | Server-to-server calls that don't need user context (e.g. | `APPID |
For page_insights and any Instagram tool you need a Page Access Token. This server resolves it lazily: if FB_ACCESS_TOKEN is a user/system token with admin rights, calling any Page/IG tool triggers a one-time /me/accounts fetch that caches each page's token. No manual swap needed.
2. App credentials (recommended)
If you set both FB_APP_ID and FB_APP_SECRET in .env:
Every request is signed with
appsecret_proof— Meta's defence against token replay if the access token leaks.debug_tokenworks (Meta requiresAPPID|APPSECRETto call that endpoint).
Find them at developers.facebook.com → My Apps → {your app} → Settings → Basic.
3. Three ways to run
Mode | When to use |
npm ( | Easiest for end users — no clone, no build |
stdio (from source) | Hacking on the server locally |
Docker (Ubuntu) | Shared server, multiple devices, mobile, teammates |
A. Install from npm
claude mcp add meta \
--env FB_ACCESS_TOKEN=<your-token> \
--env FB_APP_ID=<your-app-id> \
--env FB_APP_SECRET=<your-app-secret> \
-- npx -y meta-mcpOr manually in ~/.claude.json:
{
"mcpServers": {
"meta": {
"command": "npx",
"args": ["-y", "meta-mcp"],
"env": {
"FB_ACCESS_TOKEN": "EAA...",
"FB_APP_ID": "<your-app-id>",
"FB_APP_SECRET": "<your-app-secret>"
}
}
}
}In Claude Code: /mcp → meta: connected.
B. Local — stdio from source
git clone https://github.com/nourgithub/meta-mcp.git
cd meta-mcp
cp .env.example .env
# edit .env — set FB_ACCESS_TOKEN at minimum
# (also FB_APP_ID + FB_APP_SECRET for debug_token / appsecret_proof)
npm install
npm run build
claude mcp add meta -- node "$(pwd)/dist/index.js"C. Remote — Docker on Ubuntu
Everything ships in deploy/:
deploy/Dockerfile— multi-stage Node 20 alpine build, runs as non-root withtinifor clean signalsdeploy/docker-compose.yml— two services: the app + Caddy as TLS-terminating reverse proxydeploy/Caddyfile— auto-HTTPS via Let's Encrypt, proxies/mcp(SSE-aware) and/health
Pre-requisites: Ubuntu server with public IP and a DNS A-record pointing your domain at it.
# 1) On the server — install Docker
ssh root@SERVER_IP
apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 2) Open ports 80 and 443 (skip if no ufw)
ufw allow 80/tcp
ufw allow 443/tcpFrom your laptop, push the project to the server:
tar -czf - -C /d/meta-mcp \
--exclude='./node_modules' --exclude='./dist' --exclude='./.env' --exclude='./.git' . \
| ssh root@SERVER_IP "mkdir -p /opt/meta-mcp && tar -xzf - -C /opt/meta-mcp"Back on the server, prepare .env:
ssh root@SERVER_IP
cd /opt/meta-mcp
cp .env.example .env
nano .envFill at minimum:
FB_ACCESS_TOKEN=EAA...
FB_APP_ID=...
FB_APP_SECRET=...
MCP_TRANSPORT=http
MCP_HTTP_PORT=8765
MCP_HTTP_AUTH_TOKEN=<openssl rand -hex 32>
MCP_HTTP_ALLOWED_HOSTS=meta-mcp.example.com
META_MCP_DOMAIN=meta-mcp.example.com
ACME_EMAIL=you@example.comchmod 600 .env. Build and start:
cd /opt/meta-mcp/deploy
docker compose build
docker compose up -d
docker compose ps # both services should be "running" / "healthy"
docker compose logs -f # watch Caddy fetch the certVerify:
curl https://meta-mcp.example.com/health
# {"ok":true,"name":"meta-mcp"}
curl -i -X POST https://meta-mcp.example.com/mcp
# 401 — missing bearer (expected)Connect Claude Code:
claude mcp add --transport http meta https://meta-mcp.example.com/mcp \
--header "Authorization: Bearer <your MCP_HTTP_AUTH_TOKEN>"Pagination
All *_list tools accept:
limit— page size (default 25)auto_paginate=true— walkpaging.nextup tomax_pages(default 4) timesmax_pages— safety cap
When auto_paginate=true the response shape is:
{
"items": [ ...flattened from all pages ],
"pages": 3,
"truncated": false,
"next_cursor": "QVFIUjF..."
}Cursors are short-lived (~30 min). Don't reuse a stale next_cursor after a long delay.
Rate limits
Meta's actual budget depends on app tier (Standard / Advanced), token type, and per-object usage — there is no single fixed RPS for Graph API. The server applies:
Client-side: soft cap of
META_RPS_LIMITrequests / second (default 25, env-tunable).Header-aware cooldown: after each response we parse
X-App-Usage,X-Ad-Account-Usage,X-Business-Use-Case-Usage. If any bucket > 90%, the next request is delayed (usingestimated_time_to_regain_accessif present).Retry: on HTTP 429 or Graph error codes
4,17,32,613the request is retried with exponential backoff (max 2 retries).
Security
FB_ACCESS_TOKENis sent only asAuthorization: Bearerheader, never as a query parameter — keeps it out of proxy / CDN logs.If
FB_APP_SECRETis set, every request includesappsecret_proof(HMAC-SHA256 of token with secret). Token replay from another machine fails.Graph error bodies are scrubbed before being returned: occurrences of the token, app secret and
appsecret_proofare replaced with***to prevent leakage via tool output.HTTP mode:
MCP_HTTP_AUTH_TOKENis mandatory, compared with constant-time.MCP_HTTP_ALLOWED_HOSTSenables DNS-rebinding protection.HTTPS in Docker mode handled by Caddy automatically.
Caveats
Insights async jobs not supported. For huge Ads Insights ranges Meta recommends
POST /insights→ pollreport_run_id. This server is GET-only; use shorter ranges or export from Ads Manager.Webhook subscriptions out of scope. MCP is a request/response model; subscribing to realtime updates needs a separate server.
Page-token cache is per-process. Restarting the server clears it. First IG/Page-Insights call after restart triggers one extra
/me/accountsround-trip.
Project layout
meta-mcp/
├── src/
│ ├── index.ts # entry: picks transport, registers tools
│ ├── config.ts # loads .env, validates required vars
│ ├── client.ts # fetch wrapper, rate limiter, paginator, page-token cache
│ ├── http-server.ts # Express + StreamableHTTPServerTransport + bearer auth
│ └── tools/
│ ├── raw.ts # graph_get
│ ├── debug.ts # debug_token
│ ├── pages.ts # Facebook Pages
│ ├── marketing.ts # Ads
│ ├── instagram.ts # Instagram Graph
│ └── whatsapp.ts # WhatsApp Business
├── deploy/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── Caddyfile
├── .env.example
├── package.json
└── tsconfig.jsonSmoke test without Claude
stdio:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.jsHTTP:
curl -sS -X POST https://meta-mcp.example.com/mcp \
-H "Authorization: Bearer $MCP_HTTP_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' \
-iLicense
MIT.
Available Tools
30 toolsad_account_getGet one ad accountD
GET /act_{id}.
Inputs: account_id (required), fields.
ad_accounts_listList ad accounts accessible to the tokenD
GET /me/adaccounts.
Inputs: fields, limit.
adcreative_getGet one ad creativeD
GET /{creative_id}.
Inputs: id (required), fields.
adcreatives_listList ad creatives under an ad accountD
GET /act_{id}/adcreatives.
Inputs: account_id (required), fields, limit, auto_paginate, +1 more.
adsets_listList ad setsC
GET /{parent}/adsets. parent.type selects the path: 'account' → /act_{id}/adsets, 'campaign' → /{campaign_id}/adsets.
Inputs: parent (required), fields, filtering, effective_status, +3 more.
ads_listList adsC
GET /{parent}/ads. parent.type selects the path: 'account' → /act_{id}/ads, 'campaign' → /{campaign_id}/ads, 'adset' → /{adset_id}/ads.
Inputs: parent (required), fields, filtering, effective_status, +3 more.
campaign_getGet one campaignD
GET /{campaign_id}.
Inputs: id (required), fields.
campaigns_listList campaigns under an ad accountD
GET /act_{id}/campaigns.
Inputs: account_id (required), fields, filtering, effective_status, +3 more.
debug_tokenInspect a Meta access tokenA
GET /debug_token — returns token type (USER / PAGE / APP / SYSTEM_USER), app_id, expires_at, scopes, is_valid. If input_token is omitted, inspects the server's configured FB_ACCESS_TOKEN. Requires FB_APP_ID and FB_APP_SECRET to be configured (Meta requires APPID|APPSECRET as the access_token for this endpoint).
Inputs: input_token.
graph_getRaw GET to Meta Graph APIA
Escape-hatch: raw GET to any Graph API endpoint. Path must start with '/' and contain only safe characters. If the path starts with /v{X.Y}/ the version is stripped (server uses FB_GRAPH_VERSION). Always GET (read-only). Use only when no specialized tool fits.
Inputs: path (required), params.
ig_business_account_getGet Instagram Business / Creator accountB
GET /{ig_user_id}. Note: ig_user_id is NOT the @username — it's the IG Business Account id obtained via /{page_id}?fields=instagram_business_account. Requires the Page Access Token of the linked Facebook Page (resolved automatically via via_page_id).
Inputs: ig_user_id (required), via_page_id (required), fields.
ig_hashtag_mediaHashtag media — top or recentC
GET /{hashtag_id}/top_media or /{hashtag_id}/recent_media. user_id is required by Meta.
Inputs: hashtag_id (required), kind (required), user_id (required), fields, +3 more.
ig_hashtag_searchSearch Instagram hashtag → hashtag_idA
GET /ig_hashtag_search?q=...&user_id=... — returns the hashtag id used by ig_hashtag_media. user_id is your IG Business Account id (not the FB user id).
Inputs: q (required), user_id (required).
ig_media_getGet one IG media itemC
GET /{media_id}.
Inputs: media_id (required), via_page_id (required), fields, locale.
ig_media_insightsInsights for one IG media itemB
GET /{media_id}/insights. Metric set depends on media_type: IMAGE/CAROUSEL_ALBUM: impressions, reach, engagement, saved. VIDEO/REELS: plays, reach, engagement, video_views, saved. STORY: impressions, reach, replies, exits, taps_forward, taps_back.
Inputs: media_id (required), via_page_id (required), metric (required).
ig_media_listList IG media (posts) of a Business accountD
GET /{ig_user_id}/media.
Inputs: ig_user_id (required), via_page_id (required), fields, limit, +2 more.
ig_user_insightsAccount-level Instagram insightsB
GET /{ig_user_id}/insights. Period is required. Common metrics: impressions, reach, profile_views, follower_count (day); audience_country, audience_gender_age (lifetime).
Inputs: ig_user_id (required), via_page_id (required), metric (required), period (required), +2 more.
insightsAds Insights (sync only)A
GET /{object_id}/insights. Sync only — for huge date ranges Meta recommends async jobs (POST /insights → polling), which this read-only server does not support. object_id can be account (use 'act_{id}' or numeric and we'll prefix), campaign, adset or ad — level must match. Common fields: spend, impressions, clicks, ctr, cpc, cpm, reach, frequency, actions, action_values.
Inputs: object_id (required), level (required), fields, date_preset, +8 more.
page_getGet one Facebook PageC
GET /{page_id} — page metadata.
Inputs: page_id (required), fields, locale.
page_insightsPage Insights metricsA
GET /{page_id}/insights. Requires a Page Access Token (resolved automatically). Pass an array of metric names (e.g. [page_impressions, page_fans, page_post_engagements]). Period default is day; pass since/until as YYYY-MM-DD or unix seconds.
Inputs: page_id (required), metric (required), period, since, +1 more.
page_postsList posts on a Facebook PageA
Default edge is published_posts (Page-owned posts paged by created time). Other edges: posts (default Graph posts edge), feed (everything on the wall, including visitor posts), tagged (posts where the page is tagged).
Inputs: page_id (required), edge, fields, since, +4 more.
pages_listList Facebook Pages owned/managed by the tokenA
GET /me/accounts — returns pages with id, name and (importantly) access_token for each. Run this first to discover page_id values for other Page tools.
Inputs: fields, limit.
post_commentsList comments on a postD
GET /{post_id}/comments.
Inputs: post_id (required), via_page_id (required), fields, order, +3 more.
post_getGet one Facebook PostA
GET /{post_id}. Post id format is usually {page_id}_{post_id}. Use a Page Access Token via via_page_id (extracted from the post id prefix).
Inputs: post_id (required), via_page_id (required), fields.
wa_analyticsWhatsApp Business analyticsC
GET /{waba_id}?fields=analytics.start({start}).end({end}).granularity({granularity}) — messages sent/delivered/read by phone number, by country, etc. start/end are unix timestamps in seconds.
Inputs: waba_id (required), start (required), end (required), granularity (required), +2 more.
wa_business_owned_wabasList WhatsApp Business Accounts owned by a BusinessB
GET /{business_id}/owned_whatsapp_business_accounts — returns WABAs owned by the parent Business. Find business_id via Business Manager or /me/businesses.
Inputs: business_id (required), fields, limit.
wa_message_templatesList WA message templates under a WABAD
GET /{waba_id}/message_templates.
Inputs: waba_id (required), fields, limit, auto_paginate, +1 more.
wa_phone_number_getGet one WA phone number — quality rating / messaging tierA
GET /{phone_number_id} — includes quality_rating, messaging_limit_tier, name_status, verified_name.
Inputs: phone_number_id (required), fields.
wa_phone_numbersList phone numbers under a WABAC
GET /{waba_id}/phone_numbers.
Inputs: waba_id (required), fields.
wa_template_getGet one WA message templateC
GET /{template_id}.
Inputs: template_id (required), fields.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 30 tools
Every tool has a clearly distinct purpose targeting a specific resource (ad account, campaign, Instagram media, WhatsApp phone number, etc.). Even similar tools like ads_list and adsets_list are distinguished by the parent entity (account, campaign, adset). No ambiguity.
Naming follows a consistent snake_case pattern (resource_verb) for most tools (e.g., ad_account_get, ads_list, campaign_get). A few tools like graph_get and debug_token deviate slightly but are still readable and serve special purposes.
30 tools is on the higher side but justified by the broad scope covering Facebook Ads, Instagram, WhatsApp Business, and Page insights. Each tool addresses a specific read operation, and the count is manageable for such a large API surface.
The tool set is comprehensive for read operations on Meta's Graph API, covering ads, Instagram, WhatsApp, and Pages. However, it lacks write operations (create/update/delete) which are available via graph_get escape hatch only. This is acceptable for a read-only server, but some common workflows (e.g., posting content) are absent.
Maintenance
Related MCP Connectors
Hosted Meta Ads MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Meta Ads MCP server: run Facebook and Instagram campaigns, ad sets, ads and reports from AI.
Hosted MCP server for marketing agencies: connect Google Ads, GA4, Search Console, Meta, LinkedIn and more to Claude or ChatGPT. Pull reports and make changes via OAuth login, no setup.
Hosted Amazon Seller Central and Amazon Ads MCP server for Claude, ChatGPT, Cursor, and agents.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP Server for the Meta Marketing API. Gives Claude Desktop direct access to your ad account data — campaign performance, creative analysis, audience breakdowns, and budget pacing.10344 npmMIT
- AlicenseBqualityBmaintenanceRead-only MCP server for Meta Ads that lists and reads ad accounts, campaigns, ad sets, ads, ad images, creatives, and fetches insights at various levels.18MIT
- FlicenseAqualityDmaintenanceLocal MCP server for operating Instagram, Meta Ads, and Facebook Page from Claude Code via Meta's Graph API. Provides 20 tools for publishing, campaign management, and insights.20-
- FlicenseAqualityBmaintenanceMCP server for reading Meta Business (Facebook Pages and Instagram) data via Graph API, with cross-account portfolio aggregation to answer questions about follower growth, page insights, and metrics. It runs as a stdio or HTTP server for Claude Desktop.9-