
X (Twitter)
io.github.Role1776由 GitHub 帳號 Role1776 發布v0.1.1更新於 Oct 24, 2026
Search, read and publish X posts and manage users, lists and media via the X API v2.
概覽
一個本機 MCP 伺服器,讓助理透過付費的 X API v2 搜尋、讀取和發布 X 貼文,並管理使用者、清單和媒體。
- 可以做什麼
- 搜尋、統計和依 ID 查詢貼文,包括近期搜尋、指定使用者貼文、提及、首頁時間軸、引用、按讚和書籤,分頁需手動進行,並依查詢或 ID 回傳部分結果。發布、刪除、按讚、取消按讚、轉推和取消轉推貼文,可附帶媒體、投票、回覆和引用。查詢使用者、讀取個人資料、追蹤者、追蹤中、靜音和封鎖清單,並可追蹤、取消追蹤或靜音。建立、更新、刪除、追蹤、置頂清單並管理成員,讀取清單成員的貼文。從允許的本機目錄上傳圖片、GIF 和影片,回傳可用於發文的媒體 ID。
- 適用情境
- 適合讓助理追蹤或研究近期 X 動態、在搜尋前先估算話題規模,或在聊天用戶端中起草並發布貼文、管理清單和追蹤關係。適用於擁有已儲值 X 開發者帳號的單一帳號擁有者,不適合多帳號或歷史封存檢索。
- 執行需求
- 以本機程序執行(容器映像、預先編譯的執行檔或 MCP 套件;從原始碼建置需 Go 1.26+)。需要一個 X 應用程式的四項 OAuth 1.0a 憑證:X_API_KEY、X_API_KEY_SECRET、X_ACCESS_TOKEN 和 X_ACCESS_TOKEN_SECRET,以及 MEDIA_ROOT(指向已存在目錄的絕對路徑)。應用程式需具備讀寫權限,且變更權限後必須重新產生存取權杖。需要連線至 X API 的網路和已儲值的開發者帳號。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 X (Twitter),將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
mcp-x
An MCP server that gives an LLM the X (Twitter) API: read and search posts, manage users and lists, upload media, and publish — as the account whose keys it runs on.
[License MIT] [Go] [MCP] [Transport] [X API v2] [OAuth 1.0a] [Paid API]
Website · Costs · Credentials · Tools · Quick start · Configuration · Architecture · Contributing
What it is
mcp-x is a Model Context Protocol server written in Go. It exposes the X API v2 to any MCP-compatible client (Claude Desktop, IDE agents, custom LLM apps) as 42 tools covering posts, users, lists and media.
It authenticates with OAuth 1.0a user context, which means every call acts as a real X account — the one the four keys belong to. x_post_create publishes publicly. x_user_follow really follows. x_post_delete is irreversible. This is not a sandbox, and it is not free: see The X API costs money before you wire it into an agent.
Both transports the MCP SDK supports are available and expose the identical tool set:
- stdio — the client launches the binary and talks over stdin/stdout (the default, ideal for desktop clients).
- http — a long-running streamable HTTP server (useful for remote/shared deployments).
The X API costs money
[!WARNING] There is no free tier any more. X retired the Free/Basic/Pro subscription tiers for new developers and moved to pay-per-use credits: you buy credits upfront in the Developer Console and every request deducts from the balance in real time. Legacy Basic ($200/mo) and Pro ($5,000/mo) subscriptions survive only for accounts that already had them; Enterprise starts around $42,000/mo. A new developer account today gets pay-per-use and nothing else.
Rates at the time of writing (official pricing — always re-check the Console, they have changed several times in 2026):
Two things follow from this, and both are baked into the server:
- Reads are charged per resource returned, not per request.
max_results: 100onx_posts_searchcosts twenty times whatmax_results: 5costs for the same query. Every read tool's description tells the model to ask for the smallestmax_resultsthat answers the question, and the batching tools (x_posts_lookup,x_users_lookup) tell it to batch rather than loop. x_posts_countdoes not consume the post-read budget. It returns match counts bucketed by minute/hour/day for the same query syntax. Size a topic withx_posts_countfirst, then pay forx_posts_search.
X also deduplicates: the same resource fetched twice inside a 24-hour UTC window is billed once. And pay-per-use is capped at 3 million post reads per billing cycle — past that, only Enterprise.
When the money runs out the API answers with a distinct error, and the server maps it to a message that explicitly tells the model not to retry — see Errors.
Credentials
The server needs four OAuth 1.0a values, all from one X app:
The first pair identifies the app; the second pair identifies the account acting through it. mcp-x uses AuthenMethodOAuth1UserContext, not app-only bearer auth, because every write endpoint and every "me" endpoint (x_users_me, x_posts_home, x_bookmarks_list, mentions) requires a user context. There is no bearer-token mode.
Getting them — and the one step everybody gets wrong
- Go to developer.x.com → your project → your app.
- Open User authentication settings and set App permissions to Read and Write. Do this first.
- Only then go to Keys and tokens and generate the Access Token and Secret.
[!IMPORTANT] An access token permanently keeps the permissions the app had at the moment it was generated. If you created the token while the app was Read-only and then flipped the app to Read and Write, the token is still read-only. Nothing about the app settings page will tell you this. Every write will fail with X's
oauth1-permissionsproblem type, forever, until you go back to Keys and tokens and regenerate the Access Token and Secret.This is the single most common setup failure with the X API, which is why the server checks for it at startup and refuses to start with:
Regenerating the API Key/Secret is not the fix. Regenerate the Access Token and Secret.
Startup verification
Before registering a single tool, the server calls GET /2/users/me once (client.Bootstrap) with a 15-second deadline. This does three jobs:
- proves the four keys are valid — bad keys fail the process, not the first tool call;
- catches the read-only-token trap above;
- caches the key owner's numeric user id, because every write endpoint is
POST /2/users/:id/...and looking the id up per write would be another billed request.
A failure here is fatal by design. A server that starts and then fails every call is worse than one that does not start.
The cost of that choice is worth stating plainly: there is no way to try this server without a funded X developer account. No credentials means no startup, which means no tool list — /mcp and claude mcp list will show a failed connection and nothing else. To confirm an install short of that, run the binary with -version, and read the Tools section for what it would have exposed.
Keys are secrets
The four values are credentials for a live account with write access. Keep them in a file only you can read, pass it with -env, and never commit it — .env is gitignored, .env.example is the template. When running under an MCP client, the client's env block works too; it takes precedence over the .env file.
Tools
42 tools in four groups. Every tool carries MCP annotations: readOnlyHint on reads, destructiveHint on anything irreversible (x_post_delete, x_list_delete, unlike, unrepost, unfollow, member removal). Each returns a structured JSON payload matching its output schema; the SDK mirrors the same JSON into the text content block for clients that do not read structuredContent.
Every tool accepts an optional timeout_ms, clamped into the group's [MIN, MAX] window (see Limits).
Posts — reading
x_posts_search
Query operators go inside the query string:
x_posts_count
x_posts_lookup
x_posts_by_user
Timeline tools
x_posts_mentions, x_posts_home, x_posts_liked and x_bookmarks_list share one shape: max_results (1..100), pagination_token, timeout_ms. x_posts_quotes, x_post_liked_by and x_post_reposted_by add a required id.
Pagination is explicit and manual: a response carries next_token, and you pass it back as pagination_token for the next page. The server never walks pages on its own — each page is billed, so that decision stays with the caller.
Posts — writing
x_post_create
Publishing the same text twice in a row is rejected by X as a duplicate — the server surfaces that as a distinct message rather than a generic failure.
Users
Blocking and unblocking are deliberately not exposed. Reading the block list is.
Lists
Media
x_media_upload
Uploads an image, GIF or video with the chunked INIT → APPEND → FINALIZE flow and returns a media_id for x_post_create.
Two things to know:
MEDIA_ROOTis a hard boundary. It is required configuration with no default, and the server refuses any path resolving outside it, symlinks included. Without it an LLM with this tool could read any file on the host and post it. Choose a dedicated directory.- Video is transcoded asynchronously by X. The server polls FINALIZE status every
MEDIA_POLL_INTERVAL_MS, but a slow encode can still returnstatus: "pending". Themedia_idis valid but not yet postable — retryx_post_createshortly. Amedia_idexpires after a while and is meant for a single post.
Partial results
The fan-out tools — x_posts_search, x_posts_count, x_posts_by_user — run their inputs in parallel and return one entry per query/username, each with its own status, so one bad input does not lose the results that worked. x_posts_lookup does the same per id (not found sits next to the posts that resolved), and x_users_lookup collects unresolved names in not_found.
A call fails outright only when the input is rejected before any work starts, or when every item in it fails.
Errors
Failures come back as a tool result with isError: true and a plain-text message, not as a JSON-RPC error — the model reads the message and can correct the call itself. The messages are written for a model rather than a log reader, so the ones that must not be retried say so explicitly.
Classification happens in adapter/x/apierr, and it is deliberately defensive. X answers errors with Content-Type: application/problem+json, which the underlying gotwi client does not recognise as JSON — so the entire problem body lands verbatim in a message field instead of being decoded into typed fields. The mapper therefore concatenates every source the response can carry and matches problem types (oauth1-permissions, usage-capped, credits-depleted, rate-limit-exceeded, …) against that haystack, falling back to HTTP status codes when nothing matches.
Known limitations
- Recent search only. 7 days back, full stop. Historical/archive search is a different (Enterprise) product and is not wired up.
- Blocking is not exposed.
x_users_blockedreads the list; there is no block/unblock tool. - No streaming endpoints. Filtered/sampled stream is not implemented.
- No DMs.
- One account per process. The keys are process-wide; the key owner is resolved once at startup. Serving several accounts means several processes.
- Pagination is manual. Tools return
next_token; nothing walks pages automatically, because every page is billed.
Quick start
Install
Pick whichever fits — all of them give the identical server.
Container (no Go toolchain needed):
Pin an explicit version in anything you rely on. :latest moves on every stable release, and a release can add or change tool behaviour; server.json pins the same version the MCP Registry advertises.
Prebuilt binary — grab the archive for your platform from the latest release, unpack it, and put mcp-x on your PATH.
MCP Bundle — for clients that install .mcpb files, download mcp-x_<version>_<os>_<arch>.mcpb from the latest release and open it with your client. The bundle carries the compiled binary, so it needs neither Docker nor Go, and the client prompts for the five required values on install. Pick the file matching your OS and CPU architecture: a bundle holds one native binary.
[!NOTE] Bundles built before v0.1.1 declared no configuration fields, so the client never asked for the credentials and the server exited on startup every time. Use v0.1.1 or newer.
From source (needs Go 1.26+):
go install also works, with one caveat worth knowing before you type it:
The Go module lives in app/, so the release tags (v0.1.0) do not name it — @v0.1.0 fails outright and @latest resolves to a pseudo-version of the newest commit on main. A go install build also reports its version as dev, because the version is stamped in by the release pipeline and not by the module. For a build that matches a release exactly, use the prebuilt binary, the container, or make build from a checked-out tag.
Configure
From a clone, or from an unpacked release archive (both ship the template):
Installing from the container or an .mcpb bundle gives you no checkout to copy from — take the template from .env.example, or skip the file entirely and pass the five values in your client's env block (below).
Five values are required; everything else has a default. MEDIA_ROOT must be an absolute path to a directory that already exists — the server checks this at startup and refuses to start otherwise, rather than failing at the first upload.
Run
A successful start logs the authenticated account:
Connecting an MCP client (stdio)
Claude Code — one command, credentials inline (-- separates the server's own command from the flags above it):
Or point it at a .env file instead: -- /absolute/path/to/mcp-x -env /absolute/path/to/.env. Check the result with claude mcp list, or /mcp inside a session.
Claude Desktop and other clients that take a JSON config — point them at the built binary:
Or skip the file and pass the credentials in the env block — variables already in the environment win over the .env file, so a client's env block always takes effect:
Connecting an MCP client (container)
Run the image on stdio. Docker needs each variable named on the command line with -e for it to reach the process, and MEDIA_ROOT only makes sense if the directory is mounted:
-i is required — without it the container gets no stdin and the client sees the server die immediately. Clients that install from the MCP Registry itself build this invocation themselves and prompt for the variables declared in server.json — that is a property of the client, not of every client that can talk to this server; Claude Code's CLI, for one, does not install from the registry, so use claude mcp add above.
When the client says the connection closed
Under stdio the server's stderr belongs to the client, and most clients discard it. So a configuration problem that the server explains perfectly well in one line reaches you as nothing but:
Run the binary by hand with the same environment to see the real reason:
It prints exactly what is wrong — which variables are missing and where to get them, or that the keys were rejected, or that the access token is read-only — and exits. Almost every failed install is one of those three.
Running over HTTP
Set MCP_TRANSPORT=http and the server listens on SERVER_PORT at MCP_PATH (default http://localhost:8080/mcp).
[!CAUTION] The HTTP transport has no authentication of its own. Anyone who can reach the endpoint can publish, delete and follow as your account, on your credits. Bind it to localhost or put it behind an authenticating reverse proxy — never expose it to the open internet.
Configuration
Everything is configured through environment variables, and each value is validated before startup: a missing required key, or a non-numeric or non-positive number, is a startup error naming the variable you actually set (X_API_KEY, not APIKey). Relationships between limits are not checked at startup — see Limits. Variables already present in the environment win over a .env file.
See .env.example for the full list at its default values, ready to copy to .env.
Required
Missing any of them aborts startup with the list of what is missing plus a pointer to the Developer Console.
MCP server
The version advertised to clients is not configurable: it is stamped into the binary at build time from the git tag.
HTTP server (http transport only)
HTTP client
Logging
Limits
Each group has its own limits, so a slow media upload cannot be capped by a search timeout.
Posts
Users
Lists
Media
Each value is checked on its own — it must be greater than zero, and the ones the API itself bounds (*_MAX_IDS, POSTS_MAX_RESULTS, LISTS_MAX_RESULTS, POSTS_MAX_DAYS) are additionally capped at the API's ceiling so a typo cannot produce a request X will reject. The DEFAULT_*, MIN_* and MAX_* triples are not cross-checked against each other at startup. An inconsistent set does not stop the server; it is reconciled per request instead:
- a value the caller omits, or passes as zero or negative, falls back to the matching
DEFAULT_*; - the result is then clamped into
[MIN_*, MAX_*], so aDEFAULT_*larger than itsMAX_*simply yieldsMAX_*; - if
MIN_*exceedsMAX_*, the maximum wins.
The effective limit is therefore always within the configured maximum, and misconfiguration degrades to a working server rather than a failed start. The trade-off is that it degrades silently: POSTS_MAX_RESULTS=1 instead of 10 produces no warning, only quietly smaller responses. Worth double-checking these values when results look truncated.
Architecture
The project follows a clean, layered structure. Dependencies point inward toward the domain, and each layer talks to the next through interfaces.
Request flow for a tool call:
Two boundaries are worth calling out:
- The domain layer refuses to construct invalid values.
NewPostID,NewUserID,NewUsername,NewDraftand friends validate on construction, so a malformed id or a 300-character post is rejected locally — before it costs a request. Ids are checked against the snowflake format and usernames against X's 1–15[A-Za-z0-9_]rule in a single sharedformatpackage. - Every layer speaks the same error vocabulary. Adapters convert X's problem responses into the sentinels in
domain/x/errors.go; the transport layer is the only place that turns a sentinel into human text. That is why the same failure reads identically no matter which of the 42 tools produced it.
Development
Everything Go lives in app/, so either use the makefile from the repository root or pass -C app to the toolchain:
Mocks are generated with mockgen from the //go:generate directives next to each usecase interface:
New code is expected to come with tests. See CONTRIBUTING.md for the full pull-request guidelines.
License
Released under the MIT License.
來源:README.md,提交 d2de7ff
工具
0版本歷史
1- v0.1.1最新Sep 16, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分