Metadata-Version: 2.3
Name: threads-mcp
Version: 0.1.3
Summary: MCP server for Threads: search, feed, profiles, replies and DMs through your own logged-in browser
Keywords: mcp,threads,meta,model-context-protocol,claude,social-media,search
Author: sunnycho100
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
Requires-Dist: mcp[cli]>=2.3.0
Requires-Dist: patchright>=1.63.0
Requires-Python: >=3.12
Project-URL: Homepage, https://github.com/sunnycho100/threads-mcp
Project-URL: Issues, https://github.com/sunnycho100/threads-mcp/issues
Description-Content-Type: text/markdown

# Threads MCP

<!-- mcp-name: io.github.sunnycho100/threads-mcp -->

[![CI](https://github.com/sunnycho100/threads-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/sunnycho100/threads-mcp/actions/workflows/ci.yml)

An MCP server that gives Claude (or any MCP client) access to Threads through your own logged-in browser. Search posts by keyword and get the top results with their likes, replies and reposts, read your feed, profiles and replies, work with your DMs, and draft posts, all from your AI assistant.

Everything runs on your machine. There is no Meta developer app, no API key, and no third-party server: the tool drives a dedicated Chrome profile that only you log in to.

> Independent open-source project, not affiliated with Meta or Threads. Threads is a trademark of Meta Platforms, used here only to identify the service this software works with. Automated use of Threads may conflict with its terms; use it at a low, personal volume and at your own risk.

## What you can do

| Tool | What it does |
|---|---|
| `search_posts` | Keyword search returning the top posts, ranked by engagement. Top or recent, minimum likes, last N days, and export to JSON, CSV or Markdown. |
| `keyword_insights` | Analyze a keyword: engagement totals and medians, top authors, best posting hours, media mix, top posts. |
| `search_profiles` | Find accounts by keyword (name or bio), by relevance or follower count. |
| `get_trending` | Trending topics on Threads with their AI summary and post counts. |
| `get_feed` | Posts from your home feed. |
| `get_profile` | A user's profile: name, bio, followers, links, verified badge. |
| `get_user_posts` | A user's most recent posts. |
| `get_post` | One post with its replies. |
| `list_conversations` | Your DM inbox: who, last message, time, unread. |
| `get_conversation` | The recent messages in a DM thread, including shared posts. |
| `send_message` | Send a DM, after you confirm the preview. |
| `create_post` | Publish a thread (up to 500 characters), after you confirm the preview. |
| `reply_to_post` | Reply to a post, after you confirm the preview. |
| `session_status` | Which account the browser is logged in as. |
| `close_session` | Close the browser without logging out. |

Post-returning tools (`search_posts`, `get_feed`, `get_user_posts`, `get_post` replies) default to `format="compact"`: text trimmed to 280 characters and ids and media links left out, about a third smaller than `format="full"`, which returns every field. Exports always contain full data.

With `format="full"`, every post comes back with the same fields: `url`, `author` (username, name, verified), `text`, `created_at` (UTC), `likes`, `replies`, `reposts`, `quotes`, `score`, `media_type`, `media_urls`, `is_reply`. The `score` is `likes + 2 x replies + 3 x reposts + 2 x quotes`.

## Requirements

- macOS, Linux or Windows
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
- Google Chrome (recommended). Without it, run `uvx --from git+https://github.com/sunnycho100/threads-mcp patchright install chromium` once and the server uses Chromium instead.
- A Threads account

## Setup

### 1. Log in once

```bash
uvx threads-mcp@latest --login
```

`@latest` installs from PyPI and picks up fixes automatically, which matters because Threads changes its pages from time to time. To run the newest unreleased code instead, use `uvx --from git+https://github.com/sunnycho100/threads-mcp threads-mcp` wherever this README says `uvx threads-mcp@latest`.

A Chrome window opens on threads.com. Log in (Instagram login works) and the window closes by itself. The session is saved in `~/.threads-mcp/profile`, separate from your everyday Chrome.

Check it:

```bash
uvx threads-mcp@latest --status
```

```
logged in as @your_username
profile: /Users/you/.threads-mcp/profile
```

### 2. Add it to your MCP client

**Claude Code**

```bash
claude mcp add threads -- uvx threads-mcp@latest
```

Or, inside a clone of this repo, the included `.mcp.json` registers the server automatically.

**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows)

```json
{
  "mcpServers": {
    "threads": {
      "command": "uvx",
      "args": ["threads-mcp@latest"],
      "env": { "UV_HTTP_TIMEOUT": "300" }
    }
  }
}
```

**Cursor, Windsurf and other clients** use the same `command` and `args` in their MCP settings.

**Claude Desktop, one-click bundle**

```bash
git clone https://github.com/sunnycho100/threads-mcp && cd threads-mcp
npx @anthropic-ai/mcpb pack . threads-mcp.mcpb
```

Open `threads-mcp.mcpb` with Claude Desktop (double-click, or Settings > Extensions > Install Extension). The install dialog offers a read-only toggle. Run `--login` once in a terminal first, as above.

**Docker**

Log in from inside the container with the built-in viewer:

```bash
docker build -t threads-mcp https://github.com/sunnycho100/threads-mcp.git
docker run --rm -it -p 127.0.0.1:6080:6080 -v ~/.threads-mcp-docker:/data threads-mcp --login-viewer
```

Open the printed `http://localhost:6080/vnc.html`, enter the one-time password it prints, and log in to Threads in the window that appears. The container exits once the session is saved. The viewer listens on localhost only.

Or log in on your computer and move the session in:

```bash
uvx --from git+https://github.com/sunnycho100/threads-mcp threads-mcp --export-session ~/threads-session.json
docker run --rm -v ~/.threads-mcp-docker:/data -v ~/threads-session.json:/session.json:ro threads-mcp --import-session /session.json
rm ~/threads-session.json
```

The exported file holds your login cookies and is created readable only by you; delete it after importing. Then point your client at the container:

```json
{
  "mcpServers": {
    "threads": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-v", "/Users/you/.threads-mcp-docker:/data", "threads-mcp"]
    }
  }
}
```

Restart the client, then ask something like "search Threads for posts about rust async and show me the top 10".

## Examples

Ask in plain language; the assistant picks the tool. The calls below show what it sends.

**Top posts for a keyword**

```
search_posts(query="rust async", limit=10)
```

```json
{
  "query": "rust async", "sort": "top", "count": 10,
  "results": [
    {"url": "https://www.threads.com/@someone/post/DZ6Wq...", "author": {"username": "someone", "full_name": "Some One", "verified": false},
     "text": "...", "created_at": "2026-06-23T02:33:30Z", "likes": 2692, "replies": 334, "reposts": 377, "quotes": 12, "score": 4848,
     "media_type": "image", "media_urls": ["..."], "is_reply": false}
  ]
}
```

**Only popular posts from the last week, saved to a spreadsheet**

```
search_posts(query="indie hacker", min_likes=100, since_days=7, limit=30, export="csv")
```

The response includes `export_path`, for example `~/.threads-mcp/exports/search-indie-hacker-20261011-091500.csv`. CSV and Markdown files are UTF-8, so Korean, Japanese and emoji come through intact.

**Newest posts first**

```
search_posts(query="rust async", sort="recent", limit=20)
```

**What works for a topic**

```
keyword_insights(query="vibe coding", limit=50)
```

Returns engagement medians, the authors with the most total engagement, the hours (your local time) when the best-performing posts went up, and the media mix.

**Who to follow on a topic**

```
search_profiles(query="ios developer", sort="followers", limit=10)
```

**A post and its replies**

```
get_post(url="https://www.threads.com/@someone/post/DZ6WqOxlC-t", reply_limit=20)
```

**DMs**

```
list_conversations()
get_conversation(username="friend_name", limit=20)
```

**Writing, with a confirmation step**

```
create_post(text="Shipping a new side project today")
```

```json
{"confirm_required": true, "preview": {"action": "create_post", "text": "Shipping a new side project today", "length": 33},
 "note": "Show this preview to the user and call again with confirm=true to proceed."}
```

Nothing is posted until the assistant shows you the preview and calls again with `confirm=true`. The same applies to `send_message` and `reply_to_post`.

Usernames are accepted as `name`, `@name` or a profile URL. Post links work with `threads.com` or `threads.net`.

## How it works

Threads' web app ships its data as JSON: posts and profiles are embedded in each page, and more arrive through the page's own GraphQL requests as you scroll. The server opens the page in your logged-in profile, collects that JSON, and picks out anything shaped like a post or a user, wherever it sits. It does not replay Meta's internal API calls; it reads what the page itself loads, the way you would by scrolling.

DMs are loaded over a live connection rather than page JSON, so the DM tools read the rendered conversation instead. Posting and replying use the normal composer.

```
MCP client --stdio--> server.py      tools, confirmations, error messages
                        service.py   search, feed, profiles, posts
                        session.py   one Chrome profile, one page, one task at a time
                        extract.py   finds posts and users in the page JSON
                        ranking.py   score, sort, filter, insights, export
                        dm.py        inbox and conversations
                        compose.py   new posts and replies
```

## Safety

- **Writes need confirmation.** `send_message`, `create_post` and `reply_to_post` return a preview and do nothing until called again with `confirm=true`.
- **Human pace.** Scrolls and typing use randomized delays, and only one browser task runs at a time, even when the client calls tools in parallel.
- **Low volume.** Every list is capped at 100 items, and a call is a few page loads, not a crawl.
- **Account checks.** If Threads shows a checkpoint or account warning, the server stops all browser activity and tells you to resolve it in the app.
- **No social actions.** There are no tools for likes, follows, reposts or deletes.
- **Local data.** The browser profile and exports live in `~/.threads-mcp`. Nothing is sent anywhere except threads.com.

## Command line

```
threads-mcp               run the MCP server over stdio (what clients launch)
threads-mcp --login       open a browser window and sign in
threads-mcp --status      show the logged-in account (exit code 1 if not logged in)
threads-mcp --logout      delete the saved browser profile
threads-mcp --export-session FILE   save the login cookies (owner-only file) for Docker or another machine
threads-mcp --import-session FILE   load cookies saved by --export-session
threads-mcp --no-headless run the server with the browser window visible
threads-mcp --version
```

Options for the server and login:

| Flag | Default | Meaning |
|---|---|---|
| `--transport {stdio,streamable-http}` | `stdio` | Serve over HTTP instead of stdio. |
| `--host`, `--port` | `127.0.0.1`, `8000` | HTTP address; the endpoint is `/mcp`. |
| `--user-data-dir PATH` | `~/.threads-mcp/profile` | Browser profile to use. |
| `--login-timeout SECONDS` | `900` | How long `--login` waits for you to sign in. |
| `--browser-idle-timeout SECONDS` | `600` | Close an idle browser after this long. |
| `--viewport WxH` | `1280x900` | Browser window size. |
| `--proxy-server URL` | none | Route the browser through a proxy (`http://`, `socks5://`). |
| `--slow-mo MS` | `0` | Delay between browser actions, for debugging. |
| `--chrome-path PATH` | Google Chrome | Use a specific Chrome or Chromium executable. |

Environment variables:

| Variable | Default | Meaning |
|---|---|---|
| `THREADS_MCP_HOME` | `~/.threads-mcp` | Where the profile and exports live. |
| `THREADS_MCP_HEADLESS` | `1` | Set to `0` to show the browser window. |
| `THREADS_MCP_READONLY` | `0` | Set to `1` to refuse confirmed writes; previews still work. Handy for demos and testing. |

The browser closes itself after 10 idle minutes and reopens on the next call.

## Troubleshooting

<details>
<summary><b>"Not logged in to Threads. Run threads-mcp --login"</b></summary>

The saved session expired or was never created. Run `threads-mcp --login` (with the same `uvx --from ...` prefix you use in your client config), log in, then retry.
</details>

<details>
<summary><b>"The browser profile is in use by another threads-mcp process"</b></summary>

Only one process can use the profile at a time. Another MCP client, a second Claude window, or a `--login` window has it open. Close that, or call `close_session` from the client that holds it. Idle servers release it after 10 minutes.
</details>

<details>
<summary><b>Replies fail with "private profiles can only reply to their followers"</b></summary>

That is a Threads rule. If your profile is private, you can only reply to people who follow you. Reply to a follower's post, or switch your profile to public in the Threads app.
</details>

<details>
<summary><b>"Threads page error" or empty results</b></summary>

Threads may have changed its page layout. Run `threads-mcp --no-headless`, call the tool again and watch the window, then open an issue with the tool name and what you saw. Updating (`uvx --refresh ...`) picks up fixes.
</details>

<details>
<summary><b>Fewer results than the limit</b></summary>

Threads loads about 7 more posts per scroll after the first 20, and some keywords or profiles simply have fewer posts. Filters like `min_likes` and `since_days` are applied after loading, so they can return fewer items than `limit`.
</details>

<details>
<summary><b>The first call is slow</b></summary>

The first call starts Chrome (a few seconds). A 30-post search usually takes 10 to 20 seconds. If your client times out, raise its MCP tool timeout.
</details>

<details>
<summary><b>Chrome is not installed</b></summary>

Run `uvx --from git+https://github.com/sunnycho100/threads-mcp patchright install chromium` once. The server falls back to that Chromium automatically.
</details>

## Development

```bash
git clone https://github.com/sunnycho100/threads-mcp
cd threads-mcp
uv sync
uv run pytest -q              # unit tests, no browser needed
uv run threads-mcp --login    # once
uv run python scripts/e2e.py  # every tool against your live account
```

- Unit tests run against scrubbed fixtures captured from real pages (`tests/fixtures/`). `scripts/scrub_fixture.py` turns a raw capture into a fixture, replacing usernames, names, bios, captions and media links.
- `scripts/e2e.py` starts the server over stdio with the MCP client and calls every tool. Writes run in dry-run mode: the composer is opened, filled, checked and cancelled. Put your own test keywords in `.local/keywords.txt` (gitignored, one per line); they are never written to the report. The latest results are in [docs/E2E_REPORT.md](docs/E2E_REPORT.md).
- Releasing: `uv run python scripts/bump_version.py X.Y.Z`, commit, then push a `vX.Y.Z` tag. The release workflow tests, builds and publishes to PyPI through trusted publishing.
- Design notes: [docs/superpowers/specs/2026-10-11-threads-mcp-design.md](docs/superpowers/specs/2026-10-11-threads-mcp-design.md).
- How this compares with linkedin-mcp-server, including what is not built yet: [docs/LINKEDIN_PARITY.md](docs/LINKEDIN_PARITY.md).

## Acknowledgements

The browser-session approach follows [stickerdaniel/linkedin-mcp-server](https://github.com/stickerdaniel/linkedin-mcp-server), and reading the page's own JSON follows [xpzouying/xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp).
