instagram-mcp

instagram-mcp

Enables agents to interact with Instagram through 49 tools for direct messages, feed, profiles, search, and persona discovery, with write actions disabled by default until explicitly enabled.

Category
Visit Server

README

instagram-mcp

An MCP server that exposes instagrapi — Instagram's private mobile API — as 49 tools an agent can call.

Reading is enabled out of the box. Anything that changes the account (posting, liking, following, commenting, DMing, deleting) is refused until you explicitly turn writes on.

Setup

cp .env.example .env

Then fill in .env with either a username and password, or a sessionid cookie copied from a browser where you are already signed in (DevTools → Application → Cookies → instagram.com). The sessionid route is less likely to trigger a login challenge.

If the account uses two-factor auth, paste the authenticator "setup key" into INSTAGRAM_TOTP_SEED and codes are generated for you. Otherwise, when Instagram asks for a code, call instagram_login with verification_code.

Verify the install without touching Instagram:

.venv/Scripts/python smoke_test.py

Registering the server

Already registered for this project in ../.mcp.json. To use it elsewhere:

claude mcp add instagram -- "C:\Users\osami\OneDrive\Documents\GitHub\ayham project 2\instagram-mcp\.venv\Scripts\instagram-mcp.exe"

The executable works from any directory — it always reads .env and writes session.json next to this README.

Enabling write actions

INSTAGRAM_ALLOW_WRITES=true

Restart the server afterwards. While this is false, write tools fail with an explanation rather than doing anything, so the read-only tools stay usable.

Tools

Group Tools
Writing DMs instagram_prepare_dm, instagram_find_person, instagram_build_style_profile, instagram_get_style_profile
Persona search instagram_search_start, instagram_search_recall, instagram_search_gate, instagram_search_expand, instagram_search_enrich, instagram_search_signals, instagram_search_shortlist, instagram_search_judge, instagram_search_results, instagram_search_list
Session instagram_login_status, instagram_login, instagram_account_info
Users instagram_get_user, instagram_search_users, instagram_get_followers, instagram_get_following, instagram_get_user_medias, instagram_get_user_stories
Posts instagram_get_media, instagram_get_media_comments, instagram_get_media_likers, instagram_download_media
Discovery instagram_get_timeline_feed, instagram_get_hashtag_info, instagram_get_hashtag_medias, instagram_search_locations, instagram_get_location_medias, instagram_search_posts, instagram_similar_accounts, instagram_account_about
Direct messages instagram_list_direct_threads, instagram_get_direct_thread, instagram_send_direct_message *
Engagement instagram_like_media *, instagram_unlike_media *, instagram_comment_media *, instagram_follow_user *, instagram_unfollow_user *
Publishing instagram_upload_photo *, instagram_upload_video *, instagram_upload_reel *, instagram_upload_album *, instagram_upload_story *, instagram_delete_media *

* requires INSTAGRAM_ALLOW_WRITES=true.

Users are addressed by username or user_id. Posts are addressed by a media argument that accepts a post URL, a shortcode, or a numeric media id.

Writing DMs in your own voice

This is what the server is mainly for. The problem with letting a model write your messages is that it writes correctly — punctuated, capitalised, polite — and everyone who knows you can tell instantly.

So instagram_build_style_profile measures how you actually write, from your own sent DMs: message length, capitalisation, terminal punctuation, emoji rate, which emoji, how you spell laughter, shorthand, language mixing, and whether you send bursts of short messages instead of one composed one. It records this globally and per contact, because nobody writes to their mother the way they write to their closest friend.

Run it once:

.venv/Scripts/python -c "import asyncio,json;from instagram_mcp.server import server;print(asyncio.run(server.call_tool('instagram_build_style_profile',{})).content[0].text[:400])"

After that, instagram_prepare_dm(person="sarah") returns — in one call — the recent conversation, the measured rules of your voice, and samples of how you write to that specific person. That single call is the whole interface for drafting; there is no need to stitch together the raw thread tools.

The profile is cached in style_profile.json and never sent to Instagram. Refresh it occasionally as your writing drifts.

The skill

~/.claude/skills/instagram-dm/SKILL.md drives the whole workflow in normal conversation — "reply to ahmed", "what should I say back to her", "check my ig messages". It handles finding the person, loading your voice, drafting, and holding the draft for your approval before anything sends.

Nothing sends without you seeing the exact words first.

Finding people who match a persona

The other thing the server is for. You describe someone — female, Amsterdam, fitness, mid-twenties, blonde — and get back ranked profiles with a confidence figure per attribute.

The hard part is that Instagram has no index for any of that. It indexes four things: handle and name text, hashtags, place geotags, and the follow graph. A persona is none of them. So every attribute is either compiled into a probe against one of those four, or inferred afterwards from what came back — which makes this a funnel that trades recall for precision, not a query.

recall   hundreds of candidates, mostly wrong, from many cheap probes
gate     free: drops private accounts and shops
expand   chaining off the best survivors — the highest-precision channel
enrich   ~3 API calls each. The expensive stage, so it runs on a ranked subset
signals  free: name, pronouns, geotag clusters, captions, category, birth years
judge    vision, on the shortlist only, from one contact sheet per candidate
results  ranked, with every piece of evidence attached

What makes it work is instagram_similar_accounts, which reads Instagram's own "Suggested for you" graph, built from co-follow behaviour it already models. Once you have one good match, chaining outward from it beats any keyword search by a wide margin — which is why the text and hashtag probes exist mainly to find that first foothold.

Location is the other thing worth knowing about. Instagram's city field is almost always null and occasionally wrong — one live probe returned a place called "Hollanda" carrying coordinates in Alexandria, Egypt — and place names fragment badly, with one city arriving as "Amsterdam, Netherlands", "Amsterdam Canal District", "Red Light District, Amsterdam" and "Amsterdam Canal River". Coordinates are always present, so geotags are clustered by position rather than by name: the variants merge, and the mislabelled entry excludes itself.

One thing the design originally leaned on turned out not to exist. Instagram generates alt text for photos ("may be an image of 1 person, blonde hair, standing"), which would have been free coarse vision on every post — but it is only exposed to the web client, and came back empty on all thirty-two posts of a live probe. Appearance therefore costs a real look at real images, and the ceilings reflect that rather than pretending otherwise.

A search is a job on disk, not a function call: a real one is several hundred API calls over ten or twenty minutes against an account Instagram will rate limit, so it runs stage by stage, survives a crash, and lets you fix a bad probe plan after twenty calls instead of three hundred.

instagram_search_start(persona={"gender": {"value": "female", "required": true},
                                "city": "Amsterdam", "niche": ["fitness"],
                                "age_band": [24, 32], "hair": "blonde"})
instagram_search_recall(search_id, probes={"hashtags": [{"tag": "fitgirlnl"}],
                                           "places":   [{"query": "Amsterdam gym"}],
                                           "accounts": [{"query": "amsterdam fitness"}]})
instagram_search_gate(search_id)      # free
instagram_search_expand(search_id)    # chain off the best
instagram_search_enrich(search_id, limit=40)
instagram_search_signals(search_id)   # free, and resolves most personas outright
instagram_search_results(search_id, limit=20)

Judging the pictures

Appearance is the one thing no free signal reaches, so it has to be looked at. instagram_search_shortlist(download_images=true) fetches each candidate's profile picture and recent thumbnails and composes them into a single numbered contact sheet, rather than handing over a dozen loose files.

That is not just tidier. It costs a twelfth of the attention, the numbers let a judgement cite the tile it came from, and it makes the hardest question answerable: which of these faces is the account holder? Feeds are full of friends, partners and clients, and a judgement made on the wrong face arrives sounding exactly as confident as a right one. Seeing every picture side by side turns that into something you can just look at - find the recurring face, check it against the tile marked avatar, which is the only picture certain to be them, and report the result as owner_face_confidence. A low value there weakens how firmly every vision reading is held, rather than pretending the person fits worse than they do.

A screenshot of the profile page would show much the same thing, but that page needs a logged-in browser to render at all, while these thumbnails have already been fetched and paid for.

Reading the confidence

Every attribute carries two numbers, never one: match is how well the evidence agrees, certainty is how far that evidence can be trusted. A model that reports a single "85%" has silently multiplied them and thrown away which one was weak.

Certainty is capped per attribute and per source, so the system cannot overclaim. Hair colour read off one avatar caps at 0.45; read off several daylight posts, 0.80. Country from Instagram's own "account based in" reaches 0.95. Height caps at 0.15 — a photograph carries no scale reference — and height, ethnicity and build are advisory: reported, never allowed to move a ranking, and rejected outright if you mark them required.

Unknown is not "no". An attribute nobody could observe lowers coverage, not match, and the ranking shrinks toward the prior by how little was verified — so a 0.9 scored on two observed attributes loses to a 0.75 on six. Anything labelled unverified scored well on too little to act on.

Results are for public accounts. Private ones are dropped at the gate because they cannot be verified. Nobody under 18 is ever returned: age is read from a stated birth year and from Instagram's join date, the bottom of that estimate decides, and the check runs when age first becomes readable and again at every exit - an audit found the original gate-only version protected nothing, because the gate runs before any age has been read. Every candidate keeps full provenance — which probes found them, and what each conclusion rests on. Search jobs live in searches/ and are gitignored: they hold other people's profiles and photos.

Staying unblocked

instagrapi drives the private API that the phone app uses. Instagram detects and blocks automated behaviour, and the account it blocks is yours — so:

  • Sessions are cached in session.json and reused. Logging in from scratch repeatedly is the single fastest way to get flagged. Keep that file.
  • Requests are spaced out by a random INSTAGRAM_DELAY_MININSTAGRAM_DELAY_MAX second pause. Raise it if Instagram starts asking you to wait.
  • Back off on warnings. "Please wait a few minutes" and "action blocked" mean stop, not retry. The tools say so in their error messages.
  • Bulk reads are risky. Pulling thousands of followers in one go looks nothing like a human using the app.
  • Use a throwaway or secondary account if you are experimenting.

Layout

File Contains
instagram_mcp/server.py The 49 tool definitions
instagram_mcp/persona.py What a persona is, and the confidence maths
instagram_mcp/signals.py Reading a persona off a profile, free and offline
instagram_mcp/names.py Given names to a gender prior, offline
instagram_mcp/discovery.py The recall channels candidates come from
instagram_mcp/search.py A persona search as a resumable job on disk
instagram_mcp/sheets.py Candidate pictures composed into one judgeable sheet
instagram_mcp/client.py Login, session persistence, the write guard, threading
instagram_mcp/serialize.py Compact JSON views of instagrapi's models
instagram_mcp/errors.py Instagram exceptions turned into actionable advice
smoke_test.py Offline check: schemas, guards, serializers

.env and session.json hold credentials and live auth cookies, and searches/ holds other people's profiles and photos. All three are gitignored — keep them that way.

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
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
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
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
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
E2B

E2B

Using MCP to run code via e2b.

Official
Featured