InstantGram

InstantGram

MCP server that enables AI agents to interact with Instagram DMs by managing sessions, sending messages, and configuring webhooks through the InstantGram API.

Category
Visit Server

README

InstantGram

A TypeScript Node.js Instagram DM API server with webhook capabilities, an interactive + JSON CLI, an OpenAPI 3.1 spec, and an MCP server for AI agents.

⚠️ Disclaimer: This project uses the unofficial instagram-private-api library, which is not affiliated with or endorsed by Instagram/Meta. Automating Instagram accounts may violate their Terms of Service and can lead to temporary or permanent account restrictions. Use at your own risk — use only with accounts you own, and rate-limit yourself sensibly.


Features

  • Multi-session management — create, login, restore, delete, and export sessions in memory, persisted to disk
  • Login with 2FA — detects two-factor requirements and completes them via a verification code; handles checkpoints (auto-solve attempt + manual challenge URL fallback)
  • Custom User-Agent & proxy support (http/https/socks5)
  • Messaging API — send text and media (image/video) DMs to a username or numeric user id
  • User lookup — resolve usernames to user ids
  • Webhook system — poll the inbox for new DMs and POST HMAC-SHA256-signed events to your URL, with retries and exponential backoff on rate limits
  • Events: message.received, message.sent, session.error, webhook.test
  • AI-friendly surfaces — /openapi.json (full OpenAPI 3.1 spec), /llms.txt + /llms-full.txt docs, and a one-shot --json CLI mode
  • MCP server — Model Context Protocol tools over stdio (npm run mcp) or Streamable HTTP (/mcp), so AI assistants can create sessions, send DMs, and manage webhooks
  • Interactive CLI — create/login sessions, run the full send-message wizard, configure webhooks, watch live webhook events, export/import sessions
  • Session persistence — serialized states (cookies + device) saved to ./sessions/*.session.json and auto-loaded on boot

Installation

# Node.js >= 18 required
npm install

# Configure environment
cp .env.example .env

# Start the API server (development, with hot reload)
npm run dev

# Or production:
npm run build && npm start

# Interactive CLI (in a second terminal):
npm run cli

Environment variables (.env)

Variable Default Description
PORT 3000 HTTP port for the API server
WEBHOOK_SECRET — Secret used to sign webhook payloads (X-Webhook-Signature header)
DEFAULT_POLL_INTERVAL 5000 Default inbox poll interval in ms
LOG_LEVEL info debug | info | warn | error
SESSION_DIR ./sessions Directory for persisted session states
HTTP_PROXY / per-session proxy — Proxy for Instagram API traffic
API_BASE_URL http://localhost:3000 Base URL the CLI, the JSON mode, and the stdio MCP server use to reach the API server
AUTH_TOKEN — Bearer auth — if set, every request except OPTIONS preflights and GET /health must send Authorization: Bearer <AUTH_TOKEN>. Opt-in kill-switch for locking down a public instance; CLI/JSON/stdio-MCP attach it automatically from the same .env
INSTAGRAM_USERNAME / INSTAGRAM_PASSWORD — Fallback credentials for npm run cli -- login (keeps secrets out of shell history)
IG_APP_VERSION / IG_APP_VERSION_CODE 347.0.0.25.106 / 441775334 Instagram app version fingerprint sent in the User-Agent. The library bundles an ancient version (222.x) that Instagram rejects with unsupported_version/checkpoint_required — bump these if sessions start getting flagged

API Reference

All responses use a consistent envelope:

{
  "success": true,
  "data": { },
  "meta": { "timestamp": "2026-08-09T12:00:00.000Z", "sessionId": "…" }
}

Errors return { "success": false, "error": "…", "meta": { … } }.

Bearer auth (AUTH_TOKEN)

If AUTH_TOKEN is set in .env, the server rejects every request (except CORS OPTIONS preflights and the GET /health liveness probe) unless it carries the header:

Authorization: Bearer <AUTH_TOKEN>

This is an opt-in kill-switch: set the variable to instantly lock down a publicly exposed instance with no code changes, unset it to return to open access. The interactive CLI, one-shot JSON mode, and stdio MCP bridge read AUTH_TOKEN from the same .env and send it automatically, so local workflows keep working. Unauthorized requests get 401 with the standard error envelope.

curl -s -X POST localhost:3000/api/session/create \
  -H 'Authorization: Bearer <AUTH_TOKEN>' \
  -H 'Content-Type: application/json' -d '{}'

AI-friendly surfaces

Method Route Description
GET / JSON service index — points agents at the endpoints below
GET /api Route-group index
GET /health Liveness probe ({ status, uptime, sessions })
GET /openapi.json OpenAPI 3.1 document of every endpoint, schema, and error — the single machine-readable API contract
GET /llms.txt LLM-friendly documentation index (llms.txt convention)
GET /llms-full.txt Full markdown reference for LLM ingestion
GET / POST / DELETE /mcp MCP server over Streamable HTTP (see below)

Sessions

Method Route Body Description
POST /api/session/create { userAgent?, cookies?, proxy? } Create a session; returns { sessionId }
POST /api/session/login { sessionId, username, password } Log in. If 2FA is required returns data.twoFactor; if a checkpoint is hit returns data.challenge
POST /api/session/2fa { sessionId, code, verificationMethod? } Complete a pending 2FA login. For approval-based 2FA (verification method 2 — a push to your phone, no code) send code: "" after approving on the device. Pass verificationMethod when Instagram didn't report it
POST /api/session/logout { sessionId } Log out
POST /api/session/restore { data: SerializedSessionFile, preferredId? } Restore a session from exported JSON
POST /api/session/restore-cookies { cookies, userAgent?, proxy? } Create a session from raw browser cookies (see below)
GET /api/session — List all sessions with status
GET /api/session/:id — Single session details
GET /api/session/:id/export — Export serialized session (cookies + device)
DELETE /api/session/:id — Delete a session (stops polling too)

Messages

Method Route Body / Query Description
POST /api/message/send { sessionId, recipient, text?, mediaBase64?, mediaType? } Send a DM. recipient may be a username or numeric user id. mediaBase64 is base64-encoded file data; mediaType is image | video (auto-detected from bytes if omitted)
GET /api/user/lookup/:username ?sessionId=… Resolve a username to a numeric user id

Webhooks

Method Route Body Description
POST /api/webhook/configure { sessionId, url, secret?, events? } Set the delivery URL (and optional per-session signing secret)
POST /api/webhook/start { sessionId, intervalMs? } Start inbox polling (min 1000 ms)
POST /api/webhook/stop { sessionId } Stop polling
POST /api/webhook/test { sessionId } Send a synthetic webhook.test event through the full pipeline
GET /api/webhook/status/:sessionId — Polling state, URL, interval, last delivery
GET /api/webhook/logs/:sessionId ?limit=50 Recent delivery log entries

Webhook payloads

Every event is POSTed to your URL as JSON:

{
  "event": "message.received",
  "timestamp": 1786298000000,
  "sessionId": "8f0e9a2b-…",
  "payload": {
    "messageId": "340282366841710300949128155123748551874",
    "threadId": "340282366841710300949128155123748551874",
    "userId": "123456789",
    "username": "someone",
    "text": "Hey! 👋",
    "itemType": "text"
  }
}
Field Type Description
event string message.received | message.sent | session.error | webhook.test
timestamp number Unix epoch ms
sessionId string The session that produced the event
payload.messageId string Instagram item id
payload.threadId string Direct thread id
payload.userId string The other party's user id
payload.username string Their username (when known)
payload.text string Message text (text items)
payload.mediaUrl string CDN URL for media items (photo/video/voice/clip)
payload.itemType string Raw Instagram item type
payload.error string Error detail (session.error events)

Signature verification

Each request carries an X-Webhook-Signature header:

X-Webhook-Signature: sha256=<hex-encoded HMAC-SHA256 of the raw request body>
X-Webhook-Event: message.received

The signature is computed with the WEBHOOK_SECRET (or the per-session secret configured via /api/webhook/configure).

Node.js receiver example:

const crypto = require('crypto');

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const secret = process.env.WEBHOOK_SECRET;
  const expected = `sha256=${crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex')}`;

  if (!crypto.timingSafeEqual(
    Buffer.from(req.get('x-webhook-signature')),
    Buffer.from(expected),
  )) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body.toString());
  console.log(`${event.event} from session ${event.sessionId}`, event.payload);
  res.sendStatus(200);
});

Use express.raw() (or read the raw body) so the signature covers the exact bytes that were POSTed — re-serializing parsed JSON will break verification.

Python receiver example:

import hashlib, hmac, json
from flask import Flask, request

app = Flask(__name__)
SECRET = b"your-webhook-secret-here"

@app.post("/webhook")
def webhook():
    body = request.get_data()
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(request.headers.get("X-Webhook-Signature", ""), expected):
        return {"error": "invalid signature"}, 401
    print(json.loads(body))
    return "ok", 200

Restoring a session from raw browser cookies

No username/password required — paste the cookies you can copy from your browser's DevTools (Application → Cookies → i.instagram.com). Only sessionid is mandatory; csrftoken is strongly recommended (state-changing requests like sending messages need it). The user id is derived from the first segment of the sessionid value when ds_user_id isn't provided.

curl -s -X POST localhost:3000/api/session/restore-cookies -H 'Content-Type: application/json' -d '{
  "cookies": "sessionid=38499037450%3ABX1JtIMMfeCui8%3A10%3AAYhKmCtPNU97naqRsPuZpCUA49KbUT4SsWi2KDPUgg; csrftoken=abc123; ds_user_id=38499037450; mid=xyz",
  "userAgent": "Instagram 276.0.0.24.105 Android (30/11; 420dpi; 1080x2340; samsung; SM-G991B; o1s; exynos2100; en_US; 319462287)"
}'
# → { "success": true, "data": { "sessionId": "…", "userId": "38499037450", "status": "logged_in" }, … }

A cookie-header string and a structured object are both accepted:

// structured form
{ "cookies": { "sessionid": "…", "csrftoken": "…", "ds_user_id": "…" } }

The server verifies the cookies in the background (account.currentUser()); if Instagram rejects them (expired/invalid/flagged), the session status flips to error with an explanatory message — check GET /api/session. Cookies are bound to the device fingerprint, so passing the matching userAgent you copied from the browser helps keep the session alive.

CLI usage

npm run cli

The CLI is a self-contained tester that talks to the running API server (start it with npm run dev). Menu map:

Main menu
├── Session Management
│   ├── Create new session        (custom UA / cookies / proxy)
│   ├── Login to session          (auto-detects 2FA and prompts for the code)
│   ├── Complete 2FA login
│   ├── Restore session from JSON file
│   ├── Restore session from raw cookies
│   ├── List active sessions
│   ├── Logout from session
│   └── Delete session
├── Send Message                  (select session → recipient → text/media → confirm)
├── Webhook Management
│   ├── Configure webhook URL     (URL + optional secret + event filter)
│   ├── Start polling             (custom interval)
│   ├── Stop polling
│   ├── Send test webhook
│   ├── View webhook status
│   ├── View webhook logs
│   └── Watch live webhook events (press any key to return)
├── Utilities
│   ├── Lookup user ID from username
│   ├── Export session to file
│   └── Import session from file
└── Exit

One-shot JSON mode (AI / scripting friendly)

npm run cli -- <command> [--flag value ...] runs a single command and prints exactly one JSON document to stdout — nothing else — so output pipes straight into jq, shells, and AI tooling:

npm run cli -- list-sessions
# → {"success":true,"data":[{"id":"…","status":"logged_in",…}]}

npm run cli -- send-message --session-id <id> --recipient friend --text "hi"
# → {"success":true,"data":{"messageId":"…","threadId":"…","recipientUserId":"…","itemType":"text"}}

Errors print { "success": false, "error": "…" } and exit with code 1. Credentials for login can come from INSTAGRAM_USERNAME / INSTAGRAM_PASSWORD to keep secrets out of shell history.

Commands: health, create-session, login, complete-2fa, logout, restore-session (--file), restore-cookies, list-sessions, session-info, export-session, delete-session, send-message, lookup-user, webhook-configure, webhook-start, webhook-stop, webhook-test, webhook-status, webhook-logs, help. Run npm run cli -- help for per-command flags.


MCP server (Model Context Protocol)

InstantGram speaks MCP so AI assistants can drive it directly — create/login sessions, send DMs, look up users, manage webhooks. Every REST endpoint is exposed as a tool, and the docs are available as resources.

Tools: create_session, login, complete_2fa, logout, restore_session, restore_cookies, list_sessions, session_info, export_session, delete_session, send_message, lookup_user, webhook_configure, webhook_start, webhook_stop, webhook_test, webhook_status, webhook_logs, health

Resources: instantgram://openapi.json, instantgram://llms-full.txt

stdio (Claude Desktop, Cursor, …)

The MCP server bridges to the running API server via API_BASE_URL — start the API with npm run dev first, then:

npm run mcp

Register it in claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "instantgram": {
      "command": "npm",
      "args": ["run", "mcp"],
      "cwd": "/absolute/path/to/instantgram"
    }
  }
}

Prefer a compiled entry? npm run build then point command at node dist/mcp.js.

HTTP (Streamable HTTP)

The API server hosts MCP at /mcp (CORS-enabled; stateful sessions use the Mcp-Session-Id header):

GET    http://localhost:3000/mcp   # initialize / SSE handshake
POST   http://localhost:3000/mcp   # JSON-RPC messages
DELETE http://localhost:3000/mcp   # end the session

Point any MCP client (MCP Inspector, web apps, remote assistants) at http://localhost:3000/mcp.


Quick start (end-to-end)

# Terminal 1 — run the server
cp .env.example .env
npm install
npm run dev

# Terminal 2 — run the CLI
npm run cli

# From the CLI:
# 1. Session Management → Create new session → Login to session
# 2. (if 2FA) → enter code
# 3. Webhook Management → Configure webhook URL → Start polling
# 4. Send Message → pick session → recipient → text → confirm
# 5. Webhook Management → Watch live webhook events

Alternatively, drive everything with curl:

# Create a session
curl -s -X POST localhost:3000/api/session/create -H 'Content-Type: application/json' \
  -d '{"userAgent":"Instagram 347.0.0.25.106 Android (30/11; 420dpi; 1080x2340; samsung; SM-G991B; o1s; exynos2100; en_US; 441775334)"}'
# → {"success":true,"data":{"sessionId":"<id>"},...}

# Login
curl -s -X POST localhost:3000/api/session/login -H 'Content-Type: application/json' \
  -d '{"sessionId":"<id>","username":"myaccount","password":"hunter2"}'

# Send a DM to a username
curl -s -X POST localhost:3000/api/message/send -H 'Content-Type: application/json' \
  -d '{"sessionId":"<id>","recipient":"some.friend","text":"Hello from InstantGram!"}'

# Look up a user id
curl -s "localhost:3000/api/user/lookup/some.friend?sessionId=<id>"

# Configure a webhook and start polling
curl -s -X POST localhost:3000/api/webhook/configure -H 'Content-Type: application/json' \
  -d '{"sessionId":"<id>","url":"https://your-server.example.com/webhook"}'
curl -s -X POST localhost:3000/api/webhook/start -H 'Content-Type: application/json' \
  -d '{"sessionId":"<id>","intervalMs":5000}'

# Send media (base64 of a jpg) — use mediaType image/video
IMG_B64=$(base64 -i photo.jpg | tr -d '\n')
curl -s -X POST localhost:3000/api/message/send -H 'Content-Type: application/json' \
  -d "{\"sessionId\":\"<id>\",\"recipient\":\"some.friend\",\"mediaBase64\":\"$IMG_B64\",\"mediaType\":\"image\"}"

2FA methods

Instagram's two_factor_info.verification_method tells you which challenge was triggered:

Method Type What to do
0 SMS Enter the code texted to your phone
1 Authenticator app (TOTP) Enter the 6-digit code from your app
2 Approval from another device A push notification appears on your logged-in phone — tap "It was me", then complete with an empty code (code: "")
3 Backup codes Enter one of your one-time backup codes

The CLI detects method 2 automatically: it tells you to approve on your phone and completes the login without asking for a code.

⏱️ Approval pushes are short-lived (usually one or two minutes). Approve on your phone and confirm in the CLI promptly — if it expires, just run Login to session again to trigger a fresh push. Tapping a stale notification from an earlier login attempt always shows "expired"; use the one from the current attempt. Pending challenges older than 10 minutes are auto-cleared.

# Approval-based 2FA via the API (verificationMethod: 2 = approval push)
curl -s -X POST localhost:3000/api/session/2fa -H 'Content-Type: application/json' \
  -d '{"sessionId":"<id>","code":"","verificationMethod":"2"}'

Session persistence

After a successful login (or 2FA completion), the session's serialized state — cookies, device fingerprint, and constants — is written to SESSION_DIR (default ./sessions) as <sessionId>.session.json.

On server boot, every file in that directory is loaded back into memory, so sessions stay logged in across restarts without re-entering credentials.

  • Export: GET /api/session/:id/export or CLI Utilities → Export session to file. The exported JSON can be moved to another machine and restored with POST /api/session/restore or CLI Import session from file.
  • Rotation: If Instagram invalidates a session (e.g. password change), delete the session and its file, then log in again.

🔒 Treat sessions/ like passwords — it contains live auth cookies. It is git-ignored by default.


How it works

  1. Instagram has no public DM webhooks — this server polls ig.feed.directInbox() on a configurable interval and diffs new item ids against a per-session seen-set.
  2. New incoming items become message.received events; outbound API sends become message.sent events; poll failures become session.error events.
  3. Each event is signed with HMAC-SHA256 and delivered with up to 3 retries (1s/2s/4s backoff). On rate-limit or network errors the poll interval backs off exponentially up to 5 minutes.
  4. Sessions are isolated in memory (Map<sessionId, InstagramService>), each with its own device fingerprint, cookies, proxy, and webhook config.

Error classification

Instagram errors are normalized into categories that map to HTTP statuses and retry behavior:

Category Trigger API response
network Connection/parse failures 502
auth Bad password, invalid user, logged out 401
rate_limit Action spam / too many requests 429 (+ poll backoff)
not_found searchExact miss 404
challenge Checkpoint / unsupported version 403 (with solve URL)
two_factor Login-time flows handled inline

Why you might see checkpoint_required / unsupported_version: the bundled instagram-private-api reports an app version from ~2021 (222.0.0.13.114). Instagram flags that fingerprint. This project patches the version to 347.0.0.25.106 by default (overridable via IG_APP_VERSION/IG_APP_VERSION_CODE, or derived from your custom User-Agent). If Instagram tightens checks again, bump those env values.


Project structure

src/
  ├── index.ts                 # Express server entry (wires services + routes)
  ├── cli.ts                   # Inquirer.js interactive CLI
  ├── cli-json.ts              # One-shot JSON mode (AI / scripting friendly)
  ├── mcp.ts                   # MCP server entry (stdio bridge to the REST API)
  ├── openapi.ts               # OpenAPI 3.1 document (served at /openapi.json)
  ├── types/index.ts           # All shared interfaces
  ├── services/
  │   ├── InstagramService.ts  # IgApiClient wrapper (auth, messaging, polling)
  │   ├── SessionManager.ts    # Multi-session registry + disk persistence
  │   └── WebhookService.ts    # Polling, HMAC delivery, retries, logs
  ├── routes/
  │   ├── session.ts           # /api/session/*
  │   ├── message.ts           # /api/message/*, /api/user/lookup/*
  │   └── webhook.ts           # /api/webhook/*
  ├── mcp/
  │   ├── apiClient.ts         # Envelope-aware REST client (shared by MCP + JSON CLI)
  │   ├── tools.ts             # MCP tool definitions (one per REST endpoint)
  │   └── http.ts              # Streamable HTTP transport mounted at /mcp
  └── utils/
      ├── api.ts               # Response envelope helpers
      ├── crypto.ts            # HMAC-SHA256 sign/verify
      └── logger.ts            # Leveled, colored logging

Scripts

Script Purpose
npm run dev Run the API server with tsx watch (hot reload)
npm run build Compile TypeScript to dist/
npm start Run the compiled server
npm run cli Launch the interactive CLI
npm run cli -- <cmd> One-shot JSON mode (AI / scripting friendly)
npm run mcp Start the MCP server over stdio (requires the API server)
npm run typecheck Type-check without emitting

License

MIT

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured