instagram-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| IG_PORT | No | 3000 | |
| IG_APP_ID | No | Meta app id (token exchange/refresh, appsecret_proof, debug_token) | |
| IG_ENV_FILE | No | Env-file location override (default: XDG path) | |
| IG_AUTH_MODE | No | ig-login | fb-login; alias IG_AUTH_PATH (inferred fb-login when IG_APP_ID+IG_APP_SECRET are set) | |
| IG_HTTP_HOST | No | 127.0.0.1 | |
| IG_LOG_LEVEL | No | debug | info | warn | error | info |
| IG_MAX_ITEMS | No | fetchAll hard item cap | 200 |
| IG_TRANSPORT | No | stdio | http | stdio |
| IG_ACCOUNT_ID | No | IG professional-account ID (skip a lookup / disambiguate) | |
| IG_APP_SECRET | No | Meta app secret (secret) | |
| IG_HTTP_TOKEN | No | HTTP bearer token (secret; constant-time compare) | |
| IG_TIMEOUT_MS | No | Per-request timeout for Graph calls | 30000 |
| IG_WRITE_MODE | No | preview | apply (standing consent for writes) | preview |
| IG_PRETTY_JSON | No | Pretty-print JSON results | false |
| IG_ACCESS_TOKEN | Yes | The account's long-lived token, whichever auth path (secret) | |
| IG_PACKAGES_DENY | No | Packages to remove after profile resolution | |
| IG_TOOL_PACKAGES | No | core | reader | publisher | all, or an explicit list | core |
| IG_WRITE_JOURNAL | No | Applied-write audit log (default: $XDG_STATE_HOME/instagram-mcp-ai/writes.jsonl) | |
| IG_ACTIVE_PROFILE | No | Profile used when a tool call passes no account | default |
| IG_MAX_CONCURRENT | No | Per-host concurrency semaphore | 4 |
| IG_ALLOW_DESTRUCTIVE | No | Second gate for irreversible ops (delete_comment) | false |
| IG_PACKAGES_READONLY | No | Packages forced read-only | |
| IG_REFRESH_AFTER_DAYS | No | Path-A auto-refresh threshold | 45 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| instagram_get_accountA | Fetch the profile of the operated Instagram professional account: username, display name, biography, website, profile-picture URL, and follower / following / media counts. Read-only (GET /{ig-id}). Fields the account hides or that Meta omits are simply absent. Username, name, biography and website are account-controlled free text and are returned inside an untrusted content fence. |
| instagram_token_statusA | Report the active credential: auth path (A = ig-login / B = fb-login), whether a token is configured, the resolved account ID, and — on Path B, via debug_token — validity, granted scopes, absolute expiry and days-left (with a refresh warning as the threshold nears). Path A has no token-introspection endpoint, so its expiry is the one the login/refresh CLI recorded (IG_TOKEN_EXPIRES_AT), and unknown when none is recorded. Read-only. |
| instagram_list_commentsA | List the top-level comments on a media object, newest first, cursor-paginated, with threaded replies expanded inline under |
| instagram_get_commentA | Fetch a single comment by id, including its moderation state (hidden), parent/media context, and inline replies (repliesTruncated=true when Instagram returned only the first page of them). Comment text and usernames are returned as fenced, untrusted text. Fields Instagram does not disclose are omitted rather than nulled; a deleted comment returns an error. A reply Instagram returns without an id is left out, and omittedWithoutId plus note say how many were. |
| instagram_list_tagged_mediaA | List media the operated account has been TAGGED IN (the /tags edge), newest first, cursor-paginated. Note: tags are not @mentions — this lists posts where another account tagged this account in the media, not posts that @mention it (pull-based @mention discovery is a separate, Path-B-only capability). Captions and usernames are returned as fenced, untrusted text. An item Instagram returns without an id is left out, and omittedWithoutId plus note say how many were. |
| instagram_reply_to_commentA | Post a threaded reply under an existing comment (POST /{comment-id}/replies). Preview by default; re-run with apply:true (or set IG_WRITE_MODE=apply) to perform the reply. |
| instagram_create_commentA | Post a new top-level comment on a media object (POST /{media-id}/comments). Preview by default; re-run with apply:true (or set IG_WRITE_MODE=apply) to perform the comment. |
| instagram_hide_commentA | Hide a comment (POST /{comment-id}?hide=true) — reversible moderation, preferred over delete. Idempotent: hiding an already-hidden comment leaves it hidden. A comment the media owner made on its own media always stays visible: Instagram accepts the call but hides nothing. Preview by default; re-run with apply:true (or set IG_WRITE_MODE=apply) to perform the change. |
| instagram_unhide_commentA | Unhide a previously hidden comment (POST /{comment-id}?hide=false). Idempotent: unhiding a visible comment leaves it visible. Preview by default; re-run with apply:true (or set IG_WRITE_MODE=apply) to perform the change. |
| instagram_delete_commentA | Permanently delete a comment (DELETE /{comment-id}). IRREVERSIBLE — prefer instagram_hide_comment for moderation you may want to undo. Double-gated: it runs only with apply:true AND IG_ALLOW_DESTRUCTIVE=true; otherwise it stays a preview. |
| instagram_set_comments_enabledA | Toggle whether a media object accepts new comments (POST /{media-id}?comment_enabled=true|false). Idempotent: setting the value it already has is a no-op. Preview by default; re-run with apply:true (or set IG_WRITE_MODE=apply) to perform the change. |
| instagram_get_account_insightsA | Account-level insights for the operated Instagram professional account (GET /{ig-id}/insights). Uses the post-2025 views-centric metric set. Returns aggregated totals by default; time ranges are bounded by Meta's 90-day retention. A requested metric Instagram returned no data for is listed in missingMetrics and explained in notes; it is absent, not zero. Credentials are stripped from the paging URLs. |
| instagram_get_media_insightsA | Insights for a single media object (GET /{media-id}/insights). The valid metric set varies by media_product_type; supply media_product_type to have invalid combinations rejected client-side. Insights on media created before the account became professional, or on an expired story, may return empty or error. A requested metric Instagram returned no data for is listed in missingMetrics and explained in note; it is absent, not zero. |
| instagram_get_audience_demographicsA | Follower / engaged-audience demographics for the operated account (GET /{ig-id}/insights with metric_type=total_value). Requires a timeframe and an account with at least 100 followers; below that threshold Meta returns an error naming the 100-follower rule. A requested metric Instagram returned no data for is listed in missingMetrics and explained in note; it is absent, not zero. |
| instagram_get_online_followersA | Hourly distribution of when the account's followers are online (GET /{ig-id}/insights?metric=online_followers&period=lifetime). Data covers the last 30 days only. This metric is on the deprecation watch-list (present in the legacy reference, absent from the current docs tree) and may return a "metric no longer available" error in future API versions. When Instagram returns no online_followers data, missingMetrics and note say so rather than an empty metrics list standing alone. |
| instagram_list_mediaA | List the operated account's own media (feed posts, reels, albums), newest first, cursor-paginated. Stories are not included: Instagram serves live stories on a separate edge this tool does not read, so an empty or story-free page says nothing about active stories; read those with instagram_list_stories. Returns a single page by default; set fetchAll to aggregate pages up to the server's item cap (IG_MAX_ITEMS), in which case paging.truncated is true if more media remained. Captions are returned as fenced, untrusted text. Some fields (like_count, media_url) may be absent when Instagram does not disclose them. An item Instagram returns without an id is left out, and omittedWithoutId plus note say how many were. |
| instagram_list_storiesA | List the operated account's live stories — only those still inside Instagram's 24-hour window, newest first, cursor-paginated. An expired story is gone from this edge (and from instagram_get_media), so an empty result means no story is live right now, not that none was ever posted; feed posts, reels and albums are on instagram_list_media instead. Returns a single page by default; set fetchAll to aggregate pages up to the server's item cap (IG_MAX_ITEMS), in which case paging.truncated is true if more stories remained. Captions are returned as fenced, untrusted text. Stories carry no like_count or comments_count, and other fields (media_url) may be absent when Instagram does not disclose them. An item Instagram returns without an id is left out, and omittedWithoutId plus note say how many were. |
| instagram_get_mediaA | Fetch a single media object by id, including its carousel children (album items) under |
| instagram_create_media_containerA | Phase 1 of publishing: create a media container that Instagram ingests from a public HTTPS URL. This does NOT publish — poll instagram_get_container_status until FINISHED, then call instagram_publish_media with the returned container id. Omit media_type for a single feed image; set REELS/STORIES/CAROUSEL otherwise. Media format, size, and duration are validated by Instagram on fetch (the server never downloads the URL), so only URL form and caption limits are checked here. |
| instagram_get_container_statusA | Read a media container's processing state: status_code is IN_PROGRESS, FINISHED, ERROR, EXPIRED, or PUBLISHED. Publish only once it is FINISHED. IN_PROGRESS means keep polling (do not re-create); ERROR/EXPIRED means re-create the container. Read-only. |
| instagram_publish_mediaA | Phase 2 of publishing: publish a media container that has finished processing, returning the new media id. The container must be FINISHED (see instagram_get_container_status). This is never auto-retried — a repeated publish costs quota and posts a duplicate; retry only after confirming the previous call did not already publish. |
| instagram_get_publishing_limitA | Report the account's content-publishing usage against its rolling-window quota. quota_usage is how many posts have been published in the window (a carousel counts as one); quota_total is read live from Instagram (the documented number varies, so it is never hardcoded) and remaining is derived only when the total is known. When Instagram reports no usage the call fails rather than reporting 0. Read-only. |
| instagram_post_imageA | Publish a single feed image, or a 2–10 image carousel, in one call: create the container(s), wait for processing, then publish. Preview (the default) performs nothing. If processing exceeds the poll budget the result is status=in_progress with a resume_container_id — re-run with apply:true and resumeContainerId to finish (never create a new post, which would duplicate it). Image format, byte size, and dimensions are validated by Instagram on fetch, not here. |
| instagram_post_reelA | Publish a reel in one call: create the REELS container, wait for processing (reels can take a while), then publish. Preview performs nothing. If processing exceeds the poll budget the result is status=in_progress with a resume_container_id — re-run with apply:true and resumeContainerId to finish (never create a new post). Video duration, codec, and size are validated by Instagram on fetch, not here. |
| instagram_post_storyA | Publish a photo or video story in one call: create the STORIES container, wait for processing, then publish. Provide exactly one of imageUrl or videoUrl. Preview performs nothing. If processing exceeds the poll budget the result is status=in_progress with a resume_container_id — re-run with apply:true and resumeContainerId to finish (never create a new post). Stories expire after 24 hours. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 25 tools
Most tools target clearly distinct resources and actions (comments, insights, media, account, publishing). The main ambiguity is the dual publishing path: instagram_create_media_container + instagram_get_container_status + instagram_publish_media overlaps with instagram_post_image/reel/story, which wrap the same flow. Descriptions mitigate this, but an agent must still choose between low-level and high-level tools for the same outcome.
All 25 tools follow the instagram_ prefix with snake_case verb_noun naming (create_, get_, list_, post_, hide_, etc.). No mixed conventions; the pattern is predictable and readable throughout.
25 tools is on the heavy side for a single server; the domain is broad, but some capabilities (e.g., hide/unhide, multiple insight entry points) could potentially be consolidated. Each tool maps to a distinct API operation, so it's borderline rather than excessive.
The surface covers the core professional account lifecycle: profile, media publishing (image/reel/story/carousel), container status, publishing limit, media listing, comments moderation, and insights. Minor gaps include no tool for @mentions retrieval (explicitly noted as a separate capability) and no token refresh tool, but agents can work around these.