Instagram Relationship MCP
# Instagram Relationship MCP
A local MCP server that answers one question for an Instagram account you control:
> Which accounts do I follow that do not follow me back?
The server is read-only toward Instagram. It can sign in through a dedicated Playwright Chromium profile, read relationship data, cache derived results locally, and disconnect. It cannot follow, unfollow, block, like, comment, or send messages.
> [!IMPORTANT]
> This project uses Instagram's private, unsupported Web API rather than Meta's supported Instagram Graph API. Instagram can change these endpoints without notice. Unknown response shapes fail closed with `PRIVATE_API_CHANGED`, and rate limits stop the scan. Use only an account you control and comply with Instagram's terms and policies.
## How it works
```text
Instagram
↑ dedicated authenticated browser profile
Playwright persistent Chromium context
↑ same-origin private Web API requests
InstagramWebAdapter
↑ normalized relationship records only
RelationshipService + local derived cache
↑ business-level MCP results
ChatGPT Work, Codex, or another MCP client
```
Instagram credentials are entered only in the visible `instagram.com` browser window. Passwords, cookies, CSRF tokens, request headers, and browser storage are never accepted as MCP arguments or returned in tool results.
## Requirements
- Windows 10/11 for the commands shown below. The Node server itself is portable, but the Secure Tunnel walkthrough is Windows/PowerShell focused.
- [Git](https://git-scm.com/downloads)
- [Node.js 24 or newer](https://nodejs.org/)
- A graphical desktop session for Instagram login, 2FA, and challenges
- An Instagram account you control
- One connection option:
- ChatGPT Work with OpenAI Secure MCP Tunnel, or
- a local MCP client that can launch a stdio server
This package is installed from source; it is not published to npm.
## Install
Open PowerShell and run:
```powershell
git clone https://github.com/rankes96-dev/instagram-relationship-mcp.git
Set-Location instagram-relationship-mcp
npm ci
npm run browser:install
npm run build
npm run check
```
`npm ci` installs the exact versions in `package-lock.json`. Playwright is pinned because every Playwright release expects matching browser binaries. Run `npm run browser:install` again after changing or updating Playwright.
## Choose how to connect
### Option A: ChatGPT Work through Secure MCP Tunnel
Use this path to keep the MCP server on your computer while making it available to a ChatGPT developer-mode app. Secure MCP Tunnel uses an outbound HTTPS connection; it does not require a public MCP port or inbound firewall rule.
#### 1. Prepare OpenAI access
1. Create or inspect a tunnel in [OpenAI Platform tunnel settings](https://platform.openai.com/settings/organization/tunnels).
2. Associate the tunnel with both the Platform organization that owns it and the target ChatGPT workspace. A tunnel associated only with a Platform organization may not appear in ChatGPT Work.
3. Ensure the operator has the correct Platform permissions:
- **Tunnels Read + Manage** to create or edit tunnels.
- **Tunnels Read + Use** to run `tunnel-client` and select the tunnel in ChatGPT.
4. Ensure Developer mode is allowed by the ChatGPT workspace policy. In ChatGPT, enable it under **Settings → Security and login → Developer mode**.
5. Create a dedicated runtime API key for a user or service account whose Platform organization role has **Tunnels Read + Use**. Restrict the key where supported. Do not use an admin key for the long-running tunnel process.
See OpenAI's [Secure MCP Tunnel guide](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) for the current permission and workspace-association model.
#### 2. Install `tunnel-client`
Download the correct Windows archive from [the latest official tunnel-client release](https://github.com/openai/tunnel-client/releases/latest), extract it, and place `tunnel-client.exe` on `PATH`. Do not hard-code a release URL in an operational runbook.
Verify the binary:
```powershell
tunnel-client --version
tunnel-client help quickstart
```
#### 3. Create and validate a local stdio profile
From the repository directory, set the runtime key only in the current PowerShell process and initialize a named profile. Replace the placeholders; never commit the real key or tunnel ID.
```powershell
$env:CONTROL_PLANE_API_KEY = "<restricted-runtime-api-key>"
$repo = (Resolve-Path .).Path
$mcpCommand = "node `"$repo\scripts\tunnel-mcp-entry.mjs`""
tunnel-client init `
--sample sample_mcp_stdio_local `
--profile instagram-relationships `
--tunnel-id "tunnel_<32-lowercase-hex-characters>" `
--mcp-command $mcpCommand
tunnel-client doctor --profile instagram-relationships --explain
tunnel-client run --profile instagram-relationships
```
Keep `tunnel-client run` running while ChatGPT uses the app. Its local `/ui`, `/healthz`, `/readyz`, and `/metrics` surfaces show runtime health; the startup output prints the loopback URL.
The tunnel profile must launch [`scripts/tunnel-mcp-entry.mjs`](scripts/tunnel-mcp-entry.mjs), not `dist/index.js` directly. The wrapper removes OpenAI control-plane keys before importing the Instagram MCP server, so the Instagram subprocess does not inherit them. Prefer `CONTROL_PLANE_API_KEY`; `OPENAI_API_KEY` is only a tunnel-client fallback.
The Instagram server does not load `.env` automatically. Never put plaintext API keys in a tunnel profile or YAML file; use an `env:` reference, keep generated profiles outside Git (or ignored), and do not expose keys in command arguments or logs.
#### 4. Add the tunnel in ChatGPT Work
1. Open [ChatGPT Plugins](https://chatgpt.com/#settings/Connectors).
2. Select the plus button and enter a user-facing name and description.
3. Under **Connection**, choose **Tunnel**.
4. Select the tunnel or paste its `tunnel_id`.
5. Create the connection and review the four discovered tools.
6. Start a new chat and add the connection from the tools menu.
After changing tool names, descriptions, schemas, or annotations, restart the tunnel-backed MCP server, open the connection in ChatGPT Plugins, select **Refresh**, and test in a new chat.
### Option B: local stdio MCP client
For Codex or another local MCP client, build the project and configure the client with absolute paths:
```json
{
"mcpServers": {
"instagram-relationships": {
"command": "node",
"args": [
"C:\\absolute\\path\\to\\instagram-relationship-mcp\\dist\\index.js"
]
}
}
}
```
A complete example is available at [`docs/mcp-config.example.json`](docs/mcp-config.example.json).
The transport is stdio. The server does not bind a port. Standard output is reserved for MCP JSON-RPC; sanitized operational logs go only to standard error.
## First login and first scan
Use this sequence in ChatGPT or your MCP client:
1. Call `instagram_connect`.
2. If it returns `LOGIN_PENDING`, complete Instagram login, 2FA, or a challenge only in the visible `instagram.com` Chromium window.
3. Call `instagram_connection_status` until it returns `CONNECTED`. `CHALLENGE_REQUIRED` means the visible window still needs action.
4. Call `instagram_get_non_followers`.
Never enter an Instagram password, cookie, session ID, CSRF token, or 2FA code into ChatGPT or an MCP tool argument. If a saved session is still valid, `instagram_connect` may return `connected: true` without opening a window.
## MCP tools
### `instagram_connect`
Reuses a verified saved session or opens one visible Chromium window and returns promptly with `LOGIN_PENDING`. Repeated calls while login is pending reuse the same attempt rather than opening another browser.
### `instagram_connection_status`
Performs one bounded session check. It can return `CONNECTED`, `LOGIN_PENDING`, `NOT_CONNECTED`, `SESSION_EXPIRED`, `LOGIN_REQUIRED`, or `CHALLENGE_REQUIRED`. Once interactive login succeeds, it confirms that the saved profile also works in the background before reporting `CONNECTED`.
### `instagram_get_non_followers`
Fetches Following pages, deduplicates numeric Instagram user IDs, checks relationship statuses in bounded batches, and returns only users with explicit `followed_by === false` status.
Inputs:
- `refresh` (optional boolean): ignore a fresh derived cache and scan again.
- `limit` (optional integer, 1–100): number of users to return; default 50.
- `cursor` (optional string): opaque signed cursor returned by the previous page. Do not combine it with `refresh: true`.
The result states whether it came from `live` or `cache`, when it was computed, whether the scan is complete, and how many statuses remain `UNKNOWN`. A partial fetch is never presented as a complete result.
### `instagram_disconnect`
Closes Chromium, securely removes the dedicated browser profile, and purges local account metadata and cached relationship data. The next Instagram operation requires manual login again.
## Configuration
All settings are optional. Supply them through the MCP client's environment or the operating system. The server does **not** load `.env` automatically.
| Variable | Default | Allowed values |
| --- | ---: | --- |
| `INSTAGRAM_MCP_DATA_DIR` | `%LOCALAPPDATA%\InstagramRelationshipMcp` | Absolute local directory |
| `INSTAGRAM_MCP_CACHE_TTL_MINUTES` | `15` | `1`–`1440` |
| `INSTAGRAM_MCP_FOLLOWING_PAGE_SIZE` | `12` | `1`–`200` |
| `INSTAGRAM_MCP_RELATIONSHIP_BATCH_SIZE` | `12` | `1`–`200` |
| `INSTAGRAM_MCP_RESULT_PAGE_SIZE` | `50` | `1`–`100` |
| `INSTAGRAM_MCP_LOGIN_TIMEOUT_SECONDS` | `300` | `30`–`1800` |
| `INSTAGRAM_MCP_NAVIGATION_TIMEOUT_SECONDS` | `45` | `5`–`180` |
| `INSTAGRAM_MCP_REQUEST_TIMEOUT_SECONDS` | `30` | `5`–`120` |
| `INSTAGRAM_MCP_INTER_REQUEST_DELAY_MS` | `250` | `0`–`10000` |
| `INSTAGRAM_MCP_NETWORK_RETRIES` | `1` | `0`–`3` |
| `INSTAGRAM_MCP_MAX_FOLLOWING_PAGES` | `10000` | `1`–`100000` |
| `INSTAGRAM_MCP_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error`, `silent` |
See [`.env.example`](.env.example) as an environment-variable reference only; copying it to `.env` has no effect. Put values in the MCP client's `env` block or set them in the process that launches the server, for example:
```powershell
$env:INSTAGRAM_MCP_LOG_LEVEL = "debug"
npm start
```
Batch sizes are runtime settings, not claimed Instagram API limits.
## Local data and security
By default, data is stored outside the repository:
```text
%LOCALAPPDATA%\InstagramRelationshipMcp\
├── instagram-profile\
└── instagram.db
```
The dedicated browser profile contains Instagram authentication state. The SQLite database stores only account metadata, normalized users, and derived relationship scans. Do not point `INSTAGRAM_MCP_DATA_DIR` at your normal Chrome profile, this repository, a synchronized folder, or a shared directory.
Never commit or share:
- `.env` or runtime API keys
- tunnel IDs or tunnel-client profiles
- the Playwright browser profile
- SQLite database, WAL, or SHM files
- cookies, headers, HAR files, screenshots, or network dumps
- tunnel or MCP logs containing private account context
The included `.gitignore` excludes these local artifacts, but ignore rules are not an access-control boundary. See [`SECURITY.md`](SECURITY.md) for the threat model and safe diagnostics policy.
## Update
Stop the foreground tunnel process, then run:
```powershell
git pull
npm ci
npm run browser:install
npm run check
tunnel-client run --profile instagram-relationships
```
The browser profile and database live outside the repository and survive source updates unless `instagram_disconnect` is called. Refresh the ChatGPT developer-mode connection after MCP metadata changes.
## Development
```powershell
npm run build
npm test
npm run smoke:mcp
npm run check
npm run inspect
```
- `npm run check` builds the project, runs every test, and runs the stdio smoke test.
- `npm run smoke:mcp` builds and verifies both legacy stdio and MCP 2026-07-28 without opening a browser.
- Run `npm run build` before `npm run inspect`; the Inspector uses `dist/index.js`.
- Browser and live-account scenarios require manual testing with an account you control. Automated tests use sanitized synthetic fixtures and contain no production credentials or usernames.
## Troubleshooting
### The tunnel is not visible in ChatGPT
Confirm that the tunnel is associated with the target ChatGPT workspace—not only a Platform organization—and that the app creator has Tunnels Read + Use. Developer mode must also be permitted by workspace policy.
### ChatGPT cannot discover or call tools
Keep `tunnel-client run --profile instagram-relationships` running, then execute:
```powershell
tunnel-client doctor --profile instagram-relationships --explain
```
Check the loopback `/ui` and ensure health, readiness, and the main MCP channel are green. If metadata is stale, refresh the connection in ChatGPT Plugins and start a new chat.
### No Instagram window opens
Run `npm run browser:install`, confirm a graphical desktop session is active, and call `instagram_connect` again. A valid saved session may intentionally connect without a visible window.
### The result remains `LOGIN_PENDING`
Finish the visible browser flow, then call `instagram_connection_status`. Do not repeatedly start new connections. If the browser was closed, call `instagram_connect` once to start a fresh attempt.
### Instagram reports a rate limit or private API change
Stop and wait after `INSTAGRAM_RATE_LIMITED`; do not retry aggressively. `PRIVATE_API_CHANGED` means the expected private response contract no longer matches and requires a reviewed code update, not a guessed parser fallback.
## Licensing and reference boundary
No software license is currently included. Source availability on GitHub does not grant an open-source reuse license.
[IG-Cleaner](https://github.com/denoyey/IG-Cleaner) was reviewed only for the general idea of manual local login and a dedicated persistent browser profile. This is a clean-room implementation and does not copy IG-Cleaner's Selenium, DOM-scrolling, username-comparison, or write-action design.
TDQS
Scored across 4 tools
Each tool has a distinct role: connect initiates login, status checks login state, get_non_followers performs the main analysis, and disconnect cleans up. There is no overlap or ambiguity between them.
All tools share the 'instagram_' prefix, but the verb usage is slightly inconsistent: 'connect' and 'disconnect' are bare verbs, 'connection_status' is a noun phrase, and 'get_non_followers' uses a get_ prefix. Overall the pattern is readable and predictable.
With 4 tools, the server is well-scoped for its specific purpose of managing Instagram relationships. Each tool is necessary and none feel redundant.
The set covers the full lifecycle from connect to disconnect, with the core non-follower analysis in between. Minor gaps exist, such as no explicit refresh action or follow/unfollow operations, but these are outside the stated purpose.