# socialmag — Social Media MCP Server **One tool, all platforms.** Post to Threads, Facebook, Instagram, TikTok, Twitter/X (and YouTube when eligible) through a single MCP call. Built on CDP — no API keys, no rate limits, no approval process. Just a browser and a logged-in account. ## What It Does **Write** - **`create_post`** — Post text/media to one or more platforms in one call - **`reply_tweet`** — Reply to a tweet found by search **Read** - **`check_login`** — Verify which platforms are logged in - **`get_status`** — Last post results + open browser tabs - **`get_feed`** — Twitter timeline or search, with X operators (`from:`, `min_faves:`, …) - **`get_notifications`** — Platform activity (Threads) **Navigate** - **`open_link`** — Send a platform's existing tab to a URL, instead of spawning a new one ## Architecture ``` Claude/MCP Client dashboard.html (browser) ↓ stdio (JSON-RPC) ↓ fetch (localhost) socialmag server.py ←────────────── openbridge.py :8322 ↓ CDP WebSocket Brave Browser (dedicated instance, port 9336) ↓ real browser sessions Threads · Facebook · Instagram · TikTok · YouTube · Twitter/X ``` - **Tab reuse** — attaches to existing tabs, no cold loads - **Tab activation** — a reused tab is brought to the front, because a backgrounded tab is not guaranteed to have rendered (see *Journey*) - **Per-platform modules** — each platform has its own flow + selectors - **Sequential queue** — one platform at a time, no race conditions - **Error screenshots** — failed flows capture the page state to `/tmp/socialmag/` ## Platform Status | Platform | Text | Media | Read | Status | |----------|:----:|:-----:|:----:|--------| | Threads | ✅ | 🔲 | ✅ | **Live** — inline composer + activity scraping | | Facebook | ✅ | 🔲 | — | **Live** — dialog flow | | Instagram | — | ✅ | — | **Live** — media required | | TikTok | — | ✅ | — | **Live** — video/photo upload | | Twitter/X | ✅ | 🔲 | ✅ | **Live** — post, reply, timeline + search | | YouTube | 🔲 | 🔲 | — | **Parked** — needs 500 subs | ✅ = tested live · 🔲 = coded but untested · — = not applicable ## Stack - Python 3 (stdlib + websockets) - Chrome DevTools Protocol (CDP) - MCP stdio transport - Brave Browser with `--remote-debugging-port` ## Browser Setup ```bash # Dedicated Brave instance (separate profile, no interference) brave --remote-debugging-port=9336 \ --user-data-dir=$HOME/.local/share/brave-socialmag \ --disable-features=BeforeUnloadDialog \ --mute-audio --no-first-run ``` Log into each platform manually once. socialmag reuses those sessions. --- ## Journey So Far ### v0.1 — 2026-09-10 · it posts Five platforms coded, four live. Threads, Facebook, Instagram, TikTok posting through a real logged-in browser. YouTube parked behind its 500-subscriber gate. First automated post from the house went out that night. ### v0.2 — 2026-09-11 (early hours) · it reads Twitter/X joined as a full module — post, reply, timeline, search. `get_notifications` landed for Threads. `open_link` built for warm-tab reuse. Pushed to a private GitHub repo. **What the read side cost that the plan didn't predict:** - The Threads scraper was believed to already return a post `link`. It did not — it returned `{text}` only. Row detection had to be built from the live DOM. The rule that worked: *a row is an element that links somewhere, carries a relative-time marker, and contains no smaller element that does the same.* - A tweet's permalink is **not** "the first `/status/` href" — an article carries three (canonical, `/analytics`, `/photo/N`). It's the anchor wrapping `