Instagram MCP Server
Provides tools to scrape Instagram profiles, posts, reels, direct messages, and business insights using a custom browser orchestration engine.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Instagram MCP Server fetch Instagram profile for natgeo"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Instagram MCP Server
The Problem: Instagram has no official public API for profile scraping, insights, or messaging. AI assistants (Claude, Cursor, Windsurf) need structured Instagram data — profiles, posts, reels, DMs, business insights — but Instagram's front end is a moving target. Hashed CSS classes change weekly. Rate limits and auth barriers are aggressive. A naive selenium script breaks within days.
What this is: A purpose-built MCP server that treats Instagram's web client as an adversarial data source. It uses a custom browser orchestration engine that survives DOM churn, manages browser sessions across three runtime modes (legacy, CDP, Docker), and serializes tool execution to prevent Instagram's session state from corrupting.
Engineering highlights: innerText-based extraction (zero DOM selector dependence), state-machine driven bootstrap with background retries, sequential tool middleware to prevent concurrent navigation conflicts, and a rich error diagnostic system that maps 15+ Instagram failure modes to actionable messages.
The Problem
Instagram's web app is engineered against automation:
No public API for profiles, reels, DMs, or business insights. Everything must go through the web client.
Hashed CSS classes (
x1n2onr6,x1lliihq, etc.) change with every deploy. Any scraper relying on DOM selectors breaks within weeks.Concurrent page states interfere. If two tools navigate Instagram simultaneously (e.g., "fetch profile" and "send DM"), the session enters an inconsistent state and Instagram forces re-authentication.
Rate limits and auth barriers are invisible — no HTTP status code. They appear as in-page "blocked" overlays, requiring DOM-level detection.
Session persistence across restarts requires cookie extraction from browser-native SQLite stores, which differ by browser (Chrome, Firefox, Brave, Edge, etc.).
Existing solutions (Puppeteer wrappers, static scrapers) fail because they couple extraction logic to ephemeral layout details, or lack the state management needed for multi-tool AI workflows.
Related MCP server: Instagram MCP Server
Engineering Highlights
1. DOM-Agnostic Extraction Engine
Anti-pattern: Most scrapers use CSS selectors like div.x1n2onr6 > span._aacl. When Instagram rotates these classes, the scraper silently returns empty data.
Solution: The extractor (scraping/extractor.py) relies on innerText and URL navigation, not DOM selectors. Each "section" (posts, reels, followers) maps to exactly one page navigation (USER_SECTIONS dict). Extraction reads visible text, then strips Instagram's chrome (footer links, sidebar noise) using regex markers:
# Noise markers strip Instagram chrome instead of brittle CSS selectors
_NOISE_MARKERS = [
re.compile(r"^About\n+(?:Help|Press|API|Jobs|Terms|Privacy)", re.MULTILINE),
re.compile(r"^© \d{4} Instagram from Meta$", re.MULTILINE),
re.compile(r"^Suggested for you$", re.MULTILINE),
]This approach survives layout changes because Instagram cannot hide text content from the user without breaking its own UX — and innerText reads exactly what a human sees.
Key constraint: "One section = one navigation." Each section triggers exactly one page.goto(). Combining multiple data sources into a single navigation creates coupling that breaks when Instagram reorganized pages.
2. Three-Mode Browser Architecture
The server supports three distinct runtime policies, each solving a different operational constraint:
Mode | Entry | Cookie Source | Use Case |
Legacy | Default | SQLite cookie store detection | Desktop AI clients |
CDP |
| Live Brave browser session | Users who don't want cookie extraction |
Docker |
| Pre-exported portable auth ( | Server/headless deployments |
The CDP bridge (drivers/browser._bridge_runtime_profile()) is the most architecturally interesting: it connects to a running Brave browser via Chrome DevTools Protocol, imports cookies from the persistent source session into an ephemeral runtime profile, and isolates scraping in a separate browser context — zero interference with the user's actual browsing.
The Docker runtime solves the "headless auth" problem: Instagram's anti-bot checks detect missing GPU/display. The solution creates an authenticated profile on the host via --login, then tar archives it for injection into the container. The container never handles login — just cookie replay.
3. Sequential Tool Execution Middleware
Instagram's web app is a single-page application with mutable global state. If two MCP tool calls navigate Instagram simultaneously, the following happens:
Tool A navigates to
instagram.com/natgeo/posts/Tool B navigates to
instagram.com/direct/inbox/Instagram's SPA state corrupts — neither tool gets valid data
Instagram detects the "impossible" navigation pattern and forces re-login
Solution: An asyncio.Lock-based middleware (SequentialToolExecutionMiddleware) serializes all MCP tool calls within the same server process. Each tool waits in a queue, acquires the lock, executes, and releases:
class SequentialToolExecutionMiddleware(Middleware):
async def on_call_tool(self, context, call_next):
async with self._lock: # Only one navigation at a time
return await call_next(context)This is a middleware registered at server creation, not per-tool — zero tool implementation changes.
4. Bootstrap & Authentication State Machine
The bootstrap system (bootstrap.py) manages a complex initialization lifecycle across process restarts:
IDLE → SETUP_IN_PROGRESS → READY
↓ (failed)
FAILED → (background retry) → SETUP_IN_PROGRESSThe auth state machine runs independently:
UNKNOWN → CHECKING → READY
↓ (expired) ↓ (expired)
INVALID → RELOGIN_IN_PROGRESS → READYKey design decisions:
Background-first browser setup:
patchrightChromium downloads in a background task, not at startup. Tools become available immediately; if the browser isn't ready, they raiseBrowserSetupInProgressErrorwith a "retry in a few minutes" message.Cookie bridge, not credential store: No Instagram passwords are stored. Auth state is purely cookie-based, extracted from the host browser's SQLite store.
Auto-relogin on expiry: When Instagram invalidates a session mid-flight, the system detects it via
detect_auth_barrier()and triggers a fresh login flow — no manual intervention.
5. Rich Error Diagnostics
A naive scraper returns HTTP 200 with a "login required" page, and the AI client has no way to understand what happened.
Solution: A centralized raise_tool_error() function maps 15+ Instagram-specific exception types to user-friendly ToolError messages with auto-generated diagnostics:
except AuthenticationError:
raise ToolError(
"Authentication failed. Run with --login to re-authenticate."
) from exception
except RateLimitError:
raise ToolError(
f"Rate limit detected. Wait {exception.suggested_wait_time}s before retrying."
) from exceptionEach diagnostic includes an issue template path — a markdown file in docs/ that provides context-specific troubleshooting. This turns opaque scraping failures into actionable guidance.
Architecture Overview
MCP Client (Claude, Cursor, etc.)
│
┌─────▼──────┐
│ FastMCP │ ← MCP protocol (stdio or streamable-http)
│ Server │
└─────┬──────┘
│
┌─────▼──────────────┐
│ SequentialToolExec │ ← asyncio.Lock middleware
│ Middleware │ serializes all navigations
└─────┬──────────────┘
│
┌─────▼──────────────────┐
│ Tool Registry │ ← 28+ tools across 7 categories
│ (user / posts / search │
│ / insights / messaging │
│ / actions / gemini) │
└─────┬──────────────────┘
│
┌─────▼────────────────────────┐
│ Bootstrap & Auth State │
│ Machine │ ← manages browser lifecycle
└─────┬────────────────────────┘
│
┌─────▼──────────────────────┐
│ Browser Orchestrator │
│ (Legacy / CDP / Docker) │ ← three runtime modes
└─────┬──────────────────────┘
│
┌─────▼──────────────┐
│ innerText │
│ Extraction Engine │ ← zero DOM selector dependence
└────────────────────┘Tech Stack
Technology | Purpose |
Python 3.12+ | Type-safe async runtime with structural pattern matching |
FastMCP 3.x | MCP protocol server — enables stdio and streamable-http transports |
Patchright | Anti-detection Playwright fork — bypasses Instagram's webdriver checks |
asyncio.Lock + Middleware | Serialized tool execution — prevents concurrent navigation corruption |
Gemini 2.0 Flash | Multimodal reel analysis — video-to-text without local Whisper |
Docker | Headless deployment — portable auth via cookie archives |
Ruff / Ty | Strict linting (Ruff) and type checking (Ty, not mypy) |
Quick Start
# Install via uvx (no local install needed)
uvx instagram-scraper-mcpOn first tool call, a login window opens. Log in once; cookies persist across restarts.
See MCP Client Configuration for IDE setup.
Advanced Configurations
Mode | Command |
CDP (Brave) |
|
Docker | |
Gemini Analysis |
|
Debug Mode |
|
Tool Suite
Category | Tools | Capabilities |
Profile & Content |
| Posts, reels, stories, highlights, followers/following lists |
Search & Discovery |
| User, hashtag, and location search |
Messaging & Actions |
| Full DM and engagement suite |
Business Insights |
| Reach, impressions, demographics (Business/Creator accounts only) |
AI Analysis |
| Multimodal reel transcription and analysis via Gemini 2.0 Flash |
Transcription |
| Local Whisper-based SRT subtitle generation |
Potentialities
Headless auth recovery: Currently Docker runtime requires host-side
--login. A self-service web portal for one-time auth token generation would eliminate this friction.Multi-account session management: The sequential middleware prevents intra-session conflicts, but switching between Instagram accounts requires a separate profile. Native account switching would enable parallel multi-account extraction.
Webhook-based rate limit mitigation: Rate limits are currently synchronous (wait N seconds). An async queue with webhook callbacks would allow batch processing without tool timeouts.
GraphQL API fallback: Instagram's internal GraphQL API occasionally surfaces in responses. A hybrid strategy (extraction + API probes) could reduce page navigations for known-stable endpoints.
License & Acknowledgements
Licensed under the Apache 2.0 License.
Built with FastMCP and Patchright.
Use in accordance with Instagram's Terms of Use. Web scraping may violate Instagram's terms. This tool is for personal use only.
Developed by Ishan Parihar — If you find this useful, consider supporting
Available Tools
28 toolsanalyze_reel_with_geminiAnalyze Reel with GeminiARead-only
Analyze Instagram reel using Google Gemini 2.0 Flash.
Fast multimodal analysis that understands both audio and visuals. Returns structured insights without local transcription.
Speed: ~15-25 seconds per reel (3x faster than local Whisper)
Cost: ~$0.00017 per reel (extremely cheap)
Args: reel_url: Full Instagram reel URL (e.g., https://www.instagram.com/reel/ABC123/) ctx: FastMCP context for progress reporting analysis_type: Type of analysis: - summary: Quick overview (fastest) - transcript: Full transcription - topics: Extract topics and keywords - quotes: Notable quotes with timestamps - full: Comprehensive analysis (default)
Returns: Dict with analysis results in JSON format. Structure depends on analysis_type.
Inputs: reel_url (required), analysis_type. Returns: object
bulk_analyze_reels_with_geminiBulk Analyze User Reels with GeminiARead-only
Analyze multiple reels from a user with Gemini.
Faster than local transcription for bulk analysis. Processes reels sequentially to avoid rate limits.
Args: username: Instagram username (e.g., "instagram", "natgeo") ctx: FastMCP context for progress reporting max_reels: Maximum reels to analyze (default: 5) analysis_type: Analysis type (default: summary for speed)
Returns: Dict with analyses for each reel.
Inputs: username (required), max_reels, analysis_type. Returns: object
close_sessionClose SessionADestructive
Close the current Instagram browser session and clean up resources.
Inputs: no named parameters. Returns: object
comment_on_postComment on PostADestructive
Post a comment on a post.
Navigates to the post and submits the comment. confirm_post must be True for the comment to be posted.
Args: post_url: Instagram post URL (e.g., "https://www.instagram.com/p/ABC123/") comment: The comment text to post confirm_post: Must be True to actually post the comment ctx: FastMCP context for progress reporting
Returns: Dict with url, status, and optional message.
Inputs: comment (required), post_url (required), confirm_post (required). Returns: object
follow_userFollow UserADestructive
Follow a user or send a follow request for private accounts.
Navigates to the user's profile and clicks the Follow button. For private accounts, a follow request is sent.
Args: username: Instagram username to follow (e.g., "natgeo") ctx: FastMCP context for progress reporting
Returns: Dict with url, status, and optional message.
Inputs: username (required). Returns: object
get_activity_insightsGet Activity InsightsBRead-only
Get profile activity insights from Professional Dashboard.
Note: Professional Dashboard is only available for Business and Creator accounts.
Args: ctx: FastMCP context for progress reporting time_range: Time range for insights data (7d, 30d, 90d). Default: "7d"
Returns: Dict with url, sections (name -> raw text), and optional references. The LLM should parse the raw text to extract profile activity data.
Inputs: time_range. Returns: object
get_audience_insightsGet Audience InsightsARead-only
Get audience demographics from Professional Dashboard.
Note: Professional Dashboard is only available for Business and Creator accounts.
Args: ctx: FastMCP context for progress reporting
Returns: Dict with url, sections (name -> raw text), and optional references. The LLM should parse the raw text to extract audience demographics.
Inputs: no named parameters. Returns: object
get_business_insightsGet Business InsightsARead-only
Get Business/Creator account insights from Professional Dashboard.
Note: Professional Dashboard is only available for Business and Creator accounts.
Args: ctx: FastMCP context for progress reporting time_range: Time range for insights data (7d, 30d, 90d). Default: "7d"
Returns: Dict with url, sections (name -> raw text), and optional references. The LLM should parse the raw text in each section.
Inputs: time_range. Returns: object
get_content_insightsGet Content InsightsARead-only
Get content performance insights from Professional Dashboard.
Note: Professional Dashboard is only available for Business and Creator accounts.
Args: ctx: FastMCP context for progress reporting time_range: Time range for insights data (7d, 30d, 90d). Default: "30d"
Returns: Dict with url, sections (name -> raw text), and optional references. The LLM should parse the raw text to extract content performance data.
Inputs: time_range. Returns: object
get_direct_inboxGet Direct InboxARead-only
List recent DM conversations from the Instagram direct inbox.
Args: ctx: FastMCP context for progress reporting limit: Maximum number of conversations to load (1-50, default 20)
Returns: Dict with url, sections (inbox -> raw text), and optional references.
Inputs: limit. Returns: object
get_dm_conversationGet DM ConversationARead-only
Read a specific DM conversation.
Provide either username or thread_id to identify the conversation.
Args: ctx: FastMCP context for progress reporting thread_id: Instagram messaging thread ID username: Instagram username of the conversation participant limit: Maximum number of messages to load (1-100, default 50)
Returns: Dict with url, sections (conversation -> raw text), and optional references.
Inputs: limit, username, thread_id. Returns: object
get_hashtag_postsGet Hashtag PostsARead-only
Get posts for a hashtag.
Extracts post links from the hashtag grid page and returns them as
structured references. Use get_post_details for individual post enrichment.
Args: hashtag: Hashtag to search (without the # symbol) ctx: FastMCP context for progress reporting max_posts: Maximum number of posts to load (default 50)
Returns: Dict with url, sections (name -> raw text), references (post links), and total_posts count.
Inputs: hashtag (required), max_posts. Returns: object
get_location_postsGet Location PostsARead-only
Get posts tagged at a location.
Extracts post links from the location grid page and returns them as
structured references. Use get_post_details for individual post enrichment.
Args: location_id: Instagram location ID ctx: FastMCP context for progress reporting max_posts: Maximum number of posts to load (default 50)
Returns: Dict with url, sections (name -> raw text), references (post links), and total_posts count.
Inputs: location_id (required), max_posts. Returns: object
get_post_detailsGet Post DetailsARead-only
Get detailed post/reel information with structured data.
Returns structured data including:
id, shortcode, url
caption, timestamp
media_type (reel, video, image, carousel)
video_url, thumbnail_url
engagement (likes, views, comments, shares)
audio info (for reels)
Args: post_url: Full Instagram post URL ctx: FastMCP context for progress reporting include_comments: Whether to include comments in the response
Returns: Dict with structured post data: { url, id, shortcode, media_type, caption, timestamp, video_url, thumbnail_url, engagement: {likes, views, comments, shares}, audio: {audio_name, audio_artist}, sections: {main: text} (legacy format) }
Inputs: post_url (required), include_comments. Returns: object
get_user_highlightsGet User HighlightsARead-only
Get an Instagram user's story highlights.
Args: username: Instagram username (e.g., "instagram", "natgeo") ctx: FastMCP context for progress reporting
Returns: Dict with url and highlights list, where each highlight has: title, cover_url, highlight_id.
Inputs: username (required). Returns: object
get_user_postsGet User PostsARead-only
Get an Instagram user's posts with structured data.
Args: username: Instagram username (e.g., "instagram", "natgeo") ctx: FastMCP context for progress reporting max_posts: Maximum number of posts to retrieve (default 50)
Returns: Dict with url, posts list, total_posts count, sections, and references. Each post has: id, shortcode, url, thumbnail_url, media_type.
Inputs: username (required), max_posts. Returns: object
get_user_profileGet User ProfileARead-only
Get an Instagram user's profile.
Args: username: Instagram username (e.g., "instagram", "natgeo") ctx: FastMCP context for progress reporting sections: Comma-separated list of extra sections to scrape. The main profile page is always included. Available sections: posts, reels, tagged, followers, following Examples: "posts,reels", "tagged", "followers,following" Default (None) scrapes only the main profile page.
Returns: Dict with url, sections (name -> raw text), and optional references. Sections may be absent if extraction yielded no content for that page. Includes unknown_sections list when unrecognised names are passed. The LLM should parse the raw text in each section.
Inputs: username (required), sections. Returns: object
get_user_reelsGet User ReelsARead-only
Get an Instagram user's reels with structured data.
Returns reel IDs, URLs, thumbnails, and view counts from the grid page without navigating to individual reels (avoids N+1 rate limiting).
Args: username: Instagram username (e.g., "instagram", "natgeo") ctx: FastMCP context for progress reporting max_reels: Maximum number of reels to retrieve (default 50)
Returns:
Dict with url, reels list, total_reels count, sections, and references.
Each reel has: id, shortcode, url, thumbnail_url, view_count_text, media_type.
Use get_post_details on individual reel URLs for full engagement data.
Inputs: username (required), max_reels. Returns: object
get_user_storiesGet User StoriesARead-only
Get an Instagram user's active stories.
Args: username: Instagram username (e.g., "instagram", "natgeo") ctx: FastMCP context for progress reporting
Returns: Dict with url and stories list, where each story has: media_url, timestamp, expires_at.
Inputs: username (required). Returns: object
like_postLike PostADestructive
Like a post.
Navigates to the post and clicks the Like button.
Args: post_url: Instagram post URL (e.g., "https://www.instagram.com/p/ABC123/") ctx: FastMCP context for progress reporting
Returns: Dict with url, status, and optional message.
Inputs: post_url (required). Returns: object
save_postSave PostBDestructive
Save a post to a collection.
Navigates to the post and clicks the Save button. Optionally saves to a specific collection.
Args: post_url: Instagram post URL (e.g., "https://www.instagram.com/p/ABC123/") ctx: FastMCP context for progress reporting collection: Optional collection name to save the post into
Returns: Dict with url, status, and optional message.
Inputs: post_url (required), collection. Returns: object
search_locationsSearch LocationsARead-only
Search for Instagram locations.
Args: query: Search query (e.g., "New York", "Paris cafe") ctx: FastMCP context for progress reporting max_results: Maximum number of results to return (default 50)
Returns: Dict with url, sections (name -> raw text), and optional references. The LLM should parse the raw text to extract individual locations and their details.
Inputs: query (required), max_results. Returns: object
search_usersSearch UsersARead-only
Search for Instagram users.
Args: query: Search query (e.g., "john doe", "photographer") ctx: FastMCP context for progress reporting max_results: Maximum number of results to return (default 50)
Returns: Dict with url, sections (name -> raw text), and optional references. The LLM should parse the raw text to extract individual users and their profiles.
Inputs: query (required), max_results. Returns: object
send_dmSend DMADestructive
Send a direct message to an Instagram user.
This is a write operation — confirm_send must be True to actually send.
Args: username: Instagram username of the recipient message: The message text to send confirm_send: Must be True to send the message ctx: FastMCP context for progress reporting
Returns: Dict with url, status, sent (bool), and optional message.
Inputs: message (required), username (required), confirm_send (required). Returns: object
transcribe_reelTranscribe Single ReelARead-only
Transcribe a single Instagram reel to SRT.
Args: reel_url: Full Instagram reel URL (e.g., https://www.instagram.com/reel/ABC123/) ctx: FastMCP context for progress reporting keep_video: Keep downloaded video file (default: False)
Returns: Dict with reel_id, srt_path, transcript_preview, etc.
Inputs: reel_url (required), keep_video. Returns: object
transcribe_user_reelsTranscribe User ReelsARead-only
Download and transcribe Instagram reels to SRT subtitles.
Downloads reels, runs speech-to-text using Whisper via the caption command, and generates SRT subtitle files.
Args: username: Instagram username (e.g., "instagram", "natgeo") ctx: FastMCP context for progress reporting max_reels: Maximum reels to transcribe (default: 10) keep_videos: Keep downloaded video files (default: False)
Returns: Dict with: - url: Instagram profile URL - transcripts: List of {reel_id, video_url, srt_path, transcript_preview, reel_url} - total_reels: Number processed - temp_dir: Temporary files location - output_dir: SRT output directory
Inputs: username (required), max_reels, keep_videos. Returns: object
unfollow_userUnfollow UserADestructive
Unfollow a user.
Navigates to the user's profile and clicks the Unfollow button.
Args: username: Instagram username to unfollow (e.g., "natgeo") ctx: FastMCP context for progress reporting
Returns: Dict with url, status, and optional message.
Inputs: username (required). Returns: object
unlike_postUnlike PostADestructive
Unlike a post.
Navigates to the post and clicks the Unlike button.
Args: post_url: Instagram post URL (e.g., "https://www.instagram.com/p/ABC123/") ctx: FastMCP context for progress reporting
Returns: Dict with url, status, and optional message.
Inputs: post_url (required). Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 28 tools
Most tools target distinct resources (posts, reels, stories, highlights, insights, DMs), but get_user_profile can also fetch posts/reels via sections, overlapping with get_user_posts/get_user_reels. transcribe_reel and analyze_reel_with_gemini also have similar scopes, though descriptions clarify their different outputs.
The naming is mostly verb_noun (get_user_posts, follow_user, like_post) but has deviations: comment_on_post uses a preposition, analyze_reel_with_gemini includes a service modifier, and get_dm_conversation abbreviates 'direct message' inconsistently with get_direct_inbox.
With 28 tools, this exceeds the 3-15 well-scoped range. The broad Instagram domain justifies many features, but the count feels heavy; for instance, the four insights tools could be consolidated into one parameterized tool, and the transcription/analysis tools add redundancy.
The surface comprehensively covers viewing, searching, engaging (like/follow/comment/save), DMs, insights, and content details. Minor gaps exist: no tool to edit/delete posts or comments, and follower/following lists are only available as raw sections from get_user_profile rather than structured data.
Maintenance
Related MCP Connectors
Instagram for AI agents: publish, read comments and DMs, insights, and engage from your account.
Live public Instagram and TikTok data for AI assistants: profiles, posts, stories, followers.
Instagram MCP for DM and comment automation, AI Agent and content publishing via Meta's official API
LinkedIn, WhatsApp, Telegram and Instagram inboxes, profiles and outreach for your AI assistant.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage Instagram Business accounts by automating content publishing, scheduling posts, and analyzing performance metrics. Supports posts, stories, reels, and carousels with detailed audience insights and hashtag discovery.1-
- FlicenseBqualityDmaintenanceEnables AI agents to control Instagram accounts programmatically, supporting profile management, media interaction, direct messaging, and follower management.132-
- AlicenseAqualityDmaintenanceEnables AI agents to browse Instagram Reels by automating login, scrolling, and extracting structured metadata such as URL, creator, caption, likes, comments, and audio.72MIT

@inssist/mcpofficial
AlicenseAqualityBmaintenanceEnables AI agents to manage a personal Instagram account through a logged-in browser session, supporting actions like reading profiles and DMs, publishing posts, reels, and stories, scheduling content, and accessing insights and audience reports.60233 npm15MIT