facebook-mcp
Drive your Facebook Page from your AI assistant — publish and schedule, read insights, moderate comments and answer Messenger threads over the Meta Graph API. It runs on your machine, with your token, behind a three-host network fence.
Status, honestly. All seven packages are implemented and covered by 1,349 hermetic tests (99.1% statement coverage), but every one of those tests runs against fakes and recorded fixtures behind a network fence. Version 0.7.0 is published on npm with provenance — published, implemented and tested in isolation, not proven in production. Nothing has been verified against the live Graph API yet. Treat this as a pre-1.0 project.
Running in four steps
The package is on npm as @ivanbaev/facebook-mcp — one
b in the scope, unlike the GitHub user
IvanBBaev — so npx fetches it and there is no
install step. Node 22 or newer. Building from source stays supported if
you would rather compile it yourself.
Get it
# nothing is installed and nothing runs at install time
npx -y @ivanbaev/facebook-mcp --version
Prefer source? Clone and compile, then read
node build/index.js everywhere the steps below write
npx -y @ivanbaev/facebook-mcp.
git clone https://github.com/IvanBBaev/facebook-mcp.git cd facebook-mcp npm ci npm run build
Add a token
setup-token validates the token, resolves the Page and
writes a 0600 env file for you. Pass the token through
FB_SETUP_TOKEN rather than on the command line — an
argument would be visible in ps and in your shell history.
The value is never echoed back.
# --page pins the Page; add --dry-run first to rehearse without writing
FB_SETUP_TOKEN='<your-meta-token>' \
npx -y @ivanbaev/facebook-mcp setup-token --page=1234567890
Check it before you wire it up
doctor inspects the token — type, expiry, granted scopes —
and prints a usable / partial / blocked matrix per package, so a missing
permission surfaces here instead of inside a tool call.
npx -y @ivanbaev/facebook-mcp doctor
npx -y @ivanbaev/facebook-mcp --version # server + Node version, no credential needed
doctor, setup-token and
--version are the only arguments that exit on their own —
anything else starts the stdio server and waits on JSON-RPC.
Point your client at it
Let the client run npx, or point it at the absolute path
to build/index.js if you built from source. Start
read-only — FB_PACKAGES_READONLY drops every write tool
while keeping the reads — and open the gate once you trust what you
see.
{
"mcpServers": {
"facebook": {
"command": "npx",
"args": ["-y", "@ivanbaev/facebook-mcp"],
"env": {
"FB_SYSTEM_TOKEN": "<system-user-token>",
"FB_PAGE_ID": "1234567890",
"FB_WRITE_MODE": "plan"
}
}
}
}
On Windows the file lives at
%APPDATA%\Claude\claude_desktop_config.json.
{
"servers": {
"facebook": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@ivanbaev/facebook-mcp"],
"env": {
"FB_SYSTEM_TOKEN": "<system-user-token>",
"FB_PAGE_ID": "1234567890"
}
}
}
}
{
"mcpServers": {
"facebook": {
"command": "npx",
"args": ["-y", "@ivanbaev/facebook-mcp"],
"env": {
"FB_SYSTEM_TOKEN": "<system-user-token>",
"FB_PAGE_ID": "1234567890"
}
}
}
}
Seven packages, 37 tools
Packages are the unit of exposure: FB_TOOL_PACKAGES enables or
disables whole packages, FB_PACKAGES_DENY removes one
regardless, and FB_PACKAGES_READONLY keeps a package's reads
while dropping its writes. core is always on.
| Package | Default | Tools | Write mode | What it covers |
|---|---|---|---|---|
core |
on | 4 read | — | Identity, Page discovery and rate-limit diagnostics. Read-only, always registered. |
reader |
on | 4 read | — | A Page's own content: posts across four edges, single posts, Reels and reaction totals. |
posts |
on | 2 read 6 write | plan |
Publish, schedule, edit and delete Page posts, photos, videos and Reels; poll the upload state of a large video or Reel. |
insights |
on | 3 read | — | Page, post and Reel insights in a compact flat shape, with guidance for the metrics Meta renamed or removed. Reels read their own Graph edge, so they get their own tool. |
moderation |
on | 2 read 6 write | apply |
Read and moderate comments — list, reply, hide, delete, private reply — and maintain the blocked-users list. |
messages |
on | 2 read 1 write | plan |
Messenger for a Page: poll the inbox, read a thread with untrusted content wrapped, send one reply inside the 24-hour window. |
ads |
opt-in | 6 read 1 write | plan |
Marketing API: campaign / ad-set / ad listings with delivery truth, insights with async report runs, plan-gated status and budget control. |
What it deliberately does not do
Instagram and Threads, Stories, Events, Live video, Page profile
management, organic post targeting and boosting a post are all out of
scope for now. The business_management permission is never
requested.
Insights that still exist
Meta has removed or renamed metrics in three waves — September 2024, November 2025 and June 2026. Metrics are passed through rather than whitelisted, and the tools surface the rename guidance instead of silently returning nothing.
The full 37-tool table, with the exact Graph permission each tool needs, lives in the README.
A write model you can reason about
Every tool declares a tier. The tier — not the tool, and not the model's confidence — decides what has to happen before the Graph API is touched.
read
22 toolsNever mutates anything. Safe to expose first, and to leave on.
reversible
10 tools
Governed by the effective write mode. Under plan the tool
returns a preview with a plan_id and performs zero network
mutations; under apply it executes. Creating a post, editing
one, replying to or hiding a comment, blocking a user, sending a Messenger
reply.
irreversible
4 tools
Ignores FB_WRITE_MODE completely — no environment variable
unlocks it. Requires a per-call apply flag,
plus the plan_id of a preview you just ran,
plus an out-of-band human confirmation. Deleting a post, deleting
a comment, sending a private reply, changing an ads object.
spend
0 tools
The money-moving tier. It is defined and enforced, but no shipped tool
carries it today — ads control is irreversible instead, and
FB_ADS_BUDGET_CEILING caps what a budget change may set.
Which default applies
-
An explicit
FB_WRITE_MODEwins outright, in both directions. Even an invalid value counts as explicit, so a typo can never loosen the gate. -
Otherwise the package default applies.
posts,messagesandadsshipplan;moderationshipsapply, because hiding a comment is one click to undo and a moderation queue that previews twice is a moderation queue nobody uses. - Otherwise the compiled-in default,
plan.
A package default can never reach past reversible.
Confirmation happens out of band
For the irreversible tier the server asks a human directly, through MCP
elicitation — but only when the client actually advertises
elicitation.form; an empty elicitation: {} is not
enough. When the client cannot prompt, the call falls through to the
operator token: FB_CONFIRM_TOKEN, passed back as the tool's
confirm_token argument.
There is no silent allow. A refusal names which route failed — no confirmation channel, wrong token, or stale plan — so you can tell a misconfiguration from a denial.
Defence in depth
- Three hosts, and no others. The HTTP layer refuses any request outside the allowlist, including redirects that leave it.
- No telemetry, no phone-home. The only outbound traffic is the Graph call you asked for.
-
Secrets never surface. Tokens are redacted from logs,
errors and tool results; with
FB_APP_SECRETset every call carries anappsecret_proof, so a stolen bare token is unusable. - Untrusted content is fenced. Comments and messages come back wrapped and marked as data, so a comment that says "ignore your instructions" reads as text, not as an instruction.
-
Local files are off by default. Uploads are URL-only
until
FB_MEDIA_DIRis set, and then only inside that directory — resolved throughrealpath, so a symlink cannot walk out. -
Every write is journalled. Structured metadata only, no
tokens and no PII,
0600, rotated by size — and a journal failure never blocks the write. -
The HTTP transport fails closed. It binds loopback only
and refuses to start without
FB_HTTP_TOKEN.
Four layers, one direction
Imports point leftward only, and the rule is enforced by lint rather than by convention — so the Graph client cannot learn about MCP, and the tool layer cannot reach past the API layer to make its own HTTP call.
doctor, setup-token.
Errors that say what to do
Graph error codes are mapped to an action rather than passed through raw: what happened, whether it is retryable, and what you should change. Rate limits are read from the response headers and backed off before the platform starts refusing.
Multi-Page from one process
Named profiles — FB_PROFILE_BRAND_A_PAGE_ID and an optional
matching token — let one server drive several Pages; a tool call picks one
with profile, and FB_PAGE_ID is the fallback.
Credentials and environment
You need a Meta developer app and one access token. Because you administer the Page yourself, no Meta App Review is required — the permissions you grant to your own app are enough.
| Token | Precedence | When to use it |
|---|---|---|
FB_SYSTEM_TOKEN |
1st | A System User token from Business Manager. Recommended: it does not expire on its own, and it survives your personal password changes. |
FB_ACCESS_TOKEN |
2nd | A long-lived user token — fine for a single operator. |
FB_PAGE_TOKEN |
3rd | A long-lived Page token — the no-Business-Manager fallback. |
FB_APP_SECRET |
optional |
Not required to run. When set, every request carries an
appsecret_proof; with FB_APP_ID it also forms
the app token used to inspect tokens in doctor.
|
Permissions per package
| Package | Required Graph permissions |
|---|---|
core |
pages_show_list, pages_read_engagement
|
reader |
pages_read_engagement,
pages_read_user_content
|
posts |
pages_manage_posts, pages_read_engagement
|
insights |
read_insights |
moderation |
pages_read_user_content,
pages_manage_engagement
|
messages |
pages_messaging, pages_manage_metadata
|
ads |
ads_read, ads_management |
All 25 environment variables
| Variable | Default | Description |
|---|---|---|
FB_SYSTEM_TOKEN |
— | Secret. System User token (Business Manager). Recommended; wins over the other two. |
FB_ACCESS_TOKEN |
— | Secret. Meta user access token, long-lived preferred. |
FB_PAGE_TOKEN |
— | Secret. Long-lived Page token — the no-Business-Manager fallback. |
FB_APP_ID |
— |
Meta app ID. With FB_APP_SECRET it forms the app token
used to inspect tokens.
|
FB_APP_SECRET |
— |
Secret. When set,
appsecret_proof is attached so a stolen bare token is
unusable.
|
FB_PAGE_ID |
— |
Default Page ID for Page-scoped tools when a call omits
profile.
|
FB_API_VERSION |
v23.0 |
Graph API version to pin. Off-default values are accepted, but only the default is tested. |
FB_REQUEST_TIMEOUT_MS |
60000 |
Per-request timeout in milliseconds (1–600000). |
FB_HOST_CONCURRENCY |
4 |
Max parallel requests per Graph host (1–64). |
FB_MAX_RESULT_CHARS |
25000 |
Character budget before a tool result is truncated (500–10 000 000). |
FB_WRITE_MODE |
plan |
plan previews a write without mutating;
apply executes. Never covers the irreversible or spend
tiers.
|
FB_CONFIRM_TOKEN |
— | Secret. Out-of-band confirmation token authorizing gated write / spend actions, for clients that cannot prompt. |
FB_MEDIA_DIR |
— | Directory permitted as a source for local media uploads. Unset ⇒ URL-only, local file access disabled. |
FB_JOURNAL_PATH |
XDG / %APPDATA% state path |
Path to the append-only, rotating write journal
(0600).
|
FB_TOOL_PACKAGES |
core profile (all except ads) |
Comma-separated packages or profiles to enable.
core is always forced on; ads is opt-in.
|
FB_PACKAGES_DENY |
— |
Packages to exclude even if enabled by
FB_TOOL_PACKAGES.
|
FB_PACKAGES_READONLY |
— | Packages whose write tools are not registered; their read tools stay. |
FB_TRANSPORT |
stdio |
stdio or http (loopback-only Streamable
HTTP for local agent clients).
|
FB_HTTP_TOKEN |
— |
Secret. Bearer token required by the
http transport; it fails closed without it.
|
FB_HTTP_PORT |
3000 |
TCP port for the http transport; the bind host is
fixed to 127.0.0.1.
|
FB_AD_ACCOUNT_ID |
— | Ad account ID for the opt-in ads package. |
FB_ADS_BUDGET_CEILING |
— | Hard budget ceiling for ads writes, in minor currency units. |
FB_LOG_LEVEL |
info |
Stderr log verbosity: debug, info,
warn, error.
|
FB_PROFILE_<NAME>_PAGE_ID |
— |
Page ID for a named profile, e.g.
FB_PROFILE_BRAND_A_PAGE_ID.
|
FB_PROFILE_<NAME>_TOKEN |
— | Secret. Optional per-profile token override for the matching Page ID. |
A commented template ships as
.env.example, and
the onboarding runbook
walks through creating the app, minting the token and granting each permission.
Questions worth asking first
Is facebook-mcp affiliated with Meta?
No. This is an independent, community-built project and is not affiliated with, endorsed by, or sponsored by Meta Platforms, Inc. Facebook, Meta and related marks are trademarks of Meta Platforms, Inc., used here only nominatively to indicate compatibility.
Which credentials does it need?
A Meta developer app plus one access token: a never-expiring System User
token from Business Manager (recommended), a long-lived user token, or a
long-lived Page token. FB_APP_SECRET is optional — when set,
every call carries an appsecret_proof so a stolen bare token
is unusable. Because you administer your own Page, no Meta App Review is
required.
Is it safe to let an AI assistant write to my Page?
Writes are split into tiers. Reversible writes are governed by
FB_WRITE_MODE: the compiled-in default is plan,
which returns a preview and performs zero network mutations, and each
package may ship its own default — posts,
messages and ads default to plan,
moderation defaults to apply because hiding a
comment is one click to undo. An explicit FB_WRITE_MODE always
wins, in both directions.
Irreversible actions — delete a post, delete a comment, send a private
reply, change an ads object — ignore FB_WRITE_MODE entirely
and require a per-call apply flag, the
plan_id of a preview you just ran, and an out-of-band human
confirmation the model cannot supply itself. The recommended starting
posture is read-only.
What actually works today?
All seven tool packages are implemented: 37 tools across
core, reader, posts,
insights, moderation, messages and
ads. Six are on by default; ads is opt-in via
FB_TOOL_PACKAGES.
Version 0.7.0 is published on npm, but the project is still pre-1.0 and
the automated test suite runs entirely against fakes and recorded
fixtures behind a network fence — no behaviour has been verified against
the live Graph API yet. A manual smoke harness exists for exactly that,
gated behind FB_SMOKE=1 and never run in CI.
Is it published to npm?
Yes. The package is @ivanbaev/facebook-mcp — the npm scope
carries one b, unlike the GitHub user
IvanBBaev — and version 0.7.0 is the current
latest, so npx -y @ivanbaev/facebook-mcp runs
the server without an install step. It was published with npm provenance
by the OIDC release workflow from the tagged commit.
The same rail ships the .mcpb desktop bundle on the
v0.7.0 GitHub Release, with a build-provenance attestation
you can check yourself using
gh attestation verify facebook-mcp-0.7.0.mcpb --repo
IvanBBaev/facebook-mcp, and the MCP registry listing
io.github.IvanBBaev/facebook-mcp is live. It is still a
pre-1.0 release: published, implemented and tested in isolation, not
proven in production. Building from source stays supported as an
alternative.
Which MCP clients does it work with?
Any client that speaks the Model Context Protocol over stdio — Claude
Desktop, Claude Code, VS Code and Cursor among them. Optional client
capabilities degrade honestly: without
elicitation.form support the server falls back to the
FB_CONFIRM_TOKEN route for high-consequence writes rather than
silently allowing them, and without progress support an upload simply
reports no progress. An optional loopback-only Streamable HTTP transport is
available for advanced setups.
How can I support the project?
facebook-mcp is MIT-licensed and free, with no paid tier. It is maintained by one person in their own time, so donations are welcome — see Support below. Donating buys no priority support and no SLA; starring the repository or filing a good bug report helps just as much.
Free, MIT, and built in spare time
There is no paid tier and no hosted version. If this saves you an afternoon, a donation keeps the coffee flowing — but a star, a bug report or a fix helps just as much.
Found a bug?
Open an issue with the output of
npx -y @ivanbaev/facebook-mcp --version and the summary from
doctor — that pair answers most of the first round of
questions.
Found a security problem?
Please report it privately through GitHub's private vulnerability reporting rather than in a public issue.