Instagram MCP Server
README.md
# šø Instagram MCP Server
A production-ready **Remote MCP (Model Context Protocol) Server** that gives
Claude direct, tool-based access to your Instagram Business account through
the **Meta Graph API** ā profile data, posts, comments, publishing,
insights, analytics, hashtags, messaging, and real-time webhooks.
Built with TypeScript, Node.js, Express, and the official
`@modelcontextprotocol/sdk`.
---
## Table of Contents
- [What this is (and isn't)](#what-this-is-and-isnt)
- [Features / Tool Reference](#features--tool-reference)
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [Meta App Setup](#meta-app-setup)
- [1. Create a Meta App](#1-create-a-meta-app)
- [2. Link a Facebook Page + Instagram Business Account](#2-link-a-facebook-page--instagram-business-account)
- [3. Generate an Access Token](#3-generate-an-access-token)
- [4. Permissions & App Review](#4-permissions--app-review)
- [Configuration](#configuration)
- [Running Locally](#running-locally)
- [Deployment](#deployment)
- [Docker](#docker)
- [Render](#render)
- [Railway](#railway)
- [Fly.io](#flyio)
- [Connecting Claude](#connecting-claude)
- [Webhooks (real-time updates)](#webhooks-real-time-updates)
- [Testing](#testing)
- [Security Notes](#security-notes)
- [Known API Limitations](#known-api-limitations)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [License](#license)
---
## What this is (and isn't)
This project is a real, working integration between Claude and the
**Instagram Graph API for Professional (Business/Creator) accounts**. It is
**not** a wrapper around the consumer Instagram app, and it cannot do
anything the Graph API itself doesn't support. Where Meta's API has a real
limitation (no follower-list endpoint, no general story-history endpoint,
no competitor-audience data), this server surfaces a clear error explaining
the limitation instead of fabricating data ā see
[Known API Limitations](#known-api-limitations).
You need:
- An **Instagram Professional account** (Business or Creator)
- Linked to a **Facebook Page**
- A **Meta Developer App** with the Instagram Graph API product added
---
## Features / Tool Reference
Every feature is exposed as an MCP tool Claude can call directly.
| Category | Tools |
|---|---|
| **Profile** | `getProfile`, `getUsername`, `getBiography`, `getFollowers`, `getFollowing`, `getProfilePicture`, `getMediaCount` |
| **Media** | `getRecentPosts`, `getReels`, `getStories`*, `getMediaMetadata`, `getMediaUrl`, `getCaption`, `getTaggedUsers`, `getLocation` |
| **Comments** | `getComments`, `replyComment`, `deleteComment`, `hideComment` |
| **Publishing** | `publishPost`, `publishCarouselPost`, `publishReel`, `createMediaContainer`, `schedulePost`, `getPublishingStatus` |
| **Insights** | `getFollowerGrowth`, `getReach`, `getImpressions`, `getProfileVisits`, `getWebsiteClicks`, `getEngagement`, `getAccountsReached`, `getAccountsEngaged`, `getMediaInsights` |
| **Analytics** | `getTopPerformingPosts`, `getTopPerformingReels`, `getAverageEngagement`, `getBestPostingTime`, `weeklyAnalytics`, `monthlyAnalytics`, `comparePosts`, `compareReels`, `generateAnalyticsSummary` |
| **Messaging** | `getMessages`, `getConversationHistory`, `sendMessage`, `replyMessage` |
| **Hashtags** | `searchHashtag`, `getHashtags` |
| **Business** | `getAccountInformation`, `businessDiscovery`, `competitorAnalysis`* |
| **Webhooks** | `getRecentMentions`, `getRecentCommentEvents` |
\* `getStories` and `competitorAnalysis` intentionally return an explanatory
error ā see [Known API Limitations](#known-api-limitations).
54 tools total. Run the server and call `tools/list` over MCP, or see
[`examples/api-requests.md`](examples/api-requests.md), for the live list.
---
## Architecture
```
instagram-mcp/
āāā src/
ā āāā index.ts # process bootstrap (listen, graceful shutdown)
ā āāā server.ts # Express app assembly (middleware + routes)
ā āāā config/ # env validation (Zod) + constants
ā āāā routes/ # /mcp, /auth, /webhooks, /health
ā āāā controllers/ # OAuth setup controller
ā āāā services/
ā ā āāā graph/ # low-level Graph API HTTP client (auth, retries, error mapping)
ā ā āāā instagram/ # profile, media, comments, publishing, insights, analytics, hashtags, messaging, business discovery
ā āāā mcp/
ā ā āāā mcpServer.ts # McpServer factory ā registers every tool
ā ā āāā tools/ # one file per tool category (Zod schemas + handlers)
ā āāā middlewares/ # auth, rate limiting, validation, error handling, logging
ā āāā utils/ # logger, errors, cache, retry, crypto
ā āāā types/ # shared TypeScript types
ā āāā auth/ # OAuth token exchange logic
ā āāā storage/ # encrypted-at-rest token store
ā āāā webhooks/ # signature verification + event router
āāā tests/
ā āāā unit/ # crypto, retry, cache, service-level (mocked via nock)
ā āāā integration/ # full Express app via supertest
āāā examples/ # sample MCP config + curl requests
āāā Dockerfile / docker-compose.yml
āāā render.yaml / railway.json / fly.toml
āāā .github/workflows/ci.yml
```
**Design choices worth knowing about:**
- The Remote MCP endpoint (`POST /mcp`) uses the SDK's **stateless
Streamable HTTP transport** ā a fresh `McpServer` + transport pair per
request. This makes the server trivially horizontally scalable. It does
**not** support server-initiated push over a long-lived connection; use
the polling-style `getRecentMentions` / `getRecentCommentEvents` tools (fed
by the webhook listener) instead.
- Tokens are encrypted at rest with **AES-256-GCM** using a key derived from
`TOKEN_ENCRYPTION_KEY`.
- All Graph API calls go through a single `GraphClient` that adds
`appsecret_proof`, retries transient/rate-limited failures with backoff,
and normalizes Meta's error shapes into typed `AppError` subclasses.
- Analytics like "best posting time" and "compare posts" are **computed
locally** from real data pulled via Graph API primitives ā Meta does not
expose these as first-class endpoints.
---
## Quick Start
```bash
git clone <this-repo-url> instagram-mcp
cd instagram-mcp
npm install
cp .env.example .env
# edit .env with your Meta App credentials (see below)
npm run dev
```
Server starts on `http://localhost:3000`. Health check:
`curl http://localhost:3000/health`.
---
## Meta App Setup
### 1. Create a Meta App
1. Go to <https://developers.facebook.com/apps> ā **Create App**.
2. Choose type **Business**.
3. In the App Dashboard, add the **Instagram Graph API** product (search
"Instagram" in "Add Product").
4. Note your **App ID** and **App Secret** (Settings ā Basic) ā these go in
`META_APP_ID` / `META_APP_SECRET`.
### 2. Link a Facebook Page + Instagram Business Account
The Instagram Graph API only works through a Facebook Page connection:
1. Convert your Instagram account to a **Professional Account** (Business or
Creator) in the Instagram app: Settings ā Account type.
2. Create or use an existing **Facebook Page**.
3. Link them: Instagram app ā Settings ā Account ā Linked accounts ā
Facebook, or Facebook Page ā Settings ā Instagram ā Connect Account.
4. Note the **Facebook Page ID** (Page ā About ā Page ID), used as
`FACEBOOK_PAGE_ID`.
### 3. Generate an Access Token
**Option A ā Graph API Explorer (fastest for personal use):**
1. Go to <https://developers.facebook.com/tools/explorer/>.
2. Select your app, select your Page, and add the permissions listed below.
3. Generate a **User Access Token**, then exchange it for a **long-lived
token** (~60 days) using:
```bash
curl -G "https://graph.facebook.com/v21.0/oauth/access_token" \
-d "grant_type=fb_exchange_token" \
-d "client_id=$META_APP_ID" \
-d "client_secret=$META_APP_SECRET" \
-d "fb_exchange_token=$SHORT_LIVED_TOKEN"
```
4. Put the resulting token in `IG_LONG_LIVED_ACCESS_TOKEN`.
5. Find your Instagram Business Account ID:
```bash
curl -G "https://graph.facebook.com/v21.0/$FACEBOOK_PAGE_ID" \
-d "fields=instagram_business_account" \
-d "access_token=$IG_LONG_LIVED_ACCESS_TOKEN"
```
Put the returned `id` in `IG_BUSINESS_ACCOUNT_ID`.
This is the simplest path for a single-account, self-hosted deployment ā set
those three env vars and skip the `/auth/*` OAuth endpoints entirely.
**Option B ā Full OAuth flow (multi-user / production apps):**
1. Set `META_REDIRECT_URI` to `https://your-domain.com/auth/callback` and
add it as a **Valid OAuth Redirect URI** in the Meta App Dashboard
(Facebook Login product ā Settings).
2. `GET /auth/authorize` returns a URL ā open it in a browser, approve the
permissions.
3. Meta redirects to `/auth/callback`, which exchanges the code for a
long-lived token and stores it encrypted automatically.
4. `GET /auth/status` confirms the connection.
### 4. Permissions & App Review
Required scopes (already requested by `buildAuthorizationUrl`):
```
instagram_basic
instagram_manage_insights
instagram_manage_comments
instagram_content_publish
instagram_manage_messages
pages_show_list
pages_read_engagement
pages_manage_metadata
business_management
```
For personal/self-use on your own account, most of these work in **Development
Mode** with your own account added as a Test User ā no App Review needed.
To let *other* Instagram accounts authorize your app, Meta requires **App
Review** for `instagram_manage_comments`, `instagram_content_publish`,
`instagram_manage_messages`, and `instagram_manage_insights`.
---
## Configuration
See [`.env.example`](.env.example) for the full, documented list. Key
variables:
| Variable | Required | Description |
|---|---|---|
| `META_APP_ID` / `META_APP_SECRET` | ā
| From your Meta App dashboard |
| `TOKEN_ENCRYPTION_KEY` | ā
| ā„32 chars, encrypts tokens at rest |
| `IG_LONG_LIVED_ACCESS_TOKEN` / `IG_BUSINESS_ACCOUNT_ID` | Option A | Skip OAuth entirely |
| `META_REDIRECT_URI` | Option B | Needed only for the `/auth/*` OAuth flow |
| `MCP_AUTH_TOKEN` | Recommended | Bearer token required to call `/mcp` |
| `META_WEBHOOK_VERIFY_TOKEN` | Optional | Needed for real-time webhooks |
The server **refuses to start** if required variables are missing or
malformed (validated with Zod on boot) ā check the console output for the
exact field that failed.
---
## Running Locally
```bash
npm install
npm run dev # tsx watch mode
# or
npm run build && npm start # production build
```
Other scripts: `npm run lint`, `npm run format`, `npm run typecheck`,
`npm test`, `npm run test:coverage`.
---
## Deployment
### Docker
```bash
docker build -t instagram-mcp-server .
docker run -p 3000:3000 --env-file .env -v $(pwd)/data:/app/data instagram-mcp-server
```
Or with Compose:
```bash
docker compose up -d --build
```
### Render
1. Push this repo to GitHub.
2. In Render: **New ā Blueprint**, point at your repo ā `render.yaml` is
auto-detected.
3. Fill in the secret env vars flagged `sync: false` in the Render dashboard.
4. Deploy. Your Remote MCP URL will be
`https://<your-service>.onrender.com/mcp`.
### Railway
```bash
railway init
railway up
```
`railway.json` configures the Dockerfile build and health check
automatically. Set env vars via `railway variables set KEY=value` or the
dashboard.
### Fly.io
```bash
fly launch --no-deploy # detects fly.toml
fly secrets set META_APP_ID=... META_APP_SECRET=... TOKEN_ENCRYPTION_KEY=... MCP_AUTH_TOKEN=...
fly deploy
```
### Self-host (bare metal / VM)
```bash
npm ci --omit=dev
npm run build
NODE_ENV=production node dist/index.js
```
Put it behind a reverse proxy (nginx/Caddy) for TLS termination ā Claude's
Remote MCP requires HTTPS in production.
---
## Connecting Claude
Once deployed with a public HTTPS URL:
1. In **claude.ai**: Settings ā Connectors ā **Add custom connector**.
2. Enter your MCP URL: `https://your-domain.com/mcp`.
3. If you set `MCP_AUTH_TOKEN`, add it as a Bearer token / custom header
(`Authorization: Bearer <token>`) in the connector's auth settings.
4. Claude will call `initialize` and `tools/list` automatically and the
Instagram tools become available in your conversation.
For Claude Desktop or other MCP clients that read a JSON config file, see
[`examples/claude-mcp-config.json`](examples/claude-mcp-config.json).
See [`examples/api-requests.md`](examples/api-requests.md) for raw
`curl`-based examples of the MCP JSON-RPC protocol if you want to test
independent of Claude.
---
## Webhooks (real-time updates)
To receive comments/mentions in near-real-time instead of polling:
1. Set `META_WEBHOOK_VERIFY_TOKEN` to a random string.
2. In the Meta App Dashboard ā Webhooks ā Instagram, subscribe to
`comments`, `mentions` (and others as needed) with callback URL
`https://your-domain.com/webhooks` and the same verify token.
3. Meta will `GET /webhooks` once to verify (handled automatically), then
`POST /webhooks` on every event.
4. Call the `getRecentMentions` / `getRecentCommentEvents` MCP tools to read
the most recent events received (kept in an in-memory ring buffer ā swap
for persistent storage if you need durability across restarts).
---
## Testing
```bash
npm test # everything
npm run test:unit # unit tests (crypto, retry, cache, services via nock mocks)
npm run test:integration # full Express app via supertest, including a live MCP handshake
npm run test:coverage
```
27 tests ship with the project, covering encryption round-trips, retry/backoff
logic, caching, comment/publishing service behavior against a mocked Graph
API, and an end-to-end MCP `initialize` handshake against the real app.
---
## Security Notes
- Tokens are **never** logged (redacted in the pino logger config) and are
encrypted at rest with AES-256-GCM.
- `.env` is git-ignored; only `.env.example` (no real secrets) is committed.
- `appsecret_proof` is sent on every Graph API call by default
(`META_APP_SECRET_PROOF=true`) to prevent token replay if a token leaks.
- Webhook payloads are (optionally) signature-verified against
`X-Hub-Signature-256` using your App Secret ā wire `verifySignature` into
`webhookRoutes.ts` if you want to reject unsigned/forged payloads outright
(currently logged; enable strict rejection for production hardening).
- Set `MCP_AUTH_TOKEN` in any environment reachable from the public internet.
---
## Known API Limitations
The Instagram Graph API does not support everything a full "clone" of the
Instagram app would need. This server is honest about it:
| Requested feature | Reality |
|---|---|
| List of follower/following **usernames** | Not exposed ā only aggregate counts, for user privacy. |
| Reading **Stories** | Only stories *this app itself published* are readable, and only within 24h. No general story-history endpoint exists. |
| **Competitor analysis** (ads, demographics, audience overlap) | Business Discovery exposes only *public* profile/media stats of other accounts ā nothing beyond what's visible on their public profile. |
| Native **"schedule for later"** | No such flag exists on the Graph API; this server creates the container immediately and defers the `media_publish` call with an in-process timer (persist this in a real queue for production-grade durability across restarts). |
| DM automation outside the 24h window | Meta restricts free-form messaging to a 24-hour window after the user last messaged you, or requires approved message tags ā this is a platform policy, not a limitation of this code. |
---
## Troubleshooting
**"Environment validation failed" on startup**
Check the console output ā it lists exactly which env var is missing/invalid.
**`AuthenticationError: Instagram access token is invalid or expired`**
Long-lived tokens last ~60 days. Call `POST /auth/refresh`, or re-run the
Graph API Explorer token exchange and update `IG_LONG_LIVED_ACCESS_TOKEN`.
**`GraphApiError` with code 100 on publishing**
Usually means the media URL isn't publicly reachable/valid, or the account
lacks `instagram_content_publish` permission.
**Rate limit errors (`RateLimitError`)**
The client retries automatically with backoff; if it persists, you're
hitting Meta's per-app/per-user call limits ā space out requests or wait.
**Claude can't connect to `/mcp`**
Confirm the URL is HTTPS and publicly reachable, and that any
`MCP_AUTH_TOKEN` you configured matches exactly what's set in Claude's
connector settings.
---
## FAQ
**Do I need App Review to use this on my own account?**
No ā Development Mode + adding your own account as a Test User is enough for
personal use of all read/write tools.
**Can this manage multiple Instagram accounts?**
The current token store holds one account's credentials. For multi-account
support, extend `TokenStore` to key by account ID and add an `accountId`
parameter to the relevant tools.
**Does this work with Creator accounts, not just Business?**
Yes ā the Graph API treats Business and Creator accounts the same way for
these endpoints.
**Is there a database?**
No ā token storage is a single encrypted JSON file by default, so the
project runs anywhere without provisioning infrastructure. Swap
`src/storage/tokenStore.ts` for a Redis/Postgres-backed implementation if you
need multi-instance deployments.
---
## License
MIT ā see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues