X Reader is an EyrieCommander Codex plugin fork of xdevplatform/xmcp. It runs
a local FastMCP server that exposes a small, read-oriented X API toolset to
Codex, so agents can read posts, quoted posts, users, recent search results,
and API usage without scraping X in a browser.
This fork keeps the upstream X API MCP server intact while adding:
- Codex plugin metadata
- a source-portable plugin launcher
- a default read-only tool allowlist
- bearer-token mode for read-only local use
- safer token logging defaults
- Python 3.10+ for direct
pipinstalls, oruvfor automatic Python 3.12 environment creation - An X Developer Platform app (to get tokens)
- Optional: an xAI API key if you want to run the Grok test client
This fork can be used directly as a Codex plugin. The plugin files live in:
.codex-plugin/plugin.json.mcp.jsoncodex/run-mcp.shskills/x-reader/SKILL.md
For local use:
- Create
.envfromenv.example. - Set
X_BEARER_TOKEN. - Keep
X_AUTH_MODE=bearerfor read-only post/profile lookups without an OAuth browser flow. - Keep the default
X_API_TOOL_ALLOWLISTfor read-only Codex use unless you intentionally need more tools.
The plugin launcher creates .venv on first run. If uv is installed, it uses
Python 3.12 automatically. Otherwise it looks for Python 3.10+. You can override
with XMCP_PYTHON=/path/to/python or XMCP_VENV=/path/to/venv.
From the repo root:
./codex/run-mcp.sh
With an existing local venv:
XMCP_VENV=.venv312 ./codex/run-mcp.sh
Expected result: FastMCP starts at http://127.0.0.1:8000/mcp and lists the
allowlisted read tools.
In another terminal while the server is running:
.venv312/bin/python -c 'import asyncio
from fastmcp import Client
async def main():
async with Client("http://127.0.0.1:8000/mcp") as client:
tools = await client.list_tools()
print([tool.name for tool in tools])
asyncio.run(main())'
Expected result:
['getOpenApiSpec', 'getPostsByIds', 'searchPostsRecent', 'getPostsById', 'getPostsQuotedPosts', 'getUsage', 'getUsersByIds', 'getUsersByUsernames', 'getUsersByUsername', 'getUsersById', 'getUsersPosts']
Call getPostsById through the MCP client with a public post id. For quoted
posts, fetch the referenced quoted id as a second call.
- Create a virtual environment and install dependencies:
python -m venv .venvsource .venv/bin/activatepip install -r requirements.txt
- Create your local
.env:cp env.example .env- Required values (do not skip):
X_OAUTH_CONSUMER_KEYX_OAUTH_CONSUMER_SECRETX_BEARER_TOKEN(required for this setup; keep it set even if using OAuth1)X_AUTH_MODE(bearerfor read-only bearer-token mode,oauth1for browser OAuth1)
- OAuth1 callback (defaults are fine):
X_OAUTH_CALLBACK_HOST(default127.0.0.1)X_OAUTH_CALLBACK_PORT(default8976)X_OAUTH_CALLBACK_PATH(default/oauth/callback)X_OAUTH_CALLBACK_TIMEOUT(default300)
- Server settings (optional):
X_API_BASE_URL(defaulthttps://api.x.com)X_API_TIMEOUT(default30)MCP_HOST(default127.0.0.1)MCP_PORT(default8000)X_API_DEBUG(default1)
- Tool filtering (optional, comma-separated):
X_API_TOOL_ALLOWLIST
- Optional Grok test client:
XAI_API_KEYXAI_MODEL(defaultgrok-4-1-fast)MCP_SERVER_URL(defaulthttp://127.0.0.1:8000/mcp)
- Optional OAuth2 token generation:
CLIENT_IDCLIENT_SECRETX_OAUTH_ACCESS_TOKEN-X_OAUTH_ACCESS_TOKEN_SECRET(optional)
- Optional OAuth1 debug output:
X_OAUTH_PRINT_TOKENSX_OAUTH_PRINT_AUTH_HEADER
- Register the callback URL in your X Developer App:
http://<X_OAUTH_CALLBACK_HOST>:<X_OAUTH_CALLBACK_PORT><X_OAUTH_CALLBACK_PATH>
Example (defaults):
http://127.0.0.1:8976/oauth/callback
- Start the server:
python server.py
The MCP endpoint is http://127.0.0.1:8000/mcp by default.
- Connect an MCP client:
- Local client: point it to
http://127.0.0.1:8000/mcp. - Remote client: tunnel your local server (e.g., ngrok) and use the public URL.
Use X_API_TOOL_ALLOWLIST to load a small, explicit set of tools:
X_API_TOOL_ALLOWLIST=getUsersByUsername,createPosts,searchPostsRecent
Whitelisting is applied at startup when the OpenAPI spec is loaded, so restart the server after changes. See the full tool list below before building your allowlist.
When X_AUTH_MODE=oauth1, the server opens a browser for OAuth1 consent and waits for the
callback. Tokens are kept in memory only for the lifetime of the server
process. Set X_OAUTH_PRINT_TOKENS=1 to print tokens, or
X_OAUTH_PRINT_AUTH_HEADER=1 to print request headers.
When X_AUTH_MODE=bearer, the server uses X_BEARER_TOKEN directly and does
not launch the OAuth browser flow.
Below is the full list of tool calls you can whitelist via
X_API_TOOL_ALLOWLIST. Copy any of these into your .env allowlist.
addListsMemberaddUserPublicKeyappendMediaUploadblockUsersDmscreateCommunityNotescreateComplianceJobscreateDirectMessagesByConversationIdcreateDirectMessagesByParticipantIdcreateDirectMessagesConversationcreateListscreateMediaMetadatacreateMediaSubtitlescreatePostscreateUsersBookmarkdeleteActivitySubscriptiondeleteAllConnectionsdeleteCommunityNotesdeleteConnectionsByEndpointdeleteConnectionsByUuidsdeleteDirectMessagesEventsdeleteListsdeleteMediaSubtitlesdeletePostsdeleteUsersBookmarkevaluateCommunityNotesfinalizeMediaUploadfollowListfollowUsergetAccountActivitySubscriptionCountgetActivitySubscriptionsgetChatConversationgetChatConversationsgetCommunitiesByIdgetComplianceJobsgetComplianceJobsByIdgetConnectionHistorygetDirectMessagesEventsgetDirectMessagesEventsByConversationIdgetDirectMessagesEventsByIdgetDirectMessagesEventsByParticipantIdgetInsights28HrgetInsightsHistoricalgetListsByIdgetListsFollowersgetListsMembersgetListsPostsgetMarketplaceHandleAvailabilitygetMediaAnalyticsgetMediaByMediaKeygetMediaByMediaKeysgetMediaUploadStatusgetNewsgetOpenApiSpecgetPostsAnalyticsgetPostsByIdgetPostsByIdsgetPostsCountsAllgetPostsCountsRecentgetPostsLikingUsersgetPostsQuotedPostsgetPostsRepostedBygetPostsRepostsgetSpacesBuyersgetSpacesByCreatorIdsgetSpacesByIdgetSpacesByIdsgetSpacesPostsgetTrendsByWoeidgetTrendsPersonalizedTrendsgetUsagegetUserPublicKeysgetUsersAffiliatesgetUsersBlockinggetUsersBookmarkFoldersgetUsersBookmarksgetUsersBookmarksByFolderIdgetUsersByIdgetUsersByIdsgetUsersByUsernamegetUsersByUsernamesgetUsersFollowedListsgetUsersFollowersgetUsersFollowinggetUsersLikedPostsgetUsersListMembershipsgetUsersMegetUsersMentionsgetUsersMutinggetUsersOwnedListsgetUsersPinnedListsgetUsersPostsgetUsersRepostsOfMegetUsersTimelinehidePostsReplyinitializeMediaUploadlikePostmediaUploadmuteUserpinListremoveListsMemberByUserIdrepostPostsearchCommunitiessearchCommunityNotesWrittensearchEligiblePostssearchNewssearchPostsAllsearchPostsRecentsearchSpacessearchUserssendChatMessageunblockUsersDmsunfollowListunfollowUserunlikePostunmuteUserunpinListunrepostPostupdateActivitySubscriptionupdateLists
- Add
CLIENT_IDandCLIENT_SECRETto your.env. - Update
redirect_uriingenerate_authtoken.pyto match your app settings. - Run
python generate_authtoken.pyand follow the prompts. - Copy the printed access token into
.envasX_OAUTH_ACCESS_TOKEN. If your flow returns a secret, store it asX_OAUTH_ACCESS_TOKEN_SECRET.
- Set
XAI_API_KEYin.env. - Make sure your MCP server is running locally (or set
MCP_SERVER_URL). - If Grok is not running on your machine, use ngrok to expose your local MCP
server and set
MCP_SERVER_URLto the public HTTPS URL that ends with/mcp. Example flow:ngrok http 8000thenMCP_SERVER_URL=https://<id>.ngrok-free.dev/mcp. - Run
python test_grok_mcp.py.
- Endpoints with
/streamor/webhooksin the path are excluded. - Operations tagged
StreamorWebhooks, or marked withx-twitter-streaming: true, are excluded. - The OpenAPI spec is fetched from
https://api.twitter.com/2/openapi.jsonat startup.