Social Harvest MCP
A local Instagram exporter for MCP clients that exports profile metadata, posts, reels, captions, and comments via a manually authenticated browser session.
README
Social Harvest MCP
Local-first Instagram export, inbox search, and messaging automation for MCP-compatible AI agents.
Social Harvest connects Claude Code, Codex, OpenCode, and other MCP clients to Instagram through a browser session that you authenticate manually. It can export profiles and media, synchronize a searchable local inbox, send messages and attachments, manage common conversation actions, and monitor inbox changes.
Social Harvest executes locally, and all persistent data stays on your machine. Instagram traffic originates from the local authenticated browser session.
[!IMPORTANT] Social Harvest uses Instagram’s authenticated web experience—not Meta OAuth, the official Instagram APIs, or webhooks. Instagram can change its endpoints and controls without notice. Use this project only with accounts and content you are authorized to access.
Contents
- Highlights
- How it works
- Quick start
- Common workflows
- MCP tools
- Connect an MCP client
- Storage and privacy
- Operational semantics
- Limitations
- Development
Highlights
| Area | What Social Harvest provides |
|---|---|
| Profile export | Profile metadata, posts, reels, captions, hashtags, mentions, reported metrics, bounded comments, and optional active stories |
| Media archive | Streaming downloads for profile images, posts, reels, carousels, stories, thumbnails, and DM attachments, with SHA-256 metadata |
| Local inbox | Primary, General, Requests, groups, and spam/hidden system buckets synchronized into SQLite |
| Search | FTS5 full-text message search with thread, folder, sender, and date filters |
| Messaging | Text, local images, video, audio, GIF/sticker search, group-thread targeting, and replies |
| Conversation actions | React or unreact, mark read or unread, and accept or decline message requests |
| Automation | Background jobs, adaptive inbox monitoring, durable events, and optional idempotency keys |
| Agent safety | Bounded reads, explicit side-effect annotations, fail-closed message targeting, and confirmed purge operations |
How it works
flowchart LR
A[MCP client] --> B[Social Harvest MCP]
C[CLI] --> D[Instagram services]
B --> D
D --> E[Shared Playwright session]
E --> F[Instagram Web]
D --> G[JSON and NDJSON exports]
D --> H[SQLite inbox and FTS5]
D --> I[Content-addressed media]
H --> J[Durable event queue]
Social Harvest launches Chromium with a persistent profile directory. You complete login and any verification yourself; later CLI and MCP operations reuse that authenticated profile. Concurrent operations share one browser context while receiving isolated pages.
Quick start
Requirements
- Node.js 20 or newer
- npm
- An Instagram account permitted to access the requested content
1. Install
git clone https://github.com/IotA-asce/social-harvest-mcp.git
cd social-harvest-mcp
npm install
npx playwright install chromium
2. Authenticate Instagram
npm run login
A visible browser opens. Log in, complete any verification, return to the terminal, and press Enter. The session is saved under .playwright-profile/instagram/, which is excluded from Git.
Social Harvest never asks for or stores your Instagram password directly.
3. Verify the environment
npm run doctor
The doctor checks the Node.js version, Playwright browser availability, profile directory, and authenticated Instagram session.
4. Start the MCP server
npm run mcp
For client-specific configuration, see Connect an MCP client.
Common workflows
Export a profile and its media
npm run scrape -- --username nasa --max-items 20 --max-comments 50
Media downloading is enabled by default. Normalized assets retain their remote URL and receive local path, MIME type, byte size, and SHA-256 metadata.
Exports use a deterministic run structure:
exports/
└── instagram/
└── nasa/
└── 2026-07-28T.../
├── manifest.json
├── profile.json
├── posts.ndjson
├── reels.ndjson
├── stories.ndjson
├── comments.ndjson
├── errors.ndjson
├── checkpoint.json
└── media/
Only files containing records are created.
To backfill or resume media for an existing export:
npm run download-media -- --export-dir ./exports/instagram/nasa/2026-07-28T...
To watch the browser and include active stories:
npm run scrape -- \
--username nasa \
--max-items 20 \
--include-stories true \
--headless false
[!WARNING] Opening an active story creates Instagram’s normal story-view receipt. The story owner may see the authenticated account in their viewer list. Story export is disabled by default.
Synchronize the inbox
npm run sync-inbox -- --folders primary,general,requests,system
npm run inbox-status
Inbox sync stores normalized threads, participants, messages, attachments, reactions, receipts, and pagination state in SQLite. Attachments are stored by content hash so identical files can be reused safely.
The first successful sync of each account and folder is a silent seed: existing history is stored without generating a flood of “new message” events. Later syncs produce deduplicated events for observed changes.
Folder names exposed by the CLI and MCP are normalized as follows:
| Folder | Meaning |
|---|---|
primary |
Instagram Primary, including the internal main label |
general |
Instagram General |
requests |
Pending message requests |
system |
Current spam and hidden-request buckets |
Capture one conversation snapshot
Use instagram_get_messages when an agent needs a portable, bounded snapshot for an exact username. The complete requested history is written locally while the MCP response returns only a small preview.
dm-exports/
└── instagram/
└── username/
└── 2026-07-28T.../
├── manifest.json
├── thread.json
├── messages.ndjson
└── media/
The configured message maximum is 1,000 records per snapshot.
[!NOTE] Opening or fetching a conversation may apply Instagram’s normal read-state behavior.
Send messages and attachments
instagram_send_message targets exactly one username or stable threadId. Thread targeting supports one-to-one conversations and groups.
Supported message parts:
- text up to 1,000 characters;
- up to 10 local JPEG, PNG, MP4, MOV, or supported audio files;
- one GIF/sticker selected with
gifQueryand optionalgifIndex; - an optional
replyToMessageIdafter the conversation has been synchronized.
Text, an attachment batch, and a GIF/sticker are submitted as separate message parts. Local .gif uploads are not accepted by the current Instagram composer; use gifQuery instead.
Example agent request:
Send “The draft is ready” and ./preview.png to the synchronized Instagram
thread 123456. Use clientRequestId "draft-preview-2026-07-28".
[!CAUTION] Messaging, reactions, read-state changes, and request decisions happen immediately. Social Harvest does not provide a draft-confirm step.
Monitor changes
instagram_start_inbox_monitor performs an immediate sync and then polls at a 60-second interval with ±15% jitter. Transient failures use exponential backoff capped at 15 minutes.
Authentication, checkpoint, or challenge failures pause the monitor and release its browser session hold. Monitors never restart automatically after a pause or process restart.
Use:
instagram_inbox_monitor_statusto inspect the current process-local state;instagram_stop_inbox_monitorto stop polling;instagram_list_inbox_eventsto read durable local events;instagram_ack_inbox_eventsto mark events acknowledged locally.
Acknowledgement does not change Instagram state and does not delete the event record.
MCP tools
Core, jobs, and exports
| Tool | Purpose | Side effects |
|---|---|---|
social_capabilities |
Describe supported platforms, record types, and limits | None |
instagram_start_export |
Start a bounded profile export job | Reads Instagram; writes local files |
social_job_status |
Read status for any retained background job | None |
social_export_status |
Compatibility alias for social_job_status |
None |
social_list_export_files |
List files in an export directory | None |
social_read_export |
Read bounded JSON or NDJSON records | None |
Inbox sync and search
| Tool | Purpose | Side effects |
|---|---|---|
instagram_start_inbox_sync |
Synchronize selected inbox folders | Reads Instagram; writes local data |
instagram_get_messages |
Capture a bounded exact-username snapshot and index it locally | May mark a conversation read; writes local data |
instagram_list_conversations |
List locally synchronized threads with filters | None |
instagram_get_conversation |
Read bounded local history, optionally refreshing first | Optional Instagram read; optional local writes |
instagram_search_messages |
Search local messages through FTS5 | None |
Immediate Instagram actions
| Tool | Purpose | Side effects |
|---|---|---|
instagram_send_message |
Send text, local attachments, GIF/sticker content, or a reply | Immediate Instagram write |
instagram_set_message_reaction |
Set or remove the viewer’s emoji reaction | Immediate Instagram write |
instagram_set_conversation_state |
Mark a thread read or unread | Immediate Instagram write |
instagram_handle_message_request |
Accept or decline a pending request | Immediate, potentially destructive Instagram write |
Monitoring, events, and retention
| Tool | Purpose | Side effects |
|---|---|---|
instagram_start_inbox_monitor |
Start process-local adaptive polling | Repeated Instagram reads and local writes |
instagram_inbox_monitor_status |
Read monitor state | None |
instagram_stop_inbox_monitor |
Stop polling and release the held session | Local process state only |
instagram_list_inbox_events |
Read durable local events | None |
instagram_ack_inbox_events |
Mark events acknowledged | Local database write only |
instagram_purge_inbox_data |
Delete scoped thread/message data and unreferenced media | Destructive local write; requires confirm=true |
Export and inbox-sync tools return immediately with a job ID. Poll them with social_job_status. Jobs are retained in memory for the lifetime of the MCP server process.
Connect an MCP client
Use the included templates and replace the example project path with an absolute path to your clone.
| Client | Configuration template |
|---|---|
| Claude Code | examples/claude-code.json |
| Codex | examples/codex.toml |
| OpenCode | examples/opencode.json |
A typical agent workflow looks like this:
Use Social Harvest to synchronize my Instagram inbox without downloading media.
Poll the job until it completes, list unread conversations, and summarize the
five most recent messages without sending or changing anything.
Storage and privacy
Social Harvest excludes its default private-data directories from Git:
| Path | Contents |
|---|---|
.playwright-profile/ |
Authenticated browser state and cookies |
exports/ |
Profile exports and public/session-visible media |
dm-exports/ |
Private bounded conversation snapshots |
inbox-data/ |
SQLite inbox, event queue, mutation audit, and private media |
The inbox layout is:
inbox-data/
├── instagram.sqlite
├── instagram.sqlite-wal
├── instagram.sqlite-shm
└── media/
└── ab/
└── abcdef...<original-extension>
On POSIX platforms, Social Harvest forces inbox directories to mode 0700 and the database/media files it creates to mode 0600.
[!WARNING] The database, media, exports, and browser profile are plaintext. Filesystem permissions and the security of your device and backups are the only protection in this release.
instagram_purge_inbox_data removes a selected thread or messages before a timestamp and garbage-collects only media no longer referenced by another stored message. Durable event and mutation-audit rows remain in the SQLite database. To erase all local inbox state, stop Social Harvest and remove inbox-data/ yourself.
See SECURITY.md for acceptable-use boundaries and handling guidance.
Operational semantics
Submitted is not delivered
A mutation result with status: "submitted" means Instagram accepted the browser request or composer interaction. It is not a delivery receipt, read receipt, or guarantee that Instagram will retain the message.
Retry safety
Mutation tools accept an optional clientRequestId. Once a request ID completes, retrying it returns the stored result without repeating the browser interaction. Reusing the same ID for a different operation is rejected.
Calls without a request ID are not idempotent and can repeat an action or send duplicate content.
Partial sends
When a call includes text, attachments, and a GIF/sticker, Instagram receives separate parts. A later part can fail after an earlier part was submitted. Social Harvest reports this as direct_send_partial; inspect submitted_parts before retrying.
Fail-closed message mapping
Replies and reactions map a synchronized message ID to one unique rendered text fingerprint. If the message is not rendered, contains no uniquely usable text, or matches multiple visible messages, Social Harvest refuses the action instead of risking interaction with the wrong message. Media-only and duplicate-text messages can therefore require a different target or a later adapter improvement.
Completeness
“Complete” means all records discoverable by the authenticated browser session within the configured bounds. It does not include deleted content, expired stories, inaccessible private content, platform-hidden content, or records Instagram did not load.
Absence is never treated as deletion. Only explicit tombstones or unsend state are stored as deleted messages. Media download failures do not roll back successfully synchronized message metadata.
Limitations
- Instagram’s private web endpoints, response shapes, numeric folder selectors, and rendered controls can change without notice.
- Comments and stories are best-effort and bounded.
- Inbox history is bounded by configured folder, thread, and message pagination limits.
- Disappearing or unsupported message types may retain metadata without usable content.
- Story collection creates a normal story-view receipt.
- Reading conversations can create normal read-state behavior.
- The project does not solve CAPTCHAs, bypass access controls, rotate proxies, or attempt anti-bot evasion.
- Eligible professional accounts should prefer an official Instagram API when its permissions and data coverage satisfy the use case.
Development
Run the complete test suite and syntax checks:
npm test
npm run check
Useful diagnostics:
npm run doctor
npm run inbox-status
npm audit
The project uses Node.js ESM, Playwright, the Model Context Protocol TypeScript SDK, Zod, better-sqlite3, SQLite WAL/FTS5, and the built-in Node.js test runner.
License
Released under the MIT License.
Recommended Servers
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.
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.
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.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
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.
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.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
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.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.