by brijr
MCP Server for connecting to the Meta Marketing API
# Add to your Claude Code skills
git clone https://github.com/brijr/meta-mcpGuides for using mcp servers skills like meta-mcp.
Last scanned: 5/30/2026
{
"issues": [
{
"type": "npm-audit",
"message": "miniflare: Vulnerability found",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "wrangler: Vulnerability found",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "ws: ws: Uninitialized memory disclosure",
"severity": "medium"
}
],
"status": "PASSED",
"scannedAt": "2026-05-30T15:47:48.332Z",
"npmAuditRan": true,
"pipAuditRan": true
}No comments yet. Be the first to share your thoughts!
Top skills in this category by stars
30 days in the Featured rail · terms & refunds
A Cloudflare Workers MCP server for Meta Ads account setup, campaign management, ad sets, creatives, audiences, reporting, and batch workflows.
This repo is built on xmcp and exposes a Streamable HTTP MCP endpoint plus browser-facing Meta OAuth routes.
This repository is intended to be deployed in your own Cloudflare account with your own Meta app credentials.
It does not ship with:
You bring:
fetch calls instead of the Meta SDKGET /healthGET /appPOST /mcpGET /oauth/meta/startGET /oauth/meta/callbackThis server is multi-tenant. Every MCP request must include a bearer JWT issued by your app.
If you are open-sourcing this project, the important implication is that consumers must wire it into their own auth system. The server does not know how to identify a user or workspace without that JWT.
Required JWT claims:
sub or userIdworkspaceIdrolesExample payload:
{
"sub": "user_123",
"workspaceId": "workspace_abc",
"roles": ["admin"]
}
Why /oauth/meta/start is not a generic public link:
Implemented tool families:
The server currently registers 39 tools.
src/tools tool definitions grouped by domainsrc/lib auth, storage, OAuth, runtime, and Meta client helperssrc/services domain-specific Meta service logicsrc/middleware.ts OAuth routing and MCP JWT authcloudflare-entry.mjs Worker wrapper entry for Cloudflare-specific route interceptionschema.sql D1 schematest unit and contract-style testsInstall dependencies:
pnpm install
Run local dev:
pnpm dev
Useful scripts:
pnpm build
pnpm test
pnpm deploy
Required bindings:
META_DBMETA_OAUTH_STATERequired secrets:
JWT_SECRET or JWT_JWKS_URLMETA_APP_IDMETA_APP_SECRETMETA_TOKEN_ENCRYPTION_KEYAPP_UI_PASSWORD for the built-in admin page at /appOptional configuration:
JWT_ISSUERJWT_AUDIENCEAPP_SESSION_SECRETAPP_UI_WORKSPACE_IDAPP_UI_USER_IDMETA_REDIRECT_URIMETA_GRAPH_VERSIONMETA_OAUTH_SCOPESMETA_OAUTH_ALLOWED_RETURN_ORIGINSDefaults:
META_GRAPH_VERSION=v25.0META_OAUTH_SCOPES=ads_management,business_managementAPP_UI_WORKSPACE_ID=workspace_adminAPP_UI_USER_ID=app_adminThe Worker now includes a small browser UI at /app.
What it does:
get_ad_accountsRequired setup:
APP_UI_PASSWORD on the Worker.META_REDIRECT_URI matches your public host, for example:https://meta-mcp.gestalt.xyz/oauth/meta/callback
https://meta-mcp.gestalt.xyz/app
In your Meta app:
App Domains to your Worker domain.https://<your-worker-host>/oauth/meta/callback
If your app uses Facebook Login or Facebook Login for Business, also add that exact callback URL to the product-specific redirect URI settings.
For a Worker deployed on workers.dev, these fields usually need to match the Worker host exactly.
Apply the D1 schema:
pnpm wrangler d1 execute META_DB --remote --file schema.sql -y
Tables:
meta_connectionsmeta_ad_accounts_cacheDeploy the Worker:
pnpm deploy
After deploy:
META_REDIRECT_URI to https://<your-worker-host>/oauth/meta/callbackIf you plan to use a separate frontend or dashboard on another origin, allow that origin for post-OAuth browser redirects:
META_OAUTH_ALLOWED_RETURN_ORIGINS=https://your-ui.example.com,http://localhost:3000
Use your real frontend origin in production.
Use the same JWT secret your app uses for the Worker.
export JWT_SECRET="YOUR_JWT_SECRET"
TOKEN=$(node --input-type=module <<'NODE'
import { SignJWT } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const token = await new SignJWT({ workspaceId: 'workspace_test', roles: ['admin'] })
.setProtectedHeader({ alg: 'HS256' })
.setSubject('user_test')
.setIssuedAt()
.setExpirationTime('10m')
.sign(secret);
console.log(token);
NODE
)
curl -i \
-H "Authorization: Bearer $TOKEN" \
"https://<your-worker-host>/oauth/meta/start?workspace_id=workspace_test"
Copy the Location header into your browser and complete the Meta login flow.
Expected success page:
Meta account connected.
curl -s https://<your-worker-host>/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"jsonrpc":"2.0","id":"init-1","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"manual-test","version":"1.0.0"}}}'
curl -s https://<your-worker-host>/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"jsonrpc":"2.0","id":"tools-1","method":"tools/list","params":{}}'
After OAuth succeeds, this should return the accessible ad accounts for that workspace:
curl -s https://<your-worker-host>/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"jsonrpc":"2.0","id":"call-1","method":"tools/call","params":{"name":"get_ad_accounts","arguments":{}}}'
If you get a connect/reconnect error, the OAuth flow and the MCP call used different workspaceId values.
workers.dev domains can require extra care in Meta app settings.xmcp dev, so always verify the deployed routes after OAuth-related changes.