meta-mcp
Read-only MCP server for Meta (Facebook) Graph API, enabling access to Marketing API, Pages, Instagram, and WhatsApp Business data through Claude Code and any MCP-compatible client.
README
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 | graph_get, debug_token |
| Pages | pages_list, page_get, page_posts, post_get, post_comments, page_insights |
| Marketing | ad_accounts_list, ad_account_get, campaigns_list, campaign_get, adsets_list, ads_list, adcreatives_list, adcreative_get, insights |
ig_business_account_get, ig_media_list, ig_media_get, ig_media_insights, ig_user_insights, ig_hashtag_search, ig_hashtag_media |
|
wa_business_owned_wabas, wa_phone_numbers, wa_phone_number_get, wa_message_templates, wa_template_get, wa_analytics |
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: GET /me/accounts?fields=access_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. debug_token) |
`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 (npx) |
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-mcp
Or 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/tcp
From 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 .env
Fill 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.com
chmod 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 cert
Verify:
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.json
Smoke test without Claude
stdio:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
HTTP:
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"}}}' \
-i
License
MIT.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。