2.9 KiB
socialmag
MCP server that posts to Threads / Facebook / Instagram / YouTube / TikTok by driving abang's already-logged-in Brave over CDP. No API keys, no OAuth.
Built to PROPOSAL.md (2026-09-10). Nothing here has been run against a live
platform yet — every step whose selector or timing needs a real page is
marked # TODO: VALIDATE LIVE.
Run
cd /mnt/playground/Playground_V2
python3 -m socialmag # stdio MCP server, logs to stderr
⚠️ Must run as a package. python3 socialmag/server.py puts the package
dir first on sys.path, where selectors.py and queue.py shadow the stdlib
modules asyncio imports — asyncio then dies at import with
module 'selectors' has no attribute 'SelectSelector'. Run -m.
Brave must be listening on http://127.0.0.1:9222. Override with
SOCIALMAG_CDP_ENDPOINT.
Requires websockets (17.1 present on this box). Everything else is stdlib.
Tools
| Tool | Args | Returns |
|---|---|---|
create_post |
platforms[], text, media, visibility |
{platform: {status, detail}} |
check_login |
platforms[] (default all) |
{platform: true/false/"error: ..."} |
get_status |
— | last run's results + queue history |
text is required for threads/fb/yt and used as the caption for ig/tiktok.
media must be an absolute path to an existing file; it is required for
ig/tiktok, optional for threads/fb, and rejected for yt.
Layout
server.py MCP stdio JSON-RPC loop + the 3 tools
cdp.py CDP client: CDPClient (browser) + Tab (page verbs)
queue.py PostQueue — strictly sequential, one tab at a time
selectors.py every platform selector, one file
platforms/ one module per platform: post() + check_login()
Each platform module exposes:
async def post(cdp_client, text, media_path, options) -> {"status": ..., "detail": ...}
async def check_login(cdp_client) -> bool
post opens its own tab, drives the flow, closes the tab. A failure inside the
flow is caught by platforms.guard, which screenshots the page as it actually
looked into /tmp/socialmag/ and puts the path in the result.
House rules honoured
- No silent fallbacks. Missing media, missing text, an unknown platform, a selector that never appears, a CDP call that errors — all raise with the reason. Nothing picks a default and carries on.
- Unmapped flows refuse rather than guess: Facebook
visibilityswitching and YouTube media both raise, because clicking through an unmapped dialog outward-facing is worse than a stop. - Human-like delays (
random.uniform) between every discrete action, longer on TikTok.
Not built (out of the brief's scope)
post_youtube_video(video upload) — the brief scoped yt to community posts.socialmag.service— explicitly deferred until after live validation.- Scheduling, analytics, multi-account (proposal "Future").