xthread-agent
GitHub
v3.2.0 · MIT verified 135 offline tests extension_off 0 dependencies

Give your agent X threads, not CAPTCHAs.

xthread-agent turns any public X/Twitter status URL into a reconstructed self-reply thread, an enveloped machine-readable JSON manifest, and every video & photo on disk. It never logs in, never touches cookies, never opens a browser — and it tells you honestly what it could not get.

$ pip install xthread-agent
alt_route 2 walker slots sync_alt 2 decoder slots hub MCP server built-in bolt ~1s test suite
bash — xthread-agent
Why it exists

X locked its front door. The content is still public.

X's auth wall guards discovery APIs, while its media CDN serves bytes to anyone holding a resolved URL. xthread-agent routes through the open services that grew up around X — and never touches x.com itself. No credentials, ever.

lock_open

No login. No cookies. No browser.

The tool never presents credentials or a browser fingerprint. Public content only — deleted and protected posts fail closed, by design.

Honest access
robot_2

Built for agents, not humans

Logs on stderr, data on stdout, stable exit codes, a JSON Schema contract, and an agent.md manual — a headless agent runs it end-to-end without asking a human.

Machine-first
block

Deterministic. No LLM inside.

A pure state machine: same input, same steps, same honest envelope. Zero pip dependencies — one stdlib-only file that runs anywhere Python 3.9+ exists.

Reproducible
Features

Everything a thread-harvesting agent needs

Every claim below is backed by the offline test suite and re-verified against live endpoints.

forum

True thread reconstruction

Chain membership comes only from replying_to_status — walk up to the thread start, down through self-replies. Same-author recommendations are filtered, not harvested.

alt_route

Dual-homed discovery

Two walker slots (UnrollNow, ThreadReaderApp). If one dies or drifts, the other takes the walk transparently — and the manifest names who served it (walker_slot).

swap_horiz

Fallback decoder slot

FixTweet decodes each post; vxtwitter is the automatic network-failure fallback with an honest, provenance-tagged field subset.

movie

Best-quality media

4K-ready mp4 selection (highest-bitrate variant when the source is HLS), poster frames, photos with alt text, resumable + size-verified + atomic downloads.

link

t.co resolution

Paste a t.co shortlink: one hop is followed (HTTP redirect or the embedded-interstitial fallback), then validated — non-status destinations are rejected, never guessed.

description

Envelope contract

thread_manifest.json — a versioned, schema-validated envelope with posts, authors, timestamps, metrics, media, per-stage errors, and explicit nulls. Never fabricated data.

quote

Quoted posts included

Quoted content is captured one level deep with author, text, timestamp and media URLs — recorded, not silently dropped.

security

Defense in depth

Media downloads restricted to *.twimg.com over HTTPS, remote IDs validated before touching the filesystem, response-body caps, no shell, no eval.

degrade

Degrades, never lies

Lose the walker → root-only harvest, flagged. Lose a video variant → hls_only reason. Lose everything → status: empty + exit 1. The envelope always says what it did not get.

Architecture

Three replaceable tiers, one honest envelope

Each tier is a slot with a narrow contract. X itself is never touched; every auth burden in X's stack sits in front of discovery, not delivery.

TIER 1 — DISCOVERY

Thread Walk

unrollnow.com → threadreaderapp.com

Ordered candidate IDs from the walker slots. Root always kept, capped at 50. Candidates only — never trusted as truth.

arrow_forward
TIER 2 — DECODE

Metadata

api.fxtwitter.com → api.vxtwitter.com

Per-post JSON: text, author, timestamps, metrics, media URLs, quoted posts. 404 = filter signal, never retried.

arrow_forward
TIER 3 — DELIVER

Media Fetch

video.twimg.com · pbs.twimg.com

No auth needed once a URL is known. Streamed, size-verified, atomically renamed — a crash never corrupts a file.

schedule ~0.6s between decode calls compress 20 MB / 5 MB response caps gpp_good CDN host allowlist receipt_long per-stage errors in the envelope
Supported inputExampleBehavior
x.com / twitter.comhttps://x.com/user/status/123Full pipeline
mobile / www hostshttps://mobile.twitter.com/user/status/123Normalized
i/web + suffixes…/status/123/photo/1, ?s=20Stripped to the ID
Bare status ID123456789012345678Full pipeline
t.co shortlinkshttps://t.co/abc123One hop, then validated
Anything elseprofiles, hashtags, other hosts Rejected with a stable error code
Model Context Protocol

Plug it straight into your agent host

mcp_server.py exposes the harvester as MCP tools over the standard stdio transport — still stdlib-only, still no login. Each tool call shells out to the CLI with a hard timeout, so one contract stays the single source of truth.

download

extract_thread

Full harvest — thread reconstruction + media downloads; returns the complete envelope.

search

lookup_status

Metadata only — text, authors, timestamps, media URLs, no files on disk.

receipt_long

read_manifest

Return an existing manifest verbatim. Refuses any other filename.

data_object

get_schema

The JSON Schema (draft-07) behind the envelope contract.

mcp client config · stdio
{
  "mcpServers": {
    "xthread-agent": {
      "command": "python3",
      "args": ["/path/to/mcp_server.py"]
    }
  }
}
The 3-command contract

Any agent, any language, one pipeline

Logs always on stderr, data always on stdout. Exit 0 = harvested, 1 = nothing, 2 = usage error.

agent quickstart
# 1 · run — machine-readable summary on stdout
python3 xthread-agent.py "<status_url>" --json --quiet

# 2 · inspect — the full envelope, schema-validated
cat <out>/thread_manifest.json

# 3 · branch on the contract
status = "ok" | "partial" | "empty"   # errors[] tells you why
FAQ

The honest answers

Does it really need no login?expand_more
It never presents credentials, cookies, session tokens, or a browser fingerprint, and never touches x.com itself. It reads three public surfaces: an unrolling service, two open link-decoder workers, and X's own media CDN. That works because X's auth wall guards discovery APIs while the CDN serves bytes to anyone holding a resolved URL. It does not mean X sanctions or guarantees this access.
Is this allowed by X's Terms of Service?expand_more
Reaching publicly accessible content through third-party surfaces may be subject to X's Terms of Service, the third parties' terms, copyright and data-protection law — your responsibility, your jurisdiction. The tool reaches only what any visitor can see, fails closed on protected content, and grants no license to harvested media. If you need guaranteed, sanctioned access, use the official X API.
What happens when an upstream service dies?expand_more
Nothing explodes: discovery is dual-homed (UnrollNow → ThreadReaderApp), decoding has a fallback slot (vxtwitter), and every failure lands in the envelope's errors[] with a stable code. Worst case is a root-only harvest or an honest status: empty. Slots are designed to be replaced behind their contracts.
What can't it do?expand_more
Linear self-reply chains only (branches take the first-seen path). Quoted-post media is recorded as URLs, not downloaded. Deleted, protected, and age-restricted content fails closed. HLS-only videos without mp4 variants are reported, not downloaded. Nothing here bypasses access controls.
Why stdlib-only Python?expand_more
Deployment story: copy one file into a bare sandbox and run it. No pip, no node, no ffmpeg — anywhere Python 3.9+ exists, the agent works. The PyPI package exists for convenience and is byte-identical to the single file, enforced by a drift-guard test.
How is it tested?expand_more
135 offline tests (~1s, no network, synthetic fixtures only) covering URL normalization, both walker slots, both decoders, chain reconstruction, downloads, the envelope contract, the CLI, the MCP wrapper, and package sync. CI runs the same suite on Python 3.9–3.13 on every push. Live endpoints are deliberately never probed from CI — politeness is a hard constraint.

Stop scraping. Start harvesting.

One file, zero dependencies, an MCP server included — and an envelope that never lies to your agent.

star Star on GitHub menu_book Read agent.md
$ pip install xthread-agent