@twitterapis/mcp 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +5 -3
- package/package.json +1 -1
- package/src/tools.js +56 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.0 (2026-09-13)
|
|
4
|
+
|
|
5
|
+
- **Two new tools, 107 -> 109 (65 reads, 44 writes): `twitter_update_avatar` and
|
|
6
|
+
`twitter_update_banner`.** Both endpoints had been serving behind a feature flag
|
|
7
|
+
on the API and documented on no surface, because their upstream host was an
|
|
8
|
+
INFERENCE from the sibling profile write rather than an observation. Both have
|
|
9
|
+
now been called end to end against a live account, both success envelopes were
|
|
10
|
+
captured, and each write was confirmed applied by reading the profile back
|
|
11
|
+
through the GraphQL user lookup. The flag is deleted from the API entirely.
|
|
12
|
+
- Each takes ONE field of base64-encoded image bytes (`image` / `banner`): not a
|
|
13
|
+
URL, not multipart, and NOT a `media_id` from `twitter_media_upload`. Both tool
|
|
14
|
+
descriptions say so in the first two sentences, because a model choosing between
|
|
15
|
+
these and the media-upload tool on the word "upload" alone will get it wrong.
|
|
16
|
+
- Both descriptions also carry the two things a schema cannot: there is NO UNDO
|
|
17
|
+
and the vendor keeps no history, so read and save the current image URL first;
|
|
18
|
+
and confirm a banner write by reading `cover_picture` back specifically, since
|
|
19
|
+
at least one vendor endpoint reports that field as empty for accounts that
|
|
20
|
+
plainly have one.
|
|
21
|
+
- Both are declared `jsonBody`, and here the API's "json-only" classification is
|
|
22
|
+
EXACT rather than conservative: the shared handler reads the JSON body and has
|
|
23
|
+
no query fallback at all, so a query string genuinely cannot work.
|
|
24
|
+
|
|
3
25
|
## 0.11.1 (2026-09-13)
|
|
4
26
|
|
|
5
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ Restart Claude Desktop. The `twitter_*` tools appear in the tool picker.
|
|
|
91
91
|
|
|
92
92
|
## Tools
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
109 tools: 65 reads and 44 write actions. Most user endpoints accept `username` (handle without @) **or** `user_id` (`twitter_user_likes` and `twitter_user_tweets_complete` require `user_id`); tweet endpoints accept `id` **or** `url`; paginated endpoints return a `cursor` you pass back to get the next page. Four of the reads are free account lookups (`twitter_account_me`, `twitter_account_payments`, `twitter_feedback_get`, `twitter_feedback_list`); the 14 monitoring tools and `twitter_feedback_send` are also free (account administration, not metered reads).
|
|
95
95
|
|
|
96
96
|
Public reads (search, profiles, tweets, followers, likes) work with just your API key. The **account-only** reads (bookmarks, DMs, home timeline, followers-you-know) and **most write actions** act AS an authenticated X account, so they need a session linked to your key first (returns HTTP 409 until then). Link a session either by registering your x.com cookies (`twitter_customer_session`) or by logging in with a username/password (`twitter_user_login`). Alternatively, pass **per-call inline credentials** on any of those tools (`auth_token` + `ct0`, with optional `proxy_url` / `user_agent`) to act AS that account for a single call without pre-registering a session, so one API key can act as many accounts. For write actions, set `proxy_url` to a residential proxy, since X soft-blocks writes that egress from datacenter IPs. Each write tool is annotated `readOnlyHint: false`; reversing actions (delete, unfollow, unlike, unretweet, unbookmark, monitor/webhook delete) are annotated `destructiveHint: true` so MCP clients can prompt before running them. The **monitoring** and **feedback** tools (see below) are the exception: they administer your twitterapis.com account, not an X session, so they need only your API key, no linked session and no inline credentials.
|
|
97
97
|
|
|
@@ -170,6 +170,8 @@ Public reads (search, profiles, tweets, followers, likes) work with just your AP
|
|
|
170
170
|
| `twitter_list_add_member` / `twitter_list_remove_member` | Add / remove one account on a List you own; `member_count` comes back as proof the write landed |
|
|
171
171
|
| `twitter_media_upload` | Upload a base64 image, returns a `media_id` for `twitter_create_tweet` |
|
|
172
172
|
| `twitter_update_profile` | Change your own name, bio, location or link. A PARTIAL update: omitted fields keep their values, an empty string clears one |
|
|
173
|
+
| `twitter_update_avatar` | Replace your own profile picture. Takes base64 image bytes in `image`, not a URL and not a `media_id`. No undo, and X keeps no history |
|
|
174
|
+
| `twitter_update_banner` | Replace your own header image. Takes base64 image bytes in `banner`, not a URL and not a `media_id`. No undo, and X keeps no history |
|
|
173
175
|
|
|
174
176
|
### Drafts and scheduled posts _(require a linked X session)_
|
|
175
177
|
|
|
@@ -327,7 +329,7 @@ count: 50
|
|
|
327
329
|
|
|
328
330
|
## Pricing
|
|
329
331
|
|
|
330
|
-
Calls are billed to your twitterapis.com account. Most endpoints are $0.0008/call: nearly every read (search, profiles, tweets, followers, likes) plus the simple write actions (like, retweet, bookmark, follow and their undos, delete). At the read rate that works out to $0.04 per 1,000 tweets, since each call returns about 20 tweets. The premium endpoints cost a little more: tweet creation, sending a DM (`twitter_dm_send`), and DM reads (`twitter_dm_list`, `twitter_dm_conversation`) at $0.0016/call, full tweet history (`twitter_user_tweets_complete`) at $0.0024/call, a full tweet thread (`twitter_tweet_thread`) and a Grok answer (`twitter_grok_chat`) at $0.004/call, and the article-editing writes (`twitter_article_create`, `twitter_article_update_title`, `twitter_article_update_cover_media`, `twitter_article_update_content`, `twitter_article_publish`, `twitter_article_unpublish`) at $0.0016. The compose surface is $0.0016 for `twitter_draft_create`, `twitter_draft_edit`, `twitter_scheduled_create` and, note, BOTH LIST READS (`twitter_draft_list`, `twitter_scheduled_list`), which are the two reads that are not at the read rate; `twitter_draft_delete` and `twitter_scheduled_delete` are $0.0008/call, while `twitter_article_get`, `twitter_article_list` and `twitter_article_delete` stay at the standard $0.0008/call. `twitter_update_profile`,
|
|
332
|
+
Calls are billed to your twitterapis.com account. Most endpoints are $0.0008/call: nearly every read (search, profiles, tweets, followers, likes) plus the simple write actions (like, retweet, bookmark, follow and their undos, delete). At the read rate that works out to $0.04 per 1,000 tweets, since each call returns about 20 tweets. The premium endpoints cost a little more: tweet creation, sending a DM (`twitter_dm_send`), and DM reads (`twitter_dm_list`, `twitter_dm_conversation`) at $0.0016/call, full tweet history (`twitter_user_tweets_complete`) at $0.0024/call, a full tweet thread (`twitter_tweet_thread`) and a Grok answer (`twitter_grok_chat`) at $0.004/call, and the article-editing writes (`twitter_article_create`, `twitter_article_update_title`, `twitter_article_update_cover_media`, `twitter_article_update_content`, `twitter_article_publish`, `twitter_article_unpublish`) at $0.0016. The compose surface is $0.0016 for `twitter_draft_create`, `twitter_draft_edit`, `twitter_scheduled_create` and, note, BOTH LIST READS (`twitter_draft_list`, `twitter_scheduled_list`), which are the two reads that are not at the read rate; `twitter_draft_delete` and `twitter_scheduled_delete` are $0.0008/call, while `twitter_article_get`, `twitter_article_list` and `twitter_article_delete` stay at the standard $0.0008/call. The three profile writes (`twitter_update_profile`, `twitter_update_avatar`, `twitter_update_banner`) are $0.0016/call each. Your first $0.50 is free. See [twitterapis.com/pricing](https://www.twitterapis.com/pricing).
|
|
331
333
|
|
|
332
334
|
## Links
|
|
333
335
|
|
|
@@ -340,7 +342,7 @@ Calls are billed to your twitterapis.com account. Most endpoints are $0.0008/cal
|
|
|
340
342
|
|
|
341
343
|
**Do I need an X (Twitter) developer account?** No. Get an API key at [twitterapis.com/signup](https://www.twitterapis.com/signup); there is no application or approval step.
|
|
342
344
|
|
|
343
|
-
**Is it read-only?** No. 65 read tools work with just your API key;
|
|
345
|
+
**Is it read-only?** No. 65 read tools work with just your API key; 44 write actions (post, save or schedule a post, like, retweet, follow, DM, media upload, profile name/bio/avatar/banner updates, List create/add member/remove member, article create/edit/publish/delete, monitor/webhook create/update/delete, feedback send) act as a linked X account or per-call inline credentials, except monitor/webhook CRUD and feedback, which are account administration and need only your API key.
|
|
344
346
|
|
|
345
347
|
**Which clients are supported?** Claude Desktop, Cursor, Windsurf, and VS Code (Copilot agent mode), or any Model Context Protocol client.
|
|
346
348
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twitterapis/mcp",
|
|
3
3
|
"mcpName": "io.github.TwitterAPIs/twitterapis-mcp",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.1",
|
|
5
5
|
"description": "Official MCP server for twitterapis.com, the Twitter/X API (search, users, followers, tweets, threads, lists, likes, bookmarks, DMs) plus write actions (post/like/retweet/follow) as native tools for Claude, Cursor, and any MCP client.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/tools.js
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
// file in memory and fails if it does not match what is committed, so a hand edit
|
|
9
9
|
// here is caught rather than shipped.
|
|
10
10
|
//
|
|
11
|
-
// Catalog:
|
|
11
|
+
// Catalog: 109 tools (65 reads, 44 writes).
|
|
12
12
|
//
|
|
13
13
|
// Each tool maps 1:1 to a REST endpoint at https://api.twitterapis.com. Tool arg
|
|
14
14
|
// names map 1:1 to endpoint query params (every endpoint, including the POST
|
|
15
15
|
// write actions, reads its params from the query string), except the per-call
|
|
16
|
-
// inline credentials, which travel as x-* request headers, the
|
|
16
|
+
// inline credentials, which travel as x-* request headers, the 14
|
|
17
17
|
// jsonBody tools, whose fields travel in a JSON request body, and any arg listed
|
|
18
18
|
// in pathParams, which is substituted into the URL path (e.g. {id}) instead. A
|
|
19
19
|
// tool with `method: "POST"` or `method: "DELETE"` is a write that acts on
|
|
@@ -1196,6 +1196,58 @@ export const TOOLS = [
|
|
|
1196
1196
|
),
|
|
1197
1197
|
},
|
|
1198
1198
|
},
|
|
1199
|
+
{
|
|
1200
|
+
name: "twitter_update_avatar",
|
|
1201
|
+
path: "/twitter/user/update_avatar",
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
write: true,
|
|
1204
|
+
jsonBody: true,
|
|
1205
|
+
description:
|
|
1206
|
+
"Replace the profile picture on your authenticated account's own X profile. Takes ONE field, image, holding base64-encoded image bytes: not a URL, not multipart, and not a media_id from twitter_media_upload. It writes a real profile and takes effect immediately with NO UNDO, and X keeps no history of the previous picture, so if the old image might be wanted back, read profile_image_url with twitter_user_info and save that file BEFORE calling this. Returns ok. To confirm it applied, read the account back with twitter_user_info: X mints a new media id for every accepted upload, so profile_image_url changes even when the image is byte-identical to the one already in place. Requires an authenticated session behind your key.",
|
|
1207
|
+
shape: {
|
|
1208
|
+
image: z.string().describe(
|
|
1209
|
+
"REQUIRED. Base64-encoded image bytes. Not a URL, not multipart, and not a media_id. banner and data are accepted as aliases for this same field.",
|
|
1210
|
+
),
|
|
1211
|
+
auth_token: z.string().optional().describe(
|
|
1212
|
+
"Optional. The account's auth_token cookie, to act AS that account for this call (must be paired with ct0). Travels out of band: as the x-auth-token request header on most tools, or inside the JSON request body on the tools that take one. Never a query parameter, so it never reaches a URL or an access log.",
|
|
1213
|
+
),
|
|
1214
|
+
ct0: z.string().optional().describe(
|
|
1215
|
+
"Optional. The account's ct0 cookie, paired with auth_token. Same transport as auth_token: the x-ct0 request header, or the JSON body on a body-taking tool. Never a query parameter.",
|
|
1216
|
+
),
|
|
1217
|
+
proxy_url: z.string().optional().describe(
|
|
1218
|
+
"Optional. Residential proxy URL to egress this call through. Recommended for writes: X soft-blocks writes from datacenter IPs as automated. Sent as the x-proxy-url request header, or in the JSON body on a body-taking tool.",
|
|
1219
|
+
),
|
|
1220
|
+
user_agent: z.string().optional().describe(
|
|
1221
|
+
"Optional. User-Agent string to send for this session. Sent as the x-user-agent request header, or in the JSON body on a body-taking tool.",
|
|
1222
|
+
),
|
|
1223
|
+
},
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
name: "twitter_update_banner",
|
|
1227
|
+
path: "/twitter/user/update_banner",
|
|
1228
|
+
method: "POST",
|
|
1229
|
+
write: true,
|
|
1230
|
+
jsonBody: true,
|
|
1231
|
+
description:
|
|
1232
|
+
"Replace the wide header image on your authenticated account's own X profile. Takes ONE field, banner, holding base64-encoded image bytes: not a URL, not multipart, and not a media_id from twitter_media_upload. X renders the header as a wide strip, so a 3:1 image fills it without cropping. It writes a real profile and takes effect immediately with NO UNDO, and X keeps no history of the previous banner, so if the old image might be wanted back, read cover_picture with twitter_user_info and save that file BEFORE calling this. Returns ok. To confirm it applied, read the account back with twitter_user_info and check cover_picture specifically: at least one of X's own endpoints reports that field as empty for accounts that plainly have a banner, so an empty answer from anywhere else is not evidence the account has none. Requires an authenticated session behind your key.",
|
|
1233
|
+
shape: {
|
|
1234
|
+
banner: z.string().describe(
|
|
1235
|
+
"REQUIRED. Base64-encoded image bytes. Not a URL, not multipart, and not a media_id. image and data are accepted as aliases for this same field.",
|
|
1236
|
+
),
|
|
1237
|
+
auth_token: z.string().optional().describe(
|
|
1238
|
+
"Optional. The account's auth_token cookie, to act AS that account for this call (must be paired with ct0). Travels out of band: as the x-auth-token request header on most tools, or inside the JSON request body on the tools that take one. Never a query parameter, so it never reaches a URL or an access log.",
|
|
1239
|
+
),
|
|
1240
|
+
ct0: z.string().optional().describe(
|
|
1241
|
+
"Optional. The account's ct0 cookie, paired with auth_token. Same transport as auth_token: the x-ct0 request header, or the JSON body on a body-taking tool. Never a query parameter.",
|
|
1242
|
+
),
|
|
1243
|
+
proxy_url: z.string().optional().describe(
|
|
1244
|
+
"Optional. Residential proxy URL to egress this call through. Recommended for writes: X soft-blocks writes from datacenter IPs as automated. Sent as the x-proxy-url request header, or in the JSON body on a body-taking tool.",
|
|
1245
|
+
),
|
|
1246
|
+
user_agent: z.string().optional().describe(
|
|
1247
|
+
"Optional. User-Agent string to send for this session. Sent as the x-user-agent request header, or in the JSON body on a body-taking tool.",
|
|
1248
|
+
),
|
|
1249
|
+
},
|
|
1250
|
+
},
|
|
1199
1251
|
{
|
|
1200
1252
|
name: "twitter_draft_create",
|
|
1201
1253
|
path: "/twitter/draft/create",
|
|
@@ -1914,13 +1966,13 @@ export const TOOLS = [
|
|
|
1914
1966
|
write: true,
|
|
1915
1967
|
jsonBody: true,
|
|
1916
1968
|
description:
|
|
1917
|
-
"Replace the body content of a DRAFT or PUBLISHED article AS your authenticated account. Provide the article's id and content_state: Draft.js JSON ({ blocks: [...], entityMap: [...] }) that YOU build and
|
|
1969
|
+
"Replace the body content of a DRAFT or PUBLISHED article AS your authenticated account. Provide the article's id and content_state: Draft.js JSON ({ blocks: [...], entityMap: [...] }) that YOU build; blocks are forwarded with only data/text/key/type/entityRanges/inlineStyleRanges (X rejects any other block field, depth included). Block types X accepts: unstyled, header-two, unordered-list-item, ordered-list-item, blockquote, atomic (a one-space block carrying an entity via entityRanges [{key, offset: 0, length: 1}]); inline styles Bold and Italic. Entity data is snake_case on INPUT and X returns it camelCase: TWEET (an embedded post) {tweet_id}; MEDIA (an inline image) {caption, entity_key, media_items: [{local_media_id, media_category: 'DraftTweetImage', media_id}]} with media_id from twitter_media_upload; DIVIDER {}; LINK {url} (a Mutable entity over a text range, not atomic); MARKDOWN {markdown} (tables). A wrong field is refused by X's schema and this tool answers 422 with reason validation_failed and the offending path in detail. Requires an authenticated session with write capability behind your key. Returns the updated article object (content_state echoed camelCase, media_entities populated for MEDIA).",
|
|
1918
1970
|
shape: {
|
|
1919
1971
|
id: z.string().describe(
|
|
1920
1972
|
"The article's entity id, from twitter_article_create or twitter_article_list.",
|
|
1921
1973
|
),
|
|
1922
1974
|
content_state: z.record(z.string(), z.unknown()).describe(
|
|
1923
|
-
"Draft.js content state object: { blocks: [...], entityMap: [...] }
|
|
1975
|
+
"Draft.js content state object: { blocks: [...], entityMap: [...] } in the shape the X Article editor produces. entityMap is an ARRAY of {key: '0', value: {type, mutability, data}}; entity data keys are snake_case on input (tweet_id, media_items, local_media_id, media_category, media_id, entity_key). Unknown fields are refused by X (422, reason validation_failed, detail names the path).",
|
|
1924
1976
|
),
|
|
1925
1977
|
auth_token: z.string().optional().describe(
|
|
1926
1978
|
"Optional. The account's auth_token cookie, to act AS that account for this call (must be paired with ct0). Travels out of band: as the x-auth-token request header on most tools, or inside the JSON request body on the tools that take one. Never a query parameter, so it never reaches a URL or an access log.",
|