x-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@x-mcppost 'Hello world!' to X"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 chromium2. Save a browser session
There are two ways. Prefer the first one.
a. Import cookies from a browser you are already signed into (recommended)
.venv/bin/python scripts/import_session.pyLogging 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-setupA 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-mcpGeneric stdio config (Qwen Code, Cursor, etc.)
{
"mcpServers": {
"x": {
"command": "/path/to/x_mcp/.venv/bin/x-mcp"
}
}
}Available tools
Tool | Description |
| Check whether the saved session is live, and which handle it is logged in as. |
| Publish a new standalone post. |
| Reply to a tweet, by URL or bare id. |
| Quote-post a tweet with added text. |
| 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
CreateTweetresponse. 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 |
| Recent posts from one or more accounts. |
| A search, Latest tab by default ( |
| A post plus its replies, ranked by engagement. The context you need to reply well. |
| One pass over a worklist of accounts and searches, ranked, skipping anything already handled. |
| Build a session from cookies copied out of a browser you are already signed into. |
| Same as |
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/1234567890123456789The digest
digest.py reads targets.json (gitignored). Copy the template and edit it:
cp targets.example.json targets.jsonThree tiers, walked in order:
always_reply- accounts whose every new post you want to answer.ecosystem- larger accounts worth landing a reply under.queries- open search, filtered bymin_followers_for_open_searchso 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 1234567890123456789State 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-outreachIt 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 understate/, 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 skillLicense
MIT. See LICENSE.
Available Tools
5 toolsx_check_sessionA
Check whether the saved X session for account is live, and which handle it is logged in as.
| Name | Required | Description | Default |
|---|---|---|---|
| account | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | ||
| account | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | ||
| target | Yes | ||
| account | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | ||
| target | Yes | ||
| account | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| target | Yes | ||
| account | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
5 tool updates
v0.1.0- First observed
x_check_session - First observed
x_submit_post - First observed
x_submit_quote - First observed
x_submit_reply - First observed
x_submit_repost
TDQS
Scored across 5 tools
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.
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.
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.
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
Related MCP Connectors
X / Twitter public post, comment, reply, user, and search tools.
Draft, check and schedule posts to your connected social accounts from Claude, ChatGPT or Cursor.
Schedule, generate and publish social posts to X, LinkedIn, Instagram, Threads and YouTube
- postnextOAuthio.postnext
Schedule, publish and track posts on X, Instagram, LinkedIn, TikTok, YouTube, Threads and Bluesky.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables interaction with X (formerly Twitter), allowing for posting tweets, searching content, managing accounts, and organizing lists.8 npm3MIT
- AlicenseNot gradedqualityDmaintenanceEnables 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
- AlicenseCqualityDmaintenanceEnables you to write, refine, and publish tweets to X (Twitter) using AI assistance.1Apache 2.0
- FlicenseNot gradedqualityAmaintenanceEnables AI assistants to interact with Twitter/X using session cookies. Supports posting, searching, managing tweets, viewing trends, and user profiles.-