Skip to main content
Glama

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

--cdp flag

Live Brave browser session

Users who don't want cookie extraction

Docker

Dockerfile

Pre-exported portable auth (tar archive)

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:

  1. Tool A navigates to instagram.com/natgeo/posts/

  2. Tool B navigates to instagram.com/direct/inbox/

  3. Instagram's SPA state corrupts — neither tool gets valid data

  4. 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_PROGRESS

The auth state machine runs independently:

UNKNOWN → CHECKING → READY
   ↓ (expired)          ↓ (expired)
INVALID → RELOGIN_IN_PROGRESS → READY

Key design decisions:

  • Background-first browser setup: patchright Chromium downloads in a background task, not at startup. Tools become available immediately; if the browser isn't ready, they raise BrowserSetupInProgressError with 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 exception

Each 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-mcp

On 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)

uvx instagram-scraper-mcp --cdp

Docker

See docs/docker-hub.md

Gemini Analysis

GEMINI_API_KEY=xxx uvx instagram-scraper-mcp

Debug Mode

uvx instagram-scraper-mcp --log-level DEBUG --no-headless


Tool Suite

Category

Tools

Capabilities

Profile & Content

get_user_profile, get_user_posts, get_user_reels, get_user_stories, get_user_highlights, get_post_details

Posts, reels, stories, highlights, followers/following lists

Search & Discovery

search_users, search_hashtags, search_locations, get_hashtag_posts, get_location_posts

User, hashtag, and location search

Messaging & Actions

get_direct_inbox, get_dm_conversation, send_dm, follow_user, unfollow_user, like_post, unlike_post, save_post, comment_on_post

Full DM and engagement suite

Business Insights

get_business_insights, get_audience_insights, get_content_insights, get_activity_insights

Reach, impressions, demographics (Business/Creator accounts only)

AI Analysis

analyze_reel_with_gemini, bulk_analyze_reels_with_gemini

Multimodal reel transcription and analysis via Gemini 2.0 Flash

Transcription

transcribe_user_reels, transcribe_reel

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 tools
analyze_reel_with_geminiAnalyze Reel with GeminiA
Read-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 GeminiA
Read-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 SessionA
Destructive

Close the current Instagram browser session and clean up resources.

Inputs: no named parameters. Returns: object

comment_on_postComment on PostA
Destructive

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 UserA
Destructive

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 InsightsB
Read-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 InsightsA
Read-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 InsightsA
Read-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 InsightsA
Read-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 InboxA
Read-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 ConversationA
Read-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 PostsA
Read-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 PostsA
Read-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 DetailsA
Read-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 HighlightsA
Read-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 PostsA
Read-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 ProfileA
Read-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 ReelsA
Read-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 StoriesA
Read-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 PostA
Destructive

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 PostB
Destructive

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 LocationsA
Read-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 UsersA
Read-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 DMA
Destructive

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 ReelA
Read-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 ReelsA
Read-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 UserA
Destructive

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 PostA
Destructive

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

    A3.6/5.0

    Scored across 28 tools

    Disambiguation4/5

    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.

    Naming Consistency4/5

    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.

    Tool Count2/5

    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.

    Completeness4/5

    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

    ActivityNo data
    ResponsivenessSyncing

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables 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
      -
    • A
      license
      A
      quality
      B
      maintenance
      Enables 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.
      60
      233 npm
      15
      MIT