@exileum/meta-mcp
v8.0.0
Published
Enables AI assistants to manage Instagram and Threads accounts — publish content, handle comments, view insights, search hashtags, and manage DMs through the Meta Graph API
Maintainers
Readme
meta-mcp
Enables AI assistants to manage Instagram and Threads accounts — publish content, handle comments, view insights, search hashtags, and manage DMs through the Meta Graph API.
Prerequisites
- Node.js 22+ (LTS recommended)
Quick Start
Add to your MCP client config:
{
"mcpServers": {
"meta": {
"command": "npx",
"args": ["-y", "@exileum/meta-mcp"],
"env": {
"INSTAGRAM_ACCESS_TOKEN": "your_ig_token",
"INSTAGRAM_USER_ID": "your_ig_user_id",
"THREADS_ACCESS_TOKEN": "your_threads_token",
"THREADS_USER_ID": "your_threads_user_id"
}
}
}
}Only set the variables for the platforms you use.
Manual Installation
git clone https://github.com/exileum/meta-mcp.git
cd meta-mcp
npm install
npm run build{
"mcpServers": {
"meta": {
"command": "node",
"args": ["/path/to/meta-mcp/dist/index.js"],
"env": {
"INSTAGRAM_ACCESS_TOKEN": "your_ig_token",
"INSTAGRAM_USER_ID": "your_ig_user_id",
"THREADS_ACCESS_TOKEN": "your_threads_token",
"THREADS_USER_ID": "your_threads_user_id"
}
}
}
}Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| INSTAGRAM_ACCESS_TOKEN | For Instagram | Instagram Graph API access token |
| INSTAGRAM_USER_ID | For Instagram | Instagram Business/Creator account ID (numeric string, or "me" for the authenticated user) |
| THREADS_ACCESS_TOKEN | For Threads | Threads API access token |
| THREADS_USER_ID | For Threads | Threads user ID (numeric string, or "me" for the authenticated user) |
| META_APP_ID | For token/webhook tools | Meta App ID (numeric string) |
| META_APP_SECRET | For token/webhook tools | Meta App Secret |
| META_API_VERSION | Optional | Meta Graph API version for Instagram and Facebook endpoints — defaults to v25.0 (verified 2026-05-06). Override only when Meta deprecates a version before meta-mcp ships a new release. Format: vMAJOR.MINOR (e.g., v26.0); malformed values fall back to the default with a stderr warning. OAuth token endpoints are unversioned and unaffected by this setting |
| THREADS_API_VERSION | Optional | Threads API version — defaults to v1.0 (verified 2026-05-06). Threads runs a separate single-major-version track and is not bumped in lockstep with the Graph API. Same vMAJOR.MINOR format and fallback behavior as META_API_VERSION |
| MCP_TRANSPORT | Optional | Transport to serve MCP over — stdio (default) or http. See HTTP Transport |
| MCP_HTTP_PORT | Optional (http) | TCP port for the HTTP transport — defaults to 3000. Must be an integer 1–65535 |
| MCP_HTTP_HOST | Optional (http) | Bind address for the HTTP transport — defaults to 127.0.0.1 (loopback only). Set to 0.0.0.0 to accept connections from other hosts (e.g. in Docker), but only behind a TLS/auth reverse proxy |
| MCP_HTTP_ALLOWED_HOSTS | Optional (http) | Comma-separated host:port allowlist for DNS-rebinding protection. Defaults to loopback variants at the bound port; set this when binding to a non-loopback host so legitimate requests pass the Host check |
The server validates these at startup. Malformed values for INSTAGRAM_USER_ID, THREADS_USER_ID, or META_APP_ID cause the process to exit with Invalid meta-mcp configuration: …. Setting only one half of a credential pair (e.g., INSTAGRAM_ACCESS_TOKEN without INSTAGRAM_USER_ID) prints a stderr warning and continues; related tool invocations still fail at call time.
HTTP Transport
By default the server speaks MCP over stdio — the right choice for local clients (Claude Desktop, Claude Code, etc.). Set MCP_TRANSPORT=http to instead serve the SDK's Streamable HTTP transport, which enables remote/web-based MCP clients, cloud deployments, and multiple concurrent client sessions.
MCP_TRANSPORT=http MCP_HTTP_PORT=3000 npx @exileum/meta-mcpThe server then listens on http://127.0.0.1:3000/mcp: POST to send messages, GET for the server→client SSE stream, DELETE to end a session. Each client gets an isolated session keyed by the Mcp-Session-Id header, so multiple clients can connect at once.
Security. The transport binds to 127.0.0.1 (loopback) by default and enables DNS-rebinding protection scoped to localhost, so it is not reachable off-host out of the box. To expose it to other machines (e.g. a container), set MCP_HTTP_HOST=0.0.0.0 and run it behind a reverse proxy that terminates TLS and handles authentication — the server itself performs no auth. When bound to a non-loopback address, set MCP_HTTP_ALLOWED_HOSTS to the host:port values clients will use so the Host-header check passes (otherwise rebinding protection is disabled with a stderr warning).
Account Requirements
| Platform | Account Type | Notes | |----------|-------------|-------| | Instagram | Business or Creator | Personal accounts cannot use the Graph API. Free to switch in settings | | Threads | Any account | Instagram link no longer required since Sep 2025 | | Meta (token/webhook) | Meta Developer App | Create at developers.facebook.com |
Features
- 58 tools across Instagram (33), Threads (19), and Meta platform (6)
- Instagram: Publish photos/videos/reels/stories/carousels with alt text, manage comments, view insights, search hashtags, handle DMs, manage collaboration invites
- Threads: Publish text/images/videos/carousels with polls, GIFs, topic tags, link attachments, alt text, spoiler flags; manage replies; search posts; delete posts; view insights
- Meta: Token exchange/refresh/debug, webhook management
- 2 resources: Instagram profile, Threads profile
- 2 prompts: Cross-platform content publishing, analytics report
- Rate limit tracking via
x-app-usageheader — and automatic client-side throttling at 80% (1s slowdown) / 90% (5s backoff) so a burst of tool calls stays under Meta's per-app quota - Automatic retry for transient Meta API failures (HTTP
429/500/502/503/504, network errors,fetchtimeouts) with exponential backoff andRetry-Afterhonoring; tunable viaMetaClient'smaxRetriesoption (default 3, set to 0 to disable) - Structured error responses with
error_type(auth,validation,rate_limit,server,network,internal), HTTP status, Meta API code/subcode/type, and aremediationhint where actionable — seeCHANGELOG.mdfor the JSON shape - MCP server
instructionssent duringinitializeso clients know required env vars, the two-step publish flow, expected video processing times, and the_rateLimitenvelope without re-reading the README - MCP
notifications/progressemitted while polling container status during publishing — attach aprogressTokentoig_publish_*/threads_publish_image|video|carouselcalls and the server reports each poll attempt - Structured MCP logging via the
notifications/messagechannel (the server declares theloggingcapability) — each API call logsdebug(method + path, never the token-bearing URL), terminal failures logerror(status/code/sanitized message), rate-limit pressure logswarning, andDELETE/publish operations log aninfoaudit line. Clients can raise the floor withlogging/setLevel(default emits all levels, includingdebug) - Optional HTTP transport — set
MCP_TRANSPORT=httpto serve the MCP Streamable HTTP transport (stateful multi-session, localhost-bound by default) instead of stdio, for remote/cloud deployments — see HTTP Transport
Tools
Meta Platform (6)
| Tool | Description |
|------|-------------|
| meta_exchange_token | Exchange short-lived token for long-lived token (~60 days). Requires platform (instagram or threads) |
| meta_refresh_token | Refresh a long-lived token before expiration. Requires platform (instagram or threads) |
| meta_debug_token | Inspect token validity, expiration, and scopes |
| meta_get_app_info | Get Meta App information |
| meta_subscribe_webhook | Subscribe to webhook notifications |
| meta_get_webhook_subscriptions | List current webhook subscriptions |
Instagram — Publishing (6)
| Tool | Description |
|------|-------------|
| ig_publish_photo | Publish a photo post (supports alt_text, collaborators) |
| ig_publish_video | [DEPRECATED] Use ig_publish_reel — publishes as a Reel (legacy VIDEO media_type retired by Meta Nov 9, 2023; supports collaborators) |
| ig_publish_carousel | Publish a carousel/album (2-10 items, supports alt_text per IMAGE item, collaborators) |
| ig_publish_reel | Publish a Reel (supports collaborators) |
| ig_publish_story | Publish a Story (24hr) |
| ig_get_container_status | Check media container processing status |
Instagram — Media (5)
| Tool | Description |
|------|-------------|
| ig_get_media_list | List published media |
| ig_get_media | Get media details |
| ig_delete_media | Delete a media post (requires Facebook Login) |
| ig_get_media_insights | Get media analytics (default views, reach — override metric per media type) |
| ig_toggle_comments | Enable/disable comments on a post |
Instagram — Comments (7)
| Tool | Description |
|------|-------------|
| ig_get_comments | Get comments on a post |
| ig_get_comment | Get comment details |
| ig_post_comment | Post a comment |
| ig_get_replies | Get replies to a comment |
| ig_reply_to_comment | Reply to a comment |
| ig_hide_comment | Hide/unhide a comment |
| ig_delete_comment | Delete a comment |
Instagram — Profile & Insights (5)
| Tool | Description |
|------|-------------|
| ig_get_profile | Get account profile info |
| ig_get_account_insights | Get account-level analytics (views, reach, follower_count). Optional metric_type (total_value or time_series) controls aggregation shape |
| ig_business_discovery | Look up another business account |
| ig_get_collaboration_invites | Get pending collaboration invites |
| ig_respond_collaboration_invite | Accept/decline a collaboration invite by media_id |
Instagram — Hashtags (4)
| Tool | Description |
|------|-------------|
| ig_search_hashtag | Search hashtag by name |
| ig_get_hashtag | Get hashtag info |
| ig_get_hashtag_recent | Get recent media for a hashtag |
| ig_get_hashtag_top | Get top media for a hashtag |
Instagram — Mentions & Tags (2)
| Tool | Description |
|------|-------------|
| ig_get_mentioned_comment | Get details of a specific comment mentioning you (by comment_id from a mention webhook) |
| ig_get_tagged_media | Get media you're tagged in |
Instagram — Messaging (4)
| Tool | Description |
|------|-------------|
| ig_get_conversations | List DM conversations |
| ig_get_messages | Get messages in a conversation |
| ig_send_message | Send a DM (optional messaging_type = RESPONSE/UPDATE/MESSAGE_TAG and tag = HUMAN_AGENT for the 7-day human-agent window) |
| ig_get_message | Get message details |
Threads — Publishing (9)
| Tool | Description |
|------|-------------|
| threads_publish_text | Publish a text post in a single API call (auto_publish_text=true, default; set auto_publish=false for the legacy two-step flow). Supports polls, GIFs, link attachments, topic tags, quote posts, spoiler flag, cross-share to IG Stories, geo-gating via allowlisted_country_codes, location tagging via location_id, text attachments up to 10K chars with styling |
| threads_publish_image | Publish an image post (supports alt_text, topic tags, spoiler flag, cross-share to IG Stories, geo-gating via allowlisted_country_codes, location tagging via location_id) |
| threads_publish_video | Publish a video post (supports alt_text, topic tags, spoiler flag, cross-share to IG Stories, geo-gating via allowlisted_country_codes, location tagging via location_id) |
| threads_publish_carousel | Publish a carousel (2-20 items, supports alt_text per item, cross-share to IG Stories, geo-gating via allowlisted_country_codes and location tagging via location_id on the parent container) |
| threads_delete_post | Delete a post (max 100/day) |
| threads_get_container_status | Check container processing status (unpublished containers only) |
| threads_get_publishing_limit | Check remaining publishing quota (250 posts/day) |
| threads_repost | Repost an existing thread to your profile (requires threads_content_publish) |
| threads_search_locations | Search Threads-supported locations by query (q) or coordinates (latitude+longitude) to obtain a location_id for the four threads_publish_* tools (requires threads_location_tagging permission) |
Threads — Media & Search (3)
| Tool | Description |
|------|-------------|
| threads_get_posts | List published posts (includes topic_tag, poll, GIF fields; optional fields param to override the default field list) |
| threads_get_post | Get post details |
| threads_search_posts | Search public posts by keyword or tag (requires threads_keyword_search permission) |
Threads — Replies (4)
| Tool | Description |
|------|-------------|
| threads_get_replies | Get replies to a post (mode='top_level' default, or mode='full_tree' for the entire conversation flattened) |
| threads_reply | Reply to a post (supports image/video attachments) |
| threads_hide_reply | Hide a reply |
| threads_unhide_reply | Unhide a reply |
Threads — Mentions (1)
| Tool | Description |
|------|-------------|
| threads_get_mentions | List posts where the user was @mentioned (requires threads_manage_mentions) |
Threads — Profile (1)
| Tool | Description |
|------|-------------|
| threads_get_profile | Get Threads profile info (includes is_verified and is_eligible_for_geo_gating) |
Threads — Insights (2)
| Tool | Description |
|------|-------------|
| threads_get_post_insights | Get post analytics (views, likes, replies, reposts, quotes, shares) |
| threads_get_user_insights | Get account-level analytics (period: day/lifetime; since/until required for day) |
Resources
| Resource URI | Description |
|-------------|-------------|
| meta-mcp://instagram/profile | Instagram account profile data |
| meta-mcp://threads/profile | Threads account profile data (includes is_verified and is_eligible_for_geo_gating) |
Prompts
| Prompt | Description | Arguments (all optional) |
|--------|-------------|--------------------------|
| content_publish | Cross-post content to Instagram and Threads | platform (instagram | threads | both), content_type (text | image | video | carousel), media_url, caption |
| analytics_report | Generate combined analytics report | platform (instagram | threads | both), time_range (7d | 30d | 90d), focus (engagement | growth | content) |
Setup Guide
Step 1: Create a Meta Developer App
- Go to developers.facebook.com and log in
- Click "My Apps" -> "Create App"
- Select "Other" -> "Business" (or "None" for personal use)
- Enter an app name and create
Your META_APP_ID and META_APP_SECRET are in App Settings -> Basic.
Step 2: Instagram Setup
Requires an Instagram Business or Creator account. Switch for free in Instagram app -> Settings -> Account type. No Facebook Page linking required — this uses the Instagram API with Instagram Login.
- In your Meta App, go to "Instagram" -> "API setup with Instagram business login"
- In the "Generate access tokens" section, click "Add account" -> log in to your Instagram account
- The generated token is long-lived (~60 days) — no exchange step needed. Copy it as your
INSTAGRAM_ACCESS_TOKEN.- To refresh before expiry, use the
meta_refresh_tokentool withplatform: "instagram", or:GET https://graph.instagram.com/refresh_access_token ?grant_type=ig_refresh_token &access_token=LONG_LIVED_TOKEN
- To refresh before expiry, use the
- Get your Instagram User ID:
TheGET https://graph.instagram.com/v25.0/me?fields=user_id,username&access_token=YOUR_TOKENuser_idis yourINSTAGRAM_USER_ID. - Permissions are configured in your app's Instagram settings. Available scopes:
instagram_business_basic— required for all operationsinstagram_business_content_publish— publishing photos, reels, carouselsinstagram_business_manage_comments— reading and managing commentsinstagram_business_manage_messages— DM conversations and messaging
Step 3: Threads Setup
Works with any Threads account. Instagram link no longer required since Sep 2025.
- In your Meta App, go to "Add Products" -> add "Threads API"
- Go to "Threads API" -> "Settings":
- Add your Threads account as a Threads Tester under "Roles"
- Accept the invitation in the Threads app: Settings -> Account -> Website permissions -> Invites
- Generate an authorization URL:
For local testing, usehttps://threads.net/oauth/authorize ?client_id=YOUR_APP_ID &redirect_uri=YOUR_REDIRECT_URI &scope=threads_basic,threads_content_publish,threads_manage_insights,threads_manage_replies,threads_read_replies,threads_share_to_instagram,threads_manage_mentions,threads_keyword_search &response_type=codehttps://localhost/as redirect URI (configure in App Settings -> Threads API -> Redirect URIs). - After authorization, exchange the code for an access token:
POST https://graph.threads.net/oauth/access_token Content-Type: application/x-www-form-urlencoded client_id=YOUR_APP_ID &client_secret=YOUR_APP_SECRET &grant_type=authorization_code &redirect_uri=YOUR_REDIRECT_URI &code=AUTHORIZATION_CODE - Exchange for a long-lived token (~60 days):
GET https://graph.threads.net/access_token ?grant_type=th_exchange_token &client_secret=YOUR_APP_SECRET &access_token=SHORT_LIVED_TOKEN - Get your Threads User ID:
TheGET https://graph.threads.net/v1.0/me?fields=id,username&access_token=YOUR_TOKENidfield is yourTHREADS_USER_ID.
Token Renewal
Access tokens expire after ~60 days. Refresh before expiration (token must be at least 24h old):
- Instagram: Use
meta_refresh_tokenwithplatform: "instagram", or call:GET https://graph.instagram.com/refresh_access_token ?grant_type=ig_refresh_token &access_token=CURRENT_LONG_LIVED_TOKEN - Threads: Use
meta_refresh_tokenwithplatform: "threads", or call:GET https://graph.threads.net/refresh_access_token ?grant_type=th_refresh_token &access_token=CURRENT_LONG_LIVED_TOKEN
When you rotate a token through meta_refresh_token or meta_exchange_token, the new token is automatically applied in-memory to the running MCP server — subsequent tool calls use it immediately, no server restart needed. The new token is still returned in the response so you can persist it in your environment for the next process restart. A single [meta-mcp] <Platform> access token updated in-memory after <tool>… line is logged to stderr when this happens.
Check token status anytime with meta_debug_token.
Troubleshooting
Tool failures return isError: true with a JSON body in content[0].text matching the envelope documented in CHANGELOG.md: { error: true, error_type, http_status, code, subcode, type, step, container_id, message, remediation, fbtrace_id, raw }. The fastest path to a fix is to read error_type and the Meta API code, then jump to the matching subsection below. The full code reference is the Meta Graph API error handling guide.
On the publish tools (ig_publish_*, threads_publish_*, threads_reply), errors also include step (container creation / processing / publishing, plus child container creation / child processing / parent container creation / parent processing on carousels) and container_id when one was created. The message mirrors them: "Publish photo failed at processing (container: 17889615324): Container processing timed out after 30s". Use these to decide whether to retry the publish, clean up an orphaned container, or treat the existing container as still reusable.
error_type: "auth" — expired, revoked, or under-scoped token
Triggered by Meta API codes 190, 10, 102, HTTP 401, or type: "OAuthException". Common messages:
Error validating access token: Session has expired— long-lived tokens expire ~60 days after issue.Application does not have permission for this action— the token is missing a scope, or the account is not eligible (e.g., a Personal Instagram account on Graph API endpoints).
What to do:
- Run
meta_debug_tokento inspectexpires_at,is_valid, andscopes. - If the token is not yet expired but at least 24h old, refresh in place with
meta_refresh_token(platform: "instagram"or"threads") — this extends the lifetime by another ~60 days. If the token is already expired, the refresh endpoint will reject it; regenerate a short-lived token from the Meta App dashboard and exchange it viameta_exchange_token(or run a full re-authorization for Threads). - If scopes are missing, regenerate the token with the required permissions:
- Instagram:
instagram_business_basic(always required) plusinstagram_business_content_publish,instagram_business_manage_comments,instagram_business_manage_messagesper feature. - Threads:
threads_basic,threads_content_publish,threads_manage_insights,threads_manage_replies,threads_read_replies,threads_share_to_instagram,threads_manage_mentions,threads_keyword_searchper feature.
- Instagram:
- If your Instagram account is Personal, switch to Business or Creator for free in the Instagram app (Settings → Account type and tools → Switch to professional account). The Graph API rejects Personal accounts.
error_type: "rate_limit" — application or user quota exhausted
Triggered by Meta API codes 4, 17, 32, 341, 613, the business-use-case range 80001–80008, or HTTP 429. Includes any OAuthException with code 4 / 17 (these are surfaced as error_type: "rate_limit", not "auth", despite the type field). MetaClient automatically retries HTTP 429 up to 3 times with exponential backoff and honors any Retry-After header — a rate_limit error reaching the caller means the retry budget was exhausted.
What to do:
- Inspect the
_rateLimitfield on prior successful tool responses.callCount,totalCpuTime, andtotalTimecome from Meta'sx-app-usageheader; when any approaches100you are near the per-app threshold. - meta-mcp already self-throttles once
max(callCount, totalCpuTime, totalTime)crosses 80% (1s slowdown) or 90% (5s backoff) — watch for thewarning-level MCP log message (logger: "meta-client", withusage_pctanddelay_ms) the server emits before each throttled call. Profile reads (ig_get_profile,threads_get_profile, and the matchingmeta-mcp://*/profileresources) and hashtag-name lookups (ig_search_hashtag) are also cached in-process for 5 minutes / 7 days respectively, with cache hits skipping the network entirely. If you are still hittingrate_limiterrors despite all that, reduce request volume further. - Threads has hard daily quotas (250 publishes, 100 deletes) — query the remaining quota with
threads_get_publishing_limitbefore bulk operations.
error_type: "validation" — bad parameter, wrong ID, or unsupported field
Triggered by Meta API codes 100, 200, 803, or any unmapped 4xx HTTP status. Common pitfalls:
- Wrong user ID format —
INSTAGRAM_USER_IDandTHREADS_USER_IDmust be the numeric ID returned byGET /me?fields=user_id(Instagram) orGET /me?fields=id(Threads), or the literal"me"for the authenticated user. The Instagram username is not accepted. (#100) Messaging is not supportedonig_send_message/ig_get_conversations/ig_get_messages— the account does not have the messaging API enabled. Grantinstagram_business_manage_messageson your token and ensure DMs are enabled in the Instagram app (Settings → Privacy → Messages).- Deprecated publish endpoint —
ig_publish_videowas retired by Meta on Nov 9, 2023; useig_publish_reelfor video posts.ig_publish_storyis required for Stories. - Mutually exclusive Threads attachments — a
threads_publish_textpost can carry only one oftext_attachment,poll_options,link_attachment, orgif_attachment; combining them is rejected at the schema level. - Unsupported
metric/fieldsfor the resource — see the per-tool Meta docs (ig_get_media_insightslists per-media_typevalid metrics in its description).
Other categories
error_type: "server"(codes1,2, HTTP 5xx) — transient Meta outage.MetaClientalready retried500/502/503/504up to 3 times with exponential backoff before surfacing this; check metastatus.com if it persists.error_type: "network"—fetchtimed out or failed before reaching Meta.MetaClientalready retried thrown network errors up to 3 times; verify outbound connectivity if the error keeps reappearing.error_type: "internal"— unexpected condition that did not map to a Meta error code. Therawfield carries the sanitized original message;access_token,client_secret, andinput_tokenvalues are scrubbed to***before reporting.
API Stability
meta-mcp is consumed as an MCP server runtime, not as a library. The supported entry points are:
npx @exileum/meta-mcp(recommended for end users)node dist/index.js(manual installation)
The single programmatic export from the package root, createSandboxServer(): McpServer, exists for the Smithery sandbox runner and is the only stable JavaScript/TypeScript API.
zod and other transitive runtime dependencies are internal and not part of meta-mcp's public API. No zod symbols, types, or schemas flow through dist/index.d.ts, so zod's version may change in any release — including major version bumps — without a corresponding meta-mcp major bump.
@modelcontextprotocol/sdk is the one exception: McpServer (the return type of createSandboxServer()) is imported from that package, so a breaking change to McpServer's public interface would also be a breaking change for meta-mcp's programmatic API. In practice the MCP SDK follows semver, so consumers can treat @modelcontextprotocol/sdk as an implicit peer dependency of the createSandboxServer export.
Only the package root (@exileum/meta-mcp) is a supported import target. Deep imports into the published dist/ tree (e.g. @exileum/meta-mcp/dist/schemas.js) are blocked by the package.json exports map for any spec-compliant resolver and are not part of the public API; they may be renamed, removed, or restructured in any release.
Glama
Contributing
Contributions are welcome. See CONTRIBUTING.md for the dev setup, project layout, the tool-registration recipe, testing, commit conventions, the CHANGELOG flow, and the CI gates. Bug reports and feature requests use the issue templates; pull requests use the PR template.
License
See CHANGELOG.md for release history.
