trackmcp
Back to directory

MCP Server for connecting to the Meta Marketing API

197 stars TypeScriptOthers Updated Sep 3, 2026
facebook-adsmcpmcp-servermetameta-adsads-mcpfacebook-mcp

Documentation

Meta Ads MCP Server

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.

Open Source / Self-Hosted

This repository is intended to be deployed in your own Cloudflare account with your own Meta app credentials.

It does not ship with:

  • a hosted control plane
  • a shared Meta app
  • a built-in end-user dashboard
  • a JWT issuer for your users and workspaces

You bring:

  • your Cloudflare Worker deployment
  • your Meta developer app
  • your JWT issuer or auth provider
  • your own UI or backend that initiates the OAuth flow

What It Does

  • Runs as a Cloudflare Worker
  • Uses direct Meta Graph API `fetch` calls instead of the Meta SDK
  • Stores Meta user connections per workspace in D1
  • Stores short-lived OAuth state in KV
  • Encrypts stored Meta access tokens
  • Protects MCP requests with your app-issued JWTs

Endpoints

  • `GET /health`
  • `GET /app`
  • `POST /mcp`
  • `GET /oauth/meta/start`
  • `GET /oauth/meta/callback`

Auth Model

This 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 `userId`
  • `workspaceId`
  • optional `roles`

Example payload:

json
{
  "sub": "user_123",
  "workspaceId": "workspace_abc",
  "roles": ["admin"]
}

Why `/oauth/meta/start` is not a generic public link:

  • the server must know which workspace the Meta account should be attached to
  • that workspace context comes from the JWT
  • without it, the server cannot safely bind the resulting Meta token

Tool Surface

Implemented tool families:

  • Account and setup
  • Campaign management
  • Ad set management
  • Creative and ads
  • Audience and targeting
  • Reporting and insights
  • Batch helpers

The server currently registers 39 tools.

Project Layout

  • `src/tools` tool definitions grouped by domain
  • `src/lib` auth, storage, OAuth, runtime, and Meta client helpers
  • `src/services` domain-specific Meta service logic
  • `src/middleware.ts` OAuth routing and MCP JWT auth
  • `cloudflare-entry.mjs` Worker wrapper entry for Cloudflare-specific route interception
  • `schema.sql` D1 schema
  • `test` unit and contract-style tests

Local Development

Install dependencies:

bash
pnpm install

Run local dev:

bash
pnpm dev

Useful scripts:

bash
pnpm build
pnpm test
pnpm deploy

Cloudflare Bindings

Required bindings:

  • D1 database bound as `META_DB`
  • KV namespace bound as `META_OAUTH_STATE`

Required secrets:

  • `JWT_SECRET` or `JWT_JWKS_URL`
  • `META_APP_ID`
  • `META_APP_SECRET`
  • `META_TOKEN_ENCRYPTION_KEY`
  • `APP_UI_PASSWORD` for the built-in admin page at `/app`

Optional configuration:

  • `JWT_ISSUER`
  • `JWT_AUDIENCE`
  • `APP_SESSION_SECRET`
  • `APP_UI_WORKSPACE_ID`
  • `APP_UI_USER_ID`
  • `META_REDIRECT_URI`
  • `META_GRAPH_VERSION`
  • `META_OAUTH_SCOPES`
  • `META_OAUTH_ALLOWED_RETURN_ORIGINS`

Defaults:

  • `META_GRAPH_VERSION=v25.0`
  • `META_OAUTH_SCOPES=ads_management,business_management`
  • `APP_UI_WORKSPACE_ID=workspace_admin`
  • `APP_UI_USER_ID=app_admin`

Built-In Admin UI

The Worker now includes a small browser UI at `/app`.

What it does:

  • prompts for an admin password
  • starts the existing Meta OAuth flow without requiring you to manually mint a bearer JWT
  • shows whether a Meta account is connected for the admin workspace
  • loads accessible ad accounts using the same service logic as `get_ad_accounts`

Required setup:

1. Set `APP_UI_PASSWORD` on the Worker.

2. Make sure `META_REDIRECT_URI` matches your public host, for example:

text
https://meta-mcp.gestalt.xyz/oauth/meta/callback

3. Open:

text
https://meta-mcp.gestalt.xyz/app

Meta App Setup

In your Meta app:

1. Add the Marketing API product.

2. Add a Website platform.

3. Set the Website platform URL to your Worker origin.

4. Set `App Domains` to your Worker domain.

5. Set the callback URL to:

text
https:///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.

Database

Apply the D1 schema:

bash
pnpm wrangler d1 execute META_DB --remote --file schema.sql -y

Tables:

  • `meta_connections`
  • `meta_ad_accounts_cache`

Deployment

Deploy the Worker:

bash
pnpm deploy

After deploy:

1. note the public Worker URL

2. set `META_REDIRECT_URI` to `https:///oauth/meta/callback`

3. update the same callback in the Meta app settings

If you plan to use a separate frontend or dashboard on another origin, allow that origin for post-OAuth browser redirects:

text
META_OAUTH_ALLOWED_RETURN_ORIGINS=https://your-ui.example.com,http://localhost:3000

Use your real frontend origin in production.

Manual Test Flow

1. Generate a short-lived JWT

Use the same JWT secret your app uses for the Worker.

bash
export JWT_SECRET="YOUR_JWT_SECRET"

TOKEN=$(node --input-type=module /oauth/meta/start?workspace_id=workspace_test"

Copy the `Location` header into your browser and complete the Meta login flow.

Expected success page:

text
Meta account connected.

3. Initialize MCP

bash
curl -s https:///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"}}}'

4. List Tools

bash
curl -s https:///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":{}}'

5. Call a Real Tool

After OAuth succeeds, this should return the accessible ad accounts for that workspace:

bash
curl -s https:///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.

Notes

  • Cloudflare `workers.dev` domains can require extra care in Meta app settings.
  • The Worker entrypoint explicitly intercepts OAuth routes before delegating to the generated XMCP Worker.
  • The Cloudflare Worker build path is not identical to local `xmcp dev`, so always verify the deployed routes after OAuth-related changes.

References

Frequently asked questions

What is meta-mcp?

meta-mcp is MCP Server for connecting to the Meta Marketing API

How do I install meta-mcp?

Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.

Is meta-mcp open source?

Yes — it is hosted on GitHub at https://github.com/brijr/meta-mcp and has 197 stars.

Related MCP tools

Run your own MCP server? See who uses it and what to fix.

Measure it with TrackMCP