@twitterapis/mcp 0.2.0 → 0.3.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 +7 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/index.js +23 -9
- package/src/tools.js +35 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 (2026-06-29)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Per-call inline credentials** for multi-account use: the 16 session tools (all writes + account-only reads) accept optional `auth_token` + `ct0` (plus optional `proxy_url` / `user_agent`) to act AS that account for a single call, with no pre-registered session, so one API key can act as many accounts. Sent as `x-auth-token` / `x-ct0` / `x-proxy-url` / `x-user-agent` request headers, never in the URL or query string.
|
|
8
|
+
- For write actions, set `proxy_url` to a residential proxy: X soft-blocks writes that egress from datacenter IPs.
|
|
9
|
+
|
|
3
10
|
## 0.2.0 (2026-06-25)
|
|
4
11
|
|
|
5
12
|
### Added (full API parity)
|
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ Restart Claude Desktop. The `twitter_*` tools appear in the tool picker.
|
|
|
89
89
|
|
|
90
90
|
37 tools: 27 reads and 10 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.
|
|
91
91
|
|
|
92
|
-
Public reads (search, profiles, tweets, followers) work with just your API key. The **account-only** reads (
|
|
92
|
+
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 **all write actions** act AS an authenticated X account, so they need a session linked to your key first (returns HTTP 409 until then). 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) are annotated `destructiveHint: true` so MCP clients can prompt before running them.
|
|
93
93
|
|
|
94
94
|
### Reads
|
|
95
95
|
|
|
@@ -201,7 +201,7 @@ count: 50
|
|
|
201
201
|
|
|
202
202
|
## Pricing
|
|
203
203
|
|
|
204
|
-
Calls are billed to your twitterapis.com account
|
|
204
|
+
Calls are billed to your twitterapis.com account, every price 20% below getxapi.com per endpoint. Almost every endpoint is $0.0008/call: all reads (search, profiles, tweets, followers, likes) plus the simple write actions (like, retweet, bookmark, follow and their undos, delete, media upload). 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 and DM reads (`twitter_dm_list`, `twitter_dm_conversation`) at $0.0016/call, profile updates (name, avatar, banner) at $0.0016/call, full tweet history (`twitter_user_tweets_complete`) at $0.0024/call, and a full tweet thread (`twitter_tweet_thread`) at $0.004/call. Your first $0.50 is free. See [twitterapis.com/pricing](https://www.twitterapis.com/pricing).
|
|
205
205
|
|
|
206
206
|
## Links
|
|
207
207
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twitterapis/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"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.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/index.js
CHANGED
|
@@ -38,21 +38,35 @@ if (!API_KEY) {
|
|
|
38
38
|
// query string, so the same buildQuery path serves both; only the HTTP method
|
|
39
39
|
// differs per tool.
|
|
40
40
|
async function callEndpoint(path, args, method = "GET") {
|
|
41
|
-
|
|
41
|
+
// Pull per-call inline credentials out of args so they travel as request
|
|
42
|
+
// headers, never the query string (the API reads x-auth-token / x-ct0; passing
|
|
43
|
+
// them as query params would leak them into URLs and access logs). When
|
|
44
|
+
// supplied, this one API key acts as that account; otherwise the key's linked
|
|
45
|
+
// session is used. Lets a single key act as many accounts.
|
|
46
|
+
const { auth_token, ct0, user_agent, proxy_url, ...rest } = args || {};
|
|
47
|
+
const q = buildQuery(rest);
|
|
42
48
|
const url = `${BASE_URL}${path}${q ? `?${q}` : ""}`;
|
|
43
49
|
|
|
50
|
+
const headers = {
|
|
51
|
+
// The API accepts either header; send both for maximum compatibility.
|
|
52
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
53
|
+
"x-api-key": API_KEY,
|
|
54
|
+
accept: "application/json",
|
|
55
|
+
"user-agent": "twitterapis-mcp/0.3.0",
|
|
56
|
+
};
|
|
57
|
+
if (auth_token && ct0) {
|
|
58
|
+
headers["x-auth-token"] = auth_token;
|
|
59
|
+
headers["x-ct0"] = ct0;
|
|
60
|
+
if (user_agent) headers["x-user-agent"] = user_agent;
|
|
61
|
+
if (proxy_url) headers["x-proxy-url"] = proxy_url;
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
const ctrl = new AbortController();
|
|
45
65
|
const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
|
|
46
66
|
try {
|
|
47
67
|
const res = await fetch(url, {
|
|
48
68
|
method,
|
|
49
|
-
headers
|
|
50
|
-
// The API accepts either header; send both for maximum compatibility.
|
|
51
|
-
Authorization: `Bearer ${API_KEY}`,
|
|
52
|
-
"x-api-key": API_KEY,
|
|
53
|
-
accept: "application/json",
|
|
54
|
-
"user-agent": "twitterapis-mcp/0.2.0",
|
|
55
|
-
},
|
|
69
|
+
headers,
|
|
56
70
|
signal: ctrl.signal,
|
|
57
71
|
});
|
|
58
72
|
const body = await res.text();
|
|
@@ -85,7 +99,7 @@ async function callEndpoint(path, args, method = "GET") {
|
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
// ── MCP server ───────────────────────────────────────────────────────────────
|
|
88
|
-
const server = new McpServer({ name: "twitterapis", version: "0.
|
|
102
|
+
const server = new McpServer({ name: "twitterapis", version: "0.3.0" });
|
|
89
103
|
|
|
90
104
|
for (const tool of TOOLS) {
|
|
91
105
|
const method = tool.method || "GET";
|
package/src/tools.js
CHANGED
|
@@ -42,6 +42,25 @@ const ACCOUNT = {
|
|
|
42
42
|
"Optional. The @handle (without @) of the authenticated account to act AS, when your key manages more than one session. Omit to use your key's default session.",
|
|
43
43
|
),
|
|
44
44
|
};
|
|
45
|
+
// Per-call inline credentials. Pass an account's own X session cookies to act AS
|
|
46
|
+
// that account for this one call, without pre-registering a session, so a single
|
|
47
|
+
// API key can act as many accounts (e.g. polling several inboxes or posting from
|
|
48
|
+
// a pool). Sent as request headers, never in the URL. Omit to use the key's
|
|
49
|
+
// linked session.
|
|
50
|
+
const INLINE = {
|
|
51
|
+
auth_token: z.string().optional().describe(
|
|
52
|
+
"Optional. The account's auth_token cookie, to act AS that account for this call (must be paired with ct0). Sent as the x-auth-token header; never placed in the URL.",
|
|
53
|
+
),
|
|
54
|
+
ct0: z.string().optional().describe(
|
|
55
|
+
"Optional. The account's ct0 cookie, paired with auth_token. Sent as the x-ct0 header.",
|
|
56
|
+
),
|
|
57
|
+
proxy_url: z.string().optional().describe(
|
|
58
|
+
"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 header.",
|
|
59
|
+
),
|
|
60
|
+
user_agent: z.string().optional().describe(
|
|
61
|
+
"Optional. User-Agent string to send for this session. Sent as the x-user-agent header.",
|
|
62
|
+
),
|
|
63
|
+
};
|
|
45
64
|
|
|
46
65
|
// ── Tool catalog. Reads are GET (default); writes set method:"POST". ─────────
|
|
47
66
|
// write:true -> action mutates account/Twitter state (annotated readOnlyHint:false)
|
|
@@ -223,6 +242,7 @@ export const TOOLS = [
|
|
|
223
242
|
"Numeric user id of the target account to compute shared followers against.",
|
|
224
243
|
),
|
|
225
244
|
...PAGINATION,
|
|
245
|
+
...INLINE,
|
|
226
246
|
},
|
|
227
247
|
},
|
|
228
248
|
// ── Reads: a single tweet + its conversation ───────────────────────────────
|
|
@@ -272,14 +292,14 @@ export const TOOLS = [
|
|
|
272
292
|
path: "/twitter/user/home_timeline",
|
|
273
293
|
description:
|
|
274
294
|
"Get YOUR authenticated account's Home timeline (the 'Following'/'For you' feed), most recent first. Requires an authenticated session behind your key. Returns tweets with author and metrics plus a cursor. Use this to read what your account would see when it opens X.",
|
|
275
|
-
shape: { ...PAGINATION },
|
|
295
|
+
shape: { ...PAGINATION, ...INLINE },
|
|
276
296
|
},
|
|
277
297
|
{
|
|
278
298
|
name: "twitter_bookmarks",
|
|
279
299
|
path: "/twitter/user/bookmarks",
|
|
280
300
|
description:
|
|
281
301
|
"List YOUR authenticated account's bookmarked tweets, most recent first. Requires an authenticated session behind your key. Returns each bookmarked tweet with author and metrics plus a cursor.",
|
|
282
|
-
shape: { ...PAGINATION },
|
|
302
|
+
shape: { ...PAGINATION, ...INLINE },
|
|
283
303
|
},
|
|
284
304
|
{
|
|
285
305
|
name: "twitter_bookmark_search",
|
|
@@ -291,6 +311,7 @@ export const TOOLS = [
|
|
|
291
311
|
"Search terms to match against your bookmarked tweets' text.",
|
|
292
312
|
),
|
|
293
313
|
...PAGINATION,
|
|
314
|
+
...INLINE,
|
|
294
315
|
},
|
|
295
316
|
},
|
|
296
317
|
{
|
|
@@ -298,7 +319,7 @@ export const TOOLS = [
|
|
|
298
319
|
path: "/twitter/dm/list",
|
|
299
320
|
description:
|
|
300
321
|
"List YOUR authenticated account's Direct Message conversations (inbox), each with the participant and a conversation_id you can pass to twitter_dm_conversation. Requires an authenticated session behind your key. Read-only: this does not send DMs.",
|
|
301
|
-
shape: {},
|
|
322
|
+
shape: { ...INLINE },
|
|
302
323
|
},
|
|
303
324
|
{
|
|
304
325
|
name: "twitter_dm_conversation",
|
|
@@ -309,6 +330,7 @@ export const TOOLS = [
|
|
|
309
330
|
conversation_id: z.string().describe(
|
|
310
331
|
"The conversation_id from a twitter_dm_list entry identifying which DM thread to read.",
|
|
311
332
|
),
|
|
333
|
+
...INLINE,
|
|
312
334
|
},
|
|
313
335
|
},
|
|
314
336
|
|
|
@@ -333,7 +355,7 @@ export const TOOLS = [
|
|
|
333
355
|
media_ids: z.string().optional().describe(
|
|
334
356
|
"Optional. Comma-separated media id(s) from a prior media upload to attach (images/video).",
|
|
335
357
|
),
|
|
336
|
-
...ACCOUNT,
|
|
358
|
+
...ACCOUNT, ...INLINE,
|
|
337
359
|
},
|
|
338
360
|
},
|
|
339
361
|
{
|
|
@@ -344,7 +366,7 @@ export const TOOLS = [
|
|
|
344
366
|
destructive: true,
|
|
345
367
|
description:
|
|
346
368
|
"Delete a tweet AS your authenticated account. Irreversible: the tweet is permanently removed. You can only delete tweets your authenticated account authored. Provide the tweet id or url. Requires write capability behind your key.",
|
|
347
|
-
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
369
|
+
shape: { ...TWEET_REF, ...ACCOUNT, ...INLINE },
|
|
348
370
|
},
|
|
349
371
|
// ── Writes: engagement (favorite / retweet / bookmark) + inverses ──────────
|
|
350
372
|
{
|
|
@@ -354,7 +376,7 @@ export const TOOLS = [
|
|
|
354
376
|
write: true,
|
|
355
377
|
description:
|
|
356
378
|
"Like (favorite) a tweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key. Reverse with twitter_unfavorite_tweet.",
|
|
357
|
-
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
379
|
+
shape: { ...TWEET_REF, ...ACCOUNT, ...INLINE },
|
|
358
380
|
},
|
|
359
381
|
{
|
|
360
382
|
name: "twitter_unfavorite_tweet",
|
|
@@ -364,7 +386,7 @@ export const TOOLS = [
|
|
|
364
386
|
destructive: true,
|
|
365
387
|
description:
|
|
366
388
|
"Remove a like (unfavorite) from a tweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key.",
|
|
367
|
-
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
389
|
+
shape: { ...TWEET_REF, ...ACCOUNT, ...INLINE },
|
|
368
390
|
},
|
|
369
391
|
{
|
|
370
392
|
name: "twitter_retweet",
|
|
@@ -373,7 +395,7 @@ export const TOOLS = [
|
|
|
373
395
|
write: true,
|
|
374
396
|
description:
|
|
375
397
|
"Retweet a tweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key. Reverse with twitter_unretweet.",
|
|
376
|
-
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
398
|
+
shape: { ...TWEET_REF, ...ACCOUNT, ...INLINE },
|
|
377
399
|
},
|
|
378
400
|
{
|
|
379
401
|
name: "twitter_unretweet",
|
|
@@ -383,7 +405,7 @@ export const TOOLS = [
|
|
|
383
405
|
destructive: true,
|
|
384
406
|
description:
|
|
385
407
|
"Undo a retweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key.",
|
|
386
|
-
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
408
|
+
shape: { ...TWEET_REF, ...ACCOUNT, ...INLINE },
|
|
387
409
|
},
|
|
388
410
|
{
|
|
389
411
|
name: "twitter_bookmark_tweet",
|
|
@@ -392,7 +414,7 @@ export const TOOLS = [
|
|
|
392
414
|
write: true,
|
|
393
415
|
description:
|
|
394
416
|
"Bookmark a tweet to YOUR authenticated account's private bookmarks. Provide the tweet id or url. Requires write capability behind your key. Reverse with twitter_unbookmark_tweet.",
|
|
395
|
-
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
417
|
+
shape: { ...TWEET_REF, ...ACCOUNT, ...INLINE },
|
|
396
418
|
},
|
|
397
419
|
{
|
|
398
420
|
name: "twitter_unbookmark_tweet",
|
|
@@ -402,7 +424,7 @@ export const TOOLS = [
|
|
|
402
424
|
destructive: true,
|
|
403
425
|
description:
|
|
404
426
|
"Remove a tweet from YOUR authenticated account's bookmarks. Provide the tweet id or url. Requires write capability behind your key.",
|
|
405
|
-
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
427
|
+
shape: { ...TWEET_REF, ...ACCOUNT, ...INLINE },
|
|
406
428
|
},
|
|
407
429
|
// ── Writes: follow graph ───────────────────────────────────────────────────
|
|
408
430
|
{
|
|
@@ -416,7 +438,7 @@ export const TOOLS = [
|
|
|
416
438
|
user_id: z.string().describe(
|
|
417
439
|
"Numeric user id of the account to follow. Resolve a handle to a user_id first with twitter_user_info.",
|
|
418
440
|
),
|
|
419
|
-
...ACCOUNT,
|
|
441
|
+
...ACCOUNT, ...INLINE,
|
|
420
442
|
},
|
|
421
443
|
},
|
|
422
444
|
{
|
|
@@ -431,7 +453,7 @@ export const TOOLS = [
|
|
|
431
453
|
user_id: z.string().describe(
|
|
432
454
|
"Numeric user id of the account to unfollow.",
|
|
433
455
|
),
|
|
434
|
-
...ACCOUNT,
|
|
456
|
+
...ACCOUNT, ...INLINE,
|
|
435
457
|
},
|
|
436
458
|
},
|
|
437
459
|
];
|