Skip to main content
Glama

x-mcp

MCP server for posting to X (Twitter) through a saved browser session (Playwright + stealth), plus a set of read scripts that drive the same session against X's internal GraphQL API.

The MCP surface is write-only by design. Reading X does not need an MCP round-trip and is better done in bulk at the terminal, which is what the scripts in scripts/ do. The server does the one thing that genuinely needs a browser inside an agent loop: publishing something and confirming it actually landed.

No API keys, no developer account, no paid API tier. The identity is a browser session you log into yourself.

Not affiliated with, endorsed by, or connected to X Corp. This drives your own logged-in account through a normal browser. Read the Notes before using it.


How it works

X serves its timelines and its mutations from an internal GraphQL API. Scraping the rendered DOM means chasing class names that change every few weeks, so this does not do that. Every page gets a small init script that wraps fetch and XMLHttpRequest and keeps the JSON the X web app itself receives; the code then walks that JSON for anything that looks like a tweet, in whatever envelope it arrived (a timeline entry, a visibility wrapper, a module item, a quoted tweet).

Two useful consequences. Long posts come back whole out of note_tweet instead of truncated at 280 characters. And a post is confirmed by reading X's own CreateTweet response for the new tweet's id, rather than sleeping two seconds and assuming it worked.

Chromium runs headed (non-headless) throughout. X blocks headless browsers.


Related MCP server: X (Twitter) MCP

Prerequisites

  • Python 3.11+

  • An X account

  • Google Chrome installed. The code drives your real Chrome rather than Playwright's bundled build, which identifies itself as "Google Chrome for Testing" and is easier for X to flag. Without Chrome it falls back to the bundled build and says so on stderr.


1. Install

python3 -m venv .venv
.venv/bin/pip install -e .
.venv/bin/python -m playwright install chromium

2. Save a browser session

There are two ways. Prefer the first one.

.venv/bin/python scripts/import_session.py

Logging in through an automated browser is the step X flags, and a failed attempt can get the account's logins temporarily limited. If you are already signed into X normally, this skips that step entirely. The script asks for two cookie values, without echoing them, and writes the session file directly.

To find them, in the browser where you are already signed in: open x.com, press F12 (or Cmd+Option+I), go to Application (Chrome) or Storage (Firefox) -> Cookies -> https://x.com, and copy the values of auth_token and ct0.

auth_token is a full credential for the account, equivalent to a password. It is prompted for hidden so it stays out of your shell history, and the session file is written chmod 600.

Add --verify to open a browser afterwards and confirm the session works.

b. Log in through the automated browser

.venv/bin/x-mcp-setup

A Chrome window opens on x.com. Log in normally, including whatever 2FA X asks for. Nothing is typed for you and no password is stored. When the home timeline appears, the cookies are saved to session.json (gitignored) and the window closes.

This drives your real installed Google Chrome (channel="chrome"), not Playwright's bundled build, and keeps a persistent profile under profiles/<account>/ so repeat runs look like a returning browser rather than a fresh one. Playwright's bundled binary identifies itself as "Google Chrome for Testing" and is much easier to flag; if real Chrome cannot be launched the code says so loudly on stderr rather than quietly falling back.

If X answers with "We've temporarily limited your login", stop. Wait it out rather than retrying, since repeated attempts compound the flag, and use method (a) instead.

Either way

For a second account, add --account <name>; that reads and writes session-<name>.json instead. Every read script takes the same --account, and every MCP tool takes an account argument. X_ACCOUNT sets the default when the argument is left empty.


3. Configure your AI tool

Claude Code

claude mcp add --scope user x /path/to/x_mcp/.venv/bin/x-mcp

Generic stdio config (Qwen Code, Cursor, etc.)

{
  "mcpServers": {
    "x": {
      "command": "/path/to/x_mcp/.venv/bin/x-mcp"
    }
  }
}

Available tools

Tool

Description

x_check_session

Check whether the saved session is live, and which handle it is logged in as.

x_submit_post

Publish a new standalone post.

x_submit_reply

Reply to a tweet, by URL or bare id.

x_submit_quote

Quote-post a tweet with added text.

x_submit_repost

Repost a tweet with no added text.

All of them drive a real (headed) Chromium window with the saved session, so they behave like a human posting - no API keys, no OAuth app. Every tool takes an optional account.

Run x_check_session first: it reports whether the session is live and posts nothing.

What the posting tools guarantee

  • The target is verified before anything is typed. A reply, quote or repost navigates to the tweet and confirms that exact id is present in the captured GraphQL. If it cannot be confirmed, the tool raises rather than typing into whatever page it landed on.

  • Success is observed, not assumed. A post, reply or quote returns the new tweet's real id and URL, read out of X's CreateTweet response. A repost is confirmed by the button flipping to its unreposted state.

  • Ambiguity is reported as ambiguity. If the action cannot be confirmed the result is {"status": "unverified", ...} with a screenshot path, never a hopeful "posted". Do not retry an unverified result automatically: a retry on an ambiguous outcome double-posts, or undoes a repost that did land.

  • Every tool publishes publicly and immediately. There is no draft state. In an MCP client, the client's own approval prompt is the confirmation step.


Reading X (scripts/)

Every script opens a headed Chromium window with the saved session and reads the captured GraphQL. Run them with the venv Python.

Script

What it does

scripts/read_user.py <handle> [...]

Recent posts from one or more accounts. --with-replies, --limit, --max-age-h.

scripts/search.py "<query>"

A search, Latest tab by default (--top for Top). X operators work: from:, min_faves:, quoted phrases, OR.

scripts/read_thread.py <url-or-id> [...]

A post plus its replies, ranked by engagement. The context you need to reply well.

scripts/digest.py

One pass over a worklist of accounts and searches, ranked, skipping anything already handled.

scripts/import_session.py

Build a session from cookies copied out of a browser you are already signed into.

scripts/setup_session.py

Same as x-mcp-setup, for running from the repo without the console script.

Example:

.venv/bin/python scripts/read_user.py nasa esa --max-age-h 48
.venv/bin/python scripts/search.py '"launch window" min_faves:50' --max-age-h 24
.venv/bin/python scripts/read_thread.py https://x.com/nasa/status/1234567890123456789

The digest

digest.py reads targets.json (gitignored). Copy the template and edit it:

cp targets.example.json targets.json

Three tiers, walked in order:

  1. always_reply - accounts whose every new post you want to answer.

  2. ecosystem - larger accounts worth landing a reply under.

  3. queries - open search, filtered by min_followers_for_open_search so you get posts with an audience rather than noise.

.venv/bin/python scripts/digest.py --per-tier 10 --max-age-h 72

# after you reply to something, so it stops coming back
.venv/bin/python scripts/digest.py --replied 1234567890123456789
.venv/bin/python scripts/digest.py --ignore  1234567890123456789

State lives in state/ (gitignored): replied.json, ignored.json.


Bundled agent skill

.claude/skills/x-outreach/SKILL.md ships with the repo. Open the repo in Claude Code and it loads automatically; to make it available from anywhere:

ln -s "$(pwd)/.claude/skills/x-outreach" ~/.claude/skills/x-outreach

It tells an agent which surface to use for what (reading is the CLIs, posting is the MCP), the approval rule for anything that publishes, what to do with an unverified result, and the bar a reply has to clear. Worth reading yourself even if you never point an agent at this.


Compatibility

Verified working against X in September 2026.

This reads X's internal GraphQL, which is not a public API and changes without notice. It is built to survive the common kinds of change: responses are walked for tweet objects rather than parsed by a fixed path, and the old field locations are kept as fallbacks. Field moves still happen. A recent one relocated user fields out of legacy into core, profile_bio and relationship_counts, which silently zeroed every follower count until the parser learned both shapes.

When something does break, it surfaces in one of three ways rather than as bad data:

  • A read raises instead of returning an empty list.

  • A posting tool raises naming the selector it looked for, with a screenshot under state/.

  • A post that cannot be confirmed returns "status": "unverified" rather than a hopeful "posted".

If a field goes quietly empty rather than raising, that is a bug worth reporting.


HTTP transport (Docker)

The server speaks stdio by default. Set MCP_TRANSPORT=streamable-http (plus MCP_HOST / MCP_PORT, default port 8201) to run it as an HTTP MCP server; the included Dockerfile does this under xvfb-run.


Notes

  • This automates your own logged-in account, which X's terms restrict. Your account is the thing at risk. Keep the volume human, and do not point this at anything resembling bulk posting.

  • X rate-limits aggressive reading. If harvesting starts returning nothing, stop for an hour rather than retrying in a loop.

  • One browser window per run, targets read sequentially. A digest over ten accounts and five searches takes a few minutes. That pacing is deliberate.

  • Reads fail loudly. A login redirect or an empty capture raises instead of printing an empty list, so a dead session never looks like a quiet day.

  • The DOM selectors (tweetTextarea_0, tweetButtonInline, retweet) are X's published test ids. If X changes them, the tools raise with the selector they looked for and a screenshot under state/, rather than clicking something else.

  • One account per session file; the session file is the identity.


Layout

src/x_mcp/session.py   session files, browser context, the capture script
src/x_mcp/tweets.py    parsing captured GraphQL into one flat tweet shape
src/x_mcp/post.py      the posting client
src/x_mcp/server.py    the MCP server
scripts/               the read CLIs, sharing the same installed package
.claude/skills/        the bundled agent skill

License

MIT. See LICENSE.

Available Tools

5 tools
x_check_sessionA

Check whether the saved X session for account is live, and which handle it is logged in as.

ParametersJSON Schema
NameRequiredDescriptionDefault
accountNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It discloses what is inspected (liveness, logged-in handle) but not whether checking has side effects (e.g. triggering a login flow or refreshing a stored session), what happens if the session is dead, or any auth requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, front-loaded sentence that states the resource and the two reported facts with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Because an output schema exists, return values need not be described. However, for a session/auth-check tool with no annotations, the description omits side-effect behavior, the semantics of the optional empty `account`, and failure-mode expectations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% for the single `account` parameter, but the description ties it to 'the saved X session for `account`', giving it basic meaning. It does not explain the empty-string default or what happens when the account is omitted.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Uses a specific verb (check) with a specific resource (the saved X session) and states the two things it reports: liveness and the logged-in handle. This cleanly distinguishes it from the sibling x_submit_* tools, which are all write actions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: as a session check it is naturally a pre-flight step before the submit siblings, but the description never says when to call it or what to do with the result. No exclusions or alternatives are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

x_submit_postA

Publish a new standalone post on X for account. Publishes PUBLICLY and IMMEDIATELY - no draft step.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYes
accountNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does disclose the two most important traits: the post is PUBLIC and IMMEDIATE with 'no draft step,' which signals irreversibility. It omits auth/permission requirements and rate limits, and does not clarify what the empty-string default for `account` means.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the operation and immediately followed by the critical behavioral warning. Every clause earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The public/immediate nature of the action is well covered and an output schema exists so return values need not be described. However, for an irreversible publish tool with zero annotation coverage and 0% schema coverage, the absence of any auth/account-resolution guidance leaves a meaningful gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for both parameters. It references `account` by name in backticks but says nothing about its semantics (e.g., which account is used when it defaults to empty), and `text` is never mentioned at all.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Publish') and resource ('new standalone post on X'), and the word 'standalone' implicitly separates it from the sibling tools x_submit_reply, x_submit_quote, and x_submit_repost. An agent can identify the operation without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Standalone' hints that this is the non-reply/non-quote path, but the description never explicitly names the alternatives or states when to pick this over x_submit_reply or x_submit_quote. Usage is implied rather than guided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

x_submit_quoteA

Quote-post a tweet (URL or id) with added text as account on X. Publishes PUBLICLY and IMMEDIATELY - no draft step.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYes
targetYes
accountNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It usefully discloses that the post goes out publicly and immediately with no draft step, which is real value. It says nothing about authentication/session requirements (the sibling x_check_session implies a session is needed) or rate limits, leaving meaningful gaps for a mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the action and the target format, followed by the critical public/immediate warning. No filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained. For a mutation tool with zero annotations and 0% schema description coverage, the description covers the public/immediate consequence but omits auth/session prerequisites and rate-limit context that an agent would need to invoke safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It does clarify that 'target' accepts a URL or id, that 'text' is added content, and that 'account' selects the posting identity, but it doesn't explain the account value format or confirm text is required, leaving the bare schema partly unassisted.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Quote-post a tweet') with the target format (URL or id) and the identity used. The quote-post action is distinct from the sibling x_submit_post, x_submit_reply, and x_submit_repost operations, so an agent can route correctly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied by the verb: quoting is the condition that selects this over the sibling post/reply/repost tools, but no explicit when-to-use or when-not-to-use guidance is given. The 'PUBLICLY and IMMEDIATELY - no draft step' note hints at irreversibility but frames it as behavior, not selection guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

x_submit_replyA

Reply to a tweet (URL or id) as account on X. Publishes PUBLICLY and IMMEDIATELY - no draft step.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYes
targetYes
accountNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It discloses two critical traits: the action is public and immediate, with no draft step. This gives the agent a clear warning that this is a destructive, irreversible operation. However, it does not mention authentication requirements, rate limits, or what happens on failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two short sentences, front-loaded with the core action and scope. Every word earns its place, and there is no redundant or filler content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that an output schema exists, the description need not explain return values. It covers the essential behavior (public, immediate) and target format, but misses parameter semantics for 'text' and any authentication context. For a simple 3-parameter tool, this is nearly complete but not fully thorough.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, meaning none of the parameters have descriptions in the schema. The description only mentions that the target can be a tweet 'URL or id' and that the account parameter is used for posting identity. The 'text' parameter is not explained, and the 'account' parameter is only implied by 'as `account`'. This leaves significant gaps for an agent to understand parameter formats and constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Reply to') and resource ('tweet'), plus the crucial scope detail that the reply is posted as a specific account. It is easily distinguished from siblings like x_submit_post or x_submit_quote, which presumably post new content rather than replies.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description says 'PUBLICLY and IMMEDIATELY - no draft step', which hints at when to use it (an irreversible public action), but it does not explicitly compare it to alternatives such as x_submit_quote or x_submit_post. No conditions for choosing one over the other are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

x_submit_repostA

Repost a tweet (URL or id) with no added text as account on X. Publishes PUBLICLY and IMMEDIATELY - no draft step.

ParametersJSON Schema
NameRequiredDescriptionDefault
targetYes
accountNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden but does disclose the critical behavior: publishes publicly and immediately, no draft step. It omits auth requirements and failure/duplicate handling, but the key irreversibility warning is present.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two compact sentences, no filler. The irreversible-publish warning is front-loaded and earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and the description covers the essential action and irreversibility. Only the account parameter semantics and the relationship to sibling tools remain unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% for both parameters. The description clarifies 'target' as a tweet URL or id, which compensates partially, but 'account' (including its empty-string default behavior) is left entirely undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (repost) and resource (tweet, as URL or id) with the defining constraint 'no added text.' Clear enough to distinguish from x_submit_post, x_submit_reply, and x_submit_quote.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implicitly distinguishes from x_submit_quote by specifying 'no added text,' which is the key branch condition, but never names the sibling tools nor states when to prefer this over x_submit_post. Clear context, no explicit exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedx_check_session
    • First observedx_submit_post
    • First observedx_submit_quote
    • First observedx_submit_reply
    • First observedx_submit_repost

TDQS

A4.1/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: creating a new post, replying, quoting, reposting, and checking session state. There is no overlap or ambiguity between them.

Naming Consistency5/5

All submission tools follow the consistent x_submit_* pattern (post, reply, quote, repost), and the session check uses x_check_session. The naming is predictable and uniform.

Tool Count5/5

With only 5 tools, the server is well-scoped for its purpose of publishing to X. Each tool serves a necessary function without unnecessary bloat or gaps.

Completeness4/5

The tool set covers the core submission actions (post, reply, quote, repost) and session verification, which aligns with its stated purpose. Missing features like deleting posts or fetching timelines are not critical for a submission-focused server, but a minor gap exists for listing or managing existing content.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with X (formerly Twitter), allowing for posting tweets, searching content, managing accounts, and organizing lists.
    8 npm
    3
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with X (Twitter) to post tweets, threads, and replies while retrieving tweet metrics and account information. It supports core management tasks like deleting tweets and verifying authentication through the Twitter API.
    MIT
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to interact with Twitter/X using session cookies. Supports posting, searching, managing tweets, viewing trends, and user profiles.
    -